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

admin-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.

admin-bootstrap.tsBlame79 lines · 1 contributor
2d985e5Claude1/**
2 * Site-admin bootstrap from env. On every boot we check whether
3 * `SITE_ADMIN_USERNAME` is set and, if so, ensure that user has a row in
4 * `site_admins`. Idempotent — re-running is a cheap no-op once granted.
5 *
6 * The user must already exist in `users` for the grant to apply. If they
7 * haven't registered yet, the function logs and returns; the next boot
8 * (or the register flow, which calls `ensureEnvAdminOnRegister()`) will
9 * pick them up.
10 *
11 * This complements the bootstrap rule in `isSiteAdmin()` (oldest user is
12 * implicit admin while site_admins is empty). The env-based grant makes
13 * it deterministic across re-deploys and team handoffs.
14 */
15
16import { eq } from "drizzle-orm";
17import { db } from "../db";
18import { siteAdmins, users } from "../db/schema";
19import { grantSiteAdmin } from "./admin";
20
21/** Read SITE_ADMIN_USERNAME (lower-cased to match users.username). Returns
22 * null if unset or empty. */
23export function getEnvAdminUsername(): string | null {
24 const raw = process.env.SITE_ADMIN_USERNAME?.trim();
25 if (!raw) return null;
26 return raw.toLowerCase();
27}
28
29/** Ensure the env-named user is a site admin. Safe to call every boot. */
30export async function ensureEnvSiteAdmin(): Promise<void> {
31 const username = getEnvAdminUsername();
32 if (!username) return;
33 try {
34 const [user] = await db
35 .select({ id: users.id })
36 .from(users)
37 .where(eq(users.username, username))
38 .limit(1);
39 if (!user) {
40 console.log(
41 `[admin-bootstrap] SITE_ADMIN_USERNAME=${username} but no such user yet — will retry next boot or on register`
42 );
43 return;
44 }
45 const [existing] = await db
46 .select({ userId: siteAdmins.userId })
47 .from(siteAdmins)
48 .where(eq(siteAdmins.userId, user.id))
49 .limit(1);
50 if (existing) {
51 console.log(`[admin-bootstrap] ${username} already has admin`);
52 return;
53 }
54 const granted = await grantSiteAdmin(user.id, null);
55 if (granted) {
56 console.log(`[admin-bootstrap] granted site admin to ${username}`);
57 } else {
58 console.warn(`[admin-bootstrap] failed to grant admin to ${username}`);
59 }
60 } catch (err) {
61 console.error("[admin-bootstrap] error:", err);
62 }
63}
64
65/** Hook for the registration flow — if the just-registered user matches
66 * SITE_ADMIN_USERNAME, grant admin immediately so they don't have to
67 * wait for the next boot. */
68export async function ensureEnvAdminOnRegister(args: {
69 userId: string;
70 username: string;
71}): Promise<void> {
72 const target = getEnvAdminUsername();
73 if (!target) return;
74 if (args.username.toLowerCase() !== target) return;
75 await grantSiteAdmin(args.userId, null);
76 console.log(
77 `[admin-bootstrap] auto-granted admin to ${args.username} on register`
78 );
79}