const fs = require("fs"); const path = require("path"); const { spawn } = require("child_process"); const ROOT = path.join(__dirname, ".."); const DATA_DIR = path.join(ROOT, "data"); const LOG_DIR = path.join(ROOT, "logs"); const PID_FILE = path.join(DATA_DIR, "server.pid"); const OUT_LOG = path.join(LOG_DIR, "server.out.log"); const ERR_LOG = path.join(LOG_DIR, "server.err.log"); fs.mkdirSync(DATA_DIR, { recursive: true }); fs.mkdirSync(LOG_DIR, { recursive: true }); const command = process.argv[2] || "status"; if (command === "start") { start(); } else if (command === "stop") { stop(); } else if (command === "restart") { restart(); } else if (command === "status") { status(); } else { console.error(`Unknown command: ${command}`); process.exit(1); } function start() { const pid = readPid(); if (pid && isRunning(pid)) { console.log(`Live Event server already running with PID ${pid}`); console.log(`Logs: ${OUT_LOG}`); return; } cleanupStalePid(); const stdoutFd = fs.openSync(OUT_LOG, "a"); const stderrFd = fs.openSync(ERR_LOG, "a"); const child = spawn(process.execPath, ["server.js"], { cwd: ROOT, detached: true, windowsHide: true, stdio: ["ignore", stdoutFd, stderrFd], env: { ...process.env, RC_TIMING_PID_FILE: PID_FILE, }, }); child.unref(); fs.writeFileSync(PID_FILE, `${child.pid}\n`); console.log(`Live Event server started in background`); console.log(`PID: ${child.pid}`); console.log(`Logs: ${OUT_LOG}`); } async function stop() { const pid = readPid(); if (!pid) { console.log("Live Event server is not running"); return; } if (!isRunning(pid)) { cleanupStalePid(); console.log("Removed stale pid file"); return; } try { process.kill(pid, "SIGTERM"); } catch (error) { console.error(`Could not stop PID ${pid}: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } const stopped = await waitForExit(pid, 5000); if (!stopped && process.platform !== "win32") { try { process.kill(pid, "SIGKILL"); } catch { // ignore } await waitForExit(pid, 1000); } cleanupStalePid(); console.log(`Live Event server stopped`); } async function restart() { await stop(); start(); } function status() { const pid = readPid(); if (pid && isRunning(pid)) { console.log(`Live Event server is running`); console.log(`PID: ${pid}`); console.log(`Logs: ${OUT_LOG}`); return; } cleanupStalePid(); console.log("Live Event server is not running"); } function readPid() { if (!fs.existsSync(PID_FILE)) { return null; } const raw = fs.readFileSync(PID_FILE, "utf8").trim(); const pid = Number(raw); return Number.isFinite(pid) && pid > 0 ? pid : null; } function isRunning(pid) { try { process.kill(pid, 0); return true; } catch { return false; } } function cleanupStalePid() { const pid = readPid(); if (!pid || !isRunning(pid)) { fs.rmSync(PID_FILE, { force: true }); } } function waitForExit(pid, timeoutMs) { return new Promise((resolve) => { const started = Date.now(); const timer = setInterval(() => { if (!isRunning(pid)) { clearInterval(timer); resolve(true); return; } if (Date.now() - started >= timeoutMs) { clearInterval(timer); resolve(false); } }, 150); }); }