Compare commits

...

2 Commits

Author SHA1 Message Date
librelad
a11a7a7a71 Merge claude/2 2026-05-23 15:34:17 +01:00
librelad
27ad517626 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>
2026-05-23 15:34:17 +01:00
12 changed files with 67 additions and 8 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",