154 lines
4.3 KiB
JavaScript
154 lines
4.3 KiB
JavaScript
/**
|
||
* Modul: Setup-Script
|
||
* Zweck: Erstmalige Einrichtung - ersten Admin-User anlegen.
|
||
* Wird einmalig nach dem ersten Start ausgeführt: `node setup.js`
|
||
* Abhängigkeiten: server/db.js, bcrypt, dotenv
|
||
*/
|
||
|
||
import readline from 'node:readline';
|
||
import bcrypt from 'bcrypt';
|
||
import * as db from './server/db.js';
|
||
import os from 'node:os';
|
||
|
||
function getLocalIP() {
|
||
const interfaces = os.networkInterfaces();
|
||
for (const name of Object.keys(interfaces)) {
|
||
for (const iface of interfaces[name]) {
|
||
if (iface.family === 'IPv4' && !iface.internal) {
|
||
return iface.address;
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
const rl = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
});
|
||
|
||
function prompt(question) {
|
||
return new Promise((resolve) => rl.question(question, resolve));
|
||
}
|
||
|
||
function promptPassword(question) {
|
||
return new Promise((resolve) => {
|
||
process.stdout.write(question);
|
||
process.stdin.setRawMode(true);
|
||
process.stdin.resume();
|
||
|
||
let password = '';
|
||
process.stdin.on('data', function handler(char) {
|
||
char = char.toString();
|
||
if (char === '\r' || char === '\n') {
|
||
process.stdin.setRawMode(false);
|
||
process.stdin.removeListener('data', handler);
|
||
process.stdout.write('\n');
|
||
resolve(password);
|
||
} else if (char === '\u0003') {
|
||
process.exit();
|
||
} else if (char === '\u007f') {
|
||
if (password.length > 0) {
|
||
password = password.slice(0, -1);
|
||
process.stdout.write('\b \b');
|
||
}
|
||
} else {
|
||
password += char;
|
||
process.stdout.write('*');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
async function main() {
|
||
console.log('\n=== Oikos Setup ===\n');
|
||
|
||
// Prüfen ob bereits Admin vorhanden
|
||
const existingAdmin = db.get()
|
||
.prepare("SELECT id FROM users WHERE role = 'admin' LIMIT 1")
|
||
.get();
|
||
|
||
if (existingAdmin) {
|
||
console.log('ℹ An admin account already exists.\n');
|
||
const proceed = await prompt('Create another admin anyway? (y/N): ');
|
||
if (proceed.toLowerCase() !== 'y') {
|
||
console.log('Setup cancelled.');
|
||
rl.close();
|
||
process.exit(0);
|
||
}
|
||
}
|
||
|
||
console.log('Create admin account:\n');
|
||
|
||
const username = (await prompt('Username: ')).trim();
|
||
if (!username || username.length < 3) {
|
||
console.error('Error: username must be at least 3 characters long.');
|
||
process.exit(1);
|
||
}
|
||
|
||
const displayName = (await prompt('Display name (e.g. "Max Mustermann"): ')).trim();
|
||
if (!displayName) {
|
||
console.error('Error: display name must not be empty.');
|
||
process.exit(1);
|
||
}
|
||
|
||
const password = await promptPassword('Password: ');
|
||
if (password.length < 8) {
|
||
console.error('Error: password must be at least 8 characters long.');
|
||
process.exit(1);
|
||
}
|
||
|
||
const passwordConfirm = await promptPassword('Confirm password: ');
|
||
if (password !== passwordConfirm) {
|
||
console.error('Error: passwords do not match.');
|
||
process.exit(1);
|
||
}
|
||
|
||
const avatarColors = ['#007AFF', '#34C759', '#FF9500', '#FF3B30', '#AF52DE', '#FF2D55'];
|
||
const avatarColor = avatarColors[Math.floor(Math.random() * avatarColors.length)];
|
||
|
||
console.log('\nCreating account …');
|
||
|
||
const hash = await bcrypt.hash(password, 12);
|
||
|
||
try {
|
||
const result = db.get()
|
||
.prepare(`
|
||
INSERT INTO users (username, display_name, password_hash, avatar_color, role)
|
||
VALUES (?, ?, ?, ?, 'admin')
|
||
`)
|
||
.run(username, displayName, hash, avatarColor);
|
||
|
||
const port = process.env.PORT || 3000;
|
||
const host = getLocalIP();
|
||
|
||
console.log(`\n✅ Admin account created successfully!`);
|
||
console.log(`${'─'.repeat(40)}`);
|
||
console.log(` Username: ${username}`);
|
||
console.log(` Display name: ${displayName}`);
|
||
console.log(` Role: Admin`);
|
||
console.log(`${'─'.repeat(40)}`);
|
||
console.log(`\n🌐 Oikos is available at:\n`);
|
||
console.log(` Local: http://localhost:${port}`);
|
||
if (host) {
|
||
console.log(` Network: http://${host}:${port}`);
|
||
}
|
||
console.log(`\n Sign in with your new account.\n`);
|
||
} catch (err) {
|
||
if (err.message?.includes('UNIQUE constraint')) {
|
||
console.error(`\nError: username "${username}" is already taken.`);
|
||
} else {
|
||
console.error('\nCreation error:', err.message);
|
||
}
|
||
process.exit(1);
|
||
}
|
||
|
||
rl.close();
|
||
process.exit(0);
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error('Unexpected error:', err.message);
|
||
process.exit(1);
|
||
});
|