import type { Server } from "bun"; import { loadConfig } from "./config"; import { json, jsonError, notFound, securityHeaders } from "./http"; import { log } from "./log"; import { RateLimiter } from "./ratelimit"; import { handleAuth } from "./routes/auth"; import { handleDelete } from "./routes/delete"; import { handleDownload } from "./routes/download"; import { handleUpload } from "./routes/upload"; import { Storage } from "./storage"; const cfg = loadConfig(); const storage = new Storage(cfg.storageDir); await storage.init(); const secret = await storage.loadSecret(); const sweep = await storage.sweep(); if (sweep.removedFiles > 0 || sweep.removedMetadata > 0) { log( "info", `startup sweep removed ${sweep.removedFiles} orphaned file(s), ${sweep.removedMetadata} orphaned metadata record(s)`, ); } const limiter = new RateLimiter(cfg.rateLimitMax, cfg.rateLimitWindowMs); const startedAt = Date.now(); const STATIC_ROUTES: Record = { "/": { file: "./public/index.html", type: "text/html; charset=utf-8" }, "/index.html": { file: "./public/index.html", type: "text/html; charset=utf-8" }, "/style.css": { file: "./public/style.css", type: "text/css; charset=utf-8" }, "/app.js": { file: "./public/app.js", type: "text/javascript; charset=utf-8" }, }; const staticContent = new Map(); for (const [route, info] of Object.entries(STATIC_ROUTES)) { staticContent.set(route, { body: await Bun.file(new URL(info.file, import.meta.url)).text(), type: info.type, }); } const server = Bun.serve({ hostname: cfg.host, port: cfg.port, maxRequestBodySize: Infinity, fetch(req, srv) { return route(req, srv).catch((err) => { log("error", `unhandled error: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`); return jsonError(500, "internal error"); }); }, }); function clientIp(req: Request, srv: Server): string { if (cfg.trustProxy) { const xff = req.headers.get("x-forwarded-for"); if (xff) { const first = xff.split(",")[0]?.trim(); if (first) return first; } const cf = req.headers.get("cf-connecting-ip"); if (cf) return cf; } return srv.requestIP(req)?.address ?? "unknown"; } function serveStatic(path: string): Response { const entry = staticContent.get(path); if (!entry) return notFound(); return new Response(entry.body, { headers: { "content-type": entry.type, "cache-control": "no-cache", "content-security-policy": "default-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'", ...securityHeaders(), }, }); } function isSecure(req: Request, url: URL): boolean { if (cfg.trustProxy) return req.headers.get("x-forwarded-proto") === "https"; return url.protocol === "https:"; } async function route(req: Request, srv: Server): Promise { const url = new URL(req.url); const path = url.pathname; const method = req.method; if (method === "GET" && staticContent.has(path)) return serveStatic(path); if (path === "/api/health" && method === "GET") { return json({ status: "ok", uptimeSeconds: Math.round((Date.now() - startedAt) / 1000) }); } if (path === "/api/upload" && method === "POST") { return handleUpload(req, storage, cfg.maxFileSize); } if (path.startsWith("/api/auth/") && method === "POST") { if (cfg.rateLimitEnabled && !limiter.check(clientIp(req, srv))) { return jsonError(429, "too many requests"); } return handleAuth(req, storage, path.slice("/api/auth/".length), secret, isSecure(req, url)); } if (path.startsWith("/d/") && (method === "GET" || method === "HEAD")) { if (cfg.rateLimitEnabled && !limiter.check(clientIp(req, srv))) { return jsonError(429, "too many requests"); } return handleDownload(req, storage, path.slice(3), secret); } if (path.startsWith("/api/files/") && method === "DELETE") { if (cfg.rateLimitEnabled && !limiter.check(clientIp(req, srv))) { return jsonError(429, "too many requests"); } return handleDelete(req, storage, path.slice("/api/files/".length)); } return notFound(); } log( "info", `listening on http://${cfg.host}:${cfg.port} storage=${cfg.storageDir} maxUpload=${cfg.maxFileSize}`, ); for (const signal of ["SIGINT", "SIGTERM"] as const) { process.once(signal, () => { log("info", `received ${signal}, shutting down`); server.stop(); process.exit(0); }); } process.on("unhandledRejection", (reason) => { log("error", `unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`); });