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>
This commit is contained in:
2026-06-10 16:16:03 +02:00
parent 9a0e7e168b
commit aa4dfb75ea
13 changed files with 664 additions and 55 deletions
+22 -5
View File
@@ -54,6 +54,13 @@ type TokenAuthority interface {
Lookup(token string) (int, bool)
}
// HostMetricsProvider does a FRESH host-metrics collect (cpu%/mem/load/uptime/cpu-temp) for
// GET /host/metrics (slice 9). Satisfied by *hub.Collector (which reuses the slice-4 collector —
// no duplicate collection). Optional: when nil, /host/metrics reports "not configured".
type HostMetricsProvider interface {
HostMetricsNow(ctx context.Context) (hub.HostMetrics, error)
}
// Options configures a Server.
type Options struct {
ListenAddr string // bridge IP:port
@@ -73,7 +80,11 @@ type Options struct {
Disks DiskOps
DiskGate StorageGate
Guests2 GuestLister
Logger *slog.Logger
// HostMetrics serves GET /host/metrics (slice 9) — host-wide health (cpu%/mem/load/uptime/
// cpu-temp) + per-storage capacity, host-wide and token-authed (one-customer-per-host). When
// nil the endpoint reports "not configured" (host still reports/reconciles).
HostMetrics HostMetricsProvider
Logger *slog.Logger
}
// defaultBackupCadence is the fallback /backup/due window when none is configured.
@@ -117,6 +128,8 @@ type Server struct {
diskGate StorageGate // slice 8C (optional)
guestList GuestLister // slice 8C (optional)
hostMetrics HostMetricsProvider // slice 9 (optional)
jobsMu sync.Mutex
jobs map[int]*backupJob // per-guest backup job state (slice 8B)
@@ -149,10 +162,11 @@ func NewServer(o Options) (*Server, error) {
cadence: cadence,
logger: o.Logger,
now: func() time.Time { return time.Now().UTC() },
disks: o.Disks,
diskGate: o.DiskGate,
guestList: o.Guests2,
jobs: map[int]*backupJob{},
disks: o.Disks,
diskGate: o.DiskGate,
guestList: o.Guests2,
hostMetrics: o.HostMetrics,
jobs: map[int]*backupJob{},
}, nil
}
@@ -166,6 +180,9 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("GET /backup/due", s.withGuest(s.handleBackupDue))
mux.HandleFunc("GET /backup/status", s.withGuest(s.handleBackupStatus))
mux.HandleFunc("GET /restore-test/status", s.withGuest(s.handleRestoreTestStatus))
// Host metrics (slice 9): host-wide health + per-storage capacity for the customer's monitoring
// view. Host-wide, token-authed, fresh (a live collect, not the 15-min hub snapshot).
mux.HandleFunc("GET /host/metrics", s.withGuest(s.handleHostMetrics))
// Disk management (slice 8C) — self-scoped; format routes through the data-bearing classifier+gate.
mux.HandleFunc("GET /disks", s.withGuest(s.handleDisks))
mux.HandleFunc("POST /disks/assign", s.withGuest(s.handleDiskAssign))