hub v0.46.0: observability pass — per-box log pulls, bundle custody, 72h TTL + secret gate

log_bundle_requests + log_bundles store (gzip, newest-3, 72h TTL purged on the
60s sweep); SaveLogBundle secret gate fail-closed (blocked flag row, no payload;
REDACTED/checksums pass). Report ACK gains controller_log_requested + ingests
controller_log_tail; heartbeat envelope gains log_tail_requested + ingests
log_tail (consume-once on arrival; pre-0.83 agents stay visibly pending). Host
detail Diagnostics section: request buttons (controller/agent), state rows with
honest latency hints, View/Download endpoint. Red-proofs: gate disabled and
clear-on-arrival removed both FAIL their tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-11 16:57:47 +02:00
parent 1ee5559772
commit e35b1ae0e6
13 changed files with 898 additions and 4 deletions
+15 -1
View File
@@ -251,9 +251,23 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Offsite — read-only WG endpoint + peer registry (S2). Mutations stay on the admin API.
case path == "/offsite":
s.handleOffsite(w, r)
// Hosts — read-only fleet view (audit F-M1). GET only; no host actions.
// Hosts — read-only fleet view (audit F-M1) + the v0.46.0 log-bundle actions.
case path == "/hosts" || path == "/hosts/":
s.handleHostsList(w, r)
case strings.HasPrefix(path, "/hosts/") && strings.HasSuffix(path, "/request-logs"):
hostID := strings.TrimSuffix(strings.TrimPrefix(path, "/hosts/"), "/request-logs")
if r.Method == http.MethodPost {
s.handleRequestLogBundle(w, r, hostID)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
case strings.HasPrefix(path, "/hosts/") && strings.Contains(path, "/log-bundles/"):
rest := strings.TrimPrefix(path, "/hosts/")
if i := strings.Index(rest, "/log-bundles/"); i > 0 {
s.handleLogBundleView(w, r, rest[:i], rest[i+len("/log-bundles/"):])
} else {
http.NotFound(w, r)
}
case strings.HasPrefix(path, "/hosts/"):
hostID := strings.TrimPrefix(path, "/hosts/")
s.handleHostDetail(w, r, hostID)