feat: Implement 'ensure-genome-vault' management script
This commit is contained in:
parent
a9c5d56136
commit
add1cea732
1 changed files with 118 additions and 0 deletions
118
deploy/nexus/ensure-genome-vault.sh
Normal file
118
deploy/nexus/ensure-genome-vault.sh
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#!/bin/bash
|
||||
# ensure-genome-vault <genome> [--status-only]
|
||||
#
|
||||
# Idempotent, unified command for managing genome vaults.
|
||||
# Called by n8n during genome creation and as a safety net mechanism.
|
||||
#
|
||||
# Operation workflow:
|
||||
# - Vault absent -> Clone from Forgejo (loopback) + track develop branch
|
||||
# - Vault present -> Realign to origin/develop (treated as a rebuildable scratchpad)
|
||||
# - Post-clone/fetch -> Write raw/.stignore and register/update the Syncthing folder.
|
||||
#
|
||||
# Source of truth is Forgejo. Vaults are scratch spaces and not backed up directly.
|
||||
# All operations run locally via loopback.
|
||||
|
||||
set -euo pipefail
|
||||
genome="${1:?usage: ensure-genome-vault <genome> [--status-only]}"
|
||||
mode="${2:-}"
|
||||
|
||||
# Slug validation inside the script to prevent path/URL traversal:
|
||||
# Lowercase kebab-case, no '/', '..', or spaces.
|
||||
[[ "$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}"
|
||||
: "${SYNCTHING_URL:=http://127.0.0.1:8384}"
|
||||
|
||||
vault="${GENOME_VAULTS_ROOT}/${genome}"
|
||||
fid="${genome}-public"
|
||||
clone_url="http://${FORGEJO_USER}@${FORGEJO_HOST}/${FORGEJO_OWNER}/${genome}.git"
|
||||
export GIT_ASKPASS=/usr/local/bin/genome-askpass # Provides the n8n-bot token
|
||||
|
||||
mkdir -p "$GENOME_VAULTS_ROOT"
|
||||
|
||||
# ── 1. Clone (if missing) or realign (if present) ────────────────────────────
|
||||
if [[ ! -d "${vault}/.git" ]]; then
|
||||
[[ "$mode" == "--status-only" ]] && { printf '{"status":"absent","genome":"%s"}\n' "$genome"; exit 0; }
|
||||
git clone -q "$clone_url" "$vault"
|
||||
cd "$vault"
|
||||
if git show-ref --verify --quiet "refs/remotes/origin/${GENOME_BASE}"; then
|
||||
git switch -q -c "$GENOME_BASE" --track "origin/${GENOME_BASE}" 2>/dev/null || git switch -q "$GENOME_BASE"
|
||||
else
|
||||
# develop does not exist on remote yet: create it from current base and publish
|
||||
git switch -q -c "$GENOME_BASE"
|
||||
git push -q "$clone_url" "${GENOME_BASE}:${GENOME_BASE}"
|
||||
fi
|
||||
state="cloned"
|
||||
else
|
||||
cd "$vault"
|
||||
if [[ "$mode" == "--status-only" ]]; then
|
||||
printf '{"status":"present","genome":"%s","head":"%s"}\n' "$genome" "$(git rev-parse --short HEAD)"
|
||||
exit 0
|
||||
fi
|
||||
git fetch -q origin
|
||||
if git show-ref --verify --quiet "refs/remotes/origin/${GENOME_BASE}"; then
|
||||
git switch -q "$GENOME_BASE" 2>/dev/null || git switch -q -c "$GENOME_BASE" --track "origin/${GENOME_BASE}"
|
||||
# GUARD: hard reset is allowed ONLY if the working tree is clean.
|
||||
# If Syncthing has already written uncommitted raw files, DO NOT destroy them: soft fast-forward.
|
||||
if [[ -z "$(git status --porcelain -- raw/ 2>/dev/null)" ]]; then
|
||||
git reset -q --hard "origin/${GENOME_BASE}"
|
||||
state="realigned"
|
||||
else
|
||||
git merge -q --ff-only "origin/${GENOME_BASE}" 2>/dev/null || true
|
||||
state="realigned-kept-dirty"
|
||||
fi
|
||||
else
|
||||
git switch -q -c "$GENOME_BASE" 2>/dev/null || true
|
||||
git push -q "$clone_url" "${GENOME_BASE}:${GENOME_BASE}"
|
||||
state="base-created"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── 2. raw/.stignore + exclusion from git (infrastructure, not content) ────────────
|
||||
mkdir -p "${vault}/raw"
|
||||
cat > "${vault}/raw/.stignore" <<'EOF'
|
||||
// Knowledge Genome — Syncthing exclusions for raw/
|
||||
// NEVER unencrypted private data: git-crypt protects INSIDE the repo, not in Syncthing transit
|
||||
private
|
||||
// Obsidian / editor noise
|
||||
.obsidian
|
||||
.trash
|
||||
*.tmp
|
||||
workspace*.json
|
||||
// security
|
||||
.git
|
||||
EOF
|
||||
# .stignore must not be included in genome commits
|
||||
grep -qxF 'raw/.stignore' "${vault}/.git/info/exclude" 2>/dev/null \
|
||||
|| echo 'raw/.stignore' >> "${vault}/.git/info/exclude"
|
||||
|
||||
# ── 3. Idempotent Syncthing folder configuration (best-effort, does not block the vault) ────────
|
||||
folder_state="skipped(no api key)"
|
||||
if [[ -n "${SYNCTHING_API_KEY:-}" ]]; then
|
||||
if curl -fsS -o /dev/null -H "X-API-Key: ${SYNCTHING_API_KEY}" \
|
||||
"${SYNCTHING_URL}/rest/config/folders/${fid}" 2>/dev/null; then
|
||||
folder_state="exists"
|
||||
else
|
||||
body="$(curl -fsS -H "X-API-Key: ${SYNCTHING_API_KEY}" \
|
||||
"${SYNCTHING_URL}/rest/config/defaults/folder" \
|
||||
| jq --arg id "$fid" --arg label "${genome} (raw public)" --arg path "${vault}/raw" \
|
||||
'.id=$id | .label=$label | .path=$path | .type="sendreceive"
|
||||
| .fsWatcherEnabled=true | .rescanIntervalS=3600')"
|
||||
|
||||
if curl -fsS -o /dev/null -X PUT \
|
||||
-H "X-API-Key: ${SYNCTHING_API_KEY}" -H "Content-Type: application/json" \
|
||||
-d "$body" "${SYNCTHING_URL}/rest/config/folders/${fid}" 2>/dev/null; then
|
||||
folder_state="created"
|
||||
else
|
||||
folder_state="error(check syncthing api)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '{"status":"ok","genome":"%s","vault":"%s","state":"%s","syncthing_folder":"%s"}\n' \
|
||||
"$genome" "$vault" "$state" "$folder_state"
|
||||
Loading…
Add table
Reference in a new issue