/** * Seed script — creates the initial Admin role and admin user. * * Usage: * npx tsx scripts/seed.ts * * Requires a valid MONGODB_URI in .env (or .env.local). */ import mongoose from 'mongoose' import bcrypt from 'bcryptjs' import * as readline from 'readline' import * as dotenv from 'dotenv' // Load environment variables dotenv.config({ path: '.env.local' }) dotenv.config({ path: '.env' }) const MONGODB_URI = process.env.MONGODB_URI if (!MONGODB_URI) { console.error('❌ MONGODB_URI is not defined. Create a .env or .env.local file first.') process.exit(1) } // ─── Inline Schemas (avoid importing from src/ which needs TS paths) ───── const permissionSchema = new mongoose.Schema( { resource: { type: String, required: true }, actions: [{ type: String, required: true }], }, { _id: false } ) const roleSchema = new mongoose.Schema( { name: { type: String, required: true, unique: true, trim: true }, permissions: [permissionSchema], description: { type: String, default: '' }, isDefault: { type: Boolean, default: false }, }, { timestamps: true } ) const userSchema = new mongoose.Schema( { username: { type: String, required: true, unique: true, trim: true }, email: { type: String, required: true, unique: true, trim: true, lowercase: true }, passwordHash: { type: String, required: true }, roles: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Role' }], twoFactorCode: { type: String, default: null }, twoFactorExpiry: { type: Date, default: null }, loginAttempts: { type: Number, default: 0 }, lockUntil: { type: Date, default: null }, status: { type: String, enum: ['active', 'inactive', 'locked'], default: 'active' }, lastLogin: { type: Date, default: null }, }, { timestamps: true } ) const Role = mongoose.models.Role || mongoose.model('Role', roleSchema) const User = mongoose.models.User || mongoose.model('User', userSchema) // ─── Helpers ───────────────────────────────────────────────────── function prompt(question: string): Promise { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) return new Promise(resolve => { rl.question(question, answer => { rl.close() resolve(answer.trim()) }) }) } // ─── Admin Permissions (wildcard on every resource) ────────────── const ADMIN_PERMISSIONS = [ { resource: '*', actions: ['*'] }, ] // ─── Main ──────────────────────────────────────────────────────── async function main() { console.log('🌱 MC-Manager Seed Script\n') await mongoose.connect(MONGODB_URI!) console.log('✅ Connected to MongoDB\n') // ── 1. Create or update Admin Role ────────────────────────────── let adminRole = await Role.findOne({ name: 'Admin' }) if (adminRole) { console.log('ℹ️ Admin role already exists — updating permissions...') adminRole.permissions = ADMIN_PERMISSIONS adminRole.description = 'Full system administrator with all permissions' await adminRole.save() } else { adminRole = await Role.create({ name: 'Admin', permissions: ADMIN_PERMISSIONS, description: 'Full system administrator with all permissions', isDefault: false, }) console.log('✅ Admin role created') } // ── 2. Create default Viewer role ─────────────────────────────── let viewerRole = await Role.findOne({ name: 'Viewer' }) if (!viewerRole) { viewerRole = await Role.create({ name: 'Viewer', permissions: [ { resource: 'servers', actions: ['view'] }, { resource: 'backups', actions: ['view'] }, { resource: 'plugins', actions: ['view'] }, { resource: 'mods', actions: ['view'] }, { resource: 'players', actions: ['view'] }, ], description: 'Read-only access to servers and related resources', isDefault: true, }) console.log('✅ Viewer role created') } else { console.log('ℹ️ Viewer role already exists — skipped') } // ── 3. Create Operator role ───────────────────────────────────── let operatorRole = await Role.findOne({ name: 'Operator' }) if (!operatorRole) { operatorRole = await Role.create({ name: 'Operator', permissions: [ { resource: 'servers', actions: ['view', 'start', 'stop', 'restart', 'console'] }, { resource: 'backups', actions: ['view', 'create'] }, { resource: 'plugins', actions: ['view', 'install', 'remove', 'toggle'] }, { resource: 'mods', actions: ['view', 'install', 'remove', 'toggle'] }, { resource: 'players', actions: ['view', 'whitelist', 'op', 'ban'] }, ], description: 'Can operate servers but cannot create/delete them or manage users', isDefault: false, }) console.log('✅ Operator role created') } else { console.log('ℹ️ Operator role already exists — skipped') } // ── 4. Create Admin User ──────────────────────────────────────── const existingAdmin = await User.findOne({ username: 'admin' }) if (existingAdmin) { console.log('\nℹ️ Admin user already exists.') const reset = await prompt('Reset admin password? (y/N): ') if (reset.toLowerCase() === 'y') { const newPassword = await prompt('New password (min 8 chars): ') if (newPassword.length < 8) { console.error('❌ Password must be at least 8 characters.') await mongoose.disconnect() process.exit(1) } existingAdmin.passwordHash = await bcrypt.hash(newPassword, 12) existingAdmin.status = 'active' existingAdmin.loginAttempts = 0 existingAdmin.lockUntil = null // Ensure admin has the Admin role if (!existingAdmin.roles.some((r: mongoose.Types.ObjectId) => r.equals(adminRole._id))) { existingAdmin.roles.push(adminRole._id) } await existingAdmin.save() console.log('✅ Admin password reset successfully') } } else { console.log('\n📝 Create initial admin user:\n') const email = await prompt('Admin email: ') if (!email || !email.includes('@')) { console.error('❌ A valid email is required.') await mongoose.disconnect() process.exit(1) } const password = await prompt('Admin password (min 8 chars): ') if (password.length < 8) { console.error('❌ Password must be at least 8 characters.') await mongoose.disconnect() process.exit(1) } const passwordHash = await bcrypt.hash(password, 12) await User.create({ username: 'admin', email, passwordHash, roles: [adminRole._id], status: 'active', }) console.log('✅ Admin user created (username: admin)') } // ── Done ──────────────────────────────────────────────────────── console.log('\n🎉 Seed complete! You can now log in at http://localhost:3000') await mongoose.disconnect() process.exit(0) } main().catch(err => { console.error('❌ Seed failed:', err) process.exit(1) })