// Self-hosted entry: Bun.serve behind our reverse proxy (spec §4.10). Keep it thin. import { join, resolve } from "node:path"; import { createApp } from "./app"; import { SERVER_IDLE_TIMEOUT_S, clientIp, readConfig } from "./config"; import { DirStorage } from "./storage-dir"; const config = readConfig(process.env); const storage = config.storageDir ? await DirStorage.open(config.storageDir, config.storageMaxBytes) : null; if (storage) { const sweep = () => storage .sweep() .then((n) => n > 0 && console.log(`swept ${n} expired cover(s)`)) .catch((err) => console.error("sweep failed:", err)); await sweep(); setInterval(sweep, 24 * 60 * 60 * 1000); } const assetsRoot = resolve(config.assetsDir); let server: ReturnType; const app = createApp({ storage, publicUrl: config.publicUrl, // `relativePath` is already checked by resolveAssetPath (no dot-segments, no dotfiles). assets: async (relativePath) => { const file = Bun.file(join(assetsRoot, relativePath)); return (await file.exists()) ? new Response(file) : null; }, clientIp: (req) => clientIp(req, config.trustProxy, server.requestIP(req)?.address), }); server = Bun.serve({ port: config.port, hostname: config.hostname, idleTimeout: SERVER_IDLE_TIMEOUT_S, fetch: app }); console.log( `mangabaka-seeder on http://${server.hostname}:${server.port} · storage: ${ storage ? `${config.storageDir} (${storage.usedBytes()} of ${config.storageMaxBytes} bytes)` : "off" }`, );