feat(backup): per-app strategy override (advanced, context-aware)
Adds CFG_<APP>_BACKUP_STRATEGY (default auto) so an app's backup strategy can be overridden from its Advanced config tab, taking precedence over the global default. Added to the 10 live-capable apps, so the dropdown's 'live' option only appears where it actually works. - backupResolveStrategy now checks the per-app override before the global value. - backupAppLiveCapable / backupAppStrategyOptions expose capability + the valid option set; predicate helpers hardened with explicit returns so they behave identically with or without shell errexit. - BACKUP_STRATEGY field mapping (select, advanced) renders the dropdown. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
ab101691cc
commit
27ad517626
@ -17,6 +17,7 @@
|
||||
CFG_AUTHELIA_APP_NAME=authelia
|
||||
CFG_AUTHELIA_REQUIRES="domain,traefik"
|
||||
CFG_AUTHELIA_BACKUP=true
|
||||
CFG_AUTHELIA_BACKUP_STRATEGY=auto
|
||||
CFG_AUTHELIA_COMPOSE_FILE=default
|
||||
CFG_AUTHELIA_HEALTHCHECK=true
|
||||
CFG_AUTHELIA_AUTHELIA=false
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#
|
||||
CFG_BOOKSTACK_APP_NAME=bookstack
|
||||
CFG_BOOKSTACK_BACKUP=true
|
||||
CFG_BOOKSTACK_BACKUP_STRATEGY=auto
|
||||
CFG_BOOKSTACK_COMPOSE_FILE=default
|
||||
CFG_BOOKSTACK_HEALTHCHECK=true
|
||||
CFG_BOOKSTACK_AUTHELIA=false
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#
|
||||
CFG_HEADSCALE_APP_NAME=headscale
|
||||
CFG_HEADSCALE_BACKUP=true
|
||||
CFG_HEADSCALE_BACKUP_STRATEGY=auto
|
||||
CFG_HEADSCALE_COMPOSE_FILE=default
|
||||
CFG_HEADSCALE_HEALTHCHECK=true
|
||||
CFG_HEADSCALE_BASIC_AUTH_PASS=RANDOMIZEDPASSWORD1
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#
|
||||
CFG_INVIDIOUS_APP_NAME=invidious
|
||||
CFG_INVIDIOUS_BACKUP=false
|
||||
CFG_INVIDIOUS_BACKUP_STRATEGY=auto
|
||||
CFG_INVIDIOUS_COMPOSE_FILE=default
|
||||
CFG_INVIDIOUS_HEALTHCHECK=false
|
||||
CFG_INVIDIOUS_AUTHELIA=false
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#
|
||||
CFG_LINKDING_APP_NAME=linkding
|
||||
CFG_LINKDING_BACKUP=true
|
||||
CFG_LINKDING_BACKUP_STRATEGY=auto
|
||||
CFG_LINKDING_COMPOSE_FILE=default
|
||||
CFG_LINKDING_HEALTHCHECK=true
|
||||
CFG_LINKDING_AUTHELIA=false
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#
|
||||
CFG_MASTODON_APP_NAME=mastodon
|
||||
CFG_MASTODON_BACKUP=true
|
||||
CFG_MASTODON_BACKUP_STRATEGY=auto
|
||||
CFG_MASTODON_COMPOSE_FILE=default
|
||||
CFG_MASTODON_HEALTHCHECK=true
|
||||
CFG_MASTODON_AUTHELIA=false
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
#
|
||||
CFG_NEXTCLOUD_APP_NAME=nextcloud
|
||||
CFG_NEXTCLOUD_BACKUP=true
|
||||
CFG_NEXTCLOUD_BACKUP_STRATEGY=auto
|
||||
CFG_NEXTCLOUD_COMPOSE_FILE=default
|
||||
CFG_NEXTCLOUD_HEALTHCHECK=true
|
||||
CFG_NEXTCLOUD_AUTHELIA=false
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#
|
||||
CFG_OWNCLOUD_APP_NAME=owncloud
|
||||
CFG_OWNCLOUD_BACKUP=true
|
||||
CFG_OWNCLOUD_BACKUP_STRATEGY=auto
|
||||
CFG_OWNCLOUD_COMPOSE_FILE=default
|
||||
CFG_OWNCLOUD_HEALTHCHECK=true
|
||||
CFG_OWNCLOUD_AUTHELIA=false
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#
|
||||
CFG_TRILIUM_APP_NAME=trilium
|
||||
CFG_TRILIUM_BACKUP=true
|
||||
CFG_TRILIUM_BACKUP_STRATEGY=auto
|
||||
CFG_TRILIUM_COMPOSE_FILE=default
|
||||
CFG_TRILIUM_HEALTHCHECK=true
|
||||
CFG_TRILIUM_AUTHELIA=false
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#
|
||||
CFG_VAULTWARDEN_APP_NAME=vaultwarden
|
||||
CFG_VAULTWARDEN_BACKUP=true
|
||||
CFG_VAULTWARDEN_BACKUP_STRATEGY=auto
|
||||
CFG_VAULTWARDEN_COMPOSE_FILE=default
|
||||
CFG_VAULTWARDEN_HEALTHCHECK=false
|
||||
CFG_VAULTWARDEN_AUTHELIA=false
|
||||
|
||||
@ -55,7 +55,8 @@ backupDbDescriptors()
|
||||
backupDbHasDescriptors()
|
||||
{
|
||||
local app="$1"
|
||||
[[ -n "$(backupDbDescriptors "$app")" ]]
|
||||
if [[ -n "$(backupDbDescriptors "$app")" ]]; then return 0; fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# True when the app carries `libreportal.backup.live: "true"` — i.e. its data is
|
||||
@ -65,26 +66,60 @@ backupAppIsLiveSafe()
|
||||
local app="$1"
|
||||
local compose="$containers_dir$app/docker-compose.yml"
|
||||
[[ -f "$compose" ]] || return 1
|
||||
grep -qE '^[[:space:]]*libreportal\.backup\.live[[:space:]]*:[[:space:]]*["'\'']?true' "$compose" 2>/dev/null
|
||||
if grep -qE '^[[:space:]]*libreportal\.backup\.live[[:space:]]*:[[:space:]]*["'\'']?true' "$compose" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Resolve the effective strategy for one app. Explicit settings are honoured as
|
||||
# power-user overrides; the default "auto" goes live only where we can guarantee
|
||||
# consistency (a dumpable database, or an app blessed live-safe) and otherwise
|
||||
# falls back to the always-safe stop-snapshot-start.
|
||||
# An app can be backed up live without downtime when we can make it consistent:
|
||||
# it has a dumpable database, or it is explicitly blessed live-safe.
|
||||
backupAppLiveCapable()
|
||||
{
|
||||
local app="$1"
|
||||
if backupDbHasDescriptors "$app"; then return 0; fi
|
||||
if backupAppIsLiveSafe "$app"; then return 0; fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Strategy options valid for one app, in .config "[a:b|c:d]" syntax. live is
|
||||
# offered only where the app can actually do it, so the UI never shows a choice
|
||||
# that would just fall back to stop.
|
||||
backupAppStrategyOptions()
|
||||
{
|
||||
local app="$1"
|
||||
local opts="auto:Automatic — recommended|stop-snapshot-start:Stop → snapshot → start|pause-snapshot-unpause:Pause → snapshot → unpause"
|
||||
if backupAppLiveCapable "$app"; then
|
||||
opts="$opts|live:Live — no downtime"
|
||||
fi
|
||||
echo "$opts"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Resolve the effective strategy for one app. Order of precedence:
|
||||
# 1. per-app override CFG_<APP>_BACKUP_STRATEGY (advanced, defaults to auto)
|
||||
# 2. global default CFG_BACKUP_STRATEGY (auto)
|
||||
# An explicit stop/pause/live is honoured as-is; "auto" goes live only where the
|
||||
# app is live-capable and otherwise uses the always-safe stop-snapshot-start.
|
||||
backupResolveStrategy()
|
||||
{
|
||||
local app="$1"
|
||||
local s="${CFG_BACKUP_STRATEGY:-auto}"
|
||||
local override_key="CFG_${app^^}_BACKUP_STRATEGY"
|
||||
local s="${!override_key}"
|
||||
if [[ -z "$s" || "$s" == "auto" ]]; then
|
||||
s="${CFG_BACKUP_STRATEGY:-auto}"
|
||||
fi
|
||||
|
||||
case "$s" in
|
||||
live|pause-snapshot-unpause|stop-snapshot-start)
|
||||
echo "$s"; return 0 ;;
|
||||
esac
|
||||
if backupDbHasDescriptors "$app" || backupAppIsLiveSafe "$app"; then
|
||||
if backupAppLiveCapable "$app"; then
|
||||
echo "live"
|
||||
else
|
||||
echo "stop-snapshot-start"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Deterministic dump filename for a descriptor — backup writes it, restore reads
|
||||
|
||||
@ -159,6 +159,20 @@ PORTEOF
|
||||
"tooltip": "Enable automatic backups for this application",
|
||||
"default": false
|
||||
},
|
||||
"BACKUP_STRATEGY": {
|
||||
"category": "advanced",
|
||||
"label": "Backup Strategy",
|
||||
"type": "select",
|
||||
"tooltip": "How this app is quiesced before its backup snapshot. Automatic picks the safest zero-downtime method available (a live, consistent database dump for this app), and falls back to stopping the app if a live dump ever fails. Only shown for apps that can be backed up live.",
|
||||
"advanced": true,
|
||||
"options": [
|
||||
{"value": "auto", "label": "Automatic (recommended)"},
|
||||
{"value": "stop-snapshot-start", "label": "Stop → snapshot → start"},
|
||||
{"value": "pause-snapshot-unpause", "label": "Pause → snapshot → unpause"},
|
||||
{"value": "live", "label": "Live — no downtime"}
|
||||
],
|
||||
"default": "auto"
|
||||
},
|
||||
"MONITORING": {
|
||||
"category": "features",
|
||||
"label": "Export metrics to Grafana",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user