import "./env"; import { hash } from "bcryptjs"; import { eq, sql } from "drizzle-orm"; import { dbGlobal } from "./database/sqlite/db-bun"; import { users } from "./lib/schema/auth"; /** Match `server/service/auth/index.ts` */ const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,20}$/; const MIN_PASSWORD_LENGTH = 6; /** * Public slug is optional: use lowercased username only if it satisfies a URL-style slug * (lowercase alphanumeric only, 3–20 chars). Usernames with underscores or mixed rules * that do not fit stay null; the admin can set `publicSlug` later via profile APIs. */ const PUBLIC_SLUG_REGEX = /^[a-z0-9]{3,20}$/; function derivePublicSlug(username: string): string | null { const lower = username.toLowerCase(); return PUBLIC_SLUG_REGEX.test(lower) ? lower : null; } async function getNextUserId() { const [row] = await dbGlobal .select({ maxId: sql`COALESCE(MAX(${users.id}), 0)`, }) .from(users); return (row?.maxId ?? 0) + 1; } async function main() { const [existingAdmin] = await dbGlobal .select({ id: users.id }) .from(users) .where(eq(users.role, "admin")) .limit(1); if (existingAdmin) { console.log("Bootstrap skipped: admin exists"); process.exit(0); } const username = process.env.BOOTSTRAP_ADMIN_USERNAME; const password = process.env.BOOTSTRAP_ADMIN_PASSWORD; if (!username || !password) { console.warn( "Bootstrap skipped: set BOOTSTRAP_ADMIN_USERNAME and BOOTSTRAP_ADMIN_PASSWORD", ); process.exit(0); } if (!USERNAME_REGEX.test(username) || password.length < MIN_PASSWORD_LENGTH) { console.warn( "Bootstrap skipped: invalid BOOTSTRAP_ADMIN_USERNAME or BOOTSTRAP_ADMIN_PASSWORD", ); process.exit(0); } const passwordHash = await hash(password, 10); const userId = await getNextUserId(); const publicSlug = derivePublicSlug(username); try { await dbGlobal.insert(users).values({ id: userId, username, password: passwordHash, role: "admin", status: "active", publicSlug, }); console.log("Bootstrap complete: admin user created"); } catch (err) { console.warn("Bootstrap skipped: could not insert admin (unique conflict or DB error)", err); process.exit(0); } process.exit(0); } main().catch((e) => { console.error(e); process.exit(1); });