Rollback: Runden aus CS2-Sicherungen zurückspielen (nur Admin)
Beim Konfigurieren aktiviert CS2 Mapveto die Rundensicherung des Servers (mp_backup_round_auto, Präfix mit Match-ID). Im Admin-Modus lassen sich die Sicherungen der laufenden Map auflisten und zurückspielen. Der Live-Stand wird auf die Runden bis zur Sicherung gekürzt. Organisatoren haben keinen Zugang zu den Rollback-Routen. Sicherheit: Dateinamen werden gegen Match-ID und Muster geprüft, damit keine fremden Dateien oder Befehle in die RCON-Zeile gelangen.
This commit is contained in:
1 parent
43ecb8a1ad
commit
3bfe087b7d
13 files changed
+327
-5
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { buildMapCommands, sanitizeConsoleText } from './cs2Config.js';
|
||||
import { DemoMatchSimulator } from './demoMatchSimulator.js';
|
||||
import { backupPrefix, describeBackup, isValidBackupFile, pointsForMap } from '../rounds.js';
|
||||
import { HttpError, CLOSED_STATUSES } from '../matchService.js';
|
||||
import { randomPassword } from '../security.js';
|
||||
|
||||
@@ -86,6 +87,43 @@ export class ServerManager {
|
||||
this.timers.push(setInterval(() => this.reap().catch((e) => this.logger.error(e)), this.config.reaperIntervalSeconds * 1000));
|
||||
}
|
||||
|
||||
/** Admin: Rücksprungpunkte der aktuellen Map, neueste Runde zuerst. */
|
||||
async rollbackPoints(viewer) {
|
||||
this.service.require(viewer, 'organizer');
|
||||
const { match, seriesMap, handle } = this.rollbackContext(viewer.matchId);
|
||||
const all = await this.adapter.listRoundBackups(handle, match.id, seriesMap.map);
|
||||
return pointsForMap(all, seriesMap.map);
|
||||
}
|
||||
|
||||
/** Admin: Match auf den Stand nach einer Runde zurückspielen. */
|
||||
async rollback(viewer, file) {
|
||||
this.service.require(viewer, 'organizer');
|
||||
const { match, handle, seriesMap } = this.rollbackContext(viewer.matchId);
|
||||
if (!isValidBackupFile(file, match.id)) throw new HttpError(400, 'Ungültige Sicherungsdatei', 'invalid_input');
|
||||
const point = describeBackup(file, match.id);
|
||||
if (point.map !== seriesMap.map) throw new HttpError(409, 'Die Sicherung gehört zu einer anderen Map', 'wrong_map');
|
||||
if (this.busy.has(match.id)) throw new HttpError(409, 'Für diesen Server läuft bereits eine Aktion', 'busy');
|
||||
this.busy.add(match.id);
|
||||
try {
|
||||
await this.adapter.loadRoundBackup(handle, file);
|
||||
} catch (err) {
|
||||
throw new HttpError(502, `Zurückspielen fehlgeschlagen: ${err.message}`, 'server_error');
|
||||
} finally {
|
||||
this.busy.delete(match.id);
|
||||
}
|
||||
this.service.rewindLiveScore(match.id, point.round, file);
|
||||
}
|
||||
|
||||
rollbackContext(matchId) {
|
||||
const match = this.service.getMatch(matchId);
|
||||
const row = this.row(matchId);
|
||||
if (!row?.handle || row.status !== 'configured' || match.status !== 'live') {
|
||||
throw new HttpError(409, 'Zurückspielen ist nur bei einem laufenden Match mit bereitem Server möglich', 'wrong_state');
|
||||
}
|
||||
const seriesMap = this.service.seriesMaps(matchId).find((m) => m.number === match.current_map_number);
|
||||
return { match, row, seriesMap, handle: JSON.parse(row.handle) };
|
||||
}
|
||||
|
||||
/** Löscht ein Match samt Server-Datensatz. Ein laufender Server wird vorher freigegeben. */
|
||||
async deleteMatch(matchId) {
|
||||
this.service.getMatch(matchId);
|
||||
@@ -275,6 +313,7 @@ export class ServerManager {
|
||||
secrets: { serverPassword: row.server_password, tvPassword: row.tv_password },
|
||||
settings: this.config.match,
|
||||
logUrl: `${this.config.logReceiverUrl}/api/logs/${matchId}/${row.config_key}`,
|
||||
backupPrefix: backupPrefix(matchId),
|
||||
});
|
||||
this.simulator.stop(matchId);
|
||||
this.service.resetLiveScore(matchId, seriesMap.number);
|
||||
|
||||
Reference in new issue
Block a user