Files
felhom-controller/REPORT.md
T

3.4 KiB

REPORT — slice 8A (controller half): bootstrap.json ingestion + pinned agent local-API client (v0.35.0) (2026-06-10)

Overwrite-latest report (most recent significant work only). Cumulative history lives in CHANGELOG.md. Implements the in-guest controller half of TASK — Slice 8A. The host-agent half is in felhom-agent v0.10.0. No hub change.

Outcome

The controller now ingests the host agent's stable bootstrap.json on first run and reaches the agent's local API over the bridge with a pinned client. With the agent half, this completes the provisioning chain: a freshly provisioned guest's controller comes up configured (skipping the setup wizard) and talks to the agent — validated live end-to-end on the demo. No behaviour change for an already-configured controller.

What landed

  • internal/bootstrap — first-run bootstrap.json ingestion (config-contract decision (c)). On startup, if the controller is NOT yet configured AND the agent's back-half attached a bootstrap.json config mount, the controller seeds controller.yaml from it and comes up configured, skipping setup mode. Idempotent (an existing controller.yaml is never clobbered) and fail-safe (a malformed / absent / missing-identity / unsupported-schema bootstrap leaves the controller in setup mode — logs, never crashes). The agent emits the stable contract; the controller owns the translation (decoupled — no shared config schema).
  • internal/agentapi — a minimal pinned client for the agent local API: reaches the agent over the bridge pinning the agent leaf-cert SHA-256 from the bootstrap (fails closed on mismatch — exact leaf-DER match in VerifyPeerCertificate, the same pin convention the agent uses for the Proxmox/PBS host certs) + the per-guest bearer token. In 8A it exercises GET /storage (connectivity + the controller learning its mounts); the /backup/due quiesce loop is 8B.
  • config.LocalAPIConfig (local_api: endpoint, fingerprint, token) seeded from the bootstrap; a startup probe proves the channel and logs this guest's mounts (non-fatal).

Tests

go test ./... green. bootstrap: seeds when unconfigured (reloads configured, skips setup); never clobbers a configured controller; stays in setup on malformed / missing-identity / unsupported-schema / absent bootstrap. agentapi: correct pin + token reaches /storage; a wrong pin fails closed; a bad fingerprint is rejected at construction; colon-separated fingerprints accepted.

Live validation (demo-felhom)

The controller (v0.35.0, baked into the new golden, deployed by the golden's bootstrap unit with no registry pull) ingested bootstrap.json → seeded controller.yaml → came up CONFIGURED (felhom-controller v0.35.0 starting (customer: cust-8201, …), not setup mode), then reached the agent's real local-API GET /storage over the bridge (leaf-pin + token) → channel up, 1 mount visible. The test guest was torn down after validation.

Deferred (stated, not built)

The full local-API usage — the GET /backup/due-driven quiesce → POST /backup → unquiesce app-consistent loop → 8B. Controller de-privileging (retire the disk-execution subsystem; customer disk endpoints behind the slice-4 classifier) → 8C. No secrets committed (the per-guest token arrives via the agent-written 0600 bootstrap mount; it is never logged or committed).