/** * startup.ts — TypeScript equivalent of startup.sh * * Normalizes DB paths, starts the bot (index.ts) and web app (SvelteKit) * in the background, waits for the database and auth tables to be ready, * then runs the proxy router in the foreground. */ import { existsSync } from "node:fs"; import { resolve } from "node:path"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Track spawned children so we can clean them up on exit. */ const children: { proc: ReturnType }[] = []; function cleanup(): void { for (const { proc } of children) { try { proc.kill(); } catch { // Process already dead – ignore } } } process.on("SIGTERM", () => { cleanup(); process.exit(0); }); process.on("SIGINT", () => { cleanup(); process.exit(0); }); // --------------------------------------------------------------------------- // 1. Normalize DB paths to absolute (readlink -f equivalent) // --------------------------------------------------------------------------- let dbPath = process.env.DB_PATH; let dataDir = process.env.DATA_DIR || "/app/data"; if (dbPath) { try { dbPath = resolve(dbPath); process.env.DB_PATH = dbPath; } catch { // Equivalent to `readlink -f "$DB_PATH" || true` } } else { try { dataDir = resolve(dataDir); process.env.DATA_DIR = dataDir; } catch { // Equivalent to `readlink -f "$DATA_DIR" || true` } } // --------------------------------------------------------------------------- // 2. Determine the DB file path for wait checks // --------------------------------------------------------------------------- const dbFile: string = process.env.DB_PATH ? process.env.DB_PATH : `${process.env.DATA_DIR || "/app/data"}/tasks.db`; // --------------------------------------------------------------------------- // 3. Start the bot in the background (default port 3007) // --------------------------------------------------------------------------- const botPort = process.env.BOT_PORT || "3007"; const botProc = Bun.spawn({ cmd: ["bun", "run", "index.ts"], env: { ...process.env, PORT: botPort }, stdout: "inherit", stderr: "inherit", }); children.push({ proc: botProc }); // --------------------------------------------------------------------------- // 4. Wait for the DB file to exist (max ~30 s) // --------------------------------------------------------------------------- console.log(`[startup] Waiting for database at: ${dbFile}`); for (let i = 0; i < 150; i++) { if (existsSync(dbFile)) break; await Bun.sleep(200); } // --------------------------------------------------------------------------- // 5. Wait for auth tables (web_tokens, web_sessions) — max ~30 s // --------------------------------------------------------------------------- // Check if sqlite3 CLI is available (equivalent to `command -v sqlite3`) let hasSqlite3 = false; try { const check = Bun.spawn({ cmd: ["sqlite3", "--version"], stdout: "pipe", stderr: "pipe", }); await check.exited; hasSqlite3 = check.exitCode === 0; } catch { hasSqlite3 = false; } if (hasSqlite3) { console.log( "[startup] Checking for auth tables (web_tokens, web_sessions)...", ); let authReady = false; for (let i = 0; i < 150; i++) { if (existsSync(dbFile)) { const proc = Bun.spawn({ cmd: [ "sqlite3", dbFile, "SELECT 1 FROM sqlite_master WHERE type='table' AND name IN ('web_tokens','web_sessions') LIMIT 1;", ], stdout: "pipe", stderr: "pipe", }); const out = await new Response(proc.stdout).text(); await proc.exited; if (out.trim() === "1") { authReady = true; break; } } await Bun.sleep(200); } if (!authReady) { console.log( "[startup] Warning: auth tables may not be ready after waiting.", ); } } else { console.log( "[startup] sqlite3 not available; skipping auth-table verification (continuing).", ); } // --------------------------------------------------------------------------- // 6. Start the web app (SvelteKit) in the background (default port 3008) // --------------------------------------------------------------------------- const webPort = process.env.WEB_PORT || "3008"; const webProc = Bun.spawn({ cmd: ["bun", "./build/index.js"], cwd: resolve("apps/web"), env: { ...process.env, PORT: webPort }, stdout: "inherit", stderr: "inherit", }); children.push({ proc: webProc }); // --------------------------------------------------------------------------- // 7. Small wait to avoid race conditions // --------------------------------------------------------------------------- await Bun.sleep(1000); // --------------------------------------------------------------------------- // 8. Start the proxy router in the foreground (default port 3000) // --------------------------------------------------------------------------- const port = process.env.PORT || "3000"; const proxyProc = Bun.spawn({ cmd: ["bun", "proxy.ts"], env: { ...process.env, PORT: port }, stdout: "inherit", stderr: "inherit", }); // Wait for the proxy (foreground process) to exit, then shut down. const exitCode = await proxyProc.exited; console.log(`[startup] Proxy exited with code ${exitCode}`); cleanup(); process.exit(typeof exitCode === "number" ? exitCode : 1);