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 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`
);
}
}
|