import { resolve } from "node:path"; import Fastify from "fastify"; import { serializerCompiler, validatorCompiler, type ZodTypeProvider, } from "@fastify/type-provider-zod"; import type { AppConfig } from "./config.js"; import { loadConfig } from "./config.js"; import authPlugin from "./plugins/auth.js"; import cfClientPlugin from "./plugins/cf-client.js"; import { requireAuth } from "./plugins/auth.js"; import corsPlugin from "./plugins/cors.js"; import dbPlugin from "./plugins/db.js"; import errorHandlerPlugin from "./plugins/error-handler.js"; import { authRoutes, healthRoutes } from "./routes/health.js"; import { groupRoutes } from "./routes/groups.js"; import { serviceRoutes } from "./routes/services.js"; import { serviceGroupRoutes } from "./routes/service-groups.js"; import { serviceBindingRoutes } from "./routes/service-bindings.js"; import { domainRoutes } from "./routes/domains.js"; import { dnsRoutes } from "./routes/dns.js"; import { subdomainRoutes } from "./routes/subdomains.js"; import { certificateRoutes } from "./routes/certificates.js"; import { syncRoutes } from "./routes/sync.js"; import { originHealthCheckRoutes } from "./routes/origin-health-checks.js"; import { healthCheckRoutes } from "./routes/health-check.js"; import { domainMonitorRoutes, notificationRoutes, } from "./routes/domain-monitors.js"; import { settingsRoutes } from "./routes/settings.js"; import { integrationsVpsTrackerRoutes } from "./routes/integrations-vps-tracker.js"; import { auditRoutes } from "./routes/audit.js"; import * as certificateService from "./services/certificate-service.js"; import { createHealthCheckTask, healthEngineFallbacksFromConfig, scheduleHealthCheckJob, } from "./services/health-check-scheduler.js"; import { fireEnsureHealthWorker } from "./services/health/health-worker-deploy.js"; import { AsyncTask, CronJob } from "toad-scheduler"; export interface BuildAppOptions { config?: AppConfig; memory?: boolean; } export async function buildApp(opts: BuildAppOptions = {}) { const config = opts.config ?? loadConfig(); const app = Fastify({ logger: { level: config.logLevel }, }).withTypeProvider(); app.setValidatorCompiler(validatorCompiler); app.setSerializerCompiler(serializerCompiler); await app.register(import("@fastify/sensible")); await app.register(import("@fastify/helmet"), { contentSecurityPolicy: false }); await app.register(import("@fastify/rate-limit"), { max: 300, timeWindow: "1 minute", }); await app.register(corsPlugin); await app.register(errorHandlerPlugin); await app.register(dbPlugin, { config, memory: opts.memory }); await app.register(cfClientPlugin, { config }); await app.register(authPlugin, { config }); await app.register(healthRoutes); await app.register(authRoutes, { prefix: "/api/v1" }); await app.register(integrationsVpsTrackerRoutes, { prefix: "/api/v1" }); await app.register( async (protectedApi) => { protectedApi.addHook("onRequest", requireAuth); await protectedApi.register(groupRoutes); await protectedApi.register(serviceRoutes); await protectedApi.register(serviceGroupRoutes); await protectedApi.register(serviceBindingRoutes); await protectedApi.register(domainRoutes); await protectedApi.register(dnsRoutes); await protectedApi.register(subdomainRoutes); await protectedApi.register(certificateRoutes); await protectedApi.register(syncRoutes); await protectedApi.register(healthCheckRoutes); await protectedApi.register(originHealthCheckRoutes); await protectedApi.register(domainMonitorRoutes); await protectedApi.register(notificationRoutes); await protectedApi.register(settingsRoutes); await protectedApi.register(auditRoutes); }, { prefix: "/api/v1" }, ); const staticDir = config.staticDir ?? resolve(process.cwd(), "static"); if (config.staticDir !== null) { await app.register(import("@fastify/static"), { root: staticDir, wildcard: false, }); app.setNotFoundHandler(async (_request, reply) => { return reply.sendFile("index.html"); }); } if (!opts.memory) { await app.register(import("@fastify/schedule")); const certTask = new AsyncTask( "certificate-check", async () => { const n = await certificateService.runAllChecks(app.db); app.log.info({ checked: n }, "certificate check completed"); }, (err) => { app.log.warn({ err }, "certificate check failed"); }, ); app.scheduler.addCronJob( new CronJob( { cronExpression: config.certCheckCron }, certTask, { preventOverrun: true }, ), ); const healthTask = createHealthCheckTask(app, config); scheduleHealthCheckJob(app, config, healthTask); app.decorate("reloadHealthCheckJob", () => { scheduleHealthCheckJob(app, config, healthTask); }); if (config.cloudflareApiToken) { fireEnsureHealthWorker( app.db, app.cf, healthEngineFallbacksFromConfig(config), app.log, ); } } return app; }