import { randomBytes } from 'node:crypto'; import { writeFileSync } from 'node:fs'; import { PrismaPg } from '../../api/node_modules/@prisma/adapter-pg/dist/index.js'; import { PrismaClient } from '../../api/node_modules/@prisma/client/index.js'; const databaseUrl = process.env.DATABASE_URL; if (!databaseUrl) { throw new Error('DATABASE_URL is required'); } const prisma = new PrismaClient({ adapter: new PrismaPg(databaseUrl) }); const username = process.env.PROD_ADMIN_USERNAME || 'prod_admin'; const email = process.env.PROD_ADMIN_EMAIL || 'admin@example.com'; const configuredPassword = process.env.PROD_ADMIN_PASSWORD; const credentialFile = process.env.PROD_ADMIN_CREDENTIAL_FILE; async function main() { // production-deploy.sh builds the API before invoking this helper, so every // administrative password write shares the same versioned hasher as runtime APIs. const { hashPassword } = await import('../../api/dist/auth/password-hasher.js'); const existingUser = await prisma.user.findFirst({ where: { username, deletedAt: null }, }); const createPassword = configuredPassword || randomBytes(18).toString('base64url'); const updatePassword = configuredPassword; const passwordHash = await hashPassword(updatePassword || createPassword); const role = await prisma.role.upsert({ where: { code: 'platform_admin' }, update: { name: '平台管理员', scope: 'platform' }, create: { code: 'platform_admin', name: '平台管理员', scope: 'platform' }, }); const userData = { email, displayName: '生产平台管理员', ...(updatePassword ? { passwordHash } : {}), status: 'active', failedLoginCount: 0, lockedUntil: null, deletedAt: null, tenantId: null, }; const user = existingUser ? await prisma.user.update({ where: { id: existingUser.id }, data: userData, }) : await prisma.user.create({ data: { username, email, displayName: '生产平台管理员', passwordHash, status: 'active', }, }); await prisma.userRole.upsert({ where: { userId_roleId: { userId: user.id, roleId: role.id } }, update: {}, create: { userId: user.id, roleId: role.id }, }); const message = [ 'CMPP production admin account', `username=${username}`, `email=${email}`, existingUser ? (updatePassword ? `password=${updatePassword}` : 'password=unchanged') : `password=${createPassword}`, `generatedAt=${new Date().toISOString()}`, '', ].join('\n'); if (credentialFile) { writeFileSync(credentialFile, message, { mode: 0o600 }); } console.log(`Production admin is ready: ${email}`); if (!credentialFile) { console.log(existingUser && !updatePassword ? 'Password unchanged' : `Temporary password: ${existingUser ? updatePassword : createPassword}`); } } main() .finally(() => prisma.$disconnect()) .catch((error) => { console.error(error); process.exit(1); });