Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepgatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
index.ts5.1 KB · 130 lines
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,
};