Files
felhom-agent/REPORT.md
T
admin aa4dfb75ea slice 9: GET /host/metrics + CPU/chassis-temp collector (v0.14.0)
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>
2026-06-10 16:16:03 +02:00

3.3 KiB
Raw Blame History

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)

  • TempReader seam + SysfsTempReader: reads the CPU package temperature from sysfs — hwmon (coretemp/k10temp/zenpower/cpu_thermal, preferring the Package id 0 input) then thermal zones (preferring x86_pkg_temp/coretemp/cpu-thermal, falling back to acpitz).
  • 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 5150 °C), or any read error all degrade to nil ("n/a") — never fails the report. Same nullable contract as the per-disk SmartSummary.TemperatureC.

internal/hub — shared wire field + collector reuse

  • HostMetrics gains CPUTempC *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.
  • Collector gains a nil-safe temp TempReader (defaults to the real SysfsTempReader; SetTempReader injects a fake in tests). Collect() now sets cpu_temp_c on the report.
  • Collector.HostMetricsNow(ctx) — a fresh NodeStatus + CPU-temp read returning just the host block; the source for the local API (current cpu%/temp, not the 15-min hub snapshot).

internal/localapiGET /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 via withGuest (host-wide data; a cross-guest ?vmid= still 403). Best-effort on storage (a view error still returns the host block). Served only when the HostMetrics provider (the shared collector) is wired in buildLocalAPIServer — else 503 "not configured".

Tests (all green)

  • cputemp_test.go, hostmetrics_test.go, host_metrics_test.go: hwmon/thermal-zone selection + graceful-null, HostMetricsNow populate/null/hard-error, endpoint populated + cpu_temp_c:null serialization + 401 without a token + 403 cross-guest + 503 not-configured.
  • go test ./... green; go vet ./internal/hub ./internal/localapi clean.

Versioning / docs

  • Version 0.13.0 → 0.14.0; CHANGELOG.md updated. Doc 03 §6 (local-API surface) + §9 (roadmap + changelog) updated.

Assumption (noted, not built)

  • One customer per host (home-server model): /host/metrics is 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).