// GET /relay/health — public liveness check. No auth, no credit // accounting. Returns a minimal status object so monitoring + Recap's // /api/relay/status can verify the relay is reachable. import express from "express"; import { readFileSync } from "fs"; import path from "path"; import { fileURLToPath } from "url"; import { getConfigSnapshot } from "../config.js"; // Pull the version off server/package.json once at module load — the // build pipeline bumps that file in lockstep with each StartOS version // bump, so the health endpoint always reports a meaningful number. const __dirname = path.dirname(fileURLToPath(import.meta.url)); let VERSION = "unknown"; try { const pkg = JSON.parse( readFileSync(path.join(__dirname, "..", "package.json"), "utf8") ); VERSION = pkg.version || "unknown"; } catch { // Read failure is non-fatal — health still works, just reports // "unknown". Build pipeline shouldn't ever ship a missing // package.json so this branch is defensive only. } export function healthRouter() { const router = express.Router(); router.get("/health", async (_req, res) => { const cfg = await getConfigSnapshot(); res.json({ ok: true, service: "recap-relay", version: VERSION, backends: { gemini: !!cfg.relay_gemini_api_key, // Whether the operator-hardware path is wired up at all. // Hardware backends are now sourced from Spark Control // discovery — see hardware-config.js. Empty discovery URL // means no hardware path; downstream details (which model is // ready, transcribe vs analyze availability) are surfaced via // /admin/config's effective_* fields. hardware: !!cfg.relay_spark_control_url, }, admin_enabled: !!cfg.relay_admin_password_hash, }); }); return router; }