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
+67 -5
View File
@@ -202,6 +202,11 @@ CBLOCK
mkdir -p "$_dir"
cat > "$_dir/README.md"
}
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -626,6 +631,7 @@ networks:
"
fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << FRIGATE_COMPOSE
name: frigate
@@ -673,16 +679,71 @@ FRIGATE_COMPOSE
# reverse_proxy block) stops that trust from being spoofed by a
# request that reaches Frigate's published host port directly,
# bypassing Caddy/Authelia entirely.
# Whether to even OFFER this can't just check "$DOCKER_DIR/authelia"
# locally — Frigate's own box very often has no local Caddy at all
# (configure_caddy_for_service falls back to writing a snippet for
# a remote Caddy machine to pick up, confirmed live: this is the
# normal shape for a dedicated NVR box), in which case Authelia, if
# it exists anywhere, lives on THAT remote Caddy machine instead —
# a box this script has no filesystem access to inspect. Default to
# "y" only when local Authelia is actually confirmed; otherwise still
# offer it (default "n") and sort out local-vs-remote below once the
# admin says yes, rather than silently refusing to ask at all.
local FRIGATE_USE_AUTHELIA="n" FRIGATE_PROXY_SECRET="" AUTH_CONFIG_BLOCK=""
if [ -d "$DOCKER_DIR/authelia" ]; then
echo ""
prompt_yn "Protect Frigate with Authelia SSO (disables Frigate's own login)? (y/n):" "y" FRIGATE_USE_AUTHELIA
fi
local _frigate_local_authelia="n"
[ -d "$DOCKER_DIR/authelia" ] && _frigate_local_authelia="y"
echo ""
prompt_yn "Protect Frigate with Authelia SSO (disables Frigate's own login)? (y/n):" "$_frigate_local_authelia" FRIGATE_USE_AUTHELIA
if [[ "$FRIGATE_USE_AUTHELIA" =~ ^[Yy]$ ]]; then
FRIGATE_PROXY_SECRET="${ENV_MAP[FRIGATE_PROXY_AUTH_SECRET]:-$(generate_password 32)}"
# Local Authelia snippet (import authelia) only actually exists
# in the Caddyfile it's imported into if Authelia is on THAT
# same machine. When it's on this box, that's this box's own
# Caddy — safe to assume. When Caddy itself turns out to be
# remote (below), "local" instead means "local to wherever
# Caddy is", which this script can't see — so ask, rather than
# silently emit an import that would fail Caddy's own reload
# with "file to import not found" on that other machine.
local _frigate_auth_block=" import authelia"
if [ "$_frigate_local_authelia" != "y" ]; then
echo ""
log_info "No local Authelia on this box — Caddy for Frigate may end up on a"
log_info "different machine (decided next)."
local _authelia_with_caddy=""
prompt_yn " Does Authelia run on that SAME machine as Caddy? (y/n):" "y" _authelia_with_caddy
if [[ ! "$_authelia_with_caddy" =~ ^[Yy]$ ]]; then
# Genuinely cross-machine: Authelia is a third box,
# different from both this one and wherever Caddy ends
# up. Needs the explicit header-pinned forward_auth form
# — see CLAUDE.md's "forward_auth to a remote Authelia"
# note for why the bare "authelia:9091" shortcut can't
# be used here and X-Forwarded-Host must be pinned
# explicitly (a second Caddy hop otherwise silently
# evaluates every domain as if it were auth's own
# portal domain — confirmed live, a real incident this
# exact snippet shape was written to prevent).
local _remote_authelia_domain=""
prompt_text " Authelia's own portal domain (e.g. authelia.example.com):" "" _remote_authelia_domain
if [ -n "$_remote_authelia_domain" ]; then
_frigate_auth_block=" forward_auth https://${_remote_authelia_domain} {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
header_up X-Forwarded-Method {method}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Uri {uri}
}"
else
log_warning "No domain entered — falling back to 'import authelia', which will fail"
log_warning "Caddy's reload unless Authelia is actually local to that Caddy machine."
fi
fi
fi
configure_caddy_for_service "Frigate" "frigate:5000" "frigate" \
" import authelia" \
"$_frigate_auth_block" \
" header_up X-Proxy-Secret ${FRIGATE_PROXY_SECRET}"
if [ "${CADDY_SERVICE_CONFIGURED:-false}" = true ]; then
AUTH_CONFIG_BLOCK="auth:
@@ -780,6 +841,7 @@ snapshots:
FRIGATE_CONFIG
fi
backup_if_exists .env
cat > .env << FRIGATE_ENV
FRIGATE_MEDIA=$FRIGATE_MEDIA
CADDY_NET=$SITE_CADDY_NET