#!/usr/bin/env node // Generate an ADMIN_PASSWORD_HASH value for the panel. // // node bin/hash-password.js [password] // // Without an argument it prompts interactively (input hidden), or reads // stdin when piped. Output format: scrypt:: (64-byte key). import { scryptSync, randomBytes } from 'node:crypto'; function makeHash(password) { if (!password) { console.error('Empty password.'); process.exit(1); } const salt = randomBytes(16); const key = scryptSync(password, salt, 64); return `scrypt:${salt.toString('hex')}:${key.toString('hex')}`; } const arg = process.argv[2]; if (arg !== undefined) { console.log(makeHash(arg)); process.exit(0); } if (!process.stdin.isTTY) { let data = ''; process.stdin.setEncoding('utf8'); process.stdin.on('data', (d) => { data += d; }); process.stdin.on('end', () => { console.log(makeHash(data.replace(/\r?\n$/, ''))); }); } else { process.stdout.write('Password (input hidden): '); process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.setEncoding('utf8'); let pw = ''; process.stdin.on('data', (chunk) => { for (const ch of chunk) { if (ch === '\r' || ch === '\n') { process.stdin.setRawMode(false); process.stdin.pause(); process.stdout.write('\n'); console.log(makeHash(pw)); process.exit(0); } else if (ch === '\u0003') { // Ctrl+C process.stdout.write('\n'); process.exit(1); } else if (ch === '\u007f' || ch === '\b') { // backspace / delete pw = pw.slice(0, -1); } else { pw += ch; } } }); }