import type { FastifyInstance } from "fastify"; import { applyBody, checkBody, createUpdaterClient, requestId, UpdaterError } from "../modules/selfhost-updater"; import { requireSystemAdmin } from "../utils/requireSystemAdmin"; import { getBuildInfo } from "../utils/buildInfo"; export default async function selfhostUpdateRoutes(server: FastifyInstance, options: { client?: ReturnType }) { const client = options.client || createUpdaterClient(); server.addHook("preHandler", async (req, reply) => { reply.header("Cache-Control", "no-store"); await requireSystemAdmin(server, req, reply); }); const forward = async (reply: any, method: "GET" | "POST", path: string, body?: unknown) => { try { const result = await client(method, path, body); return reply.code(result.status).send(path === "/v1/status" ? { ...result.body as object, backend: getBuildInfo() } : result.body); } catch (error) { const safe = error instanceof UpdaterError ? error : new UpdaterError("UPDATER_UNAVAILABLE"); return reply.code(safe.status).send({ error: { code: safe.code, message: safe.message } }); } }; server.get("/admin/updates/status", async (_req, reply) => forward(reply, "GET", "/v1/status")); server.get<{ Params: { id: string } }>("/admin/updates/requests/:id", async (req, reply) => { if (!requestId.safeParse(req.params.id).success) return reply.code(400).send({ error: { code: "INVALID_REQUEST" } }); return forward(reply, "GET", `/v1/requests/${req.params.id}`); }); server.get<{ Params: { id: string } }>("/admin/updates/requests/:id/logs", async (req, reply) => { if (!requestId.safeParse(req.params.id).success) return reply.code(400).send({ error: { code: "INVALID_REQUEST" } }); return forward(reply, "GET", `/v1/requests/${req.params.id}/logs`); }); for (const command of ["check", "apply"] as const) { server.post(`/admin/updates/${command}`, { bodyLimit: 4096 }, async (req, reply) => { // The auth plugin verifies this JWT. Cookies alone cannot trigger host operations (CSRF). if (!/^Bearer \S{11,}$/.test(req.headers.authorization || "")) { return reply.code(403).send({ error: { code: "BEARER_REQUIRED", message: "Bitte erneut anmelden." } }); } const parsed = (command === "apply" ? applyBody : checkBody).safeParse(req.body); if (!parsed.success) return reply.code(400).send({ error: { code: "INVALID_REQUEST" } }); return forward(reply, "POST", `/v1/${command}`, parsed.data); }); } }