CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | import { mkdir } from "fs/promises";
import app from "./app";
import { config } from "./lib/config";
import { presenceWebsocket } from "./routes/pulls";
import { startWorker } from "./lib/workflow-runner";
import { startWebhookDeliveryWorker } from "./lib/webhook-delivery";
import { startAutopilot } from "./lib/autopilot";
import { ensureDemoContent } from "./lib/demo-seed";
import { ensureDemoActivity } from "./lib/demo-activity-seed";
import { ensureEnvSiteAdmin } from "./lib/admin-bootstrap";
import { ensureMarketplaceSeed } from "./lib/agent-marketplace-seed";
import { maybeSelfBootstrap } from "./lib/self-bootstrap";
import { notifySystemdReady } from "./lib/systemd-notify";
import { loadConfigIntoEnv } from "./lib/system-config";
import { startSshServer } from "./lib/ssh-server";
// Ensure repos directory exists
await mkdir(config.gitReposPath, { recursive: true });
// /admin/integrations boot hook — pull saved integration secrets out of the
// system_config table and into process.env BEFORE anything else reads them.
// This is the magic that lets the existing synchronous config getters
// (config.anthropicApiKey, config.resendApiKey, …) transparently pick up
// values an admin saved through the UI, with no restart needed. If the DB
// is unreachable at boot, env vars stay as the fallback — never blocks
// startup.
try {
const n = await loadConfigIntoEnv();
if (n > 0) console.log(`[system-config] loaded ${n} key(s) from DB into env`);
} catch (err) {
console.warn(
"[system-config] boot load failed (env vars remain authoritative):",
err instanceof Error ? err.message : err
);
}
// Self-bootstrap: if Gluecron's own canonical repo (`ccantynz/Gluecron.com.git`
// by default) doesn't exist on disk yet, initialize it from the GitHub mirror
// and install the post-receive hook. This is the platform's self-healing path
// — once it runs successfully on a host, future deploys flow through Gluecron
// itself with no external CI tooling. Fire-and-forget; never blocks startup.
void maybeSelfBootstrap().catch((err) => {
console.warn(`[self-bootstrap] swallowed: ${(err as Error).message}`);
});
// SSH git server (Block SSH-1). Listens for git-over-SSH on SSH_PORT
// (default 2222). Authenticates by public key from the `ssh_keys` table.
// Set SSH_PORT=0 to disable. Set SSH_HOST_KEY to a persistent PEM key
// in production to avoid "host key changed" warnings on restart.
startSshServer();
// Start the Actions-equivalent workflow worker (Block C1). Polls
// workflow_runs for queued rows and executes them sequentially.
startWorker();
// Reliable webhook delivery worker (migration 0056). Polls
// webhook_deliveries for pending rows whose next_attempt_at <= now() and
// retries with exponential backoff before dead-lettering.
startWebhookDeliveryWorker();
// Autopilot: periodic mirror sync, merge-queue progress, weekly digests,
// advisory rescans. No-op when AUTOPILOT_DISABLED=1.
startAutopilot();
// Site-admin bootstrap from env (SITE_ADMIN_USERNAME). Idempotent — if the
// user exists, they get a row in site_admins; if not, logged and retried
// on next boot. Background-fired so a slow DB doesn't block startup.
void ensureEnvSiteAdmin().catch((err) => {
console.warn(
"[admin-bootstrap] ensureEnvSiteAdmin failed:",
err instanceof Error ? err.message : err
);
});
// Agent marketplace seed. Idempotent — inserts the 4 canonical example
// listings only if they don't already exist. Background-fired with a small
// delay so the admin-bootstrap path lands first (the seed lists the
// publisher as the bootstrap admin).
void (async () => {
try {
await new Promise((r) => setTimeout(r, 1500));
await ensureMarketplaceSeed();
} catch (err) {
console.warn(
"[agent-marketplace-seed] failed:",
err instanceof Error ? err.message : err
);
}
})();
// Opt-in demo content seed on boot (DEMO_SEED_ON_BOOT=1). Idempotent, never
// throws — safe to run on every start. Block L3 layers extra activity (more
// issues, an open + merged PR on todo-api, AI-review comment, auto-merge
// audit row) so the live /demo page has content out of the box.
if (process.env.DEMO_SEED_ON_BOOT === "1") {
void (async () => {
try {
await ensureDemoContent();
await ensureDemoActivity();
} catch {
/* never throw out of boot */
}
})();
}
console.log(`
gluecron v0.1.0
──────────────────────
http://localhost:${config.port}
repos: ${config.gitReposPath}
`);
// BLOCK N2 — tell systemd we're ready. No-op when NOTIFY_SOCKET is unset
// (dev / non-systemd hosts). Fire-and-forget; never throws.
void notifySystemdReady().catch((err) => {
console.warn(
"[systemd] notifySystemdReady failed:",
err instanceof Error ? err.message : err
);
});
export default {
port: config.port,
fetch: app.fetch,
// WebSocket handler for real-time PR presence (Figma-style diff cursors).
// Bun.serve forwards WS upgrade requests through the same fetch pipeline;
// the `upgradeWebSocket` middleware in pulls.tsx calls server.upgrade() and
// returns a null response. This `websocket` object handles the ws lifecycle.
websocket: presenceWebsocket,
};
|