| import Fastify from "fastify";
|
| import fastifyStatic from "@fastify/static";
|
| import { dirname, join } from "node:path";
|
| import { fileURLToPath } from "node:url";
|
| import { ZodError } from "zod";
|
| import { config } from "./config";
|
| import "./db";
|
| import { mailboxRoutes } from "./routes/mailboxes";
|
| import { mailRoutes } from "./routes/mails";
|
| import { clawOpsRoutes } from "./routes/claw-ops";
|
| import { sendRoutes } from "./routes/send";
|
| import { eventRoutes } from "./routes/events";
|
| import { clawAuthRoutes } from "./routes/claw-auth";
|
| import { cfMailRoutes } from "./routes/cf-mail";
|
| import { aiRoutes } from "./routes/ai";
|
| import { secretRoutes } from "./routes/secrets";
|
| import { extCfRoutes } from "./routes/ext-cf";
|
| import { accessRoutes } from "./routes/access";
|
| import { sentRoutes } from "./routes/sent";
|
| import { getClientIp, isBlocked, isWhitelisted, recordFail, clearFails } from "./ip-guard";
|
| import { startAllMailboxListeners } from "./listener-manager";
|
| import { hasClawMailConfig } from "./runtime-config";
|
| import { hydrateFromSupabase } from "./hydrate";
|
| import { supabaseConfigured } from "./supabase-sync";
|
|
|
| const app = Fastify({
|
| logger: true,
|
| trustProxy: 1
|
| });
|
|
|
| function extractAdminPassword(request: any): string | undefined {
|
| const header = request.headers["x-admin-password"];
|
| if (typeof header === "string") return header;
|
| const queryPassword = request.query?.token;
|
| if (typeof queryPassword === "string") return queryPassword;
|
| return undefined;
|
| }
|
|
|
| app.addHook("onRequest", async (request, reply) => {
|
| if (request.url === "/health") return;
|
|
|
| const ip = getClientIp(request);
|
| if (isBlocked(ip)) {
|
| return reply.code(403).send({ error: "forbidden", ip });
|
| }
|
|
|
| if (!request.url.startsWith("/api/")) return;
|
| const password = extractAdminPassword(request);
|
| if (password !== config.ADMIN_PASSWORD) {
|
| if (password && !isWhitelisted(ip)) recordFail(ip);
|
| return reply.code(401).send({ error: "unauthorized" });
|
| }
|
| clearFails(ip);
|
| });
|
|
|
| app.setErrorHandler((error, _request, reply) => {
|
| if (error instanceof ZodError) {
|
| return reply.code(400).send({ error: "invalid input", details: error.issues });
|
| }
|
| app.log.error(error);
|
| return reply.code(500).send({
|
| error: error instanceof Error ? error.message : "internal server error"
|
| });
|
| });
|
|
|
| app.get("/health", async () => {
|
| return { ok: true };
|
| });
|
|
|
| await mailboxRoutes(app);
|
| await mailRoutes(app);
|
| await clawOpsRoutes(app);
|
| await sendRoutes(app);
|
| await eventRoutes(app);
|
| await clawAuthRoutes(app);
|
| await cfMailRoutes(app);
|
| await aiRoutes(app);
|
| await secretRoutes(app);
|
| await extCfRoutes(app);
|
| await accessRoutes(app);
|
| await sentRoutes(app);
|
|
|
| const __dirname = dirname(fileURLToPath(import.meta.url));
|
| const webRoot = join(__dirname, "../web");
|
| await app.register(fastifyStatic, {
|
| root: webRoot,
|
| prefix: "/"
|
| });
|
|
|
| app.setNotFoundHandler(async (_request, reply) => {
|
| return reply.sendFile("index.html");
|
| });
|
|
|
|
|
| if (supabaseConfigured()) {
|
| await hydrateFromSupabase();
|
| app.log.info("supabase persistence enabled; hydrated local cache from cloud");
|
| } else {
|
| app.log.warn("SUPABASE_URL/SUPABASE_SERVICE_KEY not set; data will not survive restarts");
|
| }
|
|
|
| if (hasClawMailConfig()) {
|
| startAllMailboxListeners();
|
| } else {
|
| app.log.warn("CLAW_API_KEY is not set; mailbox listeners are disabled until configured");
|
| }
|
|
|
| await app.listen({ host: "0.0.0.0", port: config.PORT });
|
|
|