Back up existing files before every service overwrites them

Confirmed live: install_frigate()'s fresh-install path overwrote a
working, hand-crafted docker-compose.yml (Frigate + mosquitto +
frigate-notify) with zero backup, because that file's shape didn't match
what frigate.sh's own "existing install" detection knew how to recognize.
Every service's own detection is a judgment call about what counts as
"already installed" and can miss a real setup built outside this repo's
conventions.

lib/common.sh gains backup_if_exists(FILE) — copies FILE to
FILE.bak.<timestamp> if it exists, no-ops otherwise (including DRY_RUN).
Applied before every service's own `cat > docker-compose.yml`/`cat > .env`
write across all 60 services that do one (115 call sites), plus a matching
standalone-mode stub added to every service's own bootstrap block, same
convention already used for port_in_use/find_free_port. This doesn't
replace a service's own update/fresh-reinstall detection — it's the safety
net underneath it, so a wrong detection costs a .bak file to restore from
instead of the original silently disappearing.

Also fixes the actual gap that surfaced this: services/frigate.sh's
Authelia offer only checked for Authelia installed locally on Frigate's
own box, which is never true for a dedicated NVR box with no local Caddy
either (the common shape — Caddy lives elsewhere, snippet-generation mode
already handles that). Now offers Authelia protection unconditionally and,
when Authelia isn't local, asks whether it lives on the same machine as
Caddy (still "import authelia", since that's local to wherever Caddy ends
up) or on a genuinely separate third machine (the explicit
header-pinned forward_auth form, per CLAUDE.md's "forward_auth to a remote
Authelia" note, needed because a bare authelia:9091 shortcut only works
one hop).
This commit is contained in:
Claude
2026-08-26 17:26:17 +00:00
parent 493ee30916
commit bec9228c55
61 changed files with 486 additions and 5 deletions
+26
View File
@@ -802,6 +802,32 @@ write_readme() {
chown "$ACTUAL_USER:$ACTUAL_USER" "$dir/README.md" 2>/dev/null || true
}
# Copies FILE to FILE.bak.<timestamp> if it already exists, right before a
# caller is about to overwrite it with a fresh `cat > FILE` heredoc. No-ops
# in DRY_RUN and silently no-ops if FILE doesn't exist yet (first install,
# nothing to save) — safe to call unconditionally right before every such
# write, fresh install or not.
#
# Confirmed live: install_frigate()'s fresh-install path overwrote a
# working, hand-crafted multi-container docker-compose.yml (Frigate +
# mosquitto + frigate-notify) with zero backup, because that file's shape
# didn't match what the service's own "existing install" detection knew
# how to recognize. Every service's own detection logic is a judgment call
# about what counts as "already installed" and can miss a real setup built
# outside this repo's own conventions — this exists as the safety net
# underneath that judgment call, not a replacement for it: call it right
# before any `cat > FILE` that could clobber something a user already has,
# so a wrong detection costs a `.bak` file to restore from instead of the
# original silently disappearing.
backup_if_exists() {
local file="$1"
[ "$DRY_RUN" = true ] && return 0
[ -f "$file" ] || return 0
local backup="${file}.bak.$(date +%Y%m%d-%H%M%S)"
cp -p "$file" "$backup" 2>/dev/null \
&& log_info "Backed up existing $(basename "$file") to $(basename "$backup")"
}
# ── Host port collision avoidance (shared by every service that publishes a
# fixed host port) ────────────────────────────────────────────────────────────
# With 70+ services in this repo, several ship the same default port (e.g.