package health import ( "encoding/json" "net/http" "sync/atomic" "time" ) // Status represents agent health for liveness and readiness probes. type Status struct { Status string `json:"status"` Agent string `json:"agent"` Version string `json:"version"` Uptime string `json:"uptime"` Timestamp time.Time `json:"timestamp"` } // Server exposes HTTP health endpoints for the node agent. type Server struct { addr string version string startedAt time.Time ready atomic.Bool mux *http.ServeMux } // NewServer creates a health HTTP server bound to addr. func NewServer(addr, version string) *Server { s := &Server{ addr: addr, version: version, startedAt: time.Now().UTC(), mux: http.NewServeMux(), } s.mux.HandleFunc("GET /healthz", s.handleLiveness) s.mux.HandleFunc("GET /readyz", s.handleReadiness) return s } // SetReady toggles readiness for orchestration probes. func (s *Server) SetReady(ready bool) { s.ready.Store(ready) } // Handler returns the HTTP handler for embedding in tests. func (s *Server) Handler() http.Handler { return s.mux } // ListenAndServe starts the health server. func (s *Server) ListenAndServe() error { return http.ListenAndServe(s.addr, s.mux) } func (s *Server) handleLiveness(w http.ResponseWriter, _ *http.Request) { s.writeStatus(w, http.StatusOK, "alive") } func (s *Server) handleReadiness(w http.ResponseWriter, _ *http.Request) { if !s.ready.Load() { s.writeStatus(w, http.StatusServiceUnavailable, "not_ready") return } s.writeStatus(w, http.StatusOK, "ready") } func (s *Server) writeStatus(w http.ResponseWriter, code int, status string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) _ = json.NewEncoder(w).Encode(Status{ Status: status, Agent: "node-agent", Version: s.version, Uptime: time.Since(s.startedAt).Round(time.Second).String(), Timestamp: time.Now().UTC(), }) }