feat(server): boot: migrate, load plugins, worker, scheduler, daily backup, web placeholder

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-30 21:16:15 +01:00
co-authored by Claude Opus 5.5
parent 6963e297b0
commit 02b7eed5ec
4 changed files with 107 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
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,
development: process.env.NODE_ENV !== "production",
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<void> {
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"));