Express + Socket.IO + SQLite. Veto-Engine mit konfigurierbaren Map-Pools und validierten Abläufen, rollenbasierte Berechtigungen, Server-Lebenszyklus mit Demo-, Docker- und Static-Pool-Adapter (RCON), automatisches Herunterfahren. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
1.6 KiB
JavaScript
45 lines
1.6 KiB
JavaScript
import { parseStatus, runRcon } from './rcon.js';
|
||
|
||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||
|
||
/**
|
||
* Gemeinsamer Konfigurationsablauf für echte Server per RCON:
|
||
* Befehle vor dem Mapwechsel senden, warten bis die Map geladen ist, dann Match-Einstellungen setzen.
|
||
*/
|
||
export async function configureViaRcon(target, plan, { loadTimeoutMs = 120_000, pollMs = 3000 } = {}) {
|
||
try {
|
||
await runRcon(target, plan.beforeMapChange);
|
||
} catch (err) {
|
||
// Beim changelevel trennt der Server die RCON-Verbindung teilweise – das ist kein Fehler.
|
||
if (!/geschlossen/.test(err.message)) throw new Error(`Befehle konnten nicht gesendet werden: ${err.message}`);
|
||
}
|
||
|
||
const deadline = Date.now() + loadTimeoutMs;
|
||
let lastError = null;
|
||
for (;;) {
|
||
await sleep(pollMs);
|
||
try {
|
||
const [status] = await runRcon(target, ['status']);
|
||
const parsed = parseStatus(status);
|
||
if (parsed.map === plan.map || (!parsed.map && status.includes(plan.map))) break;
|
||
lastError = `aktuell geladen: ${parsed.map ?? 'unbekannt'}`;
|
||
} catch (err) {
|
||
lastError = err.message;
|
||
}
|
||
if (Date.now() > deadline) {
|
||
throw new Error(`Map ${plan.map} wurde nicht rechtzeitig geladen (${lastError ?? 'keine Antwort'})`);
|
||
}
|
||
}
|
||
|
||
try {
|
||
await runRcon(target, plan.afterMapChange);
|
||
} catch (err) {
|
||
throw new Error(`Match-Einstellungen konnten nicht gesetzt werden: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
export async function rconStatus(target) {
|
||
const [output] = await runRcon(target, ['status'], { timeoutMs: 4000 });
|
||
return parseStatus(output);
|
||
}
|