SPP UserandClaude Haiku 5.5 fe251a13ef Turnierregeln für die CS2-Server-Konfiguration
Setzt Freezetime 20, Buytime 20, Startgeld 800, Rundenzeit 1:55 und
Halbzeit 1 fest und erlaubt Pausen mit sv_pausable 1. Die Werte
stammen aus FACEIT- und ESL-Richtwerten, nicht aus einer aktuellen
Regel des konkreten Turniers.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-26 00:48:26 +01:00

CS2 Mapveto

Webanwendung zur Organisation von Counter-Strike-2-Matches: Match-Lobbys mit Team-Einladungen, abwechselndes Map-Veto der Captains, Zuschauerzugang und automatische Verwaltung eines CS2-Servers pro Match.

  • Frontend: React 18 + Vite, deutschsprachig, responsives dunkles Design
  • Backend: Node.js (≥ 18.18), Express, Socket.IO
  • Datenbank: SQLite (better-sqlite3), Datei unter data/
  • Echtzeit: Socket.IO; jede Änderung schickt allen Teilnehmern ihren rollenabhängigen Match-Zustand
  • Serververwaltung: austauschbare Adapter hinter einer Schnittstelle (start, status, configure, stop)

Standardmäßig läuft der Demo-Adapter. Er startet keine echten CS2-Server. Oberfläche, Statusanzeigen und Verbindungsdaten sind dann überall als „Demo“ bzw. „simuliert“ gekennzeichnet. Wie die echte Anbindung eingerichtet wird, steht im Abschnitt Echte Server anbinden.


Schnellstart

Als Container (empfohlen für den Betrieb): ein Container mit Webseite, Backend und Datenbank, der die CS2-Server-Container auf demselben Docker-Host startet. Siehe docs/container.md:

docker -H ssh://USER@DOCKER-HOST-IP build -t cs2-mapveto:latest .

…dann den Stack aus der Anleitung in Portainer anlegen.

Zum Entwickeln:

npm install
cp .env.example .env      # optional, Standardwerte funktionieren für die Demo
npm run dev               # Backend auf :3000, Frontend auf :5173

Dann http://localhost:5173 öffnen.

Produktion (ein Prozess, Backend liefert das gebaute Frontend aus):

npm install
npm run build
PUBLIC_URL=https://veto.example.de npm start      # lauscht auf PORT (Standard 3000)

Tests:

npm test

Die Tests decken Veto-Engine und Ablaufvalidierung, RCON-Protokoll (gegen einen lokalen Test-RCON-Server), Befehls-Bereinigung und den kompletten API-Ablauf ab. Dazu gehören Berechtigungen, doppelte Aktionen, Server-Konfiguration mit Wiederholung, nächste Map, automatisches Herunterfahren und dass keine RCON-Daten an Clients gelangen.


Ablauf: von der Match-Erstellung bis zum Serverbeitritt

  1. Match erstellen (/neu): Der Organisator trägt beide Teamnamen ein und wählt Spielmodus (Wingman 2 gegen 2 oder Competitive 5 gegen 5), Serie (Best of 1/3/5) und wer das Veto beginnt (Münzwurf oder festes Team). Formate, für die der Map-Pool keinen gültigen Veto-Ablauf hat, sind ausgegraut und werden auch vom Backend abgelehnt. Der Browser des Organisators erhält einen geheimen Organisator-Token. Er wird lokal gespeichert und lässt sich über „Organisator-Zugang auf einem anderen Gerät öffnen“ übertragen.

  2. Server vorbereiten: Direkt beim Anlegen der Lobby fordert das Backend im Hintergrund einen Server an. Er fährt also schon während Lobby und Veto hoch. Der Status ist auf der Match-Seite live sichtbar.

  3. Einladungen: Das Match hat drei Zugangscodes (Format ABCD-EFGH) mit Links:

    • Team A: /join/<code>
    • Team B: /join/<code>
    • Zuschauer: /watch/<code> für Stream und Kommentatoren

    Diese Codes gelten nur für die Webseite. Der CS2-Serverzugang wird getrennt bereitgestellt (Schritt 6). Der Organisator kann jeden Code jederzeit neu erzeugen; der alte Link wird damit ungültig.

  4. Spieler treten bei: Über den Teamlink gibt ein Spieler seinen Namen ein (optional die SteamID64) und landet in genau diesem Team. Pro Team sind maximal 2 bzw. 5 Spieler erlaubt.

    • Der erste Spieler eines Teams wird Captain. Organisator oder Captain können den Captain wechseln.
    • Jeder Spieler setzt sich auf „bereit“. Ein Team ist bereit, sobald mindestens ein Spieler beigetreten ist, es einen Captain hat und alle beigetretenen Spieler bereit sind. Mit REQUIRE_FULL_TEAMS=true muss das Team zusätzlich vollständig sein (2 bzw. 5 Spieler).
  5. Map-Veto: Sind beide Teams bereit, startet der Organisator das Veto. Die Captains bannen und wählen abwechselnd nach dem festen Ablauf (siehe unten). Die Seite zeigt jederzeit:

    • welches Team am Zug ist
    • welche Aktion ansteht
    • den Zustand jeder Map (verfügbar, gebannt, gewählt, Entscheidungsmap)
    • den gesamten Verlauf mit Uhrzeit

    Die letzte verbleibende Map wird automatisch als Entscheidungsmap übernommen. Zuschauer und Organisator sehen alles live, können aber nicht wählen.

  6. Server konfigurieren: Nach dem Veto wird der bereits laufende Server automatisch eingerichtet: Spielmodus, erste Map, Teamnamen und Startseiten, Match-Einstellungen, Server- und CSTV-Passwort. Die restliche Map-Reihenfolge ist gespeichert.

    • Spieler sehen danach den kopierbaren Konsolenbefehl password …; connect host:port und einen steam://connect-Link.
    • Zuschauer sehen den CSTV-Zugang (eigener Port, eigenes Passwort, Verzögerung).
  7. Während der Serie: Der Organisator kann die Aufwärmphase beenden (mp_warmup_end), die nächste Map laden, den Server stoppen und das Match abschließen oder abbrechen.

  8. Fehlerfälle: Startet der Server nicht, schlägt die Konfiguration fehl oder fällt der Server aus, zeigt die Seite den Grund. Der Organisator bekommt einen Knopf für den erneuten Versuch: „Server neu starten“ bzw. „Konfiguration wiederholen“.

Rollen und Berechtigungen

Rolle Zugang über Darf
Organisator Token beim Erstellen Veto starten, Spieler entfernen, Captain setzen, Codes erneuern, Server neu starten/stoppen/konfigurieren, nächste Map, Match beenden; sieht Einladungen, Spieler- und CSTV-Verbindung
Captain Teamcode (erster Spieler oder ernannt) alles wie Spieler, dazu Veto-Aktionen seines Teams und Captain-Übergabe
Spieler Teamcode bereit melden, Team verlassen; sieht die Spieler-Verbindungsdaten nach dem Veto
Zuschauer Zuschauercode alles live verfolgen; sieht nur den CSTV-Zugang, nicht das Spielerpasswort
Gast ohne Code nur Teamnamen und Status

Organisator spielt selbst mit: In der Lobby kann der Organisator unter „Selbst mitspielen?“ direkt einem Team beitreten, oder er öffnet einen Teamlink im selben Browser. Er bleibt Organisator und ist zusätzlich Spieler bzw. Captain. Ein zweiter Browser ist nicht nötig. Technisch speichert der Browser dafür beide Zugänge und schickt den Spieler-Token im Header X-Player-Token mit. Pro Aufrufer zählt höchstens ein Spieler.

Alle Prüfungen passieren im Backend. Die Oberfläche blendet nur zusätzlich aus, was die Rolle nicht darf.

Schutzmaßnahmen

  • Fremde Teambeitritte: Jeder Code gehört zu genau einem Team eines Matches. Zuschauercodes erlauben keinen Beitritt. Volle Teams, doppelte Namen und doppelte Steam-IDs werden abgelehnt. Codebasierte Endpunkte sind pro IP begrenzt (20 Versuche pro Minute).
  • Doppelte Aktionen: Jede Veto-Aktion trägt die Nummer des Schritts. Sie wird in einer SQLite-Transaktion geprüft und gespeichert; (match_id, idx) ist Primärschlüssel. Ein doppelter Klick oder ein veralteter Stand liefert „Diese Aktion ist veraltet“. Serveraktionen sind pro Match gesperrt, solange eine läuft.
  • Unberechtigte Befehle: Veto nur durch den Captain des Teams am Zug, Server- und Match-Befehle nur durch den Organisator.
  • Tokens: Organisator- und Spieler-Tokens sind 256 Bit zufällig und werden nur als SHA-256-Hash gespeichert.
  • Server-Zugangsdaten: RCON-Passwort, Container-Handle und GSLT liegen ausschließlich in der Datenbank bzw. den Umgebungsvariablen des Backends. Die Snapshot-Funktion gibt sie nie aus (per Test abgesichert). Server- und CSTV-Passwort sind als Beitrittsdaten nötig und werden nur an die jeweils berechtigte Rolle geliefert.
  • Befehls-Injection: Teamnamen werden vor der Übergabe an die Serverkonsole von Anführungszeichen, ; und Steuerzeichen bereinigt. Passwörter sind rein alphanumerisch.

Wiederherstellung nach Neuladen oder Verbindungsabbruch

Der gesamte Zustand liegt in SQLite. Der Browser speichert pro Match nur seinen Zugangstoken (localStorage).

  • Neuladen der Seite: Die Seite lädt den Stand per REST und abonniert danach den Socket.
  • Abgebrochene Socket-Verbindung: Nach dem Reconnect meldet sich der Client automatisch neu an und erhält den vollständigen aktuellen Stand. Socket-Nachrichten enthalten immer den kompletten Zustand und keine Deltas, deshalb geht nichts verloren.
  • Backend-Neustart: Unterbrochene Serverstarts werden als fehlgeschlagen markiert (mit „Server neu starten“). Unterbrochene Konfigurationen werden automatisch wiederholt.

Automatisches Herunterfahren

Alle SERVER_REAPER_INTERVAL_SECONDS prüft das Backend alle aktiven Server:

  • Ein Match ist abgeschlossen oder abgebrochen: Server wird gestoppt.
  • Lobby, Veto oder Server ohne Spielerzahl: Ist niemand mit der Match-Seite verbunden und gab es länger als SERVER_IDLE_TIMEOUT_MINUTES keine Aktivität, wird der Server gestoppt.
  • Konfigurierter echter Server: Gestoppt wird, wenn länger als die Frist keine Spieler auf dem Server waren. Die Spielerzahl kommt per RCON aus status.
  • Unabhängig davon: Nach SERVER_MAX_LIFETIME_HOURS wird jeder Server gestoppt.

Ein gestoppter Server lässt sich vom Organisator neu starten.


Map-Pools und Veto-Abläufe

Konfiguration in server/config/map-pools.json (Pfad änderbar über MAP_POOL_FILE). Jeder Pool hat:

  • eine eigene Map-Liste mit ID, Anzeigename und Kartenfarbe
  • Teamgröße und game_type/game_mode
  • je Format einen Ablauf

Schritte im Ablauf:

Schritt Bedeutung
ban:X / ban:Y Team X bzw. Y bannt eine Map
pick:X / pick:Y Team X bzw. Y wählt eine Map für die Serie
side:X / side:Y Team wählt die Startseite (CT/T) auf der zuletzt gewählten Map bzw. der Entscheidungsmap
decider die letzte verbleibende Map wird automatisch übernommen

X ist das Team, das das Veto beginnt (Münzwurf oder vom Organisator festgelegt), Y das andere.

Beim Start wird jeder Ablauf geprüft:

  • Anzahl Picks = Maps der Serie − 1
  • Bans + Picks + 1 = Pool-Größe
  • genau eine Entscheidungsmap
  • Seitenwahl nur direkt nach einem Pick oder der Entscheidungsmap

Ungültige oder fehlende Abläufe machen nur dieses Format unauswählbar (mit Begründung in der Oberfläche).

Mitgelieferte Abläufe

Competitive (7 Maps): Ancient, Anubis, Dust II, Inferno, Mirage, Nuke, Train

Format Ablauf
BO1 X bannt, Y bannt, X bannt, Y bannt, X bannt, Y bannt → Entscheidungsmap, X wählt Seite
BO3 X bannt, Y bannt, X pickt (Y wählt Seite), Y pickt (X wählt Seite), X bannt, Y bannt → Entscheidungsmap, X wählt Seite
BO5 X bannt, Y bannt, X pickt (Y Seite), Y pickt (X Seite), X pickt (Y Seite), Y pickt (X Seite) → Entscheidungsmap, Y wählt Seite

Wingman (4 Maps): Inferno, Nuke, Overpass, Vertigo

Format Ablauf
BO1 X bannt, Y bannt, X bannt → Entscheidungsmap, Y wählt Seite
BO3 X bannt, Y pickt (X Seite), X pickt (Y Seite) → Entscheidungsmap, X wählt Seite
BO5 nicht unterstützt: Mit 4 Maps sind keine 5 Karten möglich. Mit einem Pool von mindestens 5 Maps und einem bo5-Ablauf wird es automatisch auswählbar.

Regel für Startseiten

  • Auf einer gewählten Map wählt immer das Gegnerteam des wählenden Teams die Startseite.
  • Auf der Entscheidungsmap wählt das Team, das im Ablauf dafür vorgesehen ist. In BO1 ist das in Competitive Team X, weil Y den letzten Ban hatte, und in Wingman Team Y. Ohne side-Schritt werden die Seiten im Spiel festgelegt.

Die Wahl wird als mp_teamname_1 (Team mit CT-Start) und mp_teamname_2 (T-Start) an den Server übergeben.

Bilder für Maps und Seitenwahl

  • Map-Bilder liegen lokal unter client/public/maps/<map>.webp (960 px breit, je ca. 50–90 KB). "image" in map-pools.json verweist darauf. Neue oder fehlende Bilder lädt npm run images von ghostcap-gaming/cs2-map-images, verkleinert sie und trägt den Pfad ein (npm run images -- --force lädt alle neu). Die Bilder stammen aus dem Spiel; die Rechte liegen bei Valve. Ohne Bild zeigt die Kachel ein Muster in der Map-Farbe.
  • Seiten-Embleme für CT und T liegen unter client/public/sides/ct.png und client/public/sides/t.png (quadratisch, z. B. 256×256). Fehlt eine Datei, erscheint ein schlichtes Ersatzsymbol.
  • Logo und Farben: Das MaxLAN-e.V.-Logo liegt freigestellt unter client/public/brand/maxlan-logo.webp. Die Farben stehen als Variablen am Anfang von client/src/styles.css: Logo-Blau #344A99 für Team A und Hauptbuttons, Logo-Rot #A72A1C für Team B und den Abschluss der Kopfzeile, jeweils aufgehellt für die Lesbarkeit.
  • Bestehende Matches behalten die Map-Liste, mit der sie erstellt wurden. Neue Bilder erscheinen in neu erstellten Matches.

Die mitgelieferten Map-Pools entsprechen dem Stand der Entwicklung. Valve ändert den aktiven Map-Pool regelmäßig. Map-IDs bitte gegen die eigene Serverinstallation prüfen. Eine Map, die nicht existiert, führt zu „Konfiguration fehlgeschlagen“ mit Meldung.


Echte Server anbinden

Schritt-für-Schritt mit Portainer: docs/portainer-cs2.md beschreibt, wie du einen CS2-Server als Portainer-Stack aufsetzt, dessen Spieldateien nur einmal heruntergeladen werden, und ihn mit CS2 Mapveto verbindest.

Die Serververwaltung ist hinter server/src/servers/adapter.js gekapselt. Auswahl über SERVER_ADAPTER.

Ablauf, den jeder Adapter bedient

  1. start({ matchId, mode, secrets }): Server starten bzw. reservieren.

    • Rückgabe: { handle, host, port, tvPort }.
    • secrets (RCON-, Server- und CSTV-Passwort) erzeugt das Backend pro Match.
  2. status(handle): liefert starting | running | stopped | failed, optional mit Spielerzahl.

    • Wird alle SERVER_STATUS_POLL_SECONDS abgefragt, solange der Server startet, danach alle 30 s.
  3. configure(handle, plan): in zwei Phasen.

    1. game_type, game_mode, sv_password, tv_enable, tv_delay, tv_password, dann changelevel <map>.
    2. Warten, bis status die Map meldet (max. 2 Minuten), dann die Match-Einstellungen (mp_teamname_1/2, mp_maxrounds, mp_overtime_enable, mp_match_can_clinch, mp_warmup_pausetimer 1, CS2_EXTRA_COMMANDS).

    Die zweite Phase kommt erst nach dem Mapwechsel, weil die gamemode_*.cfg des Spiels diese Werte beim Laden der Map sonst überschreibt.

  4. stop(handle): Server beenden bzw. freigeben.

Option A: Docker (SERVER_ADAPTER=docker)

Startet pro Match einen Container mit dem Community-Image joedwards32/cs2 (SteamCMD, CS2-Dedicated-Server App 730; gepflegter Nachfolger von cm2network/cs2).

Voraussetzungen:

  1. Docker auf dem Backend-Host. Das Backend ruft die Docker-CLI auf und braucht dafür Zugriff auf den Docker-Daemon.
  2. Game Server Login Token für App-ID 730 unter https://steamcommunity.com/dev/managegameservers erstellen und als CS2_GSLT eintragen. Ohne GSLT sind Server nicht öffentlich erreichbar. Pro gleichzeitig laufendem Server ist ein eigenes Token empfehlenswert.
  3. CS2_PUBLIC_HOST: IP oder DNS-Name, unter dem Spieler den Host erreichen.
  4. Ports CS2_PORT_RANGE_START–CS2_PORT_RANGE_END in Firewall bzw. Router freigeben:
    • pro Match zwei aufeinanderfolgende Ports
    • Spielport per UDP und TCP (TCP wird für RCON genutzt, nur für das Backend freigeben)
    • CSTV-Port per UDP
  5. Etwa 60 GB Speicher für die Spieldateien im Volume CS2_DOCKER_VOLUME. Das Volume wird von allen Match-Containern geteilt. Der erste Start lädt das Spiel herunter und kann deutlich länger als 10 Minuten dauern (CS2_START_TIMEOUT_MINUTES). Spieldateien am besten vorab mit einem einzelnen Container laden, bevor mehrere Matches gleichzeitig starten.

Container heißen cs2mv-<matchId> und tragen das Label cs2-mapveto=1. Stoppen bedeutet docker rm -f.

Option B: Vorhandene Server (SERVER_ADAPTER=static)

Für selbst betriebene, dauerhaft laufende CS2-Server:

SERVER_ADAPTER=static
CS2_STATIC_SERVERS=203.0.113.10:27015:rconPasswort1:27020,203.0.113.10:27025:rconPasswort2:27030

Auf jedem Server müssen gesetzt sein:

  • rcon_password
  • CSTV-Port tv_port, falls abweichend von Spielport + 5 (dieser Wert wird angenommen, wenn kein vierter Teil angegeben ist)
  • ein GSLT (sv_setsteamaccount)

Bei diesem Adapter bedeutet „Start“: einen freien Server reservieren und per RCON prüfen. „Stopp“ gibt ihn frei und setzt ein neues zufälliges sv_password. Der Serverprozess selbst wird nicht beendet.

Eigener Adapter (z. B. Hosting-API)

  1. Klasse mit name, simulated = false und den vier Methoden anlegen. Vorlage: dockerAdapter.js. Für die Konfiguration per RCON gibt es configureViaRcon aus rconConfigure.js.
  2. In createServerAdapter in adapter.js registrieren.
  3. Zugangsdaten der Hosting-API ausschließlich über Umgebungsvariablen in config.js einlesen.

Verbindung für Spieler

password <serverpasswort>; connect <host>:<port>

Zusätzlich bietet die Seite einen steam://connect/<host>:<port>/<passwort>-Link an.

Zuschauer: CSTV (früher GOTV)

CS2 bringt CSTV mit (in der Konsole weiterhin tv_*-Befehle; früher GOTV). Die Anwendung setzt:

  • tv_enable 1 (greift erst nach dem Mapwechsel, deshalb vor changelevel)
  • tv_delay (Standard 90 s) als Verzögerung gegen Ghosting
  • tv_password als eigenes Passwort, getrennt vom Spielerpasswort

Zuschauer verbinden sich mit password <tvpasswort>; connect <host>:<tvPort>. Sie belegen keinen Spielerplatz und können die Spieler nicht beeinflussen. Für größere Zuschauerzahlen unterstützt CS2 CSTV-Relays (tv_relay) und HTTP-Broadcast (tv_broadcast_url). Beides ist nicht eingebaut und kann über CS2_EXTRA_COMMANDS bzw. die Serverkonfiguration ergänzt werden.

Prüfstand der CS2-Angaben

Die offizielle Seite Counter-Strike 2/Dedicated Servers im Valve Developer Wiki war während der Entwicklung wegen eines Bot-Schutzes nicht automatisiert abrufbar. Grundlage waren:

Bekannte Werte: game_type 0 + game_mode 1 = Competitive, game_mode 2 = Wingman; CSTV-Standardport 27020.

Nicht gegen einen echten CS2-Server getestet: Docker- und Static-Adapter sowie die Auswertung der status-Ausgabe (Map-Name, Spielerzahl). Das RCON-Protokoll ist gegen einen Test-Server geprüft. Vor dem ersten echten Einsatz bitte ein Testmatch durchspielen und bei Bedarf parseStatus in server/src/servers/rcon.js an die tatsächliche Ausgabe anpassen.

Bekannte Grenzen von Vanilla-CS2

  • Ohne Server-Plugin kann CS2 Spieler nicht automatisch einem Team zuordnen. Spieler wählen ihr Team im Spiel selbst. Teamnamen und Startseiten setzt die Anwendung. Für feste Teamzuordnung, Pausen und automatischen Mapwechsel eignen sich Plugins wie MatchZy oder Get5-Nachfolger. Sie lassen sich über einen eigenen Adapter anbinden; die SteamID64 der Spieler wird dafür bereits erfasst.
  • Das Team eines Spielers ergibt sich aus der Seite, die er im Spiel wählt. Wechselt ein Spieler ins falsche Team, zählen seine Runden für das falsche Team.

Live-Ergebnisse

Beim Konfigurieren setzt CS2 Mapveto auf dem Server log on und logaddress_add_http "<LOG_RECEIVER_URL>/api/logs/<match>/<schlüssel>". Der CS2-Server schickt damit seine Logzeilen per HTTP an das Backend. Der Schlüssel ist pro Match und Serverstart zufällig, Zeilen mit falschem Schlüssel werden abgelehnt.

  • Was ausgewertet wird:
    • Matchstart nach der Aufwärmphase
    • jedes Rundenende mit Sieger und Grund (Bombe, Entschärfung, Zeit, Ausschalten)
    • die Seitenzuordnung der Teams
    • das Spielende
  • Was angezeigt wird:
    • Live-Spielstand und Rundenverlauf pro Map
    • Serienstand
    • Ergebnisse in der Übersicht
    • Stream-Seite
  • Nach dem Spielende: Ist die Serie noch offen, lädt CS2 Mapveto nach SERIES_AUTO_ADVANCE_SECONDS (Standard 30) die nächste Map. Ist sie entschieden (z. B. 2:0 im Best of 3), wird das Match abgeschlossen und der Server freigegeben. Mit -1 geht beides nur manuell.
  • Voraussetzung: Der CS2-Server muss LOG_RECEIVER_URL erreichen, im LAN z. B. http://192.168.178.31:3000. Ob Daten ankommen, zeigt das Serverpanel des Organisators. Mit LOG_DEBUG=true gibt das Backend alle empfangenen Zeilen aus.
  • Geprüft: mit echten Logzeilen eines CS2-Servers (Wingman), siehe server/test/fixtures/echte-logs-wingman.log.

Zwei Eigenheiten von CS2 berücksichtigt der Parser:

  • CS2 sendet Match_Start schon beim Laden der Map. Gezählt wird erst, nachdem der Organisator „Aufwärmphase beenden“ geklickt hat.
  • Direkt nach der Halbzeit nennt CS2 zu Rundenbeginn noch die alte Seitenzuordnung. Ausgewertet werden deshalb nur die Zeilen direkt nach einem Rundenende.

Stream-Übersicht aller Matches

Unter /stream (in der Kopfzeile verlinkt) zeigt eine 1920×1080-Seite alle laufenden Vetos und Matches gleichzeitig, live aktualisiert:

  • Karten: eine Karte pro Match.
    • Im Veto: wer am Zug ist, Fortschritt, Map-Board.
    • Im Match: aktuelle Map mit Live-Stand, CT/T-Seiten und Serienstand.
  • Raster: passt sich der Anzahl an (1, 2, 4 oder 6 Karten). Bei mehr als 6 Matches blättert die Seite alle 12 Sekunden weiter.
  • Unten: offene Lobbys und die Ergebnisse der letzten drei Stunden.
  • Datenschutz: Codes und Verbindungsdaten erscheinen nicht.
  • Schlüssel: Mit STREAM_KEY ist die Seite nur mit /stream?key=<schlüssel> erreichbar, sonst offen. ?bg=transparent funktioniert ebenfalls.

Stream-Übersicht eines Matches

Unter /stream/<Zuschauercode> gibt es eine Vollbild-Übersicht für den Stream. Den Link findet der Organisator bei den Einladungen.

  • Format: 1920×1080, skaliert auf jede Fenstergröße. In OBS als Browserquelle mit 1920×1080 einbinden. Mit ?bg=transparent ist der Hintergrund durchsichtig.
  • Ablauf: Die Seite wechselt automatisch zwischen Lobby (Aufstellungen), Map-Veto (live) und Match (Spielstand, Rundenverlauf, Map-Leiste).
  • Laufband: zeigt das letzte für Zuschauer relevante Ereignis, keine technischen Servermeldungen.
  • Datenschutz: Verbindungsdaten und Passwörter erscheinen dort nie.

Umgebungsvariablen

Alle Variablen mit Standardwerten stehen in .env.example. Die wichtigsten:

Variable Standard Zweck
PORT 3000 Port des Backends
PUBLIC_URL http://localhost:5173 Basis für Einladungslinks
DATABASE_FILE data/cs2-mapveto.db SQLite-Datei
CREATE_MATCH_PASSWORD leer Passwort zum Erstellen von Matches
REQUIRE_FULL_TEAMS false true: Veto nur mit vollständigen Teams
SERVER_ADAPTER demo demo, docker oder static
SERVER_IDLE_TIMEOUT_MINUTES 30 Frist bis zum automatischen Herunterfahren
SERVER_MAX_LIFETIME_HOURS 8 harte Obergrenze pro Server
CS2_TV_DELAY 90 CSTV-Verzögerung in Sekunden
CS2_GSLT leer Docker: Game Server Login Token; leer = LAN-Modus (sv_lan 1)
CS2_PUBLIC_HOST – Pflicht bei Docker: öffentliche Adresse
CS2_STATIC_SERVERS – Pflicht bei Static: Serverliste
DEMO_FAIL_FIRST_CONFIGURE false Demo: Konfigurationsfehler simulieren
DEMO_ROUND_SECONDS 4 Demo: Sekunden pro simulierter Runde
LOG_RECEIVER_URL PUBLIC_URL Adresse, unter der der CS2-Server das Backend für Live-Logs erreicht
LOG_DEBUG false empfangene Logzeilen im Terminal ausgeben
STREAM_KEY leer Schlüssel für die Stream-Übersicht aller Matches (/stream?key=…); leer = offen
SERIES_AUTO_ADVANCE_SECONDS 30 Wartezeit nach Map-Ende bis zur nächsten Map bzw. zum Abschluss; -1 = manuell

Benötigte Zugangsdaten für den Echtbetrieb:

  • Docker: Zugriff auf den Docker-Daemon (lokal oder per DOCKER_HOST=ssh://…), Portfreigaben, für Internet-Betrieb ein GSLT (im LAN nicht nötig)
  • Static: RCON-Passwörter der Server

RCON-Passwörter für Docker-Container erzeugt das Backend selbst.


Projektstruktur

client/                  React-Frontend (Vite)
  src/pages/             Übersicht, Match erstellen, Match-Seite, Beitritt
  src/components/        Teamkarten, Veto-Board, Serverpanel, Einladungen, Verlauf
  src/lib/               API, Socket-Hook, lokale Zugangsdaten
server/
  config/map-pools.json  Map-Pools und Veto-Abläufe
  src/veto.js            reine Veto-Logik und Ablaufvalidierung
  src/matchService.js    Lobby, Rollen, Veto, rollenabhängige Snapshots
  src/servers/           Adapter-Schnittstelle, Demo/Docker/Static, RCON, ServerManager
  src/http.js            REST-API
  src/realtime.js        Socket.IO
  test/                  node:test-Tests

REST-API (Auszug)

Alle Aktionen laufen per POST mit Authorization: Bearer <token>. Der Token ist der Organisator- oder Spieler-Token bzw. der Zuschauercode.

Endpunkt Wer
GET /api/config, GET /api/matches alle
POST /api/matches alle (ggf. mit CREATE_MATCH_PASSWORD)
POST /api/access {code} prüft einen Code
GET /api/matches/:id rollenabhängiger Zustand
POST /api/matches/:id/join {code, nickname, steamId?} mit Teamcode
…/ready, …/leave Spieler
…/veto/action {stepIndex, map?, side?} Captain am Zug
…/captain {playerId} Organisator, Captain des Teams
…/veto/start, …/kick, …/codes/regenerate, …/server/retry, …/server/stop, …/server/warmup-end, …/series/next, …/finish, …/cancel Organisator

Socket.IO: match:subscribe {matchId, token} liefert match:state bei jeder Änderung; overview:subscribe liefert overview.

S
Description
No description provided
Readme
19 MiB
0 Stars 1 Watchers 0 Forks
Languages
JavaScript 87.1%
CSS 12.3%
Shell 0.3%
Dockerfile 0.2%
HTML 0.1%