import "dotenv/config"; import { PrismaClient } from "@prisma/client"; import { PrismaPg } from "@prisma/adapter-pg"; import { hash } from "@node-rs/argon2"; import { provisionTenant } from "@/server/provision"; /** * Erst-Bootstrap für Produktiv (Ersatz für den Demo-Seed, der nur für Test/Dev ist). * Ohne diesen Schritt gibt es in Prod keinen Zugang. * * Legt an: * 1. Ersten Mandanten + Mandanten-Admin → Login unter /login * 2. Plattform-Admin (Superadmin, eigener Store) → Login unter /platform/login (MFA * wird beim ersten Login eingerichtet) * Beide mit denselben Zugangsdaten (BOOTSTRAP_ADMIN_*), analog zum Seed. * * Ausführung: im migrate-Job nach `prisma migrate deploy`, gesteuert per * BOOTSTRAP_ADMIN=true (siehe docker-compose.coolify.yml, docs/DEPLOY-PROD-CONTABO.md). * * Idempotent: provisionTenant und der platformAdmin.upsert nutzen upserts; ein bereits * gesetztes Passwort wird beim erneuten Lauf NICHT überschrieben. * * Erforderliche Umgebungsvariablen: * BOOTSTRAP_ADMIN_EMAIL, BOOTSTRAP_ADMIN_PASSWORD, BOOTSTRAP_ADMIN_NAME * BOOTSTRAP_TENANT_NAME, BOOTSTRAP_TENANT_SLUG * Optional: * BOOTSTRAP_TENANT_SHORT, BOOTSTRAP_TENANT_SECTOR */ const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }), }); function req(name: string): string { const v = process.env[name]; if (!v || v.trim() === "") { console.error(`✖ Bootstrap abgebrochen: Umgebungsvariable ${name} fehlt.`); process.exit(1); } return v.trim(); } async function main() { const email = req("BOOTSTRAP_ADMIN_EMAIL").toLowerCase(); const password = req("BOOTSTRAP_ADMIN_PASSWORD"); const name = req("BOOTSTRAP_ADMIN_NAME"); const tenantName = req("BOOTSTRAP_TENANT_NAME"); const tenantSlug = req("BOOTSTRAP_TENANT_SLUG").toLowerCase(); const short = process.env.BOOTSTRAP_TENANT_SHORT?.trim() || undefined; const sector = process.env.BOOTSTRAP_TENANT_SECTOR?.trim() || undefined; // 1. Mandant + Mandanten-Admin (Zugang /login). Idempotent (upserts). const tenant = await provisionTenant(prisma, { name: tenantName, slug: tenantSlug, short, sector, admin: { email, name, password }, }); // 2. Plattform-Admin / Superadmin (Zugang /platform/login, MFA beim ersten Login). // upsert: Passwort nur bei Neuanlage setzen — bei erneutem Lauf nicht zurücksetzen. await prisma.platformAdmin.upsert({ where: { email }, update: { name, status: "ACTIVE" }, create: { email, name, passwordHash: await hash(password), status: "ACTIVE" }, }); console.log( `✔ Bootstrap fertig: Mandant "${tenant.name}" (${tenantSlug}), Mandanten-Admin + Plattform-Admin ${email} angelegt.` ); console.log( " → /platform/login = Superadmin (MFA beim ersten Login), /login = Mandanten-Zugang. Passwort nach erstem Login ändern." ); } main() .then(() => prisma.$disconnect()) .catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });