|
|
/**
|
|
|
* 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);
|