The "Refreshing backup data..." step on every WebUI update fired one `restic stats` (restore-size mode — the slowest restic op) plus ~4 identical unfiltered `restic snapshots --json` pulls per enabled location (dashboard, snapshots, app-status, migrate each pulled their own), all over a fresh SSH connection for remote repos, on every pass with no throttle — the slow, "frozen"-looking line users hit on poor links. Two fixes: 1. Dedupe. engineSnapshotsJson transparently memoises the first unfiltered whole-repo pull per location to a shared cache dir (LP_SNAP_CACHE_DIR, set by webui_updater around the chain), so the four generators reuse one restic call instead of four. Filtered and failed/empty pulls always fall through to a live call. 2. Throttle. Backups and location changes already regenerate this data live when they happen, so the routine pass is only a drift catch-up. Split the cheap local-only generators (engines/schema/passwords — no remote I/O) out to always run, and gate the remote pull behind CFG_BACKUP_DASHBOARD_REFRESH_INTERVAL (minutes, default 30, 0 = every update). A completed backup touches a dirty marker that forces the next pass to pull; WEBUI_UPDATER_FORCE still forces a full refresh. Net: N locations x 5 remote restic calls every update -> 1 call per location, only when something actually changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
145 lines
6.2 KiB
Bash
145 lines
6.2 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" "$@"; }
|
|
# Whole-repo snapshot list. The WebUI backup refresh pulls this the same way
|
|
# (no filters) from four generators per location — dashboard, snapshots,
|
|
# app-status and migrate — which on a remote (SSH) repo is four identical restic
|
|
# round-trips. When LP_SNAP_CACHE_DIR is set (webui_updater wraps the refresh
|
|
# chain with it), memoise the first unfiltered pull per location to a file the
|
|
# sibling generators reuse; filtered/parameterised calls (extra args) and any
|
|
# failed/empty pull always fall through to a live restic call.
|
|
engineSnapshotsJson() {
|
|
local i="$1"; shift
|
|
if [[ $# -eq 0 && -n "${LP_SNAP_CACHE_DIR:-}" ]]; then
|
|
local _cf="${LP_SNAP_CACHE_DIR}/snapshots_${i}.json"
|
|
[[ -s "$_cf" ]] && { cat "$_cf"; return 0; }
|
|
local _out _rc
|
|
_out=$(engineDispatch "$(engineForLocation "$i")SnapshotsJson" "$i"); _rc=$?
|
|
[[ $_rc -eq 0 && -n "$_out" ]] && printf '%s' "$_out" > "$_cf" 2>/dev/null
|
|
printf '%s' "$_out"
|
|
return $_rc
|
|
fi
|
|
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
|
|
}
|