#!/usr/bin/env node /** * Create or update an admin user. * * Usage: * node scripts/create-admin.js * * If the username already exists, the password is updated (upsert). * Run this directly on the server after deployment. * * Example: * node scripts/create-admin.js raine supersecretpassword */ import bcrypt from 'bcryptjs'; import Database from 'better-sqlite3'; import { randomBytes } from 'crypto'; import { config } from 'dotenv'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; // Load .env from project root const __dirname = dirname(fileURLToPath(import.meta.url)); config({ path: join(__dirname, '..', '.env') }); const [,, username, password] = process.argv; if (!username || !password) { console.error('Usage: node scripts/create-admin.js '); process.exit(1); } if (username.length < 2 || username.length > 32) { console.error('Username must be between 2 and 32 characters.'); process.exit(1); } if (password.length < 8) { console.error('Password must be at least 8 characters.'); process.exit(1); } const dbPath = process.env.DATABASE_URL ?? './dev.db'; const db = new Database(dbPath); db.pragma('journal_mode = WAL'); db.pragma('foreign_keys = ON'); // Ensure the admins table exists (safe to run before full app init) db.exec(` CREATE TABLE IF NOT EXISTS admins ( id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at TEXT NOT NULL ); `); const existing = db.prepare(`SELECT id FROM admins WHERE username = ?`).get(username); const hash = bcrypt.hashSync(password, 12); const now = new Date().toISOString(); if (existing) { db.prepare(`UPDATE admins SET password_hash = ? WHERE username = ?`).run(hash, username); console.log(`✓ Password updated for admin: ${username}`); } else { const id = randomBytes(8).toString('hex'); db.prepare(`INSERT INTO admins (id, username, password_hash, created_at) VALUES (?, ?, ?, ?)`) .run(id, username, hash, now); console.log(`✓ Admin created: ${username}`); } db.close();