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
self-bootstrap.ts5.0 KB · 134 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
131
132
133
134
/**
 * Self-bootstrap — fires on boot if Gluecron's own canonical repo
 * doesn't yet exist on disk. Idempotent, safe to call every boot.
 *
 * This closes the chicken-and-egg gap where:
 *   - The site self-hosts on gluecron.com/ccantynz/Gluecron.com.git
 *   - But that bare repo was never initialized on the Fly volume
 *   - So every `git push` to it returned "Repository not found"
 *   - So nothing ever deployed through Gluecron's own canonical path
 *
 * After this boots once, the canonical repo exists, the post-receive
 * hook is installed, and Gluecron is fully self-sufficient — future
 * deploys do not require GitHub Actions, flyctl, or any external tool.
 *
 * Configuration (env vars, all optional):
 *   SELF_BOOTSTRAP_DISABLED=1  — skip entirely
 *   SELF_BOOTSTRAP_OWNER       — default "ccantynz"
 *   SELF_BOOTSTRAP_NAME        — default "Gluecron.com"
 *   SELF_BOOTSTRAP_SOURCE      — seed URL for a FRESH host with no repo yet.
 *                                No default: Gluecron is self-canonical and no
 *                                longer seeds from GitHub. On a bare new box,
 *                                set this to a backup/restore URL, or restore
 *                                the bare repo from scripts/restore.sh first.
 *
 * Never throws out of boot. Returns silently on any failure (logged).
 * Run the explicit script (`bun run scripts/self-host-bootstrap.ts`)
 * for verbose output and exit codes.
 */

import { existsSync } from "fs";
import { mkdir, writeFile, chmod, rm } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import {
  runBootstrap,
  sh,
  type BootstrapArgs,
  type BootstrapDeps,
} from "../../scripts/self-host-bootstrap";

const DEFAULT_OWNER = "ccantynz";
const DEFAULT_NAME = "Gluecron.com";
// No default source — Gluecron is its own canonical origin now. GitHub is no
// longer a seed/fallback. A fresh host with no repo seeds from an explicit
// SELF_BOOTSTRAP_SOURCE (e.g. a restored backup), or from scripts/restore.sh.
const DEFAULT_SOURCE = "";

export async function maybeSelfBootstrap(): Promise<void> {
  if (process.env.SELF_BOOTSTRAP_DISABLED === "1") {
    return;
  }

  // Lazy imports — these touch the live DB / config, and we want
  // src/lib/self-bootstrap.ts itself to be cheap to import in tests.
  const { db } = await import("../db");
  const schemaMod = await import("../db/schema");
  const { config } = await import("./config");

  const owner = process.env.SELF_BOOTSTRAP_OWNER || DEFAULT_OWNER;
  const name = process.env.SELF_BOOTSTRAP_NAME || DEFAULT_NAME;
  const source = process.env.SELF_BOOTSTRAP_SOURCE || DEFAULT_SOURCE;

  // Fast path: if the bare repo already exists on disk, the bootstrap
  // is a no-op and we skip even the DB roundtrip. The explicit script
  // is still idempotent if you want to re-verify everything.
  const barePath = join(config.gitReposPath, owner, `${name}.git`);
  if (existsSync(join(barePath, "HEAD"))) {
    return;
  }

  // Repo is missing AND no seed source is configured. This is expected on
  // the live box only if something wiped the repo — we no longer silently
  // pull from GitHub. Tell the operator how to re-seed and continue boot.
  if (!source) {
    console.warn(
      `[self-bootstrap] canonical ${owner}/${name} not found on disk and no ` +
        `SELF_BOOTSTRAP_SOURCE set. Restore it with scripts/restore.sh (repos ` +
        `snapshot) or set SELF_BOOTSTRAP_SOURCE to a seed URL, then reboot.`
    );
    return;
  }

  const args: BootstrapArgs = { owner, name, source, dryRun: false };

  // Quiet log shim — boot output is precious; only print on completion
  // or failure. The verbose script-mode logs go to stdout when you run
  // scripts/self-host-bootstrap.ts directly.
  const lines: string[] = [];
  const push = (level: string, msg: string) => lines.push(`[${level}] ${msg}`);

  const deps: BootstrapDeps = {
    db,
    schema: {
      users: schemaMod.users,
      repositories: schemaMod.repositories,
      siteAdmins: schemaMod.siteAdmins,
    },
    reposPath: config.gitReposPath,
    sh,
    fsExists: existsSync,
    fsMkdir: mkdir,
    fsWrite: (p, body) => writeFile(p, body, "utf8"),
    fsChmod: chmod,
    fsRm: rm,
    log: {
      say: (m) => push("·", m),
      ok: (m) => push("ok", m),
      warn: (m) => push("warn", m),
      bad: (m) => push("err", m),
      info: (m) => push("info", m),
    },
    tmpRoot: tmpdir(),
  };

  try {
    const result = await runBootstrap(args, deps);
    if (result.ok) {
      console.log(
        `[self-bootstrap] canonical ${owner}/${name} initialized from ${source}`
      );
    } else {
      console.warn(
        `[self-bootstrap] failed: ${result.error || "unknown"} — see scripts/self-host-bootstrap.ts to re-run with verbose output`
      );
      // Print the captured log lines so the operator can diagnose
      // without grepping process state.
      for (const l of lines.slice(-20)) console.warn(`  ${l}`);
    }
  } catch (err) {
    console.warn(
      `[self-bootstrap] crashed: ${(err as Error).message} — boot continues normally`
    );
  }
}