Files
Hartmut d4641e27aa feat: first-run setup wizard, CLI seed script, and installation docs
- /setup Server Component + SetupClient form + createFirstAdmin Server Action:
  zero-users guard (TOCTOU-safe), argon2 hash, ADMIN user creation,
  redirects to /auth/signin after setup
- scripts/setup-admin.mjs: CLI alternative for headless/container setups
- docs/installation.md: 7-section install guide (clone → configure → run → verify)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-02 20:45:15 +02:00

83 lines
2.2 KiB
JavaScript

#!/usr/bin/env node
// scripts/setup-admin.mjs
// Usage: node scripts/setup-admin.mjs --email admin@example.com --name "Admin" --password secret123
import { loadWorkspaceEnv } from "./load-env.mjs";
// Load .env if DATABASE_URL is not already set
if (!process.env.DATABASE_URL) {
const loaded = loadWorkspaceEnv();
if (loaded.length === 0 && !process.env.DATABASE_URL) {
console.error("ERROR: DATABASE_URL is not set. Create a .env file or export DATABASE_URL before running this script.");
process.exit(1);
}
}
// Parse CLI args
const args = process.argv.slice(2);
function getArg(flag) {
const index = args.indexOf(flag);
if (index === -1 || index + 1 >= args.length) return null;
return args[index + 1];
}
const email = getArg("--email");
const name = getArg("--name");
const password = getArg("--password");
const missing = [];
if (!email) missing.push("--email");
if (!name) missing.push("--name");
if (!password) missing.push("--password");
if (missing.length > 0) {
console.error(`ERROR: Missing required arguments: ${missing.join(", ")}`);
console.error("Usage: node scripts/setup-admin.mjs --email admin@example.com --name \"Admin\" --password secret123");
process.exit(1);
}
if (!email.includes("@")) {
console.error("ERROR: Invalid email address.");
process.exit(1);
}
if (password.length < 8) {
console.error("ERROR: Password must be at least 8 characters.");
process.exit(1);
}
const { PrismaClient } = await import("@prisma/client");
const { hash } = await import("@node-rs/argon2");
const prisma = new PrismaClient();
try {
const count = await prisma.user.count();
if (count > 0) {
console.log("Admin user already exists. Skipping.");
process.exit(0);
}
const passwordHash = await hash(password);
await prisma.user.create({
data: {
email: email.toLowerCase().trim(),
name: name.trim(),
passwordHash,
systemRole: "ADMIN",
isActive: true,
},
});
console.log(`Admin user created: ${email.toLowerCase().trim()}`);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.error(`ERROR: ${message}`);
process.exit(1);
} finally {
await prisma.$disconnect();
}