Files
Cs2-Mapveto/docs/portainer-cs2.md
SPP UserandClaude Opus 5.5 5ea5638115 Anleitung: CS2-Server mit Portainer und dauerhaftem Volume
docs/portainer-cs2.md beschreibt Stack, einmaligen Download, Updates ohne
Neu-Download, Anbindung per Static- oder Docker-Adapter, mehrere Server
und Fehlersuche. Standard-Image auf joedwards32/cs2 umgestellt, da
cm2network/cs2 nicht mehr gepflegt wird.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:07:17 +01:00

246 lines
11 KiB
Markdown

# 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 | Wert |
|---|---|
| `SRCDS_TOKEN` | dein GSLT |
| `CS2_RCONPW` | langes, zufälliges Passwort (nur Buchstaben und Ziffern) |
| `CS2_PW` | beliebiges Serverpasswort |
| `TV_PW` | Passwort für CSTV |
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.
```env
SERVER_ADAPTER=docker
CS2_GSLT=dein_gslt
CS2_PUBLIC_HOST=192.168.178.50
CS2_DOCKER_VOLUME=cs2-mapveto-gamefiles
CS2_DOCKER_IMAGE=joedwards32/cs2:latest
```
Wichtig dabei:
- **Den Stack-Server vorher stoppen**, damit nicht zwei Container gleichzeitig dasselbe Volume aktualisieren. Der Stack dient dann nur zum einmaligen Befüllen und für Updates.
- Für gleichzeitige Matches gilt dasselbe Problem: Alle Match-Container teilen sich das Volume und prüfen beim Start auf Updates. Das ist unkritisch, solange kein Valve-Update ansteht. Kurz nach einem CS2-Patch nur **ein** Match auf einmal starten.
- Das Backend braucht Zugriff auf den Docker-Daemon (es läuft auf demselben Host bzw. hat `/var/run/docker.sock`).
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`). 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. |