diff --git a/.gitignore b/.gitignore index 100cf3c..18e9b19 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ data/ .env.local npm-debug.log* .claude/ +dist/ diff --git a/compose.yml b/compose.yml deleted file mode 100644 index 74adcfc..0000000 --- a/compose.yml +++ /dev/null @@ -1,41 +0,0 @@ -# CS2 Mapveto auf einem Docker-Host (z. B. als Portainer-Stack). -# Die App startet pro Match einen CS2-Container auf demselben Host (Docker-Adapter). -# -# Vorher einmalig: Image bauen (siehe docs/container.md) und das Volume -# "cs2-mapveto-gamefiles" mit den CS2-Spieldateien befüllen (docs/portainer-cs2.md). - -services: - cs2-mapveto: - image: cs2-mapveto:latest - build: . - container_name: cs2-mapveto - restart: unless-stopped - ports: - - "3000:3000" - environment: - # Adresse dieses Docker-Hosts im LAN – für Einladungslinks, Spieler-Verbindungen und Live-Logs - - PUBLIC_URL=http://${HOST_IP:-192.168.178.111}:3000 - - LOG_RECEIVER_URL=http://${HOST_IP:-192.168.178.111}:3000 - - SERVER_ADAPTER=docker - - CS2_PUBLIC_HOST=${HOST_IP:-192.168.178.111} - # RCON zu den Match-Containern über die veröffentlichten Ports des Hosts - - CS2_RCON_HOST=${HOST_IP:-192.168.178.111} - # leer = LAN-Modus (sv_lan 1) - - CS2_GSLT=${CS2_GSLT:-} - - CS2_DOCKER_VOLUME=cs2-mapveto-gamefiles - - CS2_DOCKER_IMAGE=joedwards32/cs2:latest - - CS2_PORT_RANGE_START=27100 - - CS2_PORT_RANGE_END=27139 - - CREATE_MATCH_PASSWORD=${CREATE_MATCH_PASSWORD:-} - - STREAM_KEY=${STREAM_KEY:-} - - SERIES_AUTO_ADVANCE_SECONDS=30 - - SERVER_IDLE_TIMEOUT_MINUTES=30 - volumes: - # Datenbank (Matches, Ergebnisse) überdauert Updates des Containers - - cs2-mapveto-data:/data - # Zugriff auf Docker, um Match-Container zu starten und zu entfernen - - /var/run/docker.sock:/var/run/docker.sock - -volumes: - cs2-mapveto-data: - name: cs2-mapveto-data diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..199d1ec --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,31 @@ +# Vorlage für deploy/.env – kopieren und anpassen: +# cp deploy/.env.example deploy/.env + +# --- Pflicht --- +# IP oder Name des Docker-Hosts im LAN. Spieler verbinden sich dorthin. +HOST_IP=192.168.178.111 + +# --- Webseite --- +# Port der Webseite auf dem Host +WEB_PORT=3000 +# Image: lokal gebaut oder mit "docker load" geladen (siehe docs/container.md) +CS2_MAPVETO_IMAGE=cs2-mapveto:latest + +# --- Serververwaltung --- +# docker = pro Match ein CS2-Container (Standard) +# demo = nur Simulation, startet keine echten Server (zum Ausprobieren) +SERVER_ADAPTER=docker +# Game Server Login Token (App-ID 730). Leer = LAN-Modus (sv_lan 1). +CS2_GSLT= +CS2_DOCKER_IMAGE=joedwards32/cs2:latest +# Zwei Ports pro Match (Spiel TCP+UDP, CSTV UDP) – auf dem Host freigeben +CS2_PORT_RANGE_START=27100 +CS2_PORT_RANGE_END=27139 +SERVER_IDLE_TIMEOUT_MINUTES=30 + +# --- Matches und Stream --- +# Wenn gesetzt, braucht man dieses Passwort zum Anlegen von Matches +CREATE_MATCH_PASSWORD= +# Wenn gesetzt, braucht man /stream?key=… für die Stream-Übersicht +STREAM_KEY= +SERIES_AUTO_ADVANCE_SECONDS=30 diff --git a/deploy/compose.yml b/deploy/compose.yml new file mode 100644 index 0000000..13ed671 --- /dev/null +++ b/deploy/compose.yml @@ -0,0 +1,46 @@ +# CS2 Mapveto – Start auf einem beliebigen Docker-Host (Linux, z. B. Portainer-Server, NAS, eigener Rechner). +# +# Voraussetzungen: +# 1. deploy/.env anlegen (Vorlage: deploy/.env.example), mindestens HOST_IP setzen +# 2. Image cs2-mapveto:latest auf dem Host (gebaut oder mit "docker load" geladen) +# 3. Volume cs2-mapveto-gamefiles mit den CS2-Spieldateien (siehe docs/container.md) +# +# Start: docker compose --env-file deploy/.env -f deploy/compose.yml up -d +# Stoppen: docker compose --env-file deploy/.env -f deploy/compose.yml down + +name: cs2-mapveto + +services: + cs2-mapveto: + image: ${CS2_MAPVETO_IMAGE:-cs2-mapveto:latest} + container_name: cs2-mapveto + restart: unless-stopped + ports: + - "${WEB_PORT:-3000}:3000" + environment: + PORT: "3000" + # Adresse des Docker-Hosts im LAN: Einladungslinks, Spieler-Verbindungen, Live-Logs + PUBLIC_URL: "http://${HOST_IP:?HOST_IP in deploy/.env eintragen}:${WEB_PORT:-3000}" + LOG_RECEIVER_URL: "http://${HOST_IP}:${WEB_PORT:-3000}" + DATABASE_FILE: /data/cs2-mapveto.db + SERVER_ADAPTER: ${SERVER_ADAPTER:-docker} + CS2_PUBLIC_HOST: ${HOST_IP} + CS2_RCON_HOST: ${HOST_IP} + CS2_GSLT: ${CS2_GSLT:-} + CS2_DOCKER_VOLUME: cs2-mapveto-gamefiles + CS2_DOCKER_IMAGE: ${CS2_DOCKER_IMAGE:-joedwards32/cs2:latest} + CS2_PORT_RANGE_START: ${CS2_PORT_RANGE_START:-27100} + CS2_PORT_RANGE_END: ${CS2_PORT_RANGE_END:-27139} + CREATE_MATCH_PASSWORD: ${CREATE_MATCH_PASSWORD:-} + STREAM_KEY: ${STREAM_KEY:-} + SERIES_AUTO_ADVANCE_SECONDS: ${SERIES_AUTO_ADVANCE_SECONDS:-30} + SERVER_IDLE_TIMEOUT_MINUTES: ${SERVER_IDLE_TIMEOUT_MINUTES:-30} + volumes: + # Datenbank (Matches, Ergebnisse) überdauert Updates + - cs2-mapveto-data:/data + # Zugriff auf Docker, um Match-Container zu starten und zu entfernen + - /var/run/docker.sock:/var/run/docker.sock + +volumes: + cs2-mapveto-data: + name: cs2-mapveto-data diff --git a/docs/container.md b/docs/container.md index 2cfe152..66864b8 100644 --- a/docs/container.md +++ b/docs/container.md @@ -2,120 +2,171 @@ CS2 Mapveto läuft als **ein** Container: Webseite, Backend, Datenbank (SQLite) und ein Docker-Client. Er steht auf demselben Docker-Host wie die CS2-Server und startet pro Match über `/var/run/docker.sock` -einen eigenen CS2-Container (Docker-Adapter). Ein PC mit Node.js oder SSH wird dafür nicht mehr gebraucht. +einen eigenen CS2-Container. Der Host kann jeder Linux-Rechner mit Docker sein, ein AMP-Server ist nicht nötig. ``` -Docker-Host (z. B. amp-cluster, 192.168.178.111) -├── cs2-mapveto ← dieser Container, Port 3000 -├── cs2mv- ← CS2-Server pro Match, Ports ab 27100 (startet/entfernt CS2 Mapveto) -├── Volume cs2-mapveto-gamefiles CS2-Spieldateien (~73 GB, einmal befüllt) -└── Volume cs2-mapveto-data Datenbank von CS2 Mapveto +Docker-Host (beliebig, Linux, Adresse z. B. 192.168.178.111) +├── cs2-mapveto ← diese Anwendung, Port 3000 +├── cs2mv- ← CS2-Server pro Match (startet/entfernt CS2 Mapveto) +├── Volume cs2-mapveto-gamefiles CS2-Spieldateien (~73 GB) +└── Volume cs2-mapveto-data Datenbank der Anwendung ``` -> **Nur eine Instanz pro Docker-Host.** Läuft CS2 Mapveto zusätzlich auf einem PC mit `SERVER_ADAPTER=docker` -> und demselben Docker-Host, vergeben beide unabhängig voneinander Ports und Containernamen. Die PC-Instanz -> also beenden oder auf einen anderen Adapter stellen, bevor der Container läuft. +> **Nur eine Instanz pro Docker-Host.** Läuft CS2 Mapveto zusätzlich anderswo mit demselben Host, streiten +> sich beide um Ports und Containernamen. Die andere Instanz vorher beenden. --- -## 1. Image bauen +## Was du brauchst -Das Image wird auf dem Docker-Host gebaut, beim Bauen laufen auch alle Tests. Am einfachsten vom PC aus, im -Projektordner, über den SSH-Zugang zum Host: +| | | +|---|---| +| Docker + Compose | `docker compose version` muss funktionieren | +| Speicher | ~80 GB frei für Spieldateien, Images und Updates | +| CPU / RAM | pro gleichzeitigem Match etwa 1–2 Kerne und 2–3 GB RAM | +| Netz | Ports TCP/UDP 27100–27139 (Spiel, RCON) und UDP 27101… (CSTV) sowie TCP 3000 im LAN freigeben | +| Dateien | das Image `cs2-mapveto` (siehe Schritt 1) und die CS2-Spieldateien (Schritt 2) | -```powershell -docker -H ssh://cs2docker build -t cs2-mapveto:latest . +--- + +## Schritt 1: Image auf den neuen Host bringen + +Du hast zwei Wege. + +### Weg A: Image-Datei kopieren (ohne Build, schnell) + +Auf dem Rechner, auf dem das Image gebaut wurde, liegt die Datei `dist/cs2-mapveto-image.tar` (294 MB). Sie +auf den neuen Host kopieren und laden: + +```bash +scp cs2-mapveto-image.tar USER@NEUERHOST:/tmp/ +ssh USER@NEUERHOST "docker load -i /tmp/cs2-mapveto-image.tar && rm /tmp/cs2-mapveto-image.tar" ``` -Ergebnis: Image `cs2-mapveto:latest` (~300 MB) auf dem Host. Alternativ direkt auf dem Host nach einem -`git clone` des Repositorys: `docker build -t cs2-mapveto:latest .` +Prüfen mit `docker image ls cs2-mapveto`. -> Meldet Docker unter Windows `docker-credential-desktop: executable file not found`, kommt das von einer -> Docker-Desktop-Einstellung. Abhilfe für den einen Befehl: `$env:DOCKER_CONFIG = "$env:TEMP\dockercfg"` -> (Ordner mit einer Datei `config.json`, Inhalt `{}`). +### Weg B: aus dem Repository bauen -## 2. Stack in Portainer anlegen +Auf dem neuen Host ist Git nötig. Das Repository liegt auf dem Gitea (`http://192.168.178.45:3002/marcel/Cs2-Mapveto.git`): -Portainer → **Stacks** → **+ Add stack** → Name `cs2-mapveto` → *Web editor*: - -```yaml -services: - cs2-mapveto: - image: cs2-mapveto:latest - container_name: cs2-mapveto - restart: unless-stopped - ports: - - "3000:3000" - environment: - - PUBLIC_URL=http://192.168.178.111:3000 - - LOG_RECEIVER_URL=http://192.168.178.111:3000 - - SERVER_ADAPTER=docker - - CS2_PUBLIC_HOST=192.168.178.111 - - CS2_RCON_HOST=192.168.178.111 - - CS2_GSLT= - - CS2_DOCKER_VOLUME=cs2-mapveto-gamefiles - - CS2_DOCKER_IMAGE=joedwards32/cs2:latest - - CS2_PORT_RANGE_START=27100 - - CS2_PORT_RANGE_END=27139 - - CREATE_MATCH_PASSWORD= - - STREAM_KEY= - - SERIES_AUTO_ADVANCE_SECONDS=30 - - SERVER_IDLE_TIMEOUT_MINUTES=30 - volumes: - - cs2-mapveto-data:/data - - /var/run/docker.sock:/var/run/docker.sock - -volumes: - cs2-mapveto-data: - name: cs2-mapveto-data +```bash +git clone http://192.168.178.45:3002/marcel/Cs2-Mapveto.git +cd Cs2-Mapveto +docker build -t cs2-mapveto:latest . ``` -**Deploy the stack.** Danach ist CS2 Mapveto unter `http://192.168.178.111:3000` erreichbar, die Stream-Übersicht -unter `/stream`. +Beim Bauen laufen alle Tests mit. Schlägt einer fehl, bricht der Build ab. -Dieselbe Datei liegt als [`compose.yml`](../compose.yml) im Repository (dort zusätzlich mit `build: .`, für -`docker compose up -d --build` direkt auf dem Host). +--- -### Einstellungen +## Schritt 2: CS2-Spieldateien auf den neuen Host bringen -Alle Umgebungsvariablen aus [`.env.example`](../.env.example) funktionieren auch hier. Die wichtigsten: +Das Volume `cs2-mapveto-gamefiles` muss vor dem ersten Match vorhanden und befüllt sein. Sonst lädt jeder Match-Start +die ~60 GB neu. Zwei Wege: + +**a) Vom bisherigen Host kopieren** (im LAN, kein Internet nötig, dauert je nach Netz Stunden): + +```bash +ssh ALTERHOST "docker run --rm -v cs2-mapveto-gamefiles:/from alpine tar -C /from -cf - ." \ + | ssh NEUERHOST "docker volume create cs2-mapveto-gamefiles >/dev/null && docker run --rm -i -v cs2-mapveto-gamefiles:/to alpine tar -C /to -xf -" +``` + +**b) Neu laden** (braucht Internet, ~60 GB). Der Container lädt die Dateien und stoppt danach von selbst: + +```bash +docker volume create cs2-mapveto-gamefiles +docker run -d --name cs2-updater -e STEAMAPPVALIDATE=0 -e CS2_RCONPW=updater1 -v cs2-mapveto-gamefiles:/home/steam/cs2-dedicated/ joedwards32/cs2:latest +docker logs -f cs2-updater # warten auf "Success! App '730' fully installed" +docker rm -f cs2-updater +``` + +Danach liegen unter `docker system df -v` rund 73 GB im Volume. + +Das CS2-Image selbst (`joedwards32/cs2:latest`, ~3,7 GB) holt Docker beim ersten Match automatisch aus dem +Docker Hub. Ohne Internet muss es vorher ebenfalls geladen werden (`docker save`/`docker load` wie bei Schritt 1). + +--- + +## Schritt 3: Konfiguration + +Im Ordner `deploy/` die Vorlage kopieren und anpassen: + +```bash +cp deploy/.env.example deploy/.env +``` + +Das Wichtigste: | Variable | Bedeutung | |---|---| -| `PUBLIC_URL` | Adresse der Webseite für Einladungslinks | -| `LOG_RECEIVER_URL` | Adresse, an die die CS2-Container ihre Logs schicken (Live-Ergebnisse) | -| `CS2_PUBLIC_HOST` / `CS2_RCON_HOST` | IP des Docker-Hosts – Spieler verbinden sich dorthin, CS2 Mapveto steuert die Server darüber | -| `CS2_GSLT` | leer = LAN-Modus; mit Token öffentlich erreichbar (ein Token pro gleichzeitigem Match) | -| `CS2_PORT_RANGE_START/END` | Ports für Match-Container, zwei pro Match, in der Firewall freigeben | -| `CREATE_MATCH_PASSWORD` | Passwort zum Anlegen von Matches | -| `STREAM_KEY` | Schlüssel für `/stream?key=…` | +| `HOST_IP` | **Pflicht.** IP des neuen Hosts im LAN. Spieler verbinden sich dorthin, Einladungslinks zeigen darauf | +| `WEB_PORT` | Port der Webseite (Standard 3000) | +| `SERVER_ADAPTER` | `docker` für echte Server, `demo` nur zum Ausprobieren ohne CS2 | +| `CS2_GSLT` | leer = LAN-Modus (`sv_lan 1`), kein Token nötig | +| `CS2_PORT_RANGE_START` / `_END` | Ports für Match-Server, zwei pro Match | +| `CREATE_MATCH_PASSWORD` | Passwort zum Anlegen von Matches (empfohlen) | +| `STREAM_KEY` | Schlüssel für `/stream?key=…` (optional) | -**Passwörter und Schlüssel nur aus Buchstaben und Ziffern** wählen. +Passwörter nur aus Buchstaben und Ziffern wählen. -## 3. Updaten +--- -1. Neues Image bauen (Schritt 1, gleicher Befehl). -2. Portainer → Stack `cs2-mapveto` → **Update the stack** (bzw. Container → *Recreate*). +## Schritt 4: Starten -Die Datenbank liegt im Volume `cs2-mapveto-data` und bleibt erhalten. Laufende Match-Container übernimmt -CS2 Mapveto nach dem Neustart wieder; ein Serverstart, der gerade lief, wird als unterbrochen markiert und -lässt sich mit „Server neu starten“ wiederholen. +```bash +docker compose --env-file deploy/.env -f deploy/compose.yml up -d +``` -## 4. Sicherheit +Prüfen: -Der Container hat über `/var/run/docker.sock` volle Kontrolle über Docker auf dem Host – das ist nötig, um +```bash +docker compose --env-file deploy/.env -f deploy/compose.yml ps # Status "healthy" nach ~20 s +curl http://HOST_IP:3000/api/config # JSON mit "adapter": "docker" +``` + +Danach ist die Webseite unter `http://HOST_IP:3000` erreichbar, die Stream-Übersicht unter `/stream`. + +Stoppen und neu starten: + +```bash +docker compose --env-file deploy/.env -f deploy/compose.yml down +docker compose --env-file deploy/.env -f deploy/compose.yml up -d +``` + +Die Datenbank (Volume `cs2-mapveto-data`) bleibt dabei erhalten. + +### Mit Portainer statt Kommandozeile + +Portainer → Stacks → **Add stack** → Web editor: den Inhalt von `deploy/compose.yml` einfügen. Die Variablen aus +`deploy/.env` als *Environment variables* eintragen (mindestens `HOST_IP`). Danach **Deploy the stack**. + +--- + +## Aktualisieren + +1. Neues Image holen (Weg A oder B aus Schritt 1). +2. `docker compose … up -d` erneut ausführen. Compose ersetzt den Container, die Datenbank bleibt. + +--- + +## Sicherheit + +Der Container hat über `/var/run/docker.sock` volle Kontrolle über Docker auf dem Host. Das ist nötig, um Match-Container zu starten, entspricht aber Root-Rechten auf dem Host. Deshalb: - Port 3000 nur im LAN anbieten, nicht ins Internet weiterleiten. - `CREATE_MATCH_PASSWORD` setzen, wenn nicht jeder im LAN Matches anlegen soll. -- RCON-Ports (TCP 27100–27139) möglichst nur vom Host bzw. LAN erreichbar lassen. +- `deploy/.env` enthält Passwörter. Nicht ins Repository einchecken (ist in `.gitignore`). -## 5. Fehlersuche +--- + +## Fehlersuche | Symptom | Ursache / Lösung | |---|---| -| „Server wird angefordert“ bleibt stehen | Container-Log prüfen (Portainer → `cs2-mapveto` → Logs); Docker-Socket eingebunden? | -| `permission denied … docker.sock` im Log | Socket nicht eingebunden oder Docker mit Rootless-Modus – Pfad zum Socket anpassen | -| Match-Container startet, „Server startet“ endet in Fehler | Ports 27100+ von `CS2_RCON_HOST` aus erreichbar? Firewall des Hosts | -| Keine Live-Ergebnisse | `LOG_RECEIVER_URL` muss die Host-IP mit Port 3000 sein, nicht `localhost` | -| Port bereits belegt | Eine zweite CS2-Mapveto-Instanz nutzt denselben Host (siehe Hinweis oben) | +| `HOST_IP in deploy/.env eintragen` beim Start | `deploy/.env` fehlt oder `HOST_IP` ist leer | +| „Server wird angefordert“ bleibt stehen | Log des Containers prüfen: `docker logs cs2-mapveto` | +| `permission denied … docker.sock` im Log | Rootless-Docker oder anderer Socket-Pfad. Dann den Pfad im `volumes:`-Eintrag anpassen | +| Match-Server startet, Spieler finden ihn nicht | Ports 27100–27139 im Host-Netz freigeben; `HOST_IP` muss die LAN-Adresse des Hosts sein | +| Keine Live-Ergebnisse | `HOST_IP` falsch oder Port 3000 vom Match-Server aus nicht erreichbar | +| Ein Match-Start dauert sehr lange | Spieldateien fehlen im Volume und werden neu geladen (Schritt 2) |