9.9 KiB
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 installimmer-y. Fürsteamcmdvorher die Lizenz per debconf akzeptieren (siehe Schritt 2.6). - Nach Schritt 5.2 den InviteCode aus
ServerDescription.jsonextrahieren 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:
- Snapshot des frischen CPX22 anlegen (Rollback-Punkt).
- 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)
- 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
vcrun2022verhindert 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 200nachwine: Unhandleddurchsuchen, ggf.winetricks -q dotnet48nachschieben oderwinehq-stagingstattwinehq-stabletesten. - Clients finden Server nicht: Hetzner-Cloud-Firewall checken (UDP 7777/7778 wirklich offen?), Outbound zu
*.windrose.support:3478nicht 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: …