docs: hub REPORT for v0.27.0 (Hosts page, live-validated)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,52 +4,84 @@
|
||||
|
||||
---
|
||||
|
||||
# REPORT — Hub GUI correctness + pull-based config delivery (hub v0.26.0)
|
||||
# REPORT — Hosts page: read-only fleet view (hub v0.27.0)
|
||||
|
||||
**Date:** 2026-06-30 · **Repo:** `felhom.eu` (`hub/`) · **Companion:** felhom-controller v0.94.0
|
||||
**Date:** 2026-07-01 · **Repo:** `felhom.eu` (`hub/`) · **Audit finding:** F-M1 (resolved)
|
||||
|
||||
Implements the "retire the 5 inbound controls + pull-based config-refresh" task. Resolves audit
|
||||
`documentation/audits/AUDIT-hub-gui-2026-06-30.md` F-S1/F-S4 + the dead-template findings.
|
||||
## What & why
|
||||
|
||||
## Baselines
|
||||
- hub `felhom.eu` was `e51e03b` (v0.25.0) → code `a3ac6c9`, manifest `d88b43a` (**v0.26.0**, live on k3s).
|
||||
- (companion) felhom-controller v0.93.0 → v0.94.0.
|
||||
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.
|
||||
|
||||
## What changed (hub)
|
||||
- **Pull-based config delivery.** New `customer_configs.config_version` (stored counter, NOT a YAML
|
||||
hash — `configgen` emits a fresh `session_secret`+timestamp each call). `SaveCustomerConfig` bumps it
|
||||
on every save (new=1, updates increment). The report ACK (`handleReport`) advertises `config_version`
|
||||
beside the floor/latest; omitted for report-only customers.
|
||||
- **Retired the 5 inbound (hub→box) controls** (`01-topology-and-trust.md:11` never-inbound):
|
||||
Trigger Update, Push Config, Pull Config, Show Diff (+`compareYAMLValues`/`flattenYAML`/`maskSensitive`
|
||||
helpers + dead `ConfigSyncStatus`/`ConfigDiffCount`) — handlers + routes + buttons + JS deleted.
|
||||
Geo-disable KEEPS its hub→Cloudflare `RemoveGeoRules`; its inbound `notifyControllerGeoDisable` deleted.
|
||||
- **GUI staleness + dead templates.** Setup Commands now show `sudo ./felhom-host-install.sh
|
||||
--customer-id <id>` (no-echo passphrase) instead of `docker-setup.sh`; Option 2 relabelled
|
||||
"Manual config fetch (debug only)". Deleted orphaned `customer.html` + `config_detail.html`.
|
||||
## Baseline
|
||||
|
||||
hub `v0.26.0` @ `73b3f6a` (clean tree) → **`v0.27.0`**. Trunk-based `main`; commits
|
||||
`2289fc9` (code) + `f8b8730` (manifest bump).
|
||||
|
||||
## Files
|
||||
`internal/store/store.go` (column + bump + 3 SELECTs + struct), `internal/api/handler.go` (ACK),
|
||||
`internal/web/server.go` (routes + handleTriggerUpdate removed), `internal/web/configs.go`
|
||||
(handlers/helpers removed, geo trimmed), `internal/web/templates/customer_unified.html` (setup cmd,
|
||||
buttons/JS removed), deleted `customer.html`+`config_detail.html`, `manifests/hub.yaml` (tag).
|
||||
Tests: `internal/store/config_version_test.go`, `internal/api/config_version_ack_test.go`.
|
||||
|
||||
## Tests & green gate
|
||||
- `go build ./... && go vet ./... && go test ./...` — **all green**.
|
||||
- **Red-proof (version bump):** with the `config_version + 1` bump defeated, both
|
||||
`TestSaveCustomerConfig_BumpsConfigVersion` and `TestReportACK_ConfigVersion` FAIL
|
||||
(`config_version after edit = 1, want 2`) — the bump is load-bearing. Restored → green.
|
||||
- **No-inbound:** `grep client.Do internal/web/` → only Gitea registry/template fetches; **zero**
|
||||
ControllerURL targets (ControllerURL remains a display-only link).
|
||||
**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`.
|
||||
|
||||
## Live validation (demo, hub.felhom.eu + guest 9201)
|
||||
- Hub v0.26.0 rolled out on k3s (Synced/Healthy, `[INFO] felhom-hub … starting`).
|
||||
- Live ACK for demo-felhom: `{"config_version":1,"latest_version":"0.94.0","min_controller_version":"0.87.0","status":"ok"}`.
|
||||
- Bumped demo-felhom `config_version` 1→2 in the hub DB (equivalent to a config save; the SaveCustomerConfig
|
||||
bump itself is unit-tested + red-proofed). The controller detected it, re-pulled, and self-restarted
|
||||
(see controller REPORT for the box-side trace) — full pull-based delivery, no inbound connection.
|
||||
**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`).
|
||||
|
||||
## Open follow-ups (from the audit, not in scope here)
|
||||
Hosts page (F-M1); controller-side geo intent sync (geo-disable is now CF-only, no box notify);
|
||||
Show-Diff could return later as a read-only hub-vs-reported view.
|
||||
## Design decisions
|
||||
|
||||
- **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.
|
||||
|
||||
## Tests — `go build ./... && go vet ./... && go test ./...` all `ok`
|
||||
|
||||
- `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**
|
||||
|
||||
## Deploy (GitOps via ArgoCD — the `felhom` app; auto-sync off)
|
||||
|
||||
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`.
|
||||
|
||||
## Live validation (claude-in-chrome, operator-authed, hub.felhom.eu)
|
||||
|
||||
- **`/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**.
|
||||
|
||||
## Remaining audit follow-ups (not this slice)
|
||||
|
||||
Controller-side geo intent sync; a read-only reported-vs-desired "Show Diff"; the cosmetic `controllerURL`
|
||||
cleanup in `configs.go`.
|
||||
|
||||
Reference in New Issue
Block a user