Files
felhom-agent/REPORT.md
T

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 in felhom-controller v0.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 successful POST /backup flips due to false for the window (the controller won't re-quiesce in a loop); a failed backup does not satisfy the cadence. Returns age_seconds.
  • GET /backup/status — real phases idle | running | done | failed + the job id, so the controller can poll a backup to completion. (The snapshotted phase 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.