114 lines
4.4 KiB
Go
114 lines
4.4 KiB
Go
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,
|
|
},
|
|
})
|
|
}
|