Files
felhom-agent/internal/localapi/host_metrics.go
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

50 lines
2.2 KiB
Go

package localapi
import (
"net/http"
"gitea.dooplex.hu/admin/felhom-agent/internal/hub"
)
// Host metrics (slice 9, doc 03 §6). The de-privileged controller (slice 8C) can only see its own
// cgroup, so it cannot read host health itself. This endpoint re-serves the slice-4 collector's
// host + per-storage view to the customer so the controller can render the box's health.
//
// Host-wide, token-authed, fresh: the metrics are about the BOX (not per-guest), so any valid
// per-guest token gets the host-wide view (assumption: one customer per host — the home-server
// model). It is a live collect (fresh cpu%/temp), not the 15-min hub-report snapshot.
// HostMetricsResponse is GET /host/metrics: the host block + per-storage capacity targets.
type HostMetricsResponse struct {
VMID int `json:"vmid"`
Host hub.HostMetrics `json:"host"` // cpu%/mem/load/uptime/cpu_temp_c
StorageTargets []hub.StorageTarget `json:"storage_targets"` // per-storage total/used/thin-pool/SMART temp+wear
}
// handleHostMetrics serves a fresh host-health snapshot. The host block comes from a live
// collector read; the per-storage capacity comes from the observer (the same source the hub
// report uses). Best-effort on storage: a storage-view error still returns the host block (the
// CPU/mem/temp view is the headline) with an empty targets list.
func (s *Server) handleHostMetrics(w http.ResponseWriter, r *http.Request, vmid int) {
if s.hostMetrics == nil {
writeErr(w, http.StatusServiceUnavailable, "host metrics not configured on this host")
return
}
host, err := s.hostMetrics.HostMetricsNow(r.Context())
if err != nil {
s.logger.Error("local-api: /host/metrics collect", "vmid", vmid, "err", err)
writeErr(w, http.StatusBadGateway, "could not read host metrics")
return
}
targets, err := s.storage.Observe(r.Context())
if err != nil {
// Host health is the headline — don't sink it on a storage-view hiccup.
s.logger.Warn("local-api: /host/metrics storage view unavailable", "vmid", vmid, "err", err)
targets = []hub.StorageTarget{}
}
if targets == nil {
targets = []hub.StorageTarget{}
}
writeOK(w, HostMetricsResponse{VMID: vmid, Host: host, StorageTargets: targets})
}