DC-038: backup trigger.json + result.json in dashcaddy-update.sh
The host-side updater only backed up code + data/, leaving trigger.json and result.json unarchived. After a failed update, operators had to reconstruct 'what was being attempted' by joining timestamps across files. Now the backup captures both files into a 'update-state/' subdir alongside code + data backups, keyed by from-version. - New `backup_update_state()` function in dashcaddy-update.sh: idempotent, tolerates absent files (cleans up empty subdir), tolerates chattr +i (unlock/copy/relock). - Wired into main() right after `backup_data_dir`, before `cleanup_old_backups`. - Deliberately does NOT auto-restore trigger.json on rollback — the rollback handler reads a fresh trigger.json written by the operator/container; restoring the previous attempt's trigger would clobber the active rollback request. Backups are read-only forensic evidence. - New `dashcaddy-api/scripts/test-dashcaddy-update-backup.sh` (14 assertions, 5 test groups): both-files-present, partial-present, no-files-present, idempotency, main() flow ordering. All 14 pass. - Synced the duplicate at `dashcaddy-api/scripts/dashcaddy-update.sh` (md5-identical to scripts/dashcaddy-update.sh). Tests: 1214/1214 pass (zero change). Lint: 150 warnings, all pre-existing in untouched files (zero new warnings introduced).
This commit is contained in:
@@ -26,6 +26,18 @@ readonly CHANNEL_CONF="${UPDATES_DIR}/channel.conf"
|
||||
readonly DATA_SOURCE_DIR="/opt/dashcaddy/dashcaddy-api/data"
|
||||
readonly DATA_BACKUP_PREFIX="data-backup"
|
||||
|
||||
# Updater state (trigger.json / result.json) backup — keeps the audit trail
|
||||
# (what version we were attempting, what the previous update's outcome was) tied
|
||||
# to the same versioned backup directory as code + data. After a failed update,
|
||||
# operators can inspect what was attempted without correlating timestamps, and
|
||||
# rollback tooling can reconstruct a "what just happened" view of the update
|
||||
# state machine. NOTE: we do NOT auto-restore trigger.json on rollback — the
|
||||
# rollback handler reads a fresh trigger.json written by the operator/container;
|
||||
# restoring the previous attempt's trigger would clobber the active rollback
|
||||
# request. Backups here are read-only forensic evidence.
|
||||
readonly UPDATE_STATE_BACKUP_PREFIX="update-state"
|
||||
readonly TRIGGER_PROCESSING="${TRIGGER_FILE}.processing"
|
||||
|
||||
log() { echo "[dashcaddy-update] $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
||||
|
||||
# Decide if a given release channel is acceptable on this host.
|
||||
@@ -112,6 +124,40 @@ backup_data_dir() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Updater state backup (trigger.json.processing + result.json) ─────────────
|
||||
# Captures what was being attempted + the last result so post-mortem can answer
|
||||
# "why did this fail" without joining timestamps across files. Tolerates absent
|
||||
# files (first-ever run) and locked files (chattr +i). Idempotent — re-running
|
||||
# overwrites the previous backup.
|
||||
backup_update_state() {
|
||||
local backup_dir="$1"
|
||||
local state_dir="${backup_dir}/${UPDATE_STATE_BACKUP_PREFIX}"
|
||||
mkdir -p "$state_dir"
|
||||
|
||||
local copied=0
|
||||
for src in "$TRIGGER_PROCESSING" "$RESULT_FILE"; do
|
||||
if [[ -f "$src" ]]; then
|
||||
# Unlock temporarily if immutable, copy, re-lock.
|
||||
local was_locked=false
|
||||
if lsattr -d "$src" 2>/dev/null | awk '{exit !($1 ~ /i/)}'; then
|
||||
was_locked=true
|
||||
chattr -i "$src" 2>/dev/null || true
|
||||
fi
|
||||
cp -f "$src" "${state_dir}/$(basename "$src")" 2>/dev/null && copied=$(( copied + 1 ))
|
||||
if [[ "$was_locked" == "true" ]]; then
|
||||
chattr +i "$src" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if (( copied > 0 )); then
|
||||
log "Update-state backup: ${copied} file(s) -> ${state_dir}"
|
||||
else
|
||||
log "Update-state backup: nothing to back up (no trigger/result files)"
|
||||
rmdir "$state_dir" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Data restore ──────────────────────────────────────────────────────────────
|
||||
restore_data_dir() {
|
||||
local backup_dir="$1"
|
||||
@@ -329,6 +375,10 @@ main() {
|
||||
# Backup data/ directory (services.json, config.json, credentials, etc.)
|
||||
backup_data_dir "$backup_dir"
|
||||
|
||||
# Backup updater state (trigger.json.processing + result.json) so post-mortem
|
||||
# has a forensic trail tied to this exact version's backup.
|
||||
backup_update_state "$backup_dir"
|
||||
|
||||
cleanup_old_backups
|
||||
|
||||
# 3. Copy new files from staging to API source
|
||||
|
||||
Reference in New Issue
Block a user