Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 infelhom-agentv0.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-runbootstrap.jsoningestion (config-contract decision (c)). On startup, if the controller is NOT yet configured AND the agent's back-half attached abootstrap.jsonconfig mount, the controller seedscontroller.yamlfrom it and comes up configured, skipping setup mode. Idempotent (an existingcontroller.yamlis 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 inVerifyPeerCertificate, the same pin convention the agent uses for the Proxmox/PBS host certs) + the per-guest bearer token. In 8A it exercisesGET /storage(connectivity + the controller learning its mounts); the/backup/duequiesce 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).