aa4dfb75ea
Add a host-wide, token-authed GET /host/metrics local-API endpoint that re-serves the slice-4 collector's host + per-storage view to the customer (the de-privileged controller can't read the host itself). Add the one new collector — CPU/chassis temperature via sysfs hwmon/thermal-zones, graceful- null — to the shared HostMetrics struct, so the hub report carries cpu_temp_c too. Cross-repo host-report golden updated byte-identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.3 KiB
3.3 KiB
REPORT — slice 9 (agent half): host metrics to the controller (v0.14.0) (2026-06-10)
Overwrite-latest report. Cumulative history: CHANGELOG.md.
What was implemented
The agent half of slice 9 — re-serving the host's health to the customer's controller, plus the one new collector (CPU/chassis temperature). The de-privileged controller (slice 8C) sees only its own cgroup, so it cannot read host metrics itself; the agent already collects host CPU/mem/loadavg/ uptime + per-storage targets for the hub, and slice 9 exposes that to the customer over the local API.
internal/hub/cputemp.go — CPU/chassis-temp collector (new)
TempReaderseam +SysfsTempReader: reads the CPU package temperature from sysfs — hwmon (coretemp/k10temp/zenpower/cpu_thermal, preferring thePackage id 0input) then thermal zones (preferringx86_pkg_temp/coretemp/cpu-thermal, falling back toacpitz).- No external binary, no privilege (sysfs is world-readable) → the root-CLI fence is untouched.
- Graceful-null: a missing sensor, an unsupported board, an implausible reading (outside
5–150 °C), or any read error all degrade to
nil("n/a") — never fails the report. Same nullable contract as the per-diskSmartSummary.TemperatureC.
internal/hub — shared wire field + collector reuse
HostMetricsgainsCPUTempC *int(cpu_temp_c) — nullable, on the shared struct, so the hub report carries it too (operator freebie). Cross-repo host-report golden updated byte-identical with the hub's copy.Collectorgains a nil-safetemp TempReader(defaults to the realSysfsTempReader;SetTempReaderinjects a fake in tests).Collect()now setscpu_temp_con the report.Collector.HostMetricsNow(ctx)— a freshNodeStatus+ CPU-temp read returning just the host block; the source for the local API (current cpu%/temp, not the 15-min hub snapshot).
internal/localapi — GET /host/metrics (new endpoint)
host_metrics.go: host-wide health (cpu%/mem/load/uptime/cpu_temp_c) + per-storage capacity (total/used/fraction, thin-pool, SMART temp+wear). Token-authed viawithGuest(host-wide data; a cross-guest?vmid=still 403). Best-effort on storage (a view error still returns the host block). Served only when theHostMetricsprovider (the shared collector) is wired inbuildLocalAPIServer— else 503 "not configured".
Tests (all green)
cputemp_test.go,hostmetrics_test.go,host_metrics_test.go: hwmon/thermal-zone selection + graceful-null,HostMetricsNowpopulate/null/hard-error, endpoint populated +cpu_temp_c:nullserialization + 401 without a token + 403 cross-guest + 503 not-configured.go test ./...green;go vet ./internal/hub ./internal/localapiclean.
Versioning / docs
- Version
0.13.0 → 0.14.0;CHANGELOG.mdupdated. Doc 03 §6 (local-API surface) + §9 (roadmap + changelog) updated.
Assumption (noted, not built)
- One customer per host (home-server model):
/host/metricsis host-wide. A multi-customer host would leak cross-customer CPU/mem → revisit then.
Pending
- Live validation on the demo (build + deploy agent v0.14.0; controller monitoring page → real
N100 CPU%/temp + per-storage, cross-checked vs
pvesh/free/df).