package controllers import ( "net/http" "runtime/debug" "strings" "nearle/services" "nearle/utils" "github.com/gofiber/fiber/v2" ) // What is running here, and is it wired up? // // ── Why this exists ───────────────────────────────────────────────────────── // // On 2026-09-24 the assistant sat switched off in production for most of a day, // and neither of us could establish WHY from outside the container. Two // questions had no answer: // // 1. which build is deployed? A redeploy can reuse a cached image, so // "I pushed it" and "it is running" are different facts. // 2. does the server have a model? `/assistant/status` knows, but it sits // behind the session guard, and a 401 from `/v1/web` proves nothing — // the middleware answers before routing, so a route that does not exist // returns exactly the same 401 as one that does. // // Every diagnosis that day was guesswork for want of one request. Hours went // into probing CORS headers and comparing nginx versions to infer a commit, // which is what people do when a server will not simply say. // // ── What it deliberately does not say ─────────────────────────────────────── // // Booleans, never values. "The assistant has a model" is operational; WHICH // model, at which endpoint, under which key is not, and the reason string on // `/assistant/status` names environment variables — that stays behind the // guard. Nothing here distinguishes a tenant, so there is nothing to scope. // // Unauthenticated on purpose. A health check that needs a credential cannot be // used by the person trying to work out why credentials are not working, and // that is precisely when it is wanted. type HealthController struct { assistant services.AssistantService // hasDatabase is a construction-time fact, not a live ping. A query per // health check is a query per uptime probe, and "configured" is the thing // that actually differs between a broken deployment and a working one. hasDatabase bool } func NewHealthController(assistant services.AssistantService, hasDatabase bool) *HealthController { return &HealthController{assistant: assistant, hasDatabase: hasDatabase} } // Version is stamped at build time: // // go build -ldflags "-X nearle/controllers.Version=$(git rev-parse --short HEAD)" // // Left as "unknown" when nothing stamps it, which is honest — and itself worth // seeing, because it means the image was not built by the pipeline that does. var Version = "unknown" // buildVersion falls back to whatever the toolchain recorded. // // `debug.ReadBuildInfo` carries the VCS revision for a build made inside a git // checkout, so even an image built by hand usually knows its own commit. The // ldflag is preferred because a Docker build copies the tree without `.git`. func buildVersion() string { if Version != "unknown" && strings.TrimSpace(Version) != "" { return Version } info, ok := debug.ReadBuildInfo() if !ok { return "unknown" } for _, setting := range info.Settings { if setting.Key == "vcs.revision" && setting.Value != "" { if len(setting.Value) > 7 { return setting.Value[:7] } return setting.Value } } return "unknown" } func (ctl *HealthController) Health(c *fiber.Ctx) error { assistant := false if ctl.assistant != nil { assistant = ctl.assistant.Available() } return c.Status(http.StatusOK).JSON(fiber.Map{ "code": http.StatusOK, "status": true, "message": "Success", "details": fiber.Map{ "version": buildVersion(), // Can this server issue console sessions at all? // // `attachWebSession` logs a minting failure and lets the login // succeed without a token, so a server with no signing secret hands // out sessions that cannot authenticate: the console renders, and // every request after it comes back 401 with no `authorization` // header on it. False here is that, stated once, instead of found // by reading request headers on a Friday morning. "sessions": utils.WebTokenConfigured(), // True when a model is configured and the assistant can answer. False // is the answer to "I set the key and redeployed, did it take?" — // which took a day to establish without it. "assistant": assistant, "database": ctl.hasDatabase, }, }) }