Statistikportal für den All-the-Mods-10-Server (Minecraft 1.21.1, NeoForge 21.1): - mod/: serverseitige NeoForge-Mod atmstats – Ereignisse, Live-Zähler, Servergesundheit, FTB-Dateien; Outbox auf der Festplatte, signierter gzip-Versand per HTTPS mit Backoff - backend/: Fastify + PostgreSQL – idempotenter Ingest, Projektionen, Parser für SNBT, FTB Quests/Teams, world/stats, world/advancements und latest.log; Ranglisten, Snapshots, Retention, öffentliche API, SSE-Live-Updates, Admin mit argon2/CSRF, SFTP-Worker - web/: Next.js-Dashboard – Übersicht, Ranglisten, Spielerprofile, ATM10, Server, Datenquellen, Verwaltung - shared/: Protokoll v1 und API-Typen; tools/: Simulator, Log-Anonymisierer, RCON-Client - deploy/: Docker Compose hinter Traefik, Backup; docs/: Architektur, Datenquellen, Betrieb Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
// Verwaltungs-CLI:
|
|
// cli admin:create <benutzer> Passwort aus ADMIN_PASSWORD oder interaktiv
|
|
// cli token:create neues Ingest-Token (wird nur einmal angezeigt)
|
|
// cli migrate Datenbankmigrationen ausführen
|
|
import { createInterface } from 'node:readline/promises';
|
|
import { createAdmin } from './auth/admin.ts';
|
|
import { createPool } from './db/pool.ts';
|
|
import { migrate } from './db/migrate.ts';
|
|
import { createIngestToken } from './ingest/tokens.ts';
|
|
|
|
async function readPassword(): Promise<string> {
|
|
if (process.env.ADMIN_PASSWORD) return process.env.ADMIN_PASSWORD;
|
|
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
// Hide typed characters.
|
|
(rl as unknown as { _writeToOutput: (s: string) => void })._writeToOutput = (s: string) => {
|
|
if (s.includes('Passwort')) process.stdout.write(s);
|
|
};
|
|
const pw = await rl.question('Passwort (min. 12 Zeichen): ');
|
|
rl.close();
|
|
process.stdout.write('\n');
|
|
return pw;
|
|
}
|
|
|
|
async function main() {
|
|
const [command, arg] = process.argv.slice(2);
|
|
const url = process.env.DATABASE_URL;
|
|
if (!url) throw new Error('DATABASE_URL is required');
|
|
const pool = createPool(url, 2);
|
|
try {
|
|
await migrate(pool);
|
|
switch (command) {
|
|
case 'migrate':
|
|
console.log('Migrationen ausgeführt.');
|
|
break;
|
|
case 'admin:create': {
|
|
if (!arg) throw new Error('usage: admin:create <benutzer>');
|
|
await createAdmin(pool, arg, await readPassword());
|
|
console.log(`Admin "${arg}" angelegt bzw. Passwort aktualisiert.`);
|
|
break;
|
|
}
|
|
case 'token:create': {
|
|
const token = await createIngestToken(pool);
|
|
console.log('Neues Ingest-Token (in config/atmstats-server.toml als token eintragen):');
|
|
console.log(token);
|
|
break;
|
|
}
|
|
default:
|
|
console.log('Befehle: migrate | admin:create <benutzer> | token:create');
|
|
process.exitCode = 1;
|
|
}
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error((err as Error).message);
|
|
process.exit(1);
|
|
});
|