Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.4 KiB
REPORT — slice 8B (agent half): app-consistent backup policy + status phases (v0.11.0) (2026-06-10)
Overwrite-latest report (most recent significant work only). Cumulative history lives in CHANGELOG.md. Implements the host-agent half of
TASK — Slice 8B. The in-guest controller quiesce loop is infelhom-controllerv0.36.0. No hub change.
Outcome
Turns the 8A thin backup stubs into the real policy the controller's quiesce loop drives. An
agent-initiated vzdump is crash-consistent only (an LXC has no fsfreeze); the controller now stops
its app stacks around POST /backup (8B) so the captured state is clean-shutdown-consistent —
validated live end-to-end including the postgres clean-vs-crash-recovery restore contrast.
What landed (internal/localapi)
GET /backup/due— real cadence policy (replaces the 8A "never backed up" stub): due when no successful backup is recorded OR the newest one is older than the agent-local cadence (backup.backup_cadence_seconds, default 24h). A successfulPOST /backupflips due to false for the window (the controller won't re-quiesce in a loop); a failed backup does not satisfy the cadence. Returnsage_seconds.GET /backup/status— real phasesidle | running | done | failed+ the job id, so the controller can poll a backup to completion. (Thesnapshottedphase is the 8B.2 hook.)POST /backup— returns a job id +running; tracks the in-flight job and is single-flight per guest (a second POST while one runs returns the same job — no concurrent vzdump). On completion the job transitions done/failed and the result is recorded to the store.- Config:
backup.backup_cadence_seconds+BackupCadence(); the local-API server takes the cadence. - All still self-scoped (token→guest); the snapshot/rollback/storage 8A surface is unchanged.
Tests
go test ./... green; -race green on the build server (pure-Go parts). /backup/due: due when
stale / no backup, not due within the window after a success, due again past the cadence, a
failed backup does not count. /backup/status: running→done and running→failed (a gated fake
observes the running phase). POST /backup single-flight (one vzdump for concurrent POSTs). Plus the
8A self-scoping/auth suite unchanged.
Live validation (demo-felhom)
A provisioned guest (controller v0.36.0, postgres app stack) with a short test cadence: the
controller's quiesce loop hit GET /backup/due (true) → quiesced → POST /backup → the agent ran a
real vzdump (local-api: backup job complete … archive=vzdump-lxc-8202-…) → GET /backup/status
done → unquiesce. /backup/due then correctly went false for the cadence window (no re-loop).
The single-flight + phase transitions behaved against the live server. (The controller's legacy
hub-report HTTP 401 is the frozen per-customer path, unrelated to 8B.) The standing demo agent was
upgraded to v0.11.0 (local-API live on 192.168.0.162:8443); test guests torn down, cadence reset.
Deferred (stated, not built)
8B.2 downtime optimization (vzdump --mode snapshot + a snapshotted status phase so the
controller resumes at snapshot-taken) — needs snapshot-capable storage validated for LXC; the status
phase enum already carries the hook. Hub-served per-guest backup policy → slice 10. No secrets committed.