hub v0.16.0 + host-install v1.1.0: Day-0 artifact manifest + self-install the agent (BUNDLE slice)
Hub (v0.16.0):
- store: ArtifactManifest{agent,golden version+sha256} in hub_settings; Get/SetArtifactManifest.
- handler: GET /api/v1/artifacts/{id} (passphrase auth, mirrors config-retrieve). Unset => 200 empty.
- web: operator UI "Day-0 artifacts" card (POST /configs/artifacts), semver + 64-hex validation.
- artifact_test.go: returned-verbatim / unset-empty / 401 / 404 / store round-trip.
host-install (v1.1.0):
- new step 5/8 agent-install: manifest + git token (config-retrieve) -> fetch binary from Gitea ->
verify sha256 vs hub manifest (abort on mismatch) -> install non-root felhom-agent user + binary +
sudoers (visudo -cf) + canonical unit. Idempotent.
- new step 7/8 golden: local fallback else fetch+verify+import from Gitea (--force-gitea-golden).
- agent now runs non-root (privileged.mode sudo), config chowned to the service user.
- README prerequisites trimmed to: install PVE + create customer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+31
-13
@@ -5,11 +5,17 @@ Operator-side scripts for standing up a Felhom Proxmox host.
|
||||
## `felhom-host-install.sh` — Day-0 host bootstrap (operator-deploy)
|
||||
|
||||
Run on a **freshly-PVE-installed** box to fully automate Day-0: Proxmox API token →
|
||||
hub host enrollment (single secret) → agent config → guest provision → verify. It
|
||||
composes already-proven mechanisms (the `pveum` role/token sequence, the hub
|
||||
`POST /host-enroll` enrollment from option C, and `felhom-agent --selftest=provision`).
|
||||
The agent renders `bootstrap.json` into the guest and the **controller pulls its own
|
||||
`controller.yaml`** in-guest — the script never fetches it.
|
||||
hub host enrollment (single secret) → **agent install (fetch + verify + install)** →
|
||||
agent config → golden → guest provision → verify. It composes already-proven mechanisms
|
||||
(the `pveum` role/token sequence, the hub `POST /host-enroll` enrollment from option C, and
|
||||
`felhom-agent --selftest=provision`). The agent renders `bootstrap.json` into the guest and
|
||||
the **controller pulls its own `controller.yaml`** in-guest — the script never fetches that.
|
||||
|
||||
Since **v1.1.0** (BUNDLE slice) the script also **installs the agent itself**: it fetches the
|
||||
agent binary + golden from Gitea generic packages and **verifies each artifact's sha256 against
|
||||
the hub-vouched manifest** (`GET /api/v1/artifacts/{id}`) before installing/using it. The fetch
|
||||
credential is the **git token already inside the customer's `controller.yaml`** (config-retrieve) —
|
||||
**no new credential**, and the checksum **trust root is the hub**, not Gitea.
|
||||
|
||||
Grounding: [`documentation/audits/SPIKE-day0-firstboot-handshake-2026-06-26.md`](../documentation/audits/SPIKE-day0-firstboot-handshake-2026-06-26.md).
|
||||
|
||||
@@ -22,10 +28,11 @@ Grounding: [`documentation/audits/SPIKE-day0-firstboot-handshake-2026-06-26.md`]
|
||||
2. **SSH into the box as root.**
|
||||
3. **Create the customer in the hub first** (hub UI → new customer). The customer's
|
||||
**retrieval passphrase** (a 5-word Hungarian phrase) is the only secret you carry to the box.
|
||||
4. A **golden archive** must exist on the archive storage (newest `vzdump-lxc-<golden-vmid>`).
|
||||
If none exists, build one with `felhom-agent/configs/build-golden.sh` first.
|
||||
5. The **felhom-agent binary** + its systemd unit installed (the script auto-detects the
|
||||
unit's `-config` path; if the binary is absent it tells you to install it).
|
||||
|
||||
That's it. The agent binary + golden are fetched + verified + installed by the script (provided the
|
||||
operator has recorded the current artifact set in the hub UI → Configs → **Day-0 artifacts**, and
|
||||
published them via `felhom-agent/scripts/publish-agent.sh` + `configs/build-golden.sh`). A local
|
||||
golden, if present, is still used as a fallback.
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -74,6 +81,16 @@ per-host hub api_key live **only** in the agent config (`0600`, root).
|
||||
- **Token automation.** Creates/normalises the 16-priv `FelhomAgent` role, the
|
||||
`felhom-agent@pve` user + privsep token, and **both** ACL grants (user **and** token — the
|
||||
ACL is applied *after* the token exists, because `pveum user token remove` purges it).
|
||||
- **Agent install (v1.1.0).** Fetches the binary from Gitea
|
||||
(`/api/packages/admin/generic/felhom-agent/<ver>/felhom-agent`), **verifies its sha256** against the
|
||||
hub manifest, then installs the non-root `felhom-agent` service user + binary + sudoers (0440,
|
||||
`visudo -cf`-validated) + the canonical systemd unit. Idempotent: same version already installed +
|
||||
service active → skips. A sha256 mismatch **aborts** the install (verify-before-use). The agent runs
|
||||
**non-root** (`privileged.mode: "sudo"` + the sudoers allowlist), never as root.
|
||||
- **Golden (v1.1.0).** Uses a local golden when present; otherwise fetches it from Gitea
|
||||
(`/api/packages/admin/generic/felhom-golden/<ver>/golden.tar.zst`), **verifies its sha256**, and
|
||||
imports it into the archive storage's dump dir for the restore. `--force-gitea-golden` forces the
|
||||
Gitea path even when a local golden exists.
|
||||
- **DR mode** (`--mode dr`) is a documented seam only — it restores the customer's **own** PBS
|
||||
whole-CT snapshot instead of the golden. Not implemented (10D).
|
||||
|
||||
@@ -81,7 +98,8 @@ per-host hub api_key live **only** in the agent config (`0600`, root).
|
||||
|
||||
- **Serving:** place this file where the felhom.eu site serves it at
|
||||
`https://felhom.eu/scripts/felhom-host-install.sh` (a static route; verify on deploy).
|
||||
- **Agent binary delivery:** the script expects the agent pre-installed; a fetch-from-release
|
||||
step is the documented hook.
|
||||
- **Golden delivery:** the test used a local golden; central download + checksum verify is the
|
||||
remaining hook.
|
||||
- **Per-customer artifact pinning:** the hub manifest currently returns the global current artifact
|
||||
set for every customer; per-customer pinning is a future hook (`GET /api/v1/artifacts/{id}` already
|
||||
takes the customer id).
|
||||
- **Unit/sudoers integrity:** the binary + golden are sha256-verified against the hub; the unit +
|
||||
sudoers are fetched from the agent repo `main` (canonical text) and the sudoers is `visudo -cf`-validated.
|
||||
|
||||
Reference in New Issue
Block a user