#!/bin/bash # genome-raw-commit # # Commit the raw files that Syncthing has placed in the vault and push them to origin/. # - Committer = n8n-bot (sole pusher); Author = the person who wrote it (Syncthing modifiedBy -> .authors.json) # - One commit per author (single-device => one commit). No-op if there is nothing. # - JSON output built with jq (safe escaping), with a `files` array: # for each raw -> file, author, local_path, local_url (file://), remote_url (Forgejo web). set -euo pipefail genome="${1:?usage: genome-raw-commit }" # Input validation to prevent path or URL traversal inside the script [[ "$genome" =~ ^[a-z0-9][a-z0-9-]{0,63}$ ]] || { echo '{"status":"error","reason":"invalid genome name"}'; exit 1; } set -a; . "${HOME}/.config/knowledge-genome.env"; set +a : "${GENOME_VAULTS_ROOT:=/srv/genome-vaults}" : "${GENOME_BASE:=develop}" : "${FORGEJO_USER:=n8n-bot}" : "${FORGEJO_HOST:=127.0.0.1:3001}" : "${FORGEJO_OWNER:=Keru}" : "${FORGEJO_WEB_BASE:=https://git.keruhomelab.com} : "${SYNCTHING_URL:=http://127.0.0.1:8384}" : "${COMMITTER_NAME:=n8n-bot}" : "${COMMITTER_EMAIL:=n8n-bot@homelab}" : "${DEFAULT_AUTHOR_NAME:=Unknown}" : "${DEFAULT_AUTHOR_EMAIL:=unknown@syncthing}" vault="${GENOME_VAULTS_ROOT}/${genome}" fid="${genome}-public" authors_map="${GENOME_VAULTS_ROOT}/.authors.json" clone_url="http://${FORGEJO_USER}@${FORGEJO_HOST}/${FORGEJO_OWNER}/${genome}.git" export GIT_ASKPASS=/usr/local/bin/genome-askpass [[ -d "${vault}/.git" ]] || { printf '{"status":"error","reason":"vault absent","genome":"%s"}\n' "$genome"; exit 1; } cd "$vault" git config user.name "$COMMITTER_NAME" git config user.email "$COMMITTER_EMAIL" git config commit.gpgsign false grep -qxF 'raw/.stignore' "${vault}/.git/info/exclude" 2>/dev/null || echo 'raw/.stignore' >> "${vault}/.git/info/exclude" grep -qxF 'raw/.stfolder' "${vault}/.git/info/exclude" 2>/dev/null || echo 'raw/.stfolder' >> "${vault}/.git/info/exclude" git add -A -- raw/ git reset -q -- raw/.stignore raw/.stfolder 2>/dev/null || true if git diff --cached --quiet; then printf '{"status":"noop","genome":"%s"}\n' "$genome" exit 0 fi resolve_dev() { # $1 = path relativo al vault (raw/...) -> stampa lo short device id o vuoto [[ -z "${SYNCTHING_API_KEY:-}" ]] && return 0 curl -fsS -H "X-API-Key: ${SYNCTHING_API_KEY}" --get "${SYNCTHING_URL}/rest/db/file" \ --data-urlencode "folder=${fid}" --data-urlencode "file=${1#raw/}" 2>/dev/null \ | jq -r '.local.modifiedBy // empty' 2>/dev/null || true } author_for_dev() { # $1 = device id -> stampa "name\temail" local dev="$1" name="$DEFAULT_AUTHOR_NAME" email="$DEFAULT_AUTHOR_EMAIL" if [[ -n "$dev" && -f "$authors_map" ]] && jq -e --arg d "$dev" '.[$d]' "$authors_map" >/dev/null 2>&1; then name="$(jq -r --arg d "$dev" '.[$d].name' "$authors_map")" email="$(jq -r --arg d "$dev" '.[$d].email' "$authors_map")" fi printf '%s\t%s' "$name" "$email" } # raccolgo per-file (relpath, author) e raggruppo per autore per il commit declare -A G_FILES G_NAME G_EMAIL declare -a ROWS while IFS= read -r f; do [[ -z "$f" ]] && continue dev="$(resolve_dev "$f")" IFS=$'\t' read -r aname aemail <<< "$(author_for_dev "$dev")" ROWS+=("${f}"$'\t'"${aname}") key="${aname} <${aemail}>" G_FILES["$key"]+="${f}"$'\n' G_NAME["$key"]="$aname"; G_EMAIL["$key"]="$aemail" done < <(git diff --cached --name-only -- raw/) ts="$(date +%Y-%m-%dT%H:%M:%S%z)" commits=0; summary="" for key in "${!G_FILES[@]}"; do mapfile -t files < <(printf '%s' "${G_FILES[$key]}") short="$(printf '%s\n' "${files[@]}" | sed 's#^raw/##' | paste -sd, -)" msg="$(printf 'raw(%s): sync %s\n\nAdded-by-device: %s\nSyncthing-device-id: %s\nSource: syncthing-autocommit\nSynced-at: %s\n' \ "$genome" "$short" "${G_DEV[$key]}" "${G_DEV[$key]}" "$ts")" git commit -q --author="$key" -m "$msg" -- "${files[@]}" commits=$((commits+1)) summary="${summary}${summary:+; }${G_NAME[$key]}:${short}" done # Fetch updates from origin to merge upstream modifications before pushing git fetch -q origin if git show-ref --verify --quiet "refs/remotes/origin/${GENOME_BASE}"; then git rebase -q "origin/${GENOME_BASE}" \ || { git rebase --abort 2>/dev/null || true; printf '{"status":"error","reason":"rebase-conflict","genome":"%s"}\n' "$genome"; exit 1; } fi git push -q "$clone_url" "HEAD:${GENOME_BASE}" printf '{"status":"ok","genome":"%s","base":"%s","commits":%d","head":"%s","summary":"%s"}\n' \ "$genome" "$GENOME_BASE" $commits "$(git rev-parse --short HEAD)" "$summary"