commit 51b6309584e03710ee39afc419e3f15a84f1747e Author: Marek Lenczewski Date: Fri Apr 17 17:33:23 2026 +0200 init diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..dc853e0 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,339 @@ +# Windrose Dedicated Server auf Hetzner CPX22 — Runbook + +Anleitung für einen Agent zum Aufsetzen eines privaten Windrose-Dedicated-Servers +(max. 4 Spieler, Invite-Code-only) auf einem frischen Hetzner CPX22 mit Ubuntu 24.04. + +## Eckdaten + +- **Hardware:** CPX22 = 2 vCPU AMD EPYC, **4 GB RAM**, 80 GB NVMe SSD. +- **RAM-Risiko:** Windrose verlangt offiziell min. 8 GB für 2 Spieler. Wir kompensieren mit + 8 GB Swap und cappen `MaxPlayerCount=4`. Wenn Performance schlecht ist → Hetzner-Console + → Rescale auf CPX31 (8 GB RAM, ~€13/Monat), kein Datenverlust. +- **Server ist Windows-only.** Lauf über Wine + Xvfb (headless). +- **Setup-Variante:** Bare-Metal mit systemd (geringerer RAM-Footprint als Docker). +- **Connectivity:** Spieler joinen per Invite-Code. UDP 7777/7778 müssen aber trotzdem + reachable sein (NAT-Punch-Through über die öffentliche IP). +- **SteamCMD App-ID:** `4129620` (Windrose Dedicated Server, anonymous). + +## Regeln für den ausführenden Agent + +- Schritte **2, 3, 6 als root**, Schritte **4, 5, 7 als User `windrose`** (`sudo -iu windrose`). +- **Idempotent** arbeiten: vor jedem destruktiven Schritt prüfen, ob der Zielzustand schon + existiert (Swapfile, WineHQ-Repo, systemd-Unit, …) und ggf. überspringen. +- **Nicht-interaktiv**: bei `apt install` immer `-y`. Für `steamcmd` vorher die Lizenz + per debconf akzeptieren (siehe Schritt 2.6). +- Nach Schritt 5.2 den **InviteCode** aus `ServerDescription.json` extrahieren und im + finalen Report zurückmelden — den braucht der User zum Joinen. +- **Verifikation (Schritt 8) zwingend** ausführen und Ergebnisse berichten. +- **Nicht** ohne Rückfrage den Server neu starten, Snapshots löschen oder die + Hetzner-Cloud-Firewall ändern. Bei Fehlschlag: Log + Hypothese melden, nicht stumm + reparieren. +- Erwartete Dauer: 15–25 Min. (SteamCMD-Download ~3 GB + winetricks vcrun2022). + +--- + +## Schritt 1 — Hetzner-Console (manuell durch den User) + +Vor dem Server-Setup in der Hetzner Cloud Console: + +1. **Snapshot** des frischen CPX22 anlegen (Rollback-Punkt). +2. **Cloud-Firewall** anhängen mit Inbound-Regeln: + - SSH: TCP 22 (am besten nur eigene IP) + - Windrose Game: **UDP 7777** (any source) + - Windrose Query: **UDP 7778** (any source) +3. Outbound: alles erlauben (default). Wichtig für STUN/TURN nach + `*.windrose.support:3478` (UDP/TCP). + +--- + +## Schritt 2 — System-Vorbereitung (als root) + +```bash +# 2.1 Basis-Update +apt update && apt -y full-upgrade + +# 2.2 Server-User +id windrose >/dev/null 2>&1 || adduser --disabled-password --gecos "" windrose +usermod -aG sudo windrose + +# 2.3 Swap (8 GB) +if ! swapon --show | grep -q '/swapfile'; then + fallocate -l 8G /swapfile + chmod 600 /swapfile + mkswap /swapfile + swapon /swapfile + echo '/swapfile none swap sw 0 0' >> /etc/fstab +fi +echo 'vm.swappiness=10' > /etc/sysctl.d/99-swappiness.conf +sysctl --system + +# 2.4 Multiarch + Repos +dpkg --add-architecture i386 +add-apt-repository -y multiverse +add-apt-repository -y universe + +# 2.5 WineHQ-Repo (stable, aktueller als Distro-Wine) +mkdir -pm755 /etc/apt/keyrings +wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key +wget -NP /etc/apt/sources.list.d/ \ + https://dl.winehq.org/wine-builds/ubuntu/dists/noble/winehq-noble.sources +apt update + +# 2.6 SteamCMD-Lizenz vorab akzeptieren (sonst hängt apt interaktiv) +echo steam steam/question select "I AGREE" | debconf-set-selections +echo steam steam/license note '' | debconf-set-selections + +# 2.7 Pakete +DEBIAN_FRONTEND=noninteractive apt install -y --install-recommends winehq-stable +DEBIAN_FRONTEND=noninteractive apt install -y \ + software-properties-common lib32gcc-s1 steamcmd \ + xvfb cabextract winbind ufw fail2ban unzip jq wget + +# 2.8 winetricks +if [ ! -x /usr/local/bin/winetricks ]; then + wget -O /usr/local/bin/winetricks \ + https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks + chmod +x /usr/local/bin/winetricks +fi +``` + +--- + +## Schritt 3 — Lokale Firewall + SSH-Härtung (als root) + +```bash +ufw default deny incoming +ufw default allow outgoing +ufw allow 22/tcp +ufw allow 7777/udp +ufw allow 7778/udp +ufw --force enable + +systemctl enable --now fail2ban +``` + +In `/etc/ssh/sshd_config` setzen (nur wenn SSH-Key bereits funktioniert): +``` +PasswordAuthentication no +PermitRootLogin prohibit-password +``` +Dann `systemctl reload ssh`. + +--- + +## Schritt 4 — Server-Installation (als User `windrose`) + +```bash +sudo -iu windrose bash <<'EOF' +mkdir -p ~/steam ~/log ~/backup +cd ~/steam + +# 4.1 SteamCMD: Server-Files (forciert Windows-Platform) +steamcmd \ + +@sSteamCmdForcePlatformType windows \ + +force_install_dir /home/windrose/steam/windrose \ + +login anonymous \ + +app_update 4129620 validate \ + +quit + +# 4.2 Wine-Prefix initialisieren +export WINEPREFIX=/home/windrose/steam/windrose/pfx +export WINEARCH=win64 +xvfb-run -a wineboot --init +xvfb-run -a winetricks -q win10 vcrun2022 +EOF +``` + +> `vcrun2022` verhindert die häufigsten "missing MSVC runtime"-Crashes. + +--- + +## Schritt 5 — Server-Config + +### 5.1 Start-Script `/home/windrose/steam/start_windrose.sh` + +```bash +#!/usr/bin/env bash +set -e +export WINEPREFIX=/home/windrose/steam/windrose/pfx +export WINEARCH=win64 +export WINEDEBUG=-all +export DISPLAY=:99 + +if ! pgrep -f "Xvfb :99" >/dev/null; then + Xvfb :99 -screen 0 1024x768x16 & + sleep 2 +fi + +cd /home/windrose/steam/windrose +exec wine64 WindroseServer.exe +``` + +`chmod +x /home/windrose/steam/start_windrose.sh` + +### 5.2 Erst-Start (Config-Generierung) + +Als `windrose`: +```bash +~/steam/start_windrose.sh & +SERVER_PID=$! +sleep 60 # warten bis Erst-Init durch ist +kill $SERVER_PID 2>/dev/null || true +sleep 5 +``` + +Damit werden `ServerDescription.json` und der erste Welt-Ordner unter +`R5/Saved/SaveProfiles/Default/RocksDB//Worlds//` erzeugt. + +### 5.3 ServerDescription patchen + +Datei finden + patchen: +```bash +CFG=$(find /home/windrose/steam/windrose -maxdepth 4 -name ServerDescription.json | head -n1) + +# InviteCode für Report extrahieren +INVITE=$(jq -r '.ServerDescription_Persistent.InviteCode' "$CFG") + +# ServerName + MaxPlayer setzen, kein Passwort +jq '.ServerDescription_Persistent.ServerName = "Marhas Windrose" + | .ServerDescription_Persistent.MaxPlayerCount = 4 + | .ServerDescription_Persistent.IsPasswordProtected = false + | .ServerDescription_Persistent.Password = ""' \ + "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG" + +echo "INVITE_CODE=$INVITE" # !!! Dem User zurückmelden !!! +``` + +--- + +## Schritt 6 — systemd Service (als root) + +`/etc/systemd/system/windrose.service`: +```ini +[Unit] +Description=Windrose Dedicated Server +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +User=windrose +Group=windrose +WorkingDirectory=/home/windrose/steam +ExecStart=/home/windrose/steam/start_windrose.sh +Restart=always +RestartSec=10 +StandardOutput=append:/home/windrose/log/windrose.log +StandardError=append:/home/windrose/log/windrose.log +MemoryHigh=3G +MemoryMax=3500M + +[Install] +WantedBy=multi-user.target +``` + +Aktivieren: +```bash +systemctl daemon-reload +systemctl enable --now windrose +systemctl status windrose --no-pager +``` + +--- + +## Schritt 7 — Update- & Backup-Helfer (als User `windrose`) + +### 7.1 Update-Script `/home/windrose/steam/update_windrose.sh` + +```bash +#!/usr/bin/env bash +set -e +sudo systemctl stop windrose +steamcmd \ + +@sSteamCmdForcePlatformType windows \ + +force_install_dir /home/windrose/steam/windrose \ + +login anonymous \ + +app_update 4129620 validate \ + +quit +sudo systemctl start windrose +``` + +### 7.2 Backup-Script `/home/windrose/steam/backup_windrose.sh` + +```bash +#!/usr/bin/env bash +set -e +TS=$(date +%Y%m%d-%H%M%S) +SRC=/home/windrose/steam/windrose/R5/Saved +DST=/home/windrose/backup/windrose-${TS}.tar.gz +tar -czf "$DST" -C "$(dirname "$SRC")" "$(basename "$SRC")" +ls -1t /home/windrose/backup/windrose-*.tar.gz | tail -n +8 | xargs -r rm +``` + +Beide ausführbar machen: `chmod +x ~/steam/{update,backup}_windrose.sh` + +### 7.3 Cron (täglich 04:00) als `windrose` + +``` +0 4 * * * /home/windrose/steam/backup_windrose.sh >> /home/windrose/log/backup.log 2>&1 +``` + +--- + +## Schritt 8 — Verifikation (zwingend) + +```bash +systemctl status windrose --no-pager +ss -ulnp | grep -E '7777|7778' || echo "WARN: Ports nicht gebunden" +free -h +tail -n 50 /home/windrose/log/windrose.log +vmstat 5 3 # Swap-Aktivität: si/so sollten ~0 sein im Idle +``` + +Erwartet: +- Service `active (running)` +- UDP 7777 + 7778 gebunden +- Idle-RAM <2 GB belegt, Swap fast leer +- Log zeigt "Server ready" o.ä., keine Wine-Stacktraces + +Im Spiel-Client: "Join via Invite Code" → den im Report ausgegebenen Code eingeben. + +--- + +## Eskalation bei Problemen + +- **OOM / extreme Lags:** Hetzner-Console → Server stoppen → "Rescale" auf CPX31 → starten. ~3 Min., kein Datenverlust. +- **Wine-Crashes beim Start:** `journalctl -u windrose -n 200` nach `wine: Unhandled` durchsuchen, ggf. `winetricks -q dotnet48` nachschieben oder `winehq-staging` statt `winehq-stable` testen. +- **Clients finden Server nicht:** Hetzner-Cloud-Firewall checken (UDP 7777/7778 wirklich offen?), Outbound zu `*.windrose.support:3478` nicht geblockt. + +--- + +## Kritische Dateien + +| Pfad | Zweck | +|---|---| +| `/home/windrose/steam/windrose/WindroseServer.exe` | Server-Binary | +| `/home/windrose/steam/windrose/ServerDescription.json` | ServerName, InviteCode, MaxPlayer | +| `…/R5/Saved/SaveProfiles/Default/RocksDB//Worlds//WorldDescription.json` | Welt-Konfig | +| `/home/windrose/steam/windrose/pfx` | Wine-Prefix | +| `/home/windrose/steam/start_windrose.sh` | Launcher (Xvfb + wine) | +| `/etc/systemd/system/windrose.service` | systemd-Unit | +| `/home/windrose/log/windrose.log` | Server-Log | +| `/home/windrose/backup/` | tägliche Save-Backups | + +--- + +## Final-Report-Template (vom Agent zurück an User) + +``` +Status: success | failed +Invite-Code: +Server-Name: Marhas Windrose +Max-Spieler: 4 +Public-IPv4: +Service-Status: +RAM-Idle: +Swap-Aktivität: +Letzte Log-Zeilen: +Auffälligkeiten / Warnings: … +```