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
Blame · Line-by-line history

self-bootstrap.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.

self-bootstrap.tsBlame134 lines · 2 contributors
96942a6Test User1/**
2 * Self-bootstrap — fires on boot if Gluecron's own canonical repo
3 * doesn't yet exist on disk. Idempotent, safe to call every boot.
4 *
5 * This closes the chicken-and-egg gap where:
6 * - The site self-hosts on gluecron.com/ccantynz/Gluecron.com.git
7 * - But that bare repo was never initialized on the Fly volume
8 * - So every `git push` to it returned "Repository not found"
9 * - So nothing ever deployed through Gluecron's own canonical path
10 *
11 * After this boots once, the canonical repo exists, the post-receive
12 * hook is installed, and Gluecron is fully self-sufficient — future
13 * deploys do not require GitHub Actions, flyctl, or any external tool.
14 *
15 * Configuration (env vars, all optional):
16 * SELF_BOOTSTRAP_DISABLED=1 — skip entirely
17 * SELF_BOOTSTRAP_OWNER — default "ccantynz"
18 * SELF_BOOTSTRAP_NAME — default "Gluecron.com"
8ee4e5dccanty labs19 * SELF_BOOTSTRAP_SOURCE — seed URL for a FRESH host with no repo yet.
20 * No default: Gluecron is self-canonical and no
21 * longer seeds from GitHub. On a bare new box,
22 * set this to a backup/restore URL, or restore
23 * the bare repo from scripts/restore.sh first.
96942a6Test User24 *
25 * Never throws out of boot. Returns silently on any failure (logged).
26 * Run the explicit script (`bun run scripts/self-host-bootstrap.ts`)
27 * for verbose output and exit codes.
28 */
29
30import { existsSync } from "fs";
31import { mkdir, writeFile, chmod, rm } from "fs/promises";
32import { join } from "path";
33import { tmpdir } from "os";
34import {
35 runBootstrap,
36 sh,
37 type BootstrapArgs,
38 type BootstrapDeps,
39} from "../../scripts/self-host-bootstrap";
40
41const DEFAULT_OWNER = "ccantynz";
42const DEFAULT_NAME = "Gluecron.com";
8ee4e5dccanty labs43// No default source — Gluecron is its own canonical origin now. GitHub is no
44// longer a seed/fallback. A fresh host with no repo seeds from an explicit
45// SELF_BOOTSTRAP_SOURCE (e.g. a restored backup), or from scripts/restore.sh.
46const DEFAULT_SOURCE = "";
96942a6Test User47
48export async function maybeSelfBootstrap(): Promise<void> {
49 if (process.env.SELF_BOOTSTRAP_DISABLED === "1") {
50 return;
51 }
52
53 // Lazy imports — these touch the live DB / config, and we want
54 // src/lib/self-bootstrap.ts itself to be cheap to import in tests.
55 const { db } = await import("../db");
56 const schemaMod = await import("../db/schema");
57 const { config } = await import("./config");
58
59 const owner = process.env.SELF_BOOTSTRAP_OWNER || DEFAULT_OWNER;
60 const name = process.env.SELF_BOOTSTRAP_NAME || DEFAULT_NAME;
61 const source = process.env.SELF_BOOTSTRAP_SOURCE || DEFAULT_SOURCE;
62
63 // Fast path: if the bare repo already exists on disk, the bootstrap
64 // is a no-op and we skip even the DB roundtrip. The explicit script
65 // is still idempotent if you want to re-verify everything.
66 const barePath = join(config.gitReposPath, owner, `${name}.git`);
67 if (existsSync(join(barePath, "HEAD"))) {
68 return;
69 }
70
8ee4e5dccanty labs71 // Repo is missing AND no seed source is configured. This is expected on
72 // the live box only if something wiped the repo — we no longer silently
73 // pull from GitHub. Tell the operator how to re-seed and continue boot.
74 if (!source) {
75 console.warn(
76 `[self-bootstrap] canonical ${owner}/${name} not found on disk and no ` +
77 `SELF_BOOTSTRAP_SOURCE set. Restore it with scripts/restore.sh (repos ` +
78 `snapshot) or set SELF_BOOTSTRAP_SOURCE to a seed URL, then reboot.`
79 );
80 return;
81 }
82
96942a6Test User83 const args: BootstrapArgs = { owner, name, source, dryRun: false };
84
85 // Quiet log shim — boot output is precious; only print on completion
86 // or failure. The verbose script-mode logs go to stdout when you run
87 // scripts/self-host-bootstrap.ts directly.
88 const lines: string[] = [];
89 const push = (level: string, msg: string) => lines.push(`[${level}] ${msg}`);
90
91 const deps: BootstrapDeps = {
92 db,
93 schema: {
94 users: schemaMod.users,
95 repositories: schemaMod.repositories,
96 siteAdmins: schemaMod.siteAdmins,
97 },
98 reposPath: config.gitReposPath,
99 sh,
100 fsExists: existsSync,
101 fsMkdir: mkdir,
102 fsWrite: (p, body) => writeFile(p, body, "utf8"),
103 fsChmod: chmod,
104 fsRm: rm,
105 log: {
106 say: (m) => push("·", m),
107 ok: (m) => push("ok", m),
108 warn: (m) => push("warn", m),
109 bad: (m) => push("err", m),
110 info: (m) => push("info", m),
111 },
112 tmpRoot: tmpdir(),
113 };
114
115 try {
116 const result = await runBootstrap(args, deps);
117 if (result.ok) {
118 console.log(
119 `[self-bootstrap] canonical ${owner}/${name} initialized from ${source}`
120 );
121 } else {
122 console.warn(
123 `[self-bootstrap] failed: ${result.error || "unknown"} — see scripts/self-host-bootstrap.ts to re-run with verbose output`
124 );
125 // Print the captured log lines so the operator can diagnose
126 // without grepping process state.
127 for (const l of lines.slice(-20)) console.warn(` ${l}`);
128 }
129 } catch (err) {
130 console.warn(
131 `[self-bootstrap] crashed: ${(err as Error).message} — boot continues normally`
132 );
133 }
134}