LibrePortal/scripts/backup/engine/engine_dispatch.sh
librelad 038d1c0729 fix(backup): system config in scheduled backups + retention (review findings)
Final-review gaps in the system-config backup:

1. Scheduled (cron) backups skipped it — backupScheduleEnabledApps only queued
   per-app backups, so the daily schedule never refreshed the system config (and
   thus the backup-location creds could go stale). Now it queues a
   `libreportal backup system` task (or runs inline on terminal-only installs),
   and skips the reproducible libreportal app for consistency with backupAllApps.

2. No retention on system snapshots — they bypass backupAppStart's per-app forget,
   so they accumulated unbounded. Add resticForgetSystem (tag system=config,
   respects append-only + the same keep-* policy) + engineForgetSystem dispatcher;
   backupSystemConfig now applies retention across all locations after snapshotting.

Verified with stubs: backupSystemConfig snapshots AND prunes on every location;
engineForgetSystem pairs with resticForgetSystem; scheduled createTaskFile call
matches the existing 3-arg signature.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:48:18 +01:00

126 lines
5.3 KiB
Bash

#!/bin/bash
# Per-location engine dispatcher. Resolves the engine for a given location
# (CFG_BACKUP_LOC_N_ENGINE → CFG_BACKUP_ENGINE → 'restic'), then forwards to
# the engine adapter's `<engine><FunctionName>` implementation. Adapters live
# in scripts/backup/engine/<engine>_*.sh; today restic_*.sh is the only one.
engineForLocation()
{
local idx="$1"
local var="CFG_BACKUP_LOC_${idx}_ENGINE"
local e="${!var}"
[[ -z "$e" ]] && e="${CFG_BACKUP_ENGINE:-restic}"
echo "$e"
}
engineKnownIds()
{
# List adapter implementations discovered by looking for the canonical
# `<engine>BackupAppToLocation` function name registered at source time.
compgen -A function 2>/dev/null | grep -oE '^[a-z]+BackupAppToLocation$' | sed 's/BackupAppToLocation//' | sort -u
}
engineDispatch()
{
# Internal helper: call $1=<engine><FunctionName> with the remaining args.
# Falls back with a clear error if the adapter doesn't implement it.
local fn="$1"
shift
if ! declare -f "$fn" >/dev/null 2>&1; then
isError "Backup engine has no '$fn' implementation"
return 1
fi
"$fn" "$@"
}
# ---- Idx-scoped dispatchers ----------------------------------------------------
# Local/removable-drive safety guard runs before init, readiness, and any backup
# write (see backupLocationLocalGuard) — refuses to write when a REQUIRE_MOUNT
# drive isn't mounted, so restic never fills the system disk.
engineInitLocation() { local i="$1"; backupLocationLocalGuard "$i" || return 1; engineDispatch "$(engineForLocation "$i")InitLocation" "$i"; }
engineEnsureLocationReady() { local i="$1"; backupLocationLocalGuard "$i" || return 1; engineDispatch "$(engineForLocation "$i")EnsureLocationReady" "$i"; }
enginePasswordEnsure() { local i="$1"; engineDispatch "$(engineForLocation "$i")PasswordEnsure" "$i"; }
engineLocationUri() { local i="$1"; engineDispatch "$(engineForLocation "$i")LocationUri" "$i"; }
engineLocationStats() { local i="$1"; engineDispatch "$(engineForLocation "$i")LocationStats" "$i"; }
engineEnvExport() { local i="$1"; engineDispatch "$(engineForLocation "$i")EnvExport" "$i"; }
engineEnvUnset() { local i="$1"; engineDispatch "$(engineForLocation "${i:-1}")EnvUnset"; }
engineBackupApp() { local i="$1"; shift; backupLocationLocalGuard "$i" || return 1; engineDispatch "$(engineForLocation "$i")BackupAppToLocation" "$i" "$@"; }
engineBackupSystem() { local i="$1"; shift; backupLocationLocalGuard "$i" || return 1; engineDispatch "$(engineForLocation "$i")BackupSystemToLocation" "$i" "$@"; }
engineRestoreSystemLatest() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")RestoreSystemLatest" "$i" "$@"; }
engineRestoreSnapshot() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")RestoreSnapshot" "$i" "$@"; }
engineSnapshotLatestId() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")SnapshotLatestId" "$i" "$@"; }
engineSnapshotsJson() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")SnapshotsJson" "$i" "$@"; }
engineSystemSnapshotsJson() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")SystemSnapshotsJson" "$i" "$@"; }
engineSnapshotListFiles() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")SnapshotListFiles" "$i" "$@"; }
engineForgetApp() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")ForgetApp" "$i" "$@"; }
engineForgetSystem() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")ForgetSystem" "$i" "$@"; }
engineCheckLocation() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")CheckLocation" "$i" "$@"; }
engineDumpFile() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")DumpFile" "$i" "$@"; }
# ---- Aggregate helpers (iterate enabled locations) ---------------------------
engineInstallAll()
{
if ! declare -f resticEnabledLocations >/dev/null 2>&1; then
isError "engineInstallAll: location helpers not loaded yet"
return 1
fi
declare -A seen
local idx engine fn
while IFS= read -r idx; do
[[ -z "$idx" ]] && continue
engine=$(engineForLocation "$idx")
[[ -n "${seen[$engine]}" ]] && continue
seen[$engine]=1
fn="${engine}Install"
if declare -f "$fn" >/dev/null 2>&1; then
"$fn"
fi
done < <(resticEnabledLocations)
}
engineInitAllLocations()
{
isHeader "Backup Location Initialization"
local idx
while IFS= read -r idx; do
[[ -z "$idx" ]] && continue
engineInitLocation "$idx"
done < <(resticEnabledLocations)
}
engineEnsureAllLocationsReady()
{
engineInstallAll
local idx
while IFS= read -r idx; do
[[ -z "$idx" ]] && continue
engineEnsureLocationReady "$idx"
done < <(resticEnabledLocations)
}
engineForgetAppAllLocations()
{
local app="$1"
local idx
while IFS= read -r idx; do
[[ -z "$idx" ]] && continue
engineForgetApp "$idx" "$app"
done < <(resticEnabledLocations)
}
engineCheckAllLocations()
{
local pct="$1"
local idx
local failed=0
while IFS= read -r idx; do
[[ -z "$idx" ]] && continue
engineCheckLocation "$idx" "$pct" || failed=$((failed + 1))
done < <(resticEnabledLocations)
return $failed
}