From c9c456f3409d6a4b3c9bdcebd54422998d489bac Mon Sep 17 00:00:00 2001 From: MickLesk Date: Fri, 19 Jun 2026 22:47:03 +0200 Subject: [PATCH] feat(tools): auto-restore update backups on failure Arm create_backup with an ERR trap, add clear_update_backup, and ship a CI checker for update scripts that mutate config without backups. Co-authored-by: Cursor --- misc/tools.func | 20 +++++++++++++ tools/ci/check-update-backup.sh | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100755 tools/ci/check-update-backup.sh diff --git a/misc/tools.func b/misc/tools.func index 6a0c3ed88..72183c842 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -1162,6 +1162,7 @@ create_backup() { if [[ -f "$manifest" ]]; then msg_ok "Existing backup found at ${store}, skipping backup" + trap '_restore_update_backup_on_error' ERR return 0 fi @@ -1186,6 +1187,24 @@ create_backup() { echo "$path" >>"$manifest" done msg_ok "Backed up data to ${store}" + trap '_restore_update_backup_on_error' ERR +} + +_restore_update_backup_on_error() { + local _err=$? + trap - ERR + if [[ -f "${BACKUP_DIR:-/opt/${NSAPP:-app}.backup}/.manifest" ]]; then + msg_error "Update failed (exit ${_err}) – restoring backup" + restore_backup + fi + exit "${_err:-1}" +} + +clear_update_backup() { + local store="${BACKUP_DIR:-/opt/${NSAPP:-app}.backup}" + [[ -d "$store" ]] || return 0 + rm -rf "$store" + trap - ERR } restore_backup() { @@ -1208,6 +1227,7 @@ restore_backup() { cp -a "$src" "$path" done <"$manifest" rm -rf "$store" + trap - ERR msg_ok "Restored data" } diff --git a/tools/ci/check-update-backup.sh b/tools/ci/check-update-backup.sh new file mode 100755 index 000000000..fe84750a7 --- /dev/null +++ b/tools/ci/check-update-backup.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Copyright (c) 2021-2026 community-scripts ORG +# License: MIT +# +# Flags ct/*.sh update_script blocks that mutate config/data destructively +# without calling create_backup. Used in CI / local review before merge. + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +CT_DIR="${ROOT}/ct" +FAIL=0 +CHECKED=0 +FLAGGED=0 + +check_file() { + local file="$1" + local base content block + base="$(basename "$file")" + content="$(<"$file")" + [[ "$content" == *"function update_script"* ]] || return 0 + CHECKED=$((CHECKED + 1)) + + block="$(python3 - "$file" <<'PY' +import re, sys +text = open(sys.argv[1]).read() +m = re.search(r'function update_script\(\).*?(?=^function |\Z)', text, re.S | re.M) +print(m.group() if m else "") +PY +)" + + [[ -n "$block" ]] || return 0 + [[ "$block" == *"create_backup"* ]] && return 0 + + if echo "$block" | grep -qE 'sed -i|\.env|settings\.(py|json)|config\.(json|yml|yaml)|/etc/[^ ]+\.(conf|env)'; then + if echo "$block" | grep -qE 'rm -rf|find .* -delete|mv .*\.(bak|old)'; then + echo "MISSING create_backup: ct/${base}" + FLAGGED=$((FLAGGED + 1)) + FAIL=1 + fi + fi +} + +for f in "$CT_DIR"/*.sh; do + [[ -f "$f" ]] || continue + check_file "$f" +done + +echo "Checked ${CHECKED} update scripts, flagged ${FLAGGED} without create_backup" +exit "$FAIL"