Files
Cs2-Mapveto/docs/portainer-cs2.md
T
mregosar 84dad0993c Docker-Adapter mit echtem Host getestet, zwei Fehler behoben
- CSTV belegt einen Spielerplatz: Container starten jetzt mit 6 bzw. 12
  Plätzen (Spieler + CSTV + Reserve), sonst kam der letzte Spieler nicht drauf
- 'Live-Daten empfangen' wird auch in der Aufwärmphase angezeigt, wenn
  der Server Logs liefert, die den Spielstand noch nicht ändern
- Anleitung: Serverliste, Plätze, Bots zum Testen

Getestet auf amp-cluster (Docker per SSH, LAN-Modus): Container-Start,
Veto, Konfiguration, Live-Runden, automatischer Mapwechsel, Entfernen.
2026-10-25 22:09:43 +01:00

285 lines
14 KiB
Markdown
Raw 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.
# CS2-Server mit Portainer einrichten
Diese Anleitung richtet einen CS2-Dedicated-Server als Portainer-Stack ein, dessen Spieldateien (ca. 60 GB) **einmal** heruntergeladen und danach dauerhaft in einem Docker-Volume gespeichert werden. Neustarts, Image-Updates und neue Matches laden die Dateien nicht neu.
Anschließend wird der Server mit CS2 Mapveto verbunden.
---
## So funktioniert das Speichern der Dateien
Das Image [`joedwards32/cs2`](https://github.com/joedwards32/CS2) installiert das Spiel mit SteamCMD nach `/home/steam/cs2-dedicated/`. Bei **jedem** Containerstart prüft es mit `app_update`, ob es ein Update gibt.
- **Beim ersten Start** ist das Verzeichnis leer, also werden die kompletten ~60 GB geladen.
- **Bei jedem weiteren Start** liegen die Dateien schon im Volume. SteamCMD vergleicht nur die Version, das dauert Sekunden bis wenige Minuten.
- **Wenn Valve ein Update veröffentlicht**, lädt SteamCMD nur die geänderten Teile (Delta-Update).
Entscheidend ist also: Das Verzeichnis `/home/steam/cs2-dedicated/` muss auf einem **benannten Volume** (oder einem Host-Ordner) liegen, nicht im Container selbst. Dann übersteht es auch das Löschen und Neuerstellen des Containers.
> Hinweis zum Image: `cm2network/cs2` ist die ältere Fassung desselben Projekts und wird seit September 2025 nicht mehr aktualisiert. Verwende `joedwards32/cs2` (auch als `ghcr.io/joedwards32/cs2`). Die Umgebungsvariablen sind identisch.
---
## Voraussetzungen
- Docker-Host mit Portainer (CE reicht), **Linux** empfohlen
- **ca. 70 GB freier Speicher** für Spieldateien und Updates, idealerweise auf einer SSD
- Mindestens 2 CPU-Kerne und 4 GB RAM pro gleichzeitig laufendem Server
- **Game Server Login Token (GSLT)** für App-ID **730**: <https://steamcommunity.com/dev/managegameservers>. Ohne Token ist der Server nur im LAN-Modus nutzbar (siehe unten). Pro gleichzeitig laufendem Server brauchst du ein eigenes Token.
- Freigegebene Ports (Router/Firewall), pro Server:
- Spielport, Standard **27015**, **UDP und TCP**. Über TCP läuft auch RCON, das nur das Backend erreichen muss.
- CSTV-Port, Standard **27020**, **UDP**
---
## Schritt 1: Stack in Portainer anlegen
1. Portainer öffnen, die richtige **Environment** wählen (dein Docker-Host).
2. Links auf **Stacks** und dann **+ Add stack** klicken.
3. **Name:** `cs2`
4. **Build method:** *Web editor*. Diesen Inhalt einfügen:
```yaml
services:
cs2-1:
image: joedwards32/cs2:latest
container_name: cs2-1
restart: unless-stopped
environment:
- SRCDS_TOKEN=${SRCDS_TOKEN} # GSLT aus Schritt "Voraussetzungen"
- STEAMAPPVALIDATE=0 # 0 = nur Update prüfen, NICHT jedes Mal alles verifizieren
- CS2_SERVERNAME=MaxLAN CS2 #1
- CS2_PORT=27015
- CS2_RCONPW=${CS2_RCONPW} # RCON-Passwort, wird in CS2 Mapveto eingetragen
- CS2_PW=${CS2_PW} # Serverpasswort (CS2 Mapveto setzt pro Match ein eigenes)
- CS2_MAXPLAYERS=12
- CS2_SERVER_HIBERNATE=0 # kein Ruhemodus, sonst reagiert RCON verzögert
- CS2_LAN=0 # 1 = reiner LAN-Betrieb (siehe unten)
- CS2_GAMETYPE=0
- CS2_GAMEMODE=1
- CS2_MAPGROUP=mg_active
- CS2_STARTMAP=de_dust2
- TV_ENABLE=1 # CSTV für Zuschauer und Stream
- TV_PORT=27020
- TV_PW=${TV_PW}
- TV_RELAY_PW=${TV_PW}
- TV_DELAY=90
- TV_AUTORECORD=0
volumes:
- cs2-gamefiles:/home/steam/cs2-dedicated/
ports:
- "27015:27015/tcp"
- "27015:27015/udp"
- "27020:27020/udp"
stdin_open: true
tty: true
volumes:
cs2-gamefiles:
# Fester Name, damit Portainer keinen Stack-Präfix voranstellt
# und CS2 Mapveto (Docker-Adapter) dasselbe Volume findet.
name: cs2-mapveto-gamefiles
```
5. Darunter bei **Environment variables** auf *+ Add an environment variable* klicken und anlegen:
| Name | Beispielwert | Bedeutung |
|---|---|---|
| `SRCDS_TOKEN` | `A1B2C3D4E5F6…` | dein GSLT; leer lassen für LAN-Modus |
| `CS2_RCONPW` | `Kx7fQ2mPz9Lw4tNb` | RCON-Passwort, lang und zufällig |
| `CS2_PW` | `Lan2026` | Serverpasswort |
| `TV_PW` | `Cstv2026` | Passwort für CSTV |
> **Nur Buchstaben und Ziffern verwenden.** Das Startskript des Images setzt die Werte ungeschützt in einen
> Shell-Befehl ein. Leerzeichen, Klammern, Kommas, Anführungszeichen oder `$` führen dazu, dass der Server
> nicht startet (`syntax error near unexpected token`) und Docker ihn endlos neu startet.
So stehen die Zugangsdaten nicht im Stack-Text selbst.
6. **Deploy the stack** klicken.
---
## Schritt 2: Ersten Download abwarten
1. In Portainer auf **Containers** und dann **cs2-1** klicken, danach auf **Logs**.
2. Ein Haken bei *Auto-refresh logs* hilft. Du siehst den SteamCMD-Fortschritt, z. B. `Update state (0x61) downloading, progress: 12.34 %`.
3. Je nach Leitung dauert das **30 Minuten bis mehrere Stunden**. Den Container in dieser Zeit nicht neu starten.
4. Fertig ist der Server, wenn im Log `Success! App '730' fully installed.` steht und danach der Serverstart folgt. Am Ende stehen Zeilen wie `GC Connection established for server` bzw. `Connection to Steam servers successful.`
Ab jetzt liegen die Dateien im Volume `cs2-mapveto-gamefiles`. Unter **Volumes** siehst du es mit etwa 60 GB.
**Test:** In der CS2-Konsole `connect <Host-IP>:27015; password <CS2_PW>` eingeben.
---
## Schritt 3: Updates und Neustarts ohne neuen Download
| Aktion in Portainer | Was passiert mit den Dateien |
|---|---|
| Container **Restart** | bleiben erhalten, nur Update-Prüfung |
| Stack **Update the stack** (z. B. geänderte Variablen) | bleiben erhalten |
| **Re-pull image and redeploy** | neues Image, Dateien bleiben im Volume |
| Container **Remove** und neu deployen | bleiben erhalten (Volume ist unabhängig) |
| Stack **löschen** | Volume bleibt standardmäßig bestehen |
| **Volume löschen** (*Volumes*, *Remove*) | **Dateien sind weg**, nächster Start lädt 60 GB neu |
Wenn Valve CS2 aktualisiert, lehnen Server mit alter Version Spieler ab. Dann reicht ein **Restart** des Containers, er zieht nur das Delta.
> Setze `STEAMAPPVALIDATE` nur auf `1`, wenn Dateien beschädigt sind. Dann werden alle 60 GB geprüft, was sehr lange dauert. Danach wieder auf `0` stellen.
---
## Schritt 4: Mit CS2 Mapveto verbinden
Für einen dauerhaft laufenden Server wie diesen ist der **Static-Adapter** gedacht. CS2 Mapveto reserviert ihn pro Match, setzt Passwörter und Map per RCON und gibt ihn nach dem Match wieder frei. Der Container läuft dabei durchgehend.
In der `.env` von CS2 Mapveto:
```env
SERVER_ADAPTER=static
# host:spielport:rconPasswort:cstvPort
CS2_STATIC_SERVERS=192.168.178.50:27015:DEIN_CS2_RCONPW:27020
```
- `192.168.178.50` durch die IP des Docker-Hosts ersetzen, die **Spieler** erreichen. Sie wird ihnen als Verbindungsadresse angezeigt.
- Das Backend von CS2 Mapveto muss dieselbe Adresse per **TCP** auf dem Spielport erreichen (RCON).
- Danach CS2 Mapveto neu starten (`npm run dev` bzw. `npm start`).
**Prüfen:** Ein neues Match erstellen. Im Serverpanel muss nach wenigen Sekunden **„Server bereit“** erscheinen, ohne „Demo“. Bleibt es bei „Server startet“, zeigt die Meldung darunter den RCON-Fehler, z. B. falsches Passwort oder Port nicht erreichbar.
---
## Mehrere Matches gleichzeitig
Jeder gleichzeitig laufende Server braucht eigene Ports, ein eigenes GSLT und, wichtig, ein **eigenes Installationsverzeichnis**. Zwei Container dürfen **nicht gleichzeitig** dasselbe Volume aktualisieren, sonst können sich SteamCMD-Downloads gegenseitig beschädigen.
Zweiten Server im selben Stack ergänzen:
```yaml
cs2-2:
image: joedwards32/cs2:latest
container_name: cs2-2
restart: unless-stopped
environment:
- SRCDS_TOKEN=${SRCDS_TOKEN_2}
- STEAMAPPVALIDATE=0
- CS2_SERVERNAME=MaxLAN CS2 #2
- CS2_PORT=27025
- CS2_RCONPW=${CS2_RCONPW}
- CS2_PW=${CS2_PW}
- CS2_SERVER_HIBERNATE=0
- TV_ENABLE=1
- TV_PORT=27030
- TV_PW=${TV_PW}
- TV_DELAY=90
volumes:
- cs2-gamefiles-2:/home/steam/cs2-dedicated/
ports:
- "27025:27025/tcp"
- "27025:27025/udp"
- "27030:27030/udp"
stdin_open: true
tty: true
```
…und unter `volumes:` ergänzen:
```yaml
cs2-gamefiles-2:
name: cs2-mapveto-gamefiles-2
```
In CS2 Mapveto beide eintragen:
```env
CS2_STATIC_SERVERS=192.168.178.50:27015:PASSWORT:27020,192.168.178.50:27025:PASSWORT:27030
```
**Damit der zweite Server nicht erneut 60 GB aus dem Internet lädt**, gibt es zwei Wege:
1. **Volume kopieren (einmalig):** Server 1 in Portainer stoppen, dann auf dem Docker-Host:
```bash
docker volume create cs2-mapveto-gamefiles-2
docker run --rm -v cs2-mapveto-gamefiles:/from -v cs2-mapveto-gamefiles-2:/to alpine sh -c "cp -a /from/. /to/"
```
Danach Server 1 wieder starten und Server 2 deployen. Er findet die Dateien schon vor und prüft nur noch die Version.
2. **[Lancache](https://lancache.net/)** im LAN betreiben, typisch für LAN-Partys: Steam-Downloads werden lokal zwischengespeichert. Jeder weitere Server lädt dann mit LAN-Geschwindigkeit. Im Stack dafür beim Service `dns: - <Lancache-IP>` ergänzen (siehe [Beispiel des Images](https://github.com/joedwards32/CS2/blob/main/examples/docker-compose-lancache.yml)).
---
## Alternative: CS2 Mapveto startet Container selbst (Docker-Adapter)
Statt dauerhaft laufender Server kann CS2 Mapveto pro Match einen eigenen Container starten und danach wieder entfernen (`SERVER_ADAPTER=docker`). Damit auch dabei nichts neu geladen wird, nutzt der Adapter das Volume **`cs2-mapveto-gamefiles`**. Das ist genau das Volume, das der Stack oben angelegt und befüllt hat.
### 1. Docker-Zugriff von CS2 Mapveto auf den Docker-Host
Läuft CS2 Mapveto auf einem anderen Rechner als Docker (z. B. Windows-PC → Docker-Host im LAN), steuert es Docker per SSH:
1. Auf dem Docker-Host den Benutzer in die Docker-Gruppe aufnehmen (einmalig, danach neu anmelden):
```bash
sudo usermod -aG docker server
```
2. Auf dem Rechner mit CS2 Mapveto einen SSH-Eintrag anlegen (Windows: `C:\Users\<Name>\.ssh\config`) und die Anmeldung per Schlüssel einrichten:
```
Host cs2docker
HostName 192.168.178.111
User server
```
3. Testen, **auf dem Rechner mit CS2 Mapveto**, nicht auf dem Docker-Host:
```powershell
docker -H ssh://cs2docker ps
```
### 2. `.env` von CS2 Mapveto
```env
SERVER_ADAPTER=docker
DOCKER_HOST=ssh://cs2docker
# leer = LAN-Modus (sv_lan 1), sonst ein Game Server Login Token
CS2_GSLT=
# Adresse, unter der Spieler den Docker-Host erreichen
CS2_PUBLIC_HOST=192.168.178.111
# Adresse, unter der CS2 Mapveto RCON erreicht – bei getrennten Rechnern ebenfalls der Docker-Host
CS2_RCON_HOST=192.168.178.111
CS2_DOCKER_VOLUME=cs2-mapveto-gamefiles
CS2_DOCKER_IMAGE=joedwards32/cs2:latest
# zwei Ports pro Match (Spiel TCP+UDP, CSTV UDP), in der Firewall freigeben
CS2_PORT_RANGE_START=27100
CS2_PORT_RANGE_END=27139
# Container müssen CS2 Mapveto für die Live-Ergebnisse erreichen
LOG_RECEIVER_URL=http://192.168.178.31:3000
```
Wichtig dabei:
- **Den Stack-Server stoppen**, sobald die Spieldateien geladen sind. Der Stack dient nur zum einmaligen Befüllen und für Updates. Das Volume bleibt beim Stoppen und Löschen des Stacks erhalten.
- **Starts laufen nacheinander:** Jeder Match-Container prüft beim Start per SteamCMD auf Updates. Damit nie zwei Container gleichzeitig das Volume aktualisieren, startet CS2 Mapveto den nächsten erst, wenn der vorige seinen Update-Check abgeschlossen hat. Nach einem CS2-Patch dauert der erste Start dadurch länger, die übrigen warten kurz.
- **Ein GSLT gilt nur für einen laufenden Server.** Ohne Token (LAN-Modus) gibt es diese Grenze nicht.
- **Hardware:** Ein CS2-Server braucht etwa 1–2 CPU-Kerne und 2–3 GB RAM. 6 Kerne und 16 GB reichen für etwa 2–3 gleichzeitige Matches.
- **Plätze:** CSTV belegt selbst einen Spielerplatz. Der Adapter startet Container deshalb mit 6 (Wingman) bzw. 12 (5 gegen 5) Plätzen: alle Spieler, CSTV und eine Reserve. Bei festen Servern (AMP, Stack) `maxplayers` genauso hoch setzen, sonst kommt der letzte Spieler nicht drauf.
- **Serverliste:** Match-Container tauchen im LAN-Tab von CS2 nicht auf. Der Client sucht dort nur auf den Ports 27015–27020, und Broadcasts erreichen Docker-Container kaum. Spieler verbinden sich über den Befehl bzw. den Knopf „In CS2 verbinden“ auf der Match-Seite. Jedes Match hat ohnehin ein eigenes Passwort.
- **Bots zum Testen:** Ohne menschliche Spieler entfernt CS2 Bots zum Matchstart wieder (`bot_join_after_player 1`). Für Tests `CS2_EXTRA_COMMANDS=bot_join_after_player 0;bot_quota 4;bot_quota_mode fill` setzen.
Für eine LAN-Party mit festen Servern ist der **Static-Adapter mit Stack aus Schritt 1** die einfachere und robustere Wahl.
---
## LAN-Betrieb ohne GSLT
Für reine LAN-Veranstaltungen kann `CS2_LAN=1` gesetzt werden (`sv_lan 1`). Beim Docker-Adapter genügt es, `CS2_GSLT` leer zu lassen. Der Server ist dann nur im lokalen Netz erreichbar und braucht kein Token. Spieler müssen im selben Netz sein. Für Zuschauer über das Internet oder externe Teams ist ein GSLT nötig.
---
## Fehlersuche
| Symptom | Ursache / Lösung |
|---|---|
| Download startet bei jedem Neustart von vorn | Volume fehlt oder ist falsch eingehängt. Prüfen, ob `/home/steam/cs2-dedicated/` auf ein Volume zeigt (Container, *Volumes*). |
| `Permission denied` beim Download | Bei **Host-Ordnern** statt Volume: `chown -R 1000:1000 /pfad/zum/ordner`, weil der Container als Benutzer `steam` (UID 1000) läuft. |
| `Error! App '730' state is 0x202` | Zu wenig Speicher. Mindestens 70 GB frei halten. |
| Spieler werden mit „Server läuft auf älterer Version“ abgewiesen | Valve-Update: Container neu starten. |
| CS2 Mapveto meldet „RCON-Passwort wurde abgelehnt“ | `CS2_RCONPW` im Stack und Passwort in `CS2_STATIC_SERVERS` vergleichen. |
| CS2 Mapveto meldet „Zeitüberschreitung“ | TCP-Port 27015 vom Backend aus nicht erreichbar (Firewall) oder Server lädt noch. |
| Server nicht in der Serverliste, aber per IP erreichbar | GSLT fehlt oder ist ungültig. Token prüfen, pro Server ein eigenes. |