diff --git a/REPORT.md b/REPORT.md index f6a844e..85cfcaa 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,75 +1,46 @@ -# REPORT — agent v0.19.0: bootstrap contract v2 (relay hub passphrase, no host key in guest) (2026-06-11) +# REPORT — agent v0.20.0: golden stacks-dir bind + per-guest hostname/CT + bake base-infra images (2026-06-11) -> Overwrite-latest report. Cumulative history: [CHANGELOG.md](CHANGELOG.md). +**Repo:** `felhom-agent` · **Version:** 0.20.0 · **Date:** 2026-06-11 +**Pushed commit:** `1799fcd` · paired with `felhom-controller` v0.41.0 (`abbd948`) + golden rebake. -Lockstep two-repo change with `felhom-controller` v0.40.0. Fixes the onboarding **401** found last -session: the agent used to bake its **host** hub key into the guest's `bootstrap.json`, but the hub's -`/api/v1/report` authenticates a **customer-scoped** key, so the controller could never report ONLINE. -The agent now bakes a **v2 bootstrap** carrying only what the controller needs to **pull** its own -config from the hub — the agent never touches the customer-scoped key or the CF tokens. Validated live -on the demo (guest 9201). +## What shipped (all in `configs/build-golden.sh` + the provision path; no change to proxmox/authz/token fences) -## What changed -- **`internal/provision/doc.go`** — `SchemaV1 → SchemaV2 = "felhom.bootstrap/v2"`. `DocCustomer` drops - `name`/`domain`/`email` (keeps `id`); `DocHub` drops `api_key`/`host_id`, adds **`retrieval_password`** - (SECRET); `DocLocalAPI` unchanged. Byte-compatible with the controller's `internal/bootstrap.Bootstrap`. -- **`internal/provision/backhalf.go`** — renders the v2 Doc; validation now requires `customer.id` + - `hub.url` + `hub.retrieval_password` (was `customer.id` + `customer.domain`). Mint / 0600 write / - `chown 100000:100000` / `pct set -mp9 …,ro=1` unchanged. -- **`cmd/felhom-agent/main.go` `--selftest=provision`** — **new required `-hub-password`** flag (the - customer's hub retrieval passphrase; the customer must already exist in the hub). Stops baking - `cfg.Hub.APIKey` / `cfg.Hub.HostID`. `-customer-domain/-name/-email` still accepted (bring-up may use - them) but **not** baked into the v2 Doc. Success text updated (reboot → controller pulls + merges). -- **`configs/build-golden.sh`** — default `CONTROLLER_IMAGE` bumped off the stale `:v0.35.0` → - `:0.40.0` (matches the registry's no-`v` tag convention; latent footgun fixed). +- **Section-G mount fix (load-bearing):** the in-guest controller writes app/infra compose stacks under + `/opt/docker/stacks` *inside its container*, but the baked controller-bootstrap `docker run` never + bind-mounted that path — so `docker compose up` (run by the GUEST daemon over the shared socket) + resolved every relative bind source on the guest filesystem, silently creating empty dirs and breaking + **every** bind-mounted stack (base infra AND customer apps). The bootstrap unit now `mkdir -p + /opt/docker/stacks` and adds a **same-path host bind** `-v /opt/docker/stacks:/opt/docker/stacks` (a + named volume would NOT fix this). Empirically confirmed on guest 9201 before writing the fix. +- **Per-guest container hostname (3A):** the bootstrap unit parses `customer.id` from + `/etc/felhom-bootstrap/bootstrap.json` with a portable `sed` (NO jq in the golden) and passes + `--hostname ` to `docker run`. **Security-hardened** (flagged by the commit security + review): the id is validated to a DNS-safe label (reject spaces/slashes/leading-dash) and passed via a + quoted array, so a malformed/hostile id can't smuggle docker flags (e.g. `--privileged`) into the + de-privileged run. Empty/invalid → no `--hostname` (fail-safe). +- **Per-guest CT/LXC name (3B):** `--selftest=provision` defaults `-hostname` to the DNS-safe-sanitized + `-customer-id` when not given, so the bring-up's existing `SetConfig hostname` step names the CT + meaningfully instead of inheriting the golden's `felhom-golden`. New `sanitizeHostname` (lowercase, + collapse invalid → `-`, trim, ≤63). +- **Bake base-infra images:** the golden pulls the three PINNED, PUBLIC base-infra images + (`traefik:v3.6.7`, `cloudflare/cloudflared:2026.6.0`, `gtstef/filebrowser:1.3.3-stable`) into its Docker + storage so the controller's first-boot bring-up is OFFLINE-capable. A `docker manifest inspect` **hard + gate** fails the bake early on a bad pin. Tags MUST match the controller's `internal/infra` constants. -## Cross-repo contract checksum-diff (rendered bootstrap.json field set) -Agent renderer output ingested by the controller's `json.Unmarshal` — **every field populated**, exact: +## Live validation (demo host felhom-pve / PVE 9.2.2) -| level | fields (agent emits == controller ingests) | -|---|---| -| top | `schema, customer, hub, local_api` | -| customer | `id` | -| hub | `url, retrieval_password` | -| local_api | `endpoint, fingerprint, token` | +- Agent **v0.20.0** built (go1.26, ldflags `-X main.version=0.20.0`) and deployed to + `/usr/local/bin/felhom-agent`; service active. +- **Golden rebaked** → `local:backup/vzdump-lxc-9100-2026_06_11-15_06_05.tar.zst` (877 MB, up from ~599 MB + ≈ the three baked infra images). Controller `:0.41.0` pulled (digest `0a9d456…`) + all 3 infra pins + resolved past the hard gate. `/etc/felhom-controller-image` reads `:0.41.0`. +- **Guest 9201 destroyed + re-provisioned** from the new golden: provision header `hostname=demo-felhom`; + CT config `hostname: demo-felhom`; runtime hostname `demo-felhom`; controller `os.Hostname()` = + `demo-felhom`. Back-half minted the per-guest token + attached the `mp9` bootstrap mount. +- After `pct reboot` + `systemctl restart felhom-agent`: the baked controller-bootstrap deployed the + controller, which pulled its config and stood up the base stack — **4 containers running**, Health = OK, + cloudflared tunnel registered, the `/opt/docker/stacks` bind resolving end-to-end. -## Tests — `internal/provision`, all green -- `doc_test.go`: pins the v2 key set — `customer:[id]`, `hub:[url, retrieval_password]` (asserts **no** - `api_key`/`host_id`), `local_api:[endpoint, fingerprint, token]`; schema == `felhom.bootstrap/v2`. -- `backhalf_test.go`: asserts the rendered bootstrap has `Schema==SchemaV2`, `hub.url` + - `hub.retrieval_password` (no host key), the per-guest token in `local_api`, 0600, chown to - `100000:100000`, `pct set -mp9 …,ro=1`, and the token never leaks into the `Result`. - -`go build ./... && go test ./...` green. - -## Live validation (demo Proxmox `felhom-pve`) -- Agent **v0.19.0** built (`-X main.version=0.19.0`), streamed to the demo host (sha256-verified), - installed at `/usr/local/bin/felhom-agent`, daemon restarted (`active`, `felhom-agent 0.19.0`). -- Golden re-baked with controller `:0.40.0` → - `local:backup/vzdump-lxc-9100-2026_06_11-13_26_45.tar.zst` (baked image confirmed `:0.40.0`). -- `--selftest=provision -customer-id demo-felhom -hub-password ` → guest 9201 brought up + - bootstrap mounted. The written `bootstrap.json` (0600) has `customer:[id]`, `hub:[url, - retrieval_password]` (**no host key**), `local_api:{endpoint,fingerprint,token}`. ✓ -- After `pct reboot` the golden's baked unit deployed controller `:0.40.0`, which **pulled** its - config from the hub (customer-scoped key `4b11c0c3…`) and merged in the per-guest `local_api`; the - guest reported **ONLINE at v0.40.0** (no 401). The agent baked **no** customer secret beyond the - per-guest local-API token (passphrase aside — see below). ✓ -- 8C invariant unaffected: agent-direct `POST /disks/format` on data-bearing `/dev/sdb1` → **HTTP 403 - pending_signature**, disk untouched. ✓ - -## What broke / what's missing -- **Finding #1 still open (separate spec):** provisioning while the daemon runs leaves the new guest's - local-API channel 401 until `systemctl restart felhom-agent` (the running daemon doesn't reload the - freshly-minted token). Workaround applied; reproduced. -- **Passphrase-at-rest:** the retrieval passphrase rides in the guest's 0600 `bootstrap.json` (decision - (d) — the agent relays it, never the customer key/CF tokens). Acceptable for now (single-customer - scoped; matches the old `docker-setup.sh --hub-password` model); post-onboard hardening (detach mount - / rotate) is a separate future item. -- Operational note: the UTF-8 (Hungarian) passphrase must be transported byte-exact (base64) to the - provision call; a `kubectl cp` stderr line polluted the first capture and produced a garbage - passphrase (re-extracted + re-provisioned cleanly). - -## Versions / artifacts -- Agent **v0.19.0** (CHANGELOG updated). Pushed to `main`: commit `e5a1819` (code) — this REPORT in - the follow-up commit. Lockstep controller **v0.40.0** (commit `6a594f9`). New golden volid above. -- No secrets committed (passphrase, customer key, CF tokens, local-api token — out-of-band/redacted). +## Notes +- The registry credential used for the bake was staged transiently as a 0600 file on the host and removed + by the rebake; it is **stored out-of-band** and never committed.