You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
5.4 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 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<typeof Bun.spawn> }[] = [];
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);