docs: v0.99.0 — CHANGELOG/CONTEXT/REUSE/README for the restore-path fixes (F1/F3/O4)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
+32
-11
@@ -441,8 +441,14 @@ backups/primary/<app>/
|
||||
Docker image** — only the pinned image tag(s) (re-pulled on restore) and the *names* of the secret /
|
||||
`data_key` env vars. Rationale: app.yaml + the encryption key live on the guest rootfs → already in
|
||||
the PBS whole-guest snapshot, and the hub is deliberately zero-knowledge. Restore recovers the
|
||||
original secrets from the guest's own app.yaml (live, or via PBS) and **regenerates nothing**; for a
|
||||
`data_key` app it **fails closed** (refuse + warn) if the key can't be recovered.
|
||||
original secrets from the guest's own app.yaml (live, or via PBS); for a `data_key` app it
|
||||
**fails closed** (refuse + warn) if the key can't be recovered — data-keys are NEVER generated.
|
||||
**Resettable secrets (O4, v0.99.0):** an unrecoverable resettable secret (DB password etc.) gets a
|
||||
**generated replacement** from its catalog `generate` spec (`stacks.GenerateSecretForField` via the
|
||||
`backup.SetSecretGenerator` seam) instead of redeploying blank (which failed compose-up); the new
|
||||
value persists encrypted through the normal `RecreateStackFromUnit` → `SaveAppConfig` path. Fields
|
||||
with no `generate` spec still proceed with a loud "may fail to start" WARN. Residual case: a restored
|
||||
volume tar carrying the OLD internal credential hash may still need a manual in-DB reset.
|
||||
- Helpers: `RecoveryUnitPath` / `RecoveryUnitComposePath` / `RecoveryUnitManifestPath`
|
||||
(`internal/appbackup/paths.go`). Capture: `Manager.CaptureRecoveryUnit` (`internal/backup/recovery_unit.go`),
|
||||
run from the daily DB dump and the periodic `RefreshCache` (idempotent checksum-skip). The non-secret
|
||||
@@ -492,15 +498,23 @@ re-validates the pin off-disk at run time. `Manager.Tier2Info(stackName)` is the
|
||||
- **Validation** after each dump: checks file size, header presence, counts `CREATE TABLE`
|
||||
- Results cached in `settings.json` surviving container restarts
|
||||
|
||||
**Phase 1b — Docker Volume Dumps** (`internal/backup/backup.go`, runs after DB dumps)
|
||||
**Phase 1b — Docker Volume Dumps** (`internal/backup/backup.go` `runVolumeDumps`, part of the same run)
|
||||
|
||||
- Iterates all deployed stacks that have Docker named volumes (`GetDockerVolumes()`)
|
||||
- **v0.34.0:** Each stack is stopped before dump, restarted after (`DumpAppVolumesSafe()`) — prevents inconsistent tars of live databases. Protected stacks (traefik, etc.) that reject StopStack are skipped with a warning.
|
||||
- **F3 (v0.99.0):** re-wired into the nightly/manual app-data backup run (`runDBDumpsInternal`) —
|
||||
after the restic removal `DumpAppVolumesSafe` had no caller, so `volume-dumps/` was never produced.
|
||||
Runs AFTER the DB dumps and BEFORE `captureAllRecoveryUnits` so the manifests enumerate fresh tars.
|
||||
- Gate order (load-bearing): protected-stack (`cfg.IsProtectedStack`) and has-volumes
|
||||
(`GetDockerVolumes()`) checks come BEFORE `DumpAppVolumesSafe` — the Safe variant stops the stack
|
||||
before its own volume check, so unconditional calls would bounce every volume-less app nightly.
|
||||
Disconnected/decommissioned drives skip with the same summary style as the DB loop.
|
||||
- Each volume-bearing stack is stopped before dump, restarted after (`DumpAppVolumesSafe()`) —
|
||||
prevents inconsistent tars of live databases.
|
||||
- For each volume: `docker run --rm -v <vol>:/vol:ro -v <dumpDir>:/out alpine tar cf /out/<vol>.tar -C /vol .`
|
||||
- 10-minute timeout per volume; warnings on failure (non-fatal)
|
||||
- 10-minute timeout per volume; a per-stack failure lands in the run summary as `FAIL <app> volumes:`,
|
||||
flips the run's Success flag and fails the run (no silent partial) — other stacks still proceed
|
||||
- Stale tars cleaned up (volumes that no longer exist)
|
||||
- Volume names resolved with project prefix via `ResolveDockerVolumeNames()` (e.g., `mealie_mealie_data`)
|
||||
- Dumps written to `AppVolumeDumpPath(appDrive, stackName)`
|
||||
- Dumps written to `AppVolumeDumpPath(nsRoot, stackName)`
|
||||
|
||||
**Phase 2 — Restic Snapshot** (`internal/backup/restic.go`, scheduled 03:00)
|
||||
|
||||
@@ -589,10 +603,17 @@ appear in the restore dropdown with per-app snapshot filtering.
|
||||
| DB only, no HDD/volumes | Yes | Yes | n/a | n/a |
|
||||
| Config only | Yes | — | n/a | n/a |
|
||||
|
||||
**Snapshot API** (`/api/backup/snapshots?stack=<name>`):
|
||||
- Returns snapshots **only from the app's home drive** primary repo (prevents showing irrelevant snapshots from other drives)
|
||||
- Appends a synthetic Tier 2 entry (ID `tier2-rsync`) from cross-drive config when last backup was successful
|
||||
- Dropdown groups by tier: "1. szint — Helyi mentes" and "2. szint — Masodlagos masolat"
|
||||
**Snapshot API** (`GET /api/backup/snapshots?stack=<name>` — F1, v0.99.0):
|
||||
- Backed by `backup.Manager.ListRestorePoints` (`internal/backup/restore_points.go`). The keep-side
|
||||
restore has exactly **one** restore point per app — the current recovery unit — so the endpoint
|
||||
returns at most one entry: `time` = newest artifact mtime (manifest / db-dumps / volume-dumps),
|
||||
`short_id:"helyi"`, `tier:1`, `drive_label` from the storage registry (empty on the SSD fallback)
|
||||
- **Never emits tier-2 entries**: Tier-2 copies are not restorable via `POST /backup/restore` (it
|
||||
only reads the primary unit) — listing them would silently restore tier-1 data while claiming tier-2
|
||||
- Guards: empty/traversal stack name → 400 (`validStackParam`), unknown stack → 404, known stack
|
||||
with no unit yet → `ok:true, data:[]` (the UI shows "Nincs elérhető mentés")
|
||||
- History: the route was a restic-era leftover fetched by the template but unregistered — the
|
||||
dropdown could never populate and the restore button never enabled (drill finding F1)
|
||||
|
||||
**Restore type info** shown per-app when selected in dropdown (Hungarian banners):
|
||||
- Has HDD or Docker volumes: "Teljes visszaallitas: adatbazis + konfiguracio + felhasznaloi adatok"
|
||||
|
||||
Reference in New Issue
Block a user