controller v0.93.0: NAS Part B off-box backup target (restic-over-SFTP)
Encrypted restic repo over SFTP for the app-data tier (the off-site 3-2-1 leg). A dead NAS fails fast via -oConnectTimeout (spike Q8), never hangs the runner; secrets are 0600 files (ride DR via PBS whole-CT); init-if-absent, retention forget --prune, restore, single-flight, per-app toggle + UI. restic re-added to the image. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
@@ -1,5 +1,36 @@
|
||||
## Changelog
|
||||
|
||||
### v0.93.0 — NAS Part B: off-box backup target (restic-over-SFTP) (2026-06-30)
|
||||
Closes the NAS arc: back the app-data tier (each off-box app's recovery unit + DB dumps + volume tars) up
|
||||
to the customer's NAS as an **encrypted restic repo over SFTP** — the "1 off-site" leg of 3-2-1, distinct
|
||||
from the local cross-drive rsync copy and the agent's PBS whole-CT DR. No kernel mount; restic talks SFTP
|
||||
to the NAS directly. Spike-validated (SPIKE-nas-storage Q8).
|
||||
- **`Dockerfile`:** restic was dropped when cross-drive migrated restic→rsync — re-added `restic` +
|
||||
`openssh-client` (restic's sftp backend shells out to `ssh`); version pinned by the Debian release.
|
||||
- **`internal/backup/offbox.go` (NEW):** the restic-SFTP backend + orchestration.
|
||||
- **Fail-fast (the load-bearing spike Q8 lesson):** every restic call carries
|
||||
`-o sftp.args=…-oConnectTimeout=10…` so a dead NAS errors in ~10 s instead of a multi-minute TCP hang.
|
||||
Also `-oStrictHostKeyChecking=yes -oUserKnownHostsFile=<pinned>` (no blind TOFU) + `-oBatchMode=yes`.
|
||||
- init-if-absent (idempotent — a present repo is reused, never re-init), per-app `restic backup --tag`,
|
||||
`forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune` retention, single-flight (shares
|
||||
`m.running`) + migration-guard, restic's **own exit code** checked (never pipe-swallowed), restore via
|
||||
`restic restore latest --tag <app> --target <scratch>` (non-destructive).
|
||||
- **Secrets:** the SSH private key + the auto-generated repo password are 0600 files in the data dir —
|
||||
never logged, never in a committed/non-0600 file; the repo is encrypted so the NAS sees only ciphertext.
|
||||
They ride DR via the PBS whole-CT snapshot of the rootfs (the data dir), so a rebuilt box can reach the
|
||||
off-box repo (the recovery-unit/dr-recipe stay secret-free).
|
||||
- **`internal/settings/settings.go`:** `OffboxTarget` (host/port/user/repo path/schedule + runtime status,
|
||||
no secrets) + per-app `AppBackupPrefs.Offbox` toggle + helpers.
|
||||
- **`cmd/controller/main.go`:** daily `offbox-backup` schedule (04:15) gated on enabled+configured; a
|
||||
failure (incl. fail-fast dead-NAS) alerts the operator via the allowlisted `backup_failed` event.
|
||||
- **UI (`backups.html`):** "Külső (NAS) mentés" section — target config (host/port/user/repo + out-of-band
|
||||
SSH key + known_hosts textareas), status (last run / repo size / snapshots), per-app toggles, run-now,
|
||||
restore-to-scratch.
|
||||
- Tests: ConnectTimeout present in base args + **the fail-fast companion red-proof** (a fake SSH transport
|
||||
hangs to the ctx deadline WITHOUT the arg, fails fast WITH it); dead-NAS run fails fast + alerts + records
|
||||
status; restore round-trip byte-identical (SFTP-shaped seam); single-flight skip; repo idempotency;
|
||||
secrets are 0600. `go build/vet/test ./...` green.
|
||||
|
||||
### v0.92.0 — NAS network storage Part A2: registry + UI + per-share health (2026-06-30)
|
||||
The controller side of NAS network storage, proxying to the validated agent foundation (felhom-agent
|
||||
v0.50.0 `/netstorage/*`). An operator can add a customer's NAS share and point a media app at it — all via
|
||||
|
||||
Reference in New Issue
Block a user