3 Commits

Author SHA1 Message Date
librelad
c9779d6581 restore: create staging as the principal that writes to it
`restore system` reported

    ✓ Success System config restored to: /libreportal-system/restore/system-config

for a directory that did not exist. Nothing had been written — on the step the
whole restore ordering depends on, since the system config carries every other
backup location's credentials.

Restore stages through $SYSTEM_DIR, which the manager owns, but the thing that
writes into the staging tree is restic, and runBackupOp runs it as the container
user. Both call sites created the directory as the wrong principal, in opposite
directions:

  backupRestoreSystemConfig  runFileOp mkdir  -> container user; denied on the
                             0751 manager-owned restore_dir, and unchecked
  storageRestoreAppTo        runInstallOp mkdir -> manager; restic could then
                             not create anything beneath it

Restic reports a permission denial as "ignoring error ..." and still exits 0, so
the callers' success checks were satisfied either way.

libreportal-ownership gains restore-stage (creates it cowner:MANAGER 0750 —
owner writes, manager traverses to confirm and review) and restore-unstage
(removes it; neither principal can, so staging trees simply accumulated). Both
confine the path to one component directly under the restore/migrate area.
footprint_version 8 -> 9.

backupRestoreSystemConfig now verifies the tree landed as the user that wrote
it, because the manager cannot read inside its own staging directory.

Verified on a live install: system config stages 57 real files, and the
relocation branch of storageRestoreAppTo ran for the first time — speedtest
restored from a snapshot taken at /libreportal-containers/speedtest into
/libreportal-alt/speedtest via stage-and-move, staging cleaned up afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:41:21 +01:00
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
librelad
fe770ae699 feat(backup): system-config snapshot + skip the reproducible WebUI; reserved-name docs
(a) Docs: reserve tools/ scripts/ resources/ as LibrePortal folder names (apps must
not bind-mount to them); document resources/ as the home for nest-able data AND for
.sh payloads that execute on load (vs scripts/ for sourced functions); document the
backup model (what's captured vs reproducible).

(b) System-config backup so a bare-metal restore is self-sufficient — this is why
the system root is its own tree. New scripts/backup/system/backup_system.sh:
- backupSystemConfig snapshots <system>/configs (global settings, WebUI creds, and
  the BACKUP-LOCATION creds — otherwise the keys to reach your own backups live only
  on the box) to every enabled location. Lightweight static-dir snapshot — it does
  NOT go through backupAppStart (no containers to quiesce / DBs to dump).
- restic adapter resticBackupSystemToLocation (tag system=config) + dispatcher
  engineBackupSystem; restore via resticRestoreSystemLatest / engineRestoreSystemLatest
  + backupRestoreSystemConfig (restores to a STAGING dir — never auto-overwrites
  live config).
- backupAllApps runs it after the app loop.

WebUI exclusion: backupAllApps skips the 'libreportal' app — its frontend + generated
JSON regenerate, and its only state (the login) is in the system config now captured
above. Nothing in its data dir warrants a snapshot.

Verified with stubs: app loop skips libreportal + invokes the system backup; the
system backup dispatches to both locations; backup/restore function names pair with
the dispatcher. NOTE: restic-only (the sole live engine adapter); end-to-end repo
round-trip still needs a live box before being relied on.

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