Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

index.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

index.tsBlame131 lines · 3 contributors
fc1817aClaude1import { mkdir } from "fs/promises";
2import app from "./app";
3import { config } from "./lib/config";
25b1ff7Claude4import { presenceWebsocket } from "./routes/pulls";
eafe8c6Claude5import { startWorker } from "./lib/workflow-runner";
8405c43Claude6import { startWebhookDeliveryWorker } from "./lib/webhook-delivery";
2b821b7Claude7import { startAutopilot } from "./lib/autopilot";
988380aClaude8import { ensureDemoContent } from "./lib/demo-seed";
52ad8b1Claude9import { ensureDemoActivity } from "./lib/demo-activity-seed";
2d985e5Claude10import { ensureEnvSiteAdmin } from "./lib/admin-bootstrap";
5ca514aClaude11import { ensureMarketplaceSeed } from "./lib/agent-marketplace-seed";
96942a6Test User12import { maybeSelfBootstrap } from "./lib/self-bootstrap";
f764c07Claude13import { notifySystemdReady } from "./lib/systemd-notify";
509c376Claude14import { loadConfigIntoEnv } from "./lib/system-config";
60323c5Claude15import { startSshServer } from "./lib/ssh-server";
fc1817aClaude16
17// Ensure repos directory exists
18await mkdir(config.gitReposPath, { recursive: true });
19
509c376Claude20// /admin/integrations boot hook — pull saved integration secrets out of the
21// system_config table and into process.env BEFORE anything else reads them.
22// This is the magic that lets the existing synchronous config getters
23// (config.anthropicApiKey, config.resendApiKey, …) transparently pick up
24// values an admin saved through the UI, with no restart needed. If the DB
25// is unreachable at boot, env vars stay as the fallback — never blocks
26// startup.
27try {
28 const n = await loadConfigIntoEnv();
29 if (n > 0) console.log(`[system-config] loaded ${n} key(s) from DB into env`);
30} catch (err) {
31 console.warn(
32 "[system-config] boot load failed (env vars remain authoritative):",
33 err instanceof Error ? err.message : err
34 );
35}
36
96942a6Test User37// Self-bootstrap: if Gluecron's own canonical repo (`ccantynz/Gluecron.com.git`
8ee4e5dccanty labs38// by default) doesn't exist on disk yet, install the post-receive hook and
39// (only if SELF_BOOTSTRAP_SOURCE is set) seed it. Gluecron is self-canonical —
40// it no longer pulls from GitHub; a fresh host re-seeds from a restored backup.
41// Once the repo exists on a host, deploys flow through Gluecron itself with no
42// external CI tooling. Fire-and-forget; never blocks startup.
96942a6Test User43void maybeSelfBootstrap().catch((err) => {
44 console.warn(`[self-bootstrap] swallowed: ${(err as Error).message}`);
45});
46
60323c5Claude47// SSH git server (Block SSH-1). Listens for git-over-SSH on SSH_PORT
48// (default 2222). Authenticates by public key from the `ssh_keys` table.
49// Set SSH_PORT=0 to disable. Set SSH_HOST_KEY to a persistent PEM key
50// in production to avoid "host key changed" warnings on restart.
51startSshServer();
52
eafe8c6Claude53// Start the Actions-equivalent workflow worker (Block C1). Polls
54// workflow_runs for queued rows and executes them sequentially.
55startWorker();
56
8405c43Claude57// Reliable webhook delivery worker (migration 0056). Polls
58// webhook_deliveries for pending rows whose next_attempt_at <= now() and
59// retries with exponential backoff before dead-lettering.
60startWebhookDeliveryWorker();
61
2b821b7Claude62// Autopilot: periodic mirror sync, merge-queue progress, weekly digests,
63// advisory rescans. No-op when AUTOPILOT_DISABLED=1.
64startAutopilot();
65
2d985e5Claude66// Site-admin bootstrap from env (SITE_ADMIN_USERNAME). Idempotent — if the
67// user exists, they get a row in site_admins; if not, logged and retried
68// on next boot. Background-fired so a slow DB doesn't block startup.
a28cedeClaude69void ensureEnvSiteAdmin().catch((err) => {
70 console.warn(
71 "[admin-bootstrap] ensureEnvSiteAdmin failed:",
72 err instanceof Error ? err.message : err
73 );
74});
2d985e5Claude75
5ca514aClaude76// Agent marketplace seed. Idempotent — inserts the 4 canonical example
77// listings only if they don't already exist. Background-fired with a small
78// delay so the admin-bootstrap path lands first (the seed lists the
79// publisher as the bootstrap admin).
80void (async () => {
81 try {
82 await new Promise((r) => setTimeout(r, 1500));
83 await ensureMarketplaceSeed();
84 } catch (err) {
85 console.warn(
86 "[agent-marketplace-seed] failed:",
87 err instanceof Error ? err.message : err
88 );
89 }
90})();
91
988380aClaude92// Opt-in demo content seed on boot (DEMO_SEED_ON_BOOT=1). Idempotent, never
52ad8b1Claude93// throws — safe to run on every start. Block L3 layers extra activity (more
94// issues, an open + merged PR on todo-api, AI-review comment, auto-merge
95// audit row) so the live /demo page has content out of the box.
988380aClaude96if (process.env.DEMO_SEED_ON_BOOT === "1") {
52ad8b1Claude97 void (async () => {
98 try {
99 await ensureDemoContent();
100 await ensureDemoActivity();
101 } catch {
102 /* never throw out of boot */
103 }
104 })();
988380aClaude105}
106
fc1817aClaude107console.log(`
108 gluecron v0.1.0
109 ──────────────────────
110 http://localhost:${config.port}
111 repos: ${config.gitReposPath}
112`);
113
f764c07Claude114// BLOCK N2 — tell systemd we're ready. No-op when NOTIFY_SOCKET is unset
115// (dev / non-systemd hosts). Fire-and-forget; never throws.
a28cedeClaude116void notifySystemdReady().catch((err) => {
117 console.warn(
118 "[systemd] notifySystemdReady failed:",
119 err instanceof Error ? err.message : err
120 );
121});
f764c07Claude122
fc1817aClaude123export default {
124 port: config.port,
125 fetch: app.fetch,
25b1ff7Claude126 // WebSocket handler for real-time PR presence (Figma-style diff cursors).
127 // Bun.serve forwards WS upgrade requests through the same fetch pipeline;
128 // the `upgradeWebSocket` middleware in pulls.tsx calls server.upgrade() and
129 // returns a null response. This `websocket` object handles the ws lifecycle.
130 websocket: presenceWebsocket,
fc1817aClaude131};