docs: REPORT for hub v0.28.0 + host-install v1.2.0 (live-validated)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 06:55:17 +02:00
parent b77df3c761
commit 83c4f7f794
+78 -61
View File
@@ -4,84 +4,101 @@
---
# REPORT — Hosts page: read-only fleet view (hub v0.27.0)
# REPORT — Configuration-tab move + online setup command + vmid auto-detect (hub v0.28.0 / host-install v1.2.0)
**Date:** 2026-07-01 · **Repo:** `felhom.eu` (`hub/`) · **Audit finding:** F-M1 (resolved)
**Date:** 2026-07-01 · **Repo:** `felhom.eu` (`hub/` + `scripts/` + `manifests/`)
## What & why
Three operator-requested improvements.
The agent enrolls as a **host** and the hub stores rich host state (identity, agent version, guests,
storage targets with SMART, DR/escrow, staleness) and **alerts** on it — but the entire host domain was
**invisible in the GUI** (email-only). This adds a **Hosts** nav section: a fleet **list** + a per-host
**detail** page. **Read-only** (GET only, no host actions/mutation routes) — this surfaces state the
pull/desired-state model demands; it does not reintroduce the inbound control retired in v0.26.0.
## Baselines
## Baseline
hub `v0.27.0` @ `54daaf9`**`v0.28.0`**; `scripts/felhom-host-install.sh` `v1.1.0`**`v1.2.0`**.
Trunk-based `main`. Commits: `562c0df` (Parts A/B/C code), `629196c` (hub image manifest), `b77df3c`
(script header version).
hub `v0.26.0` @ `73b3f6a` (clean tree) → **`v0.27.0`**. Trunk-based `main`; commits
`2289fc9` (code) + `f8b8730` (manifest bump).
## Part A — global settings moved to the Configuration tab
## Files
The two **global** cards ("Managed updates — global floor", "Day-0 artifacts — agent & golden") were on
the Customers page; they now live on Configuration.
**Created**
- `hub/internal/web/hosts.go``handleHostsList`, `handleHostDetail`, `hostStatus`/`hostStatusClass`/
`hostStatusLabel`, `parseHostVitals`, `parseHostStorageTargets`, `customerName`.
- `hub/internal/web/hosts_test.go` — 5 tests (incl. the no-secret assertion).
- `hub/internal/web/templates/hosts.html`, `hub/internal/web/templates/host_detail.html`.
- `internal/web/server.go``handleConfiguration` supplies `GlobalFloor` + `Artifacts` + `CSRFField`;
the two POST routes moved to `/configuration/global-floor` + `/configuration/artifacts`.
- `internal/web/configs.go``handleSetGlobalFloor`/`handleSetArtifacts` redirects `/configs?flash=…`
`/configuration?flash=…` (5 redirects); `handleConfigList` data trimmed to `{Customers, ActiveNav,
Flash}` (dropped the now-unused `GlobalFloor`/`Artifacts`/`CSRFField`; `globalFloor` still computed for
per-customer effective-floor).
- `templates/configuration.html` — the two cards + their 5 flash banners added. `templates/configs.html`
— cards + flashes removed; back to customer list + "Add Customer".
- `internal/web/render_test.go` — updated: asserts the global cards are **gone** from configs.html and
**present** (with `/configuration/*` actions + a flash) on configuration.html.
**Modified**
- `hub/internal/store/store.go`**new** `ListGuestsForHost(hostID) ([]Guest, error)` + `scanGuest`
helper over `guestRealitySelectCols` (reality columns only; **omits** `api_key`, `desired_spec_json`).
- `hub/internal/store/host_test.go``TestListGuestsForHost`.
- `hub/internal/web/server.go``GET /hosts` (+`/hosts/`) and `GET /hosts/{id}` routes (modelled on
the `/apps` pair); new `timeAgoPtr` template helper for `*time.Time`.
- 7 existing templates (`dashboard`, `configs`, `apps`, `config_form`, `configuration`,
`customer_unified`, `app_detail`) — added the `Hosts` nav link (nav is duplicated per page, not a
shared partial).
- `hub/CHANGELOG.md` (newest-on-top), `manifests/hub.yaml` (`:0.26.0``:0.27.0`).
## Part B — online setup command + serving fix + passphrase hardening
## Design decisions
- **B1 (`scripts/…`):** `read_passphrase` now reads `read -rsp … < /dev/tty` — the no-echo prompt works
whether the script is a local file or piped to bash. `--passphrase-file` unchanged; passphrase still
never on argv/logs/state.
- **B2 (serving):** `https://felhom.eu/scripts/felhom-host-install.sh` was **404** — the webpage nginx
doc-root is `.../current/website` and git-sync only synced `/website/`. Fixed in
`manifests/webpage.yaml`: added `/scripts/` to the git-sync sparse-checkout + an nginx
`location /scripts/ { root .../current; default_type text/plain; }`. Single source of truth (the repo's
`/scripts`), no duplicated copy. **Now 200** (verified live, serves `SCRIPT_VERSION="1.2.0"`).
- **B3 (`customer_unified.html`):** new **"Option 1: Online install (recommended)"** — download-then-run
(`curl -fsSL … -o … && sudo bash … --customer-id {{.CustomerID}}`) with a copy button; the passphrase is
**not** templated (entered at the prompt). Former local-file command demoted to Option 2, debug curl to
Option 3. Rationale note: fetched over TLS, inspect-before-run — not `curl | sudo bash`.
- **Status badge reuses the alerting threshold.** `hostStatus()` reuses `s.staleThreshold` and the
`HostStalenessChecker`'s bands (stale after the threshold, down at 2×) — one definition, so the GUI
badge agrees with the emails. `nil` last-report → "waiting for first report".
- **No new ingestion path.** Vitals + rich storage (role/state/fill/thin-pool/SMART) are parsed from the
already-stored latest `report_json` (`GetLatestHostReportJSON`); worst-fill from `GetHostStorageTargets`.
Guest counts from `ListGuestsForHost` (per-host accurate).
- **No secrets.** The guests reader never selects `api_key`/`desired_spec_json`; DR/escrow are shown as
presence booleans only — never the opaque blobs. A test asserts the host `api_key` is absent from the
rendered detail body.
- **Nil-safe.** No report / no guests / no storage / no DR all render empty states, never a panic.
## Part C — vmid auto-detect (`scripts/…`)
## Tests — `go build ./... && go vet ./... && go test ./...` all `ok`
- New `VMID_EXPLICIT` flag (set by `--vmid`). The pre-flight guard now determines "in use" against the
**`pct list` + `qm list`** id-set (LXC + VMs share the space) via new helpers `used_vmids` /
`_vmid_in_use` / `next_free_vmid`.
- **explicit `--vmid` + in use** → deterministic: die unless `--force`.
- **default 9201 + in use + no `--force`** → auto-pick the next free id (scan upward) + **confirm** from
`/dev/tty` (`[y/N]`); `die "no free vmid confirmed"` on decline. Never silent.
- **default 9201 + `--force`** → destructive over-provision, no prompt.
- `--vmid` help + `usage()` range updated (and usage now reaches `-h, --help`).
- `TestListGuestsForHost` — none→empty; multiple→vmid-ordered; a foreign-host guest excluded; the
`api_key` column is not surfaced. **PASS**
- `TestHostStatus` — nil→pending, fresh→ok, 45m→stale, 3h→down. **PASS**
- `TestHandleHostsList` — 2 host rows, ONLINE + NO REPORT badges, worst-fill (73%) rendered, no `<button>`. **PASS**
- `TestHandleHostDetail` — identity/guest/controller-version/storage/SMART(41°C)/role/customer-link
present; DR + escrow both "present"; **`api_key` absent (no-secrets)**; no `<button>`. **PASS**
- `TestHandleHostDetail_Unknown` — unknown host → 404. **PASS**
- `TestHandleHostDetail_NoReport` — renders "waiting for first report", no panic. **PASS**
## Green gates
## Deploy (GitOps via ArgoCD — the `felhom` app; auto-sync off)
- Hub: `go build ./... && go vet ./... && go test ./...` — all `ok` (incl. the updated
`TestTemplates_FloorRender`).
- Script: `bash -n` clean. `shellcheck` (koalaman container on the build server): my additions clean;
the only output is **2 pre-existing** SC2015 *info* notices (lines 833/843, unrelated verify
functions — present in HEAD, not touched). Local unit test of `next_free_vmid` (3 cases) passes.
Image `gitea.dooplex.hu/admin/felhom-hub:0.27.0` built+pushed on the build server (180); `manifests/hub.yaml`
bumped + committed; ArgoCD `felhom` hard-refreshed → OutOfSync → synced → **Synced / Healthy**;
`deploy/hub` rolled out on image `:0.27.0`; logs show `[INFO] Listening on :8080`.
## Deploy
## Live validation (claude-in-chrome, operator-authed, hub.felhom.eu)
hub image `:0.28.0` built+pushed on 180; `manifests/hub.yaml` bumped; ArgoCD `felhom` synced →
**Synced/Healthy**; `deploy/hub` on `:0.28.0`. `felhom-webpage` rollout-restarted so it re-clones with
the new sparse-checkout (`/scripts/`) + new nginx config.
- **`/hosts`** — lists `demo-felhom-01` **ONLINE** (agent `0.51.0`, guests 11/15, CPU 3% / MEM 21% /
DISK 29%, worst storage 29% `local`) + two drill hosts **NO REPORT**. Hosts nav active; footer `0.27.0`.
- **`/hosts/demo-felhom-01`** — Identity (customer **Demo Ügyfél** cross-linked to `/customers/…`, agent
`0.51.0`, enrolled 21d ago, last report 8 min ago, desired generation 2), Vitals, **Guests** (15 rows,
VMID/name/status running=green/stopped=red/last-seen), **Storage Targets** (felhom-usb **SMART PASSED
33°C**; local-lvm lvmthin 11% fill / 11% thin-pool; others UNKNOWN/graceful "—"), **DR / Backup** DR
Recipe + Key Escrow both **present**. Confirmed: **no action buttons**, and the page text carries **no
`api_key`/secret**.
## Live proofs
## Remaining audit follow-ups (not this slice)
- **Part A (claude-in-chrome, operator-authed):** Configuration tab shows both cards with current values
(global floor v0.87.0; agent 0.43.0 / golden 0.85.1); "Save global floor" → `/configuration?flash=floor_set`
with the green "Controller-version floor saved." banner (persist + flash on Configuration). The Customers
page shows only the customer list + "Add Customer" (cards gone; footer `0.28.0`).
- **Part B (curl + browser):** `curl https://felhom.eu/scripts/felhom-host-install.sh`**HTTP 200**,
serves `SCRIPT_VERSION="1.2.0"`. The customer page (`demo-felhom`) shows Option 1 online install with the
id filled + copy button, Option 2 local, Option 3 debug — passphrase not in Option 1.
- **Part C (SSH, felhom-pve):** the deployed script's own vmid functions against real `pct`+`qm`:
used-set `{9000,9001,9100,9201,9999}`, 9201 in use → `next_free_vmid`=**9202** (verified genuinely free).
The actual guard block (extracted from the served script): explicit+no-force → deterministic die;
default+no-force → logs "next free vmid is 9202" + prompts on `/dev/tty` → clean `die "no free vmid
confirmed"` on no-confirm; default+`--force` → destructive-over-provision warning, no prompt. (A fully
interactive real run isn't automatable now that both prompts read `/dev/tty`; the guard was proven with
real host state + the real code path.)
## Observations
- The `/dev/tty` reads make the script prompts unattended-proof by design — so an automated run past the
prompts isn't possible; Part C was proven via the real functions/guard against live `pct`+`qm`.
- shellcheck shows 2 pre-existing SC2015 *info* notices unrelated to this change (left untouched — fixing
them would alter unrelated verify logic).
- The customer page still renders the retrieval passphrase in the debug Option 3 + the credential box
(pre-existing); the new Option 1 deliberately does not.
## Remaining audit follow-ups (unchanged)
Controller-side geo intent sync; a read-only reported-vs-desired "Show Diff"; the cosmetic `controllerURL`
cleanup in `configs.go`.