feat(updates): seamless release flow — push-notify, VERSION copy, robust mirror

- self-updater: per-instance notify secret (auto-generated), notifyAndApply()
  triggers an immediate check+apply for the publishing host
- routes: POST /api/system/update-notify (X-DashCaddy-Notify-Secret gated,
  added to public-routes allowlist so TOTP doesn't block machine-to-machine)
- dashcaddy-update.sh: include VERSION in backup/deploy/rollback copy lists;
  belt-and-suspenders write trigger.json commit to VERSION post-deploy.
  Fixes drift where /app/VERSION stayed at the old commit after self-update.
- release.sh: mirror failures are non-fatal+loud; HTTP-verify get2 after
  rsync; auto-notify co-located instance via /opt/dashcaddy/updates/notify-secret
  (or honour DASHCADDY_NOTIFY_TARGETS for multi-instance setups).
This commit is contained in:
Sami
2026-05-16 23:46:53 -07:00
parent edac587c5c
commit c66fe498b6
6 changed files with 148 additions and 11 deletions
+62 -2
View File
@@ -116,9 +116,59 @@ ssh "$RELEASE_HOST" "set -e
EOF
"
# ── 6. Mirror to backup host ──────────────────────────────────────────────
# ── 6. Mirror to backup host (non-fatal — primary is canonical) ───────────
echo "[6/6] Mirroring to $MIRROR_HOST"
ssh "$RELEASE_HOST" "rsync -aq --delete /var/www/get.dashcaddy.net/release/ $MIRROR_HOST:/var/www/get2.dashcaddy.net/release/"
MIRROR_OK=true
if ssh "$RELEASE_HOST" "rsync -aq --delete /var/www/get.dashcaddy.net/release/ $MIRROR_HOST:/var/www/get2.dashcaddy.net/release/" 2>&1; then
echo " → mirrored"
else
MIRROR_OK=false
echo " ! MIRROR FAILED — get2.dashcaddy.net is stale. Primary continues." >&2
fi
# ── Optional: notify known instances to update immediately ────────────────
# Set DASHCADDY_NOTIFY_TARGETS="<url>|<secret>,<url>|<secret>" to push.
# If unset, we try the co-located instance at localhost:3001 using the
# generated secret at /opt/dashcaddy/updates/notify-secret (silently skipped
# if either is missing).
if [[ -n "${DASHCADDY_NOTIFY_TARGETS:-}" ]]; then
echo "[notify] pushing release to configured targets"
IFS=',' read -ra TARGETS <<< "$DASHCADDY_NOTIFY_TARGETS"
for t in "${TARGETS[@]}"; do
url="${t%%|*}"
secret="${t#*|}"
[[ "$url" == "$secret" ]] && { echo " ! malformed target ($t) — need url|secret" >&2; continue; }
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 -X POST \
-H "X-DashCaddy-Notify-Secret: $secret" \
-H 'Content-Type: application/json' \
-d "{\"version\":\"$VERSION\",\"commit\":\"$COMMIT\"}" \
"$url" || true)
if [[ "$code" =~ ^2[0-9][0-9]$ ]]; then
echo "$url notified (HTTP $code)"
else
echo " ! $url notify FAILED (HTTP $code)" >&2
fi
done
else
# Co-located default
LOCAL_NOTIFY=$(ssh "$RELEASE_HOST" '
if [[ -r /opt/dashcaddy/updates/notify-secret ]] && curl -fsS --max-time 2 http://localhost:3001/api/health >/dev/null 2>&1; then
secret=$(cat /opt/dashcaddy/updates/notify-secret)
curl -s -o /dev/null -w "%{http_code}" --max-time 5 -X POST \
-H "X-DashCaddy-Notify-Secret: $secret" \
-H "Content-Type: application/json" \
-d "{\"version\":\"'"$VERSION"'\",\"commit\":\"'"$COMMIT"'\"}" \
http://localhost:3001/api/system/update-notify
else
echo skip
fi
' 2>/dev/null || true)
case "$LOCAL_NOTIFY" in
2*) echo "[notify] co-located instance on $RELEASE_HOST → HTTP $LOCAL_NOTIFY" ;;
skip) ;; # no secret or instance not up — silent
*) echo "[notify] co-located instance notify failed (HTTP $LOCAL_NOTIFY)" >&2 ;;
esac
fi
# ── Verify ───────────────────────────────────────────────────────────────
echo
@@ -132,5 +182,15 @@ SHA_HTTP="$(curl -fsSL --max-time 30 "https://get.dashcaddy.net/release/dashcadd
[[ "$SHA_LOCAL" == "$SHA_HTTP" ]] || { echo "SHA mismatch on served tarball" >&2; exit 1; }
echo " tarball sha256 → $SHA_HTTP"
if [[ "$MIRROR_OK" == "true" ]]; then
GET2_VER="$(curl -fsSL --max-time 5 https://get2.dashcaddy.net/release/version.json 2>/dev/null | node -p "try{JSON.parse(require('fs').readFileSync('/dev/stdin')).version}catch{'unreachable'}" 2>/dev/null || echo unreachable)"
if [[ "$GET2_VER" == "$VERSION" ]]; then
echo " get2.dashcaddy.net → $GET2_VER"
else
echo " ! get2.dashcaddy.net serves '$GET2_VER' (expected $VERSION) — check Caddy/DNS for get2" >&2
fi
fi
echo
echo "Done. v$VERSION published from commit $COMMIT."
[[ "$MIRROR_OK" == "true" ]] || echo "(reminder: mirror to get2 failed — investigate $MIRROR_HOST)"