import { db } from "@/db/client"; import { storageTiers } from "@/db/schema"; import { getAgentStatuses, AGENT_TIMEOUT_SECONDS } from "@/lib/agents"; import { timingSafeEqual } from "node:crypto"; export const dynamic = "force-dynamic"; // Read-only view of agent liveness, consumed by the local mount daemon // (agent/ampelos-mountd.sh) so it can decide whether a tier's storage host is // up. Deliberately read-only: the daemon holds the privilege, this endpoint // never performs a mount. function authorize(request: Request) { const expected = process.env.AMPELOS_AGENT_TOKEN; if (!expected) return { ok: false, status: 503, error: "AMPELOS_AGENT_TOKEN is not configured" }; const header = request.headers.get("authorization") ?? ""; const provided = header.startsWith("Bearer ") ? header.slice(7) : ""; const a = Buffer.from(provided); const b = Buffer.from(expected); if (!provided || a.length !== b.length || !timingSafeEqual(a, b)) { return { ok: false, status: 401, error: "unauthorized" }; } return { ok: true as const }; } export async function GET(request: Request) { const auth = authorize(request); if (!auth.ok) return Response.json({ error: auth.error }, { status: auth.status }); const [agents, tiers] = await Promise.all([ getAgentStatuses(), db.select().from(storageTiers), ]); const agentByName = new Map(agents.map((agent) => [agent.agent, agent])); return Response.json({ timeoutSeconds: AGENT_TIMEOUT_SECONDS, agents: agents.map((agent) => ({ agent: agent.agent, online: agent.online, secondsSinceSeen: agent.secondsSinceSeen, hostname: agent.hostname, })), tiers: tiers.map((tier) => ({ tier: tier.tier, basePath: tier.basePath, agentName: tier.agentName, alwaysOnline: tier.alwaysOnline, // null when the tier names no agent: availability is then unmanaged. agentOnline: tier.agentName ? Boolean(agentByName.get(tier.agentName)?.online) : null, })), }); }