package api import ( "context" "log/slog" "net/http" "time" "github.com/alexei/tinyforge/internal/metrics" ) // livez always returns 200 if the process is up. Used by container // orchestrators / load balancers / Docker HEALTHCHECK as the "is the // binary alive" probe. Intentionally does NOT touch the DB or Docker — // a slow DB must not cause restart loops. func (s *Server) livez(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = w.Write([]byte("ok\n")) } // readyz returns 200 only when the process can actually serve traffic: // SQLite is reachable, the encryption key is loaded, the deployer is // not draining. The response body is intentionally minimal — the // specific failing probe name is recorded in slog (operator-visible) // rather than returned to unauthenticated callers. This avoids handing // reconnaissance to an attacker who can hit /readyz during an outage // ("DB down" vs "encryption key missing" leaks operational state). func (s *Server) readyz(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) defer cancel() // DB ping: cheap and exact — exercises the connection pool, file // lock, and busy-timeout. A failing ping means SQLite WAL is wedged // or the data dir is gone. if err := s.store.DB().PingContext(ctx); err != nil { slog.Warn("readyz: db ping failed", "error", err) w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("not ready\n")) return } // Encryption key sanity: if it's zero we cannot decrypt any stored // secret, so the deployer paths will all explode at first use. if s.encKey == ([32]byte{}) { slog.Warn("readyz: encryption key not loaded") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("not ready\n")) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = w.Write([]byte("ready\n")) } // metricsExport writes the process-wide metrics registry in Prometheus // text format. Admin-only by router placement; surface is intentionally // thin (no histograms / quantiles, only counters) to keep the binary // dependency-free. func (s *Server) metricsExport(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8") _ = metrics.DefaultRegistry.WritePrometheus(w) }