diff --git a/containers/libreportal/frontend/html/backup-content.html b/containers/libreportal/frontend/html/backup-content.html index 624d52f..937ca7b 100644 --- a/containers/libreportal/frontend/html/backup-content.html +++ b/containers/libreportal/frontend/html/backup-content.html @@ -93,6 +93,9 @@

System config

Global settings, WebUI login & backup-location credentials +
+ No backup yet +

Snapshot the LibrePortal system config to every enabled location so a bare-metal restore is self-sufficient — without it, the credentials needed to reach your own diff --git a/containers/libreportal/frontend/js/components/backup/backup-page.js b/containers/libreportal/frontend/js/components/backup/backup-page.js index bc90606..bb9c13e 100644 --- a/containers/libreportal/frontend/js/components/backup/backup-page.js +++ b/containers/libreportal/frontend/js/components/backup/backup-page.js @@ -550,6 +550,16 @@ class BackupPage { appGrid.innerHTML = apps.map(app => this.renderAppTile(app)).join(''); } + const sysStatus = document.getElementById('backup-system-status'); + if (sysStatus) { + const sys = d.system || {}; + const hasSys = !!sys.latest_snapshot; + sysStatus.innerHTML = ` + + ${hasSys ? 'Last backed up ' + this.formatRelative(sys.latest_time) : 'No backup yet'} + `; + } + if (!locs.length) { locSummary.innerHTML = `

No locations enabled.
`; } else { diff --git a/scripts/backup/engine/engine_dispatch.sh b/scripts/backup/engine/engine_dispatch.sh index d4a8917..81a52d6 100644 --- a/scripts/backup/engine/engine_dispatch.sh +++ b/scripts/backup/engine/engine_dispatch.sh @@ -53,6 +53,7 @@ engineRestoreSystemLatest() { local i="$1"; shift; engineDispatch "$(engineForL 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" "$@"; } engineCheckLocation() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")CheckLocation" "$i" "$@"; } diff --git a/scripts/backup/engine/restic_snapshots.sh b/scripts/backup/engine/restic_snapshots.sh index f0d262d..503d919 100644 --- a/scripts/backup/engine/restic_snapshots.sh +++ b/scripts/backup/engine/restic_snapshots.sh @@ -18,6 +18,22 @@ resticSnapshotsJson() return $rc } +resticSystemSnapshotsJson() +{ + local idx="$1" + local host_filter="$2" + + resticEnvExport "$idx" || return 1 + + local args=(snapshots --json --no-lock --tag "system=config") + [[ -n "$host_filter" ]] && args+=(--host "$host_filter") + + runBackupOp restic "${args[@]}" 2>/dev/null + local rc=$? + resticEnvUnset + return $rc +} + resticSnapshotLatestId() { local idx="$1" diff --git a/scripts/webui/data/generators/backup/webui_backup_dashboard.sh b/scripts/webui/data/generators/backup/webui_backup_dashboard.sh index 65d532b..fe91b9e 100644 --- a/scripts/webui/data/generators/backup/webui_backup_dashboard.sh +++ b/scripts/webui/data/generators/backup/webui_backup_dashboard.sh @@ -55,6 +55,10 @@ webuiGenerateBackupDashboard() primary_idx=$(resticEnabledLocations | head -1) while IFS= read -r app; do [[ -z "$app" ]] && continue + # The WebUI app is reproducible and skipped by backupAllApps — its + # state rides in the system-config snapshot below, so don't show a + # perpetually-"No backup yet" tile for it. + [[ "$app" == "libreportal" ]] && continue local latest_id="" latest_time="" if [[ -n "$primary_idx" ]]; then local snap_json @@ -69,6 +73,17 @@ webuiGenerateBackupDashboard() fi apps_json+="]" + # System-config backup status (tag system=config) — its own tracked item. + local system_id="" system_time="" + local sys_idx + sys_idx=$(resticEnabledLocations | head -1) + if [[ -n "$sys_idx" ]]; then + local sys_json + sys_json=$(engineSystemSnapshotsJson "$sys_idx" "$CFG_INSTALL_NAME" 2>/dev/null) + system_id=$(echo "$sys_json" | grep -o '"short_id":"[^"]*"' | tail -1 | cut -d'"' -f4) + system_time=$(echo "$sys_json" | grep -o '"time":"[^"]*"' | tail -1 | cut -d'"' -f4) + fi + local content="{" content+="\"generated_at\":\"$generated_at\"," content+="\"install_name\":\"${CFG_INSTALL_NAME:-libreportal}\"," @@ -76,7 +91,8 @@ webuiGenerateBackupDashboard() content+="\"verify_after\":${CFG_BACKUP_VERIFY_AFTER:-false}," content+="\"strategy\":\"${CFG_BACKUP_STRATEGY:-auto}\"," content+="\"locations\":$locations_json," - content+="\"apps\":$apps_json" + content+="\"apps\":$apps_json," + content+="\"system\":{\"latest_snapshot\":\"$system_id\",\"latest_time\":\"$system_time\"}" content+="}" echo "$content" | runFileWrite "$output_file"