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
+17 -8
View File
@@ -86,12 +86,13 @@ main() {
fi
# Parse trigger.json (uses python3 which is available on all supported distros)
local action version from_version staging_dir api_source_dir
local action version from_version staging_dir api_source_dir commit
action=$(python3 -c "import json; print(json.load(open('${TRIGGER_FILE}'))['action'])")
version=$(python3 -c "import json; print(json.load(open('${TRIGGER_FILE}'))['version'])")
from_version=$(python3 -c "import json; print(json.load(open('${TRIGGER_FILE}'))['fromVersion'])")
staging_dir=$(python3 -c "import json; print(json.load(open('${TRIGGER_FILE}'))['stagingDir'])")
api_source_dir=$(python3 -c "import json; print(json.load(open('${TRIGGER_FILE}'))['apiSourceDir'])")
commit=$(python3 -c "import json; print(json.load(open('${TRIGGER_FILE}')).get('commit') or '')")
log "=== ${action^^}: v${from_version} -> v${version} ==="
log "Staging: ${staging_dir}"
@@ -114,19 +115,20 @@ main() {
log "Backing up current API files to ${backup_dir}"
# Copy all JS files, package.json, Dockerfile, and tracked subdirs
for item in "$api_source_dir"/*.js "$api_source_dir"/package.json "$api_source_dir"/package-lock.json "$api_source_dir"/Dockerfile "$api_source_dir"/openapi.yaml; do
for item in "$api_source_dir"/*.js "$api_source_dir"/package.json "$api_source_dir"/package-lock.json "$api_source_dir"/Dockerfile "$api_source_dir"/openapi.yaml "$api_source_dir"/VERSION; do
[[ -f "$item" ]] && cp -f "$item" "$backup_dir/" 2>/dev/null || true
done
[[ -d "$api_source_dir/routes" ]] && cp -rf "$api_source_dir/routes" "$backup_dir/"
[[ -d "$api_source_dir/src" ]] && cp -rf "$api_source_dir/src" "$backup_dir/"
# Save version marker
echo "$from_version" > "$backup_dir/VERSION"
# VERSION (commit hash) was copied from api_source_dir above; preserve as-is
# so a rollback restores the original commit marker. The version *string* is
# already encoded in the backup dir name (${from_version}).
cleanup_old_backups
# 4. Copy new files from staging to API source
log "Deploying new API files..."
for item in "$staging_dir"/*.js "$staging_dir"/package.json "$staging_dir"/package-lock.json "$staging_dir"/Dockerfile "$staging_dir"/openapi.yaml; do
for item in "$staging_dir"/*.js "$staging_dir"/package.json "$staging_dir"/package-lock.json "$staging_dir"/Dockerfile "$staging_dir"/openapi.yaml "$staging_dir"/VERSION; do
[[ -f "$item" ]] && cp -f "$item" "$api_source_dir/" 2>/dev/null || true
done
if [[ -d "$staging_dir/routes" ]]; then
@@ -138,6 +140,13 @@ main() {
cp -rf "$staging_dir/src" "$api_source_dir/src"
fi
# Belt-and-suspenders: always write the commit from trigger.json to VERSION,
# even if the tarball didn't include one. The container's self-updater uses
# this to detect the "same version, different commit" case.
if [[ -n "$commit" ]]; then
echo "$commit" > "$api_source_dir/VERSION"
fi
# 5. Rebuild container
log "Rebuilding container..."
cd "$api_source_dir"
@@ -153,7 +162,7 @@ main() {
log "ERROR: Docker build failed — rolling back"
# Restore backup
for item in "$backup_dir"/*.js "$backup_dir"/package.json "$backup_dir"/package-lock.json "$backup_dir"/Dockerfile "$backup_dir"/openapi.yaml; do
for item in "$backup_dir"/*.js "$backup_dir"/package.json "$backup_dir"/package-lock.json "$backup_dir"/Dockerfile "$backup_dir"/openapi.yaml "$backup_dir"/VERSION; do
[[ -f "$item" ]] && cp -f "$item" "$api_source_dir/" 2>/dev/null || true
done
if [[ -d "$backup_dir/routes" ]]; then
@@ -178,7 +187,7 @@ main() {
log "ERROR: Container restart failed — rolling back"
# Restore backup
for item in "$backup_dir"/*.js "$backup_dir"/package.json "$backup_dir"/package-lock.json "$backup_dir"/Dockerfile "$backup_dir"/openapi.yaml; do
for item in "$backup_dir"/*.js "$backup_dir"/package.json "$backup_dir"/package-lock.json "$backup_dir"/Dockerfile "$backup_dir"/openapi.yaml "$backup_dir"/VERSION; do
[[ -f "$item" ]] && cp -f "$item" "$api_source_dir/" 2>/dev/null || true
done
if [[ -d "$backup_dir/routes" ]]; then
@@ -208,7 +217,7 @@ main() {
log "ERROR: Health check failed after update — rolling back"
# Restore backup
for item in "$backup_dir"/*.js "$backup_dir"/package.json "$backup_dir"/package-lock.json "$backup_dir"/Dockerfile "$backup_dir"/openapi.yaml; do
for item in "$backup_dir"/*.js "$backup_dir"/package.json "$backup_dir"/package-lock.json "$backup_dir"/Dockerfile "$backup_dir"/openapi.yaml "$backup_dir"/VERSION; do
[[ -f "$item" ]] && cp -f "$item" "$api_source_dir/" 2>/dev/null || true
done
if [[ -d "$backup_dir/routes" ]]; then