Files
windrose-server/SETUP.md
Marek Lenczewski 51b6309584 init
2026-04-17 17:33:23 +02:00

9.9 KiB
Raw Blame History

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: 1525 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)

# 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)

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)

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

#!/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:

~/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/<version>/Worlds/<world_id>/ erzeugt.

5.3 ServerDescription patchen

Datei finden + patchen:

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:

[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:

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

#!/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

#!/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)

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/<ver>/Worlds/<id>/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: <CODE>
Server-Name: Marhas Windrose
Max-Spieler: 4
Public-IPv4: <hetzner-ip>
Service-Status: <output von systemctl status windrose>
RAM-Idle: <free -h>
Swap-Aktivität: <vmstat>
Letzte Log-Zeilen: <tail -n 20 windrose.log>
Auffälligkeiten / Warnings: …