Files
my-protfolio/scripts/maintenance.ts

48 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createInterface } from 'node:readline/promises';
import { Writable } from 'node:stream';
import { dataDirectory } from '../src/server/db';
import { initialize } from '../src/server/initialize';
import { provisionOwner } from '../src/server/owner';
import { importLegacy } from '../src/server/legacy-import';
import { backup, restore } from '../src/server/backup';
import { withContentLock } from '../src/server/write-lock';
async function credentials() {
if (process.env.OWNER_EMAIL && process.env.OWNER_PASSWORD) return { email: process.env.OWNER_EMAIL, password: process.env.OWNER_PASSWORD };
if (!process.stdin.isTTY) throw new Error('Use an interactive terminal, or supply OWNER_EMAIL and OWNER_PASSWORD through the environment');
let muted = false;
const output = new Writable({ write(chunk, _encoding, callback) { if (!muted) process.stdout.write(chunk); callback(); } });
const prompt = createInterface({ input: process.stdin, output, terminal: true });
try {
const email = (await prompt.question('Owner email: ')).trim();
process.stdout.write('Password (12–128 characters, hidden): '); muted = true;
const password = await prompt.question(''); muted = false; process.stdout.write('\n');
process.stdout.write('Confirm password (hidden): '); muted = true;
const confirmation = await prompt.question(''); muted = false; process.stdout.write('\n');
if (password !== confirmation) throw new Error('Passwords do not match');
return { email, password };
} finally { prompt.close(); }
}
async function main() {
const [command, argument, target] = process.argv.slice(2);
if (command === 'restore') {
if (!argument || !target) throw new Error('Usage: restore BACKUP_DIRECTORY NEW_DATA_DIRECTORY');
restore(argument, target); console.log('Restored into the new directory. Stop the app before switching its data volume.'); return;
}
if (!['init', 'create-owner', 'create-user', 'reset-password', 'import-legacy', 'migrate-legacy-media', 'backup'].includes(command)) throw new Error('Commands: init, create-owner (alias: create-user), reset-password, import-legacy BUNDLE_DIRECTORY, migrate-legacy-media BUNDLE_DIRECTORY, backup DIRECTORY, restore BACKUP_DIRECTORY NEW_DATA_DIRECTORY');
const connection = initialize();
try {
if (command === 'create-owner' || command === 'create-user' || command === 'reset-password') {
const { email, password } = await credentials();
await withContentLock(() => provisionOwner(connection, email, password, command === 'reset-password'));
console.log(command === 'reset-password' ? 'Password updated. All sessions revoked.' : 'Owner created.');
} else if (command === 'import-legacy' || command === 'migrate-legacy-media') {
await withContentLock(() => importLegacy(connection, argument, command === 'migrate-legacy-media'));
console.log(command === 'import-legacy' ? 'Portfolio imported.' : 'Legacy media references migrated; content preserved.');
}
else if (command === 'backup') { if (!argument) throw new Error('Supply a new backup directory'); await backup(connection, dataDirectory(), argument); console.log('Backup complete.'); }
else console.log('Database initialized.');
} finally { connection.sqlite.close(); }
}
main().catch(error => { console.error(error instanceof Error ? error.message : 'Maintenance failed'); process.exitCode = 1; });