Files
triggershell/app/server.ts
T
valknarandClaude Sonnet 5 ced99a8e75 Initial implementation of TriggerShell
A Python CLI (typer) that bootstraps Node/pnpm and launches a Next.js 16 web
app for running configured shell scripts: YAML config validated by a shared
Zod schema, dynamic per-script forms mapped to shadcn controls, argv-safe
execa execution with live WebSocket streaming, SQLite/Drizzle run history,
and optional argon2 session + API token auth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 18:37:30 +02:00

72 lines
2.2 KiB
TypeScript

import "./src/bootstrap/async-local-storage-polyfill";
import { createServer } from "node:http";
import { parse } from "node:url";
import next from "next";
import { WebSocketServer } from "ws";
import { getConfig } from "./src/lib/config/load";
import { migrateOnBoot } from "./src/lib/db/client";
import { syncAuthFromConfig } from "./src/lib/auth/sync";
import { reconcileOrphanedRuns } from "./src/lib/runner/engine";
import { killAllRuns } from "./src/lib/runner/registry";
import { attachWsServer, authenticateUpgrade } from "./src/lib/ws/server";
const dev = process.env.NODE_ENV !== "production";
const { config } = getConfig();
const port = Number(process.env.PORT ?? config.server.port);
const hostname = process.env.HOST ?? config.server.host;
migrateOnBoot();
syncAuthFromConfig();
reconcileOrphanedRuns();
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const httpServer = createServer((req, res) => {
handle(req, res, parse(req.url ?? "/", true));
});
const wss = new WebSocketServer({ noServer: true });
attachWsServer(wss);
httpServer.on("upgrade", (req, socket, head) => {
const { pathname } = parse(req.url ?? "/");
if (pathname !== "/ws/runs") {
socket.destroy();
return;
}
authenticateUpgrade(req)
.then((ok) => {
if (!ok) {
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
socket.destroy();
return;
}
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit("connection", ws, req);
});
})
.catch(() => socket.destroy());
});
httpServer.listen(port, hostname, () => {
console.log(
`> triggershell ready on http://${hostname}:${port} (${dev ? "development" : "production"})`,
);
});
const shutdown = (signal: string) => {
console.log(`> received ${signal}, shutting down...`);
killAllRuns();
httpServer.close(() => process.exit(0));
// Force-exit if graceful shutdown hangs (e.g. a stuck WS connection).
setTimeout(() => process.exit(1), 5000).unref();
};
process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));
});