import { mkdir } from "node:fs/promises"; import { join } from "node:path"; import { backupHandler } from "@sttracker/core/backup"; import { syncHandler } from "@sttracker/core/connections"; import { startScheduler, startWorker } from "@sttracker/core/jobs"; import { loadPlugins } from "@sttracker/core/registry"; import { createDb } from "@sttracker/db/db"; import { migrateToLatest } from "@sttracker/db/migrate"; import index from "../../web/index.html"; import { createApp } from "./app.ts"; import { ensureBackupScheduled } from "./boot.ts"; import { loadConfig } from "./config.ts"; const BACKUP_CHECK_MS = 60 * 60 * 1000; const config = loadConfig(); await mkdir(join(config.dataDir, "plugins"), { recursive: true }); const db = await createDb(config.databaseUrl); await migrateToLatest(db); const registry = await loadPlugins(config.pluginDirs); const ensureBackup = () => ensureBackupScheduled(db).catch((e) => console.error("[backup] scheduling failed", e)); const runBackup = backupHandler({ db, dataDir: config.dataDir }); const worker = startWorker(db, { sync: syncHandler({ db, secret: config.secret, registry }), backup: async () => { try { await runBackup(); } finally { // The finishing job still holds lockKey "backup" until the worker marks it done; queue the next one shortly after. setTimeout(() => void ensureBackup(), 5_000); } }, }); const scheduler = startScheduler(db); await ensureBackup(); // Safety net: re-queue the daily backup if a run's follow-up was missed (e.g. it failed for good). const backupTimer = setInterval(() => void ensureBackup(), BACKUP_CHECK_MS); const app = createApp({ db, registry, secret: config.secret, dataDir: config.dataDir, publicUrl: config.publicUrl }); const server = Bun.serve({ port: config.port, // Production unless asked: dev mode adds HMR and verbose error pages. // `bun --hot` puts the flag in execArgv, not Bun.argv. development: process.env.NODE_ENV === "development" || process.execArgv.includes("--hot"), routes: { "/api/*": (req, srv) => app.fetch(req, srv), "/ingest/*": (req, srv) => app.fetch(req, srv), "/*": index, }, fetch: (req, srv) => app.fetch(req, srv), }); console.log(`STTracker listening on ${server.url} (${db.dialect}, ${registry.list().length} plugin(s))`); let stopping = false; async function shutdown(signal: string): Promise { if (stopping) return; stopping = true; console.log(`${signal}: shutting down`); clearInterval(backupTimer); scheduler.stop(); await server.stop(); await worker.stop(); await db.close(); process.exit(0); } process.on("SIGTERM", () => void shutdown("SIGTERM")); process.on("SIGINT", () => void shutdown("SIGINT"));