# Shared Spindle publish helpers, sourced by BOTH .tangled/workflows/deploy.yml (push -> publish all) # and .tangled/workflows/reconcile.yml (schedule -> republish drifted). One copy so the publish path # can never drift between the two. POSIX sh (Spindle runs step commands in sh); no side effects on # source, just function definitions. Assumes git, curl, node and coreutils (timeout, wc) are on PATH # and that $atprotocc_sites (Codeberg write token) is set for any push/create. # retry CMD... : run CMD up to 3 times with quadratic backoff. Returns CMD's status on success, or 1. retry() { n=1; while true; do "$@" && return 0; [ "$n" -ge 3 ] && return 1; sleep $((n * n * 5)); n=$((n + 1)); done; } # notify MSG : POST MSG to $deploy_alert_url if set (best-effort, never fails the run), always log it. notify() { if [ -n "${deploy_alert_url:-}" ]; then curl -fsS -m 10 -d "$1" "$deploy_alert_url" >/dev/null 2>&1 || true; fi echo "$1" >&2 } # sha_ref : the source commit this run publishes, as a FULL sha. Prefer the injected TANGLED_SHA; # fall back to HEAD. Full (not --short) so the .deployed-sha marker and the reconciler compare cleanly. sha_ref() { echo "${TANGLED_SHA:-$(git rev-parse HEAD)}"; } # live_sha REPO : the .deployed-sha currently published on REPO's Codeberg pages branch (empty if the # repo/branch/marker does not exist). -L: Codeberg raw may redirect; -f: 404 -> empty, not the error body. live_sha() { curl -fsSL -m 15 "https://codeberg.org/gxjansen/$1/raw/branch/pages/.deployed-sha" 2>/dev/null | tr -d '[:space:]'; } # ensure_repo REPO : create the Codeberg Pages repo if missing (a newly added scope has none yet). ensure_repo() { _repo="$1" if ! retry curl -sf -o /dev/null "https://codeberg.org/api/v1/repos/gxjansen/$_repo"; then echo "==> $_repo does not exist on Codeberg; creating it" retry curl -s -o /dev/null -w " create $_repo -> HTTP %{http_code}\n" -X POST \ -H "Authorization: token ${atprotocc_sites}" -H "Content-Type: application/json" \ -d "{\"name\":\"$_repo\",\"private\":false,\"description\":\"Codeberg Pages for $_repo (auto-created by deploy)\"}" \ "https://codeberg.org/api/v1/user/repos" || true fi } # build_scope SCOPE : build one country scope into dist/. Bounded by a timeout so a hung upstream # fetch cannot starve the run. Returns non-zero on build failure. build_scope() { rm -rf dist && timeout 300 env SITE_SCOPE="$1" npm run build; } # publish_dir DIR REPO LABEL SHA : guard DIR against a collapsed build, then force-push it to REPO's # pages branch carrying the .deployed-sha marker. Returns non-zero (without pushing) on a collapsed # build, or on a commit/push failure, so the caller can collect the failure and alert. Never pushes a # blank/tiny homepage over a good live one. publish_dir() { _dir="$1"; _repo="$2"; _label="$3"; _sha="$4" if [ ! -s "$_dir/index.html" ] || [ "$(wc -c < "$_dir/index.html")" -lt 2000 ]; then echo "!! collapsed build for $_label ($_dir/index.html missing or < 2000 bytes); skipping push" >&2 return 1 fi ensure_repo "$_repo" echo "$_sha" > "$_dir/.deployed-sha" ( cd "$_dir" && git init -q && git checkout -q -b pages && git add -A && \ git -c user.name="atproto deploy" -c user.email="deploy@spindle.local" \ commit -q -m "deploy $_label $_sha" ) || return 1 retry timeout 180 git -C "$_dir" push -f \ "https://gxjansen:${atprotocc_sites}@codeberg.org/gxjansen/$_repo.git" pages }