From f79e2156340c905aa032b3d96caded81fa30a0aa Mon Sep 17 00:00:00 2001 From: Guido Jansen Date: Tue, 22 Sep 2026 10:44:30 +0200 Subject: [PATCH] fix(deploy): base the stats cron on Tangled, not the diverged Codeberg mirror The 6h NAS stats job cloned the Codeberg source mirror, committed the refreshed stats JSON, and then pushed to Tangled. But the source mirror and Tangled main had diverged two ways: the mirror kept gaining stats commits while Tangled gained the new country scopes (es, pt, cz, barcelona). Neither branch was a fast-forward of the other, so the job's push to Tangled was rejected every run and the Spindle never redeployed. The live sites froze at the last manual push; newly joined members never surfaced, total on seedless scopes like .pt. Clone Tangled (the single source of truth) so the stats commit is always based on the true HEAD and the deploy push fast-forwards. Force the Codeberg source mirror to track Tangled as a pure downstream copy (the NAS bootstrap and the raw python helpers are fetched from it), so it can never drift ahead again. --- scripts/nl-stats-publish.sh | 44 +++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/scripts/nl-stats-publish.sh b/scripts/nl-stats-publish.sh index fdf441f..fa16963 100644 --- a/scripts/nl-stats-publish.sh +++ b/scripts/nl-stats-publish.sh @@ -169,7 +169,12 @@ $DOCKER run --rm -e REPO_URL="$REPO_URL" -e BRANCH="$BRANCH" -e DEPLOY="$DEPLOY" # so copy it to a private path rather than loosening the file on the host. install -m 600 /tangled_key /root/tangled_key export GIT_SSH_COMMAND="ssh -i /root/tangled_key -o IdentitiesOnly=yes -o User=git -o StrictHostKeyChecking=accept-new" - git clone --quiet --branch "$BRANCH" "$REPO_URL" /site + # Clone from Tangled (the home base and single source of truth), NOT the Codeberg source + # mirror. Cloning the mirror caused a two-way divergence deadlock: the mirror gained stats + # commits while Tangled gained feature commits, so neither push fast-forwarded and every + # deploy silently froze. Basing the stats commit on Tangled HEAD makes the deploy push a + # guaranteed fast-forward. + git clone --quiet --branch "$BRANCH" "$TANGLED_URL" /site cd /site cp /data/nl-stats.json src/data/nl-stats.json cp /data/europe-stats.json src/data/europe-stats.json @@ -182,29 +187,30 @@ $DOCKER run --rm -e REPO_URL="$REPO_URL" -e BRANCH="$BRANCH" -e DEPLOY="$DEPLOY" if ! git diff --quiet -- src/data/nl-stats.json src/data/europe-stats.json src/data/europe-facts.json src/data/be-facts.json src/data/no-facts.json; then git add src/data/nl-stats.json src/data/europe-stats.json src/data/europe-facts.json src/data/be-facts.json src/data/no-facts.json git commit -q -m "chore(stats): refresh nl-stats.json + europe-stats.json + europe-facts.json" - git push -q origin "$BRANCH" - echo "[nl-stats] committed refreshed stats to $BRANCH (codeberg)" + echo "[nl-stats] committed refreshed stats to $BRANCH" else echo "[nl-stats] stats unchanged" fi - # Tangled is the home base and must not fall behind Codeberg. Non-fatal: a - # deploy is still worth doing if only the mirror push fails, but it says so - # loudly rather than drifting in silence. - git remote add tangled "$TANGLED_URL" 2>/dev/null || git remote set-url tangled "$TANGLED_URL" - if git push -q tangled "$BRANCH"; then - echo "[nl-stats] pushed $BRANCH to tangled (home base)" + # Push to Tangled (origin, since we cloned it). This is the deploy trigger: the Spindle CI + # (.tangled/workflows/deploy.yml) fires on push to main, builds every scope, and publishes each + # dist/ to its Codeberg Pages repo. Fast-forward by construction (this commit is based on + # Tangled HEAD), so it no longer deadlocks the way pushing the diverged mirror to Tangled did. + if git push -q origin "$BRANCH"; then + echo "[nl-stats] pushed $BRANCH to tangled (home base) -- Spindle will redeploy all scopes" else - echo "[nl-stats] WARNING: push to tangled FAILED - remotes are now out of sync" >&2 + echo "[nl-stats] WARNING: push to tangled FAILED" >&2 + fi + # Keep the Codeberg source mirror as a pure downstream copy of Tangled: FORCE it to match, + # never fast-forward-merge. The NAS bootstrap fetches this script and the python helpers from + # Codeberg raw main, so the mirror must track Tangled or the host runs stale code. Force (not a + # plain push) guarantees the mirror can never drift ahead again and re-create the deadlock; any + # stats commit it held is regenerated next run. Non-fatal. + git remote add codeberg "$REPO_URL" 2>/dev/null || git remote set-url codeberg "$REPO_URL" + if git push -qf codeberg "$BRANCH"; then + echo "[nl-stats] force-synced $BRANCH to the codeberg source mirror" + else + echo "[nl-stats] WARNING: codeberg source-mirror sync failed (non-fatal)" >&2 fi - # Deploy is handled by the Tangled Spindle CI (.tangled/workflows/deploy.yml), triggered by the - # tangled push above: it builds all seven scopes and publishes each to its Codeberg Pages repo. - # This job no longer deploys -- it used to loop deploy-codeberg.sh per scope, which duplicated - # the Spindle and could clobber it from a stale clone. Stats still reach the live sites because - # the stats commit + push retriggers the Spindle, which rebuilds with the fresh JSON. (This also - # retires the old "only .nl auto-deploys" trap: the per-scope loop needed a token with write to - # all seven Pages repos, but the .codeberg-token used here only has atprotonl-website, so .be/.eu/.no - # silently returned 403. Moot now -- the Spindle deploys with its own all-seven secret; the token is - # still used, but only to clone + push the source mirror above, which it can do.) else npm ci --silent npm run build -- 2.51.2