From ee7c49b533a2f9dcdb2f472a57cdade1cddf9513 Mon Sep 17 00:00:00 2001 From: Joel Maxwell Date: Thu, 8 Oct 2026 14:41:13 -0300 Subject: [PATCH] Add healthcheck.sh and update managed-windrose files --- docker-compose.yml | 71 +++++++------ dockerfile | 73 +++++++------ entrypoint.sh | 254 ++++++++++++++++++++++++++++++++------------- 3 files changed, 259 insertions(+), 139 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 43b4acc..5373991 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,39 +1,50 @@ services: windrose: build: . - container_name: windrose-servermanager + container_name: managed-windrose + hostname: managed-windrose + restart: unless-stopped + stop_grace_period: 90s + network_mode: host - # IMPORTANT: Linux/TrueNAS paths ONLY - volumes: - - /mnt/tank/windrose/server:/server:rw - - /mnt/tank/windrose/server/R5:/server/R5:rw - - /mnt/tank/windrose/data/Saved:/data/Saved:rw - - ports: - - "27015:27015/udp" - - "27015:27015/tcp" + env_file: + - .env environment: - - WINEPREFIX=/data/wine/wineprefix - - WINEARCH=win64 - - WINDROSE_HOME=/server - - WINDROSE_R5=/server/R5 - - SAVED_DIR=/data/Saved + PUID: ${PUID:-1000} + PGID: ${PGID:-1000} + WINDROSE_APP_ID: ${WINDROSE_APP_ID:-4129620} + STEAM_LOGIN: ${STEAM_LOGIN:-anonymous} + STEAM_PASS: ${STEAM_PASS:-} + UPDATE_ON_START: ${UPDATE_ON_START:-true} + GENERATE_SETTINGS: ${GENERATE_SETTINGS:-true} + INVITE_CODE: ${INVITE_CODE:-} + SERVER_NAME: ${SERVER_NAME:-Managed Windrose Server} + SERVER_NOTE: ${SERVER_NOTE:-} + SERVER_PASSWORD: ${SERVER_PASSWORD:-} + MAX_PLAYERS: ${MAX_PLAYERS:-4} + P2P_PROXY_ADDRESS: ${P2P_PROXY_ADDRESS:-127.0.0.1} + MULTIHOME: ${MULTIHOME:-0.0.0.0} + WINEARCH: win64 + WINEPREFIX: /home/steam/.wine + WINEDEBUG: -all + DISPLAY: ":99" + PORT: ${PORT:-7777} + QUERYPORT: ${QUERYPORT:-7778} - # Intel ARC GPU Vulkan - - LIBVA_DRIVER_NAME=iHD - - VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/intel_icd.x86_64.json:/usr/share/vulkan/icd.d/intel_icd.i686.json + volumes: + - /mnt/tank/windrose/data:/data + - /mnt/tank/windrose/steam-home:/home/steam - # DXVK overrides - - WINEDLLOVERRIDES=d3d11,dxgi,d3d10core,d3d9=native,builtin + healthcheck: + test: ["CMD-SHELL", "/healthcheck.sh"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 300s - # GPU passthrough - devices: - - /dev/dri:/dev/dri - - # Required for Wine + GPU access - cap_add: - - SYS_ADMIN - - tty: true - stdin_open: true + logging: + driver: json-file + options: + max-size: "20m" + max-file: "5" diff --git a/dockerfile b/dockerfile index c90c16e..8395fd2 100644 --- a/dockerfile +++ b/dockerfile @@ -1,45 +1,50 @@ -# Dockerfile FROM ubuntu:22.04 +LABEL org.opencontainers.image.source="https://gitea.theminisip.ca/mini-server-manager/managed-windrose" \ + org.opencontainers.image.description="Managed Windrose Dedicated Server for Mini-Server-Manager" \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.authors="Irken Laboratories" + ENV DEBIAN_FRONTEND=noninteractive -ENV WINEPREFIX=/data/wine/wineprefix -ENV WINEARCH=win64 -ENV WINDROSE_HOME=/server -ENV WINDROSE_R5=/server/R5 -ENV SAVED_DIR=/data/Saved +ENV USER=steam +ENV HOME=/home/steam +ENV STEAMCMDDIR=/opt/steamcmd +ENV DISPLAY=:99 +ENV WINEDEBUG=-all +ENV LANG=en_US.UTF-8 +ENV LC_ALL=en_US.UTF-8 -# Install WineHQ (correct method for Ubuntu 22.04) -RUN dpkg --add-architecture i386 \ - && apt-get update \ - && apt-get install -y --no-install-recommends \ - ca-certificates wget curl gnupg2 software-properties-common \ - xvfb x11vnc pulseaudio cabextract unzip p7zip-full \ - && 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/jammy/winehq-jammy.sources \ - && apt-get update \ - && apt-get install -y --install-recommends winehq-stable \ - && rm -rf /var/lib/apt/lists/* +RUN dpkg --add-architecture i386 && \ + apt update && \ + apt install -y \ + curl ca-certificates \ + xvfb xauth \ + wine64 wine32 winbind \ + lib32gcc-s1 lib32stdc++6 \ + libc6:i386 libstdc++6:i386 \ + libncurses5:i386 libtinfo5:i386 \ + locales \ + jq \ + procps \ + mesa-vulkan-drivers mesa-vulkan-drivers:i386 \ + libvulkan1 libvulkan1:i386 \ + intel-media-va-driver-non-free vainfo \ + && rm -rf /var/lib/apt/lists/* -# Intel ARC GPU + Vulkan support -RUN dpkg --add-architecture i386 \ - && apt-get update \ - && apt-get install -y --no-install-recommends \ - mesa-vulkan-drivers mesa-vulkan-drivers:i386 \ - libvulkan1 libvulkan1:i386 \ - intel-media-va-driver-non-free vainfo \ - && rm -rf /var/lib/apt/lists/* +RUN sed -i 's/^# \(en_US.UTF-8 UTF-8\)/\1/' /etc/locale.gen && locale-gen +RUN mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix +RUN useradd -u 1000 -m -s /bin/bash steam -# Install DXVK 2.3 (runtime DLLs only) -RUN wget -O dxvk.tar.gz https://github.com/doitsujin/dxvk/releases/download/v2.3/dxvk-2.3.tar.gz \ - && tar -xvf dxvk.tar.gz \ - && rm dxvk.tar.gz \ - && mv dxvk-2.3 /opt/dxvk +RUN mkdir -p /opt/steamcmd && \ + curl -sSL https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz \ + | tar -xz -C /opt/steamcmd && \ + chown -R steam:steam /opt/steamcmd /home/steam -# Create directories for mounts -RUN mkdir -p ${WINDROSE_HOME} ${WINDROSE_R5} +RUN mkdir -p /data && chown -R steam:steam /data COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +COPY healthcheck.sh /healthcheck.sh +RUN chmod +x /entrypoint.sh /healthcheck.sh +WORKDIR /home/steam ENTRYPOINT ["/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh index dabc3b9..5ab9802 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,87 +1,191 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail -WINEPREFIX=${WINEPREFIX:-/data/wine/wineprefix} -WINDROSE_HOME=${WINDROSE_HOME:-/server} -WINDROSE_R5=${WINDROSE_R5:-/server/R5} -SAVED_DIR=${SAVED_DIR:-/data/Saved} +APPID=${WINDROSE_APP_ID:-4129620} +SERVERDIR=${SERVERDIR:-/data} +STEAM_HOME=${STEAM_HOME:-/home/steam} +WINEPREFIX=${WINEPREFIX:-$STEAM_HOME/.wine} +STEAM_LOGIN=${STEAM_LOGIN:-anonymous} +STEAM_PASS=${STEAM_PASS:-} +UPDATE_ON_START=${UPDATE_ON_START:-true} +GENERATE_SETTINGS=${GENERATE_SETTINGS:-true} +PUID=${PUID:-1000} +PGID=${PGID:-1000} -SERVER_EXE="${WINDROSE_R5}/Binaries/Win64/WindroseServer-Win64-Shipping.exe" +PORT=${PORT:-7777} +QUERYPORT=${QUERYPORT:-7778} +MULTIHOME=${MULTIHOME:-0.0.0.0} -export WINEDEBUG=-all -export DISPLAY= -export XDG_RUNTIME_DIR=/tmp/runtime-wine -mkdir -p "$XDG_RUNTIME_DIR" +INVITE_CODE=${INVITE_CODE:-} +SERVER_NAME=${SERVER_NAME:-} +SERVER_NOTE=${SERVER_NOTE:-} +SERVER_PASSWORD=${SERVER_PASSWORD:-} +MAX_PLAYERS=${MAX_PLAYERS:-4} +P2P_PROXY_ADDRESS=${P2P_PROXY_ADDRESS:-127.0.0.1} +FIRST_RUN_TIMEOUT=${FIRST_RUN_TIMEOUT:-180} -# Intel ARC Vulkan ICDs -export VK_ICD_FILENAMES="/usr/share/vulkan/icd.d/intel_icd.x86_64.json:/usr/share/vulkan/icd.i686.json" -export LIBVA_DRIVER_NAME=iHD +SERVER_DESC="$SERVERDIR/R5/ServerDescription.json" +SERVER_PID="" +XVFB_PID="" -DXVK_32="/opt/dxvk/x32" -DXVK_64="/opt/dxvk/x64" +log() { echo "[windrose] $*"; } +quote() { printf '%q' "$1"; } +run_as_steam() { su -s /bin/bash steam -c "$*"; } -# Initialize wineprefix if missing -if [ ! -d "$WINEPREFIX" ] || [ ! -f "$WINEPREFIX/system.reg" ]; then - echo "Initializing wineprefix at $WINEPREFIX" - mkdir -p "$WINEPREFIX" - wineboot --init || true +ensure_user_mapping() { + groupmod -o -g "$PGID" steam 2>/dev/null || true + usermod -o -u "$PUID" steam 2>/dev/null || true + mkdir -p "$SERVERDIR" "$STEAM_HOME" + chown -R steam:steam /opt/steamcmd "$STEAM_HOME" "$SERVERDIR" 2>/dev/null || true +} - echo "Installing DXVK DLLs into wineprefix..." - WIN_SYS32="$WINEPREFIX/drive_c/windows/system32" - WIN_SYSWOW64="$WINEPREFIX/drive_c/windows/syswow64" +cleanup_xvfb() { + if [ -n "$XVFB_PID" ] && kill -0 "$XVFB_PID" 2>/dev/null; then + kill "$XVFB_PID" 2>/dev/null || true + fi + rm -f /tmp/.X99-lock 2>/dev/null || true +} - mkdir -p "$WIN_SYS32" "$WIN_SYSWOW64" +shutdown_server() { + log "Stopping Windrose dedicated server" + pkill -TERM -u steam -f 'WindroseServer-Win64-Shipping.exe' 2>/dev/null || true + pkill -TERM -u steam -f 'wineserver' 2>/dev/null || true + for _ in $(seq 1 60); do + if ! pgrep -u steam -f 'WindroseServer-Win64-Shipping.exe|wineserver' >/dev/null 2>&1; then + break + fi + sleep 1 + done + pkill -KILL -u steam -f 'WindroseServer-Win64-Shipping.exe|wineserver' 2>/dev/null || true +} - cp "$DXVK_64"/*.dll "$WIN_SYS32"/ - cp "$DXVK_32"/*.dll "$WIN_SYSWOW64"/ +trap 'shutdown_server; exit 0' TERM INT +trap 'cleanup_xvfb' EXIT + +init_xvfb() { + rm -f /tmp/.X99-lock 2>/dev/null || true + Xvfb :99 -screen 0 1024x768x16 -nolisten tcp >/dev/null 2>&1 & + XVFB_PID=$! + sleep 1 +} + +init_wine() { + mkdir -p "$WINEPREFIX" + chown -R steam:steam "$STEAM_HOME" 2>/dev/null || true + if [ ! -f "$WINEPREFIX/system.reg" ]; then + log "Initializing Wine prefix" + run_as_steam "WINEPREFIX=$(quote "$WINEPREFIX") wineboot -i >/dev/null 2>&1 || true" + fi +} + +update_server() { + if [ "$UPDATE_ON_START" != "true" ]; then + log "UPDATE_ON_START=false, skipping SteamCMD update" + return + fi + + local login_cmd + if [ "$STEAM_LOGIN" = "anonymous" ]; then + login_cmd='+login anonymous' + elif [ -n "$STEAM_PASS" ]; then + login_cmd="+login $(quote "$STEAM_LOGIN") $(quote "$STEAM_PASS")" + else + login_cmd="+login $(quote "$STEAM_LOGIN")" + fi + + log "Updating or validating server files via SteamCMD (AppID $APPID)" + run_as_steam "mkdir -p $(quote "$SERVERDIR") && /opt/steamcmd/steamcmd.sh +force_install_dir $(quote "$SERVERDIR") $login_cmd +app_update $(quote "$APPID") validate +quit" +} + +find_server_exe() { + find "$SERVERDIR" -iname 'WindroseServer-Win64-Shipping.exe' 2>/dev/null | head -n 1 || true +} + +first_run_generate_config() { + local exe="$1" + if [ "$GENERATE_SETTINGS" != "true" ] || [ -f "$SERVER_DESC" ]; then + return + fi + + log "First run detected, generating default server config" + run_as_steam "WINEPREFIX=$(quote "$WINEPREFIX") wine $(quote "$exe") -log -MULTIHOME=$(quote "$MULTIHOME") -PORT=$(quote "$PORT") -QUERYPORT=$(quote "$QUERYPORT") >/tmp/windrose-first-run.log 2>&1" & + local warmup_pid=$! + + local count=0 + while [ ! -f "$SERVER_DESC" ] && [ "$count" -lt "$FIRST_RUN_TIMEOUT" ]; do + sleep 1 + count=$((count + 1)) + done + + kill "$warmup_pid" 2>/dev/null || true + wait "$warmup_pid" 2>/dev/null || true + pkill -TERM -u steam -f 'wineserver' 2>/dev/null || true + + if [ ! -f "$SERVER_DESC" ]; then + log "WARNING: ServerDescription.json was not generated within ${FIRST_RUN_TIMEOUT}s" + log "Wine output:" + tail -n 40 /tmp/windrose-first-run.log 2>/dev/null || true + fi +} + +patch_server_config() { + if [ "$GENERATE_SETTINGS" != "true" ]; then + log "GENERATE_SETTINGS=false, skipping JSON patching" + return + fi + if [ ! -f "$SERVER_DESC" ]; then + log "ServerDescription.json not found, skipping patch" + return + fi + + log "Patching ServerDescription.json from environment" + tr -d '\r' < "$SERVER_DESC" | jq \ + --arg invite "$INVITE_CODE" \ + --arg name "$SERVER_NAME" \ + --arg note "$SERVER_NOTE" \ + --arg password "$SERVER_PASSWORD" \ + --arg proxy "$P2P_PROXY_ADDRESS" \ + --argjson maxplayers "$MAX_PLAYERS" \ + ' + .ServerDescription_Persistent.P2pProxyAddress = $proxy | + if $invite != "" then .ServerDescription_Persistent.InviteCode = $invite else . end | + if $name != "" then .ServerDescription_Persistent.ServerName = $name else . end | + if $note != "" then .ServerDescription_Persistent.Note = $note else . end | + if $password != "" then + .ServerDescription_Persistent.IsPasswordProtected = true | + .ServerDescription_Persistent.Password = $password + else + .ServerDescription_Persistent.IsPasswordProtected = false | + .ServerDescription_Persistent.Password = "" + end | + .ServerDescription_Persistent.MaxPlayerCount = $maxplayers + ' > "$SERVER_DESC.tmp" + mv "$SERVER_DESC.tmp" "$SERVER_DESC" + chown steam:steam "$SERVER_DESC" 2>/dev/null || true +} + +start_server() { + local exe="$1" + log "Starting Windrose dedicated server" + log "Executable: $exe" + log "Bind: $MULTIHOME Port: $PORT QueryPort: $QUERYPORT" + run_as_steam "WINEPREFIX=$(quote "$WINEPREFIX") wine $(quote "$exe") -log -MULTIHOME=$(quote "$MULTIHOME") -PORT=$(quote "$PORT") -QUERYPORT=$(quote "$QUERYPORT")" & + SERVER_PID=$! + wait "$SERVER_PID" +} + +ensure_user_mapping +init_xvfb +init_wine +update_server + +SERVER_EXE=$(find_server_exe) +if [ -z "$SERVER_EXE" ]; then + log "ERROR: Windrose server executable not found under $SERVERDIR" + find "$SERVERDIR" -maxdepth 4 2>/dev/null || true + exit 1 fi -# Force DXVK to be used -export WINEDLLOVERRIDES="d3d11,dxgi,d3d10core,d3d9=native,builtin" - -mkdir -p "$SAVED_DIR" - -case "${1:-}" in - run-server) - echo "Starting Windrose dedicated server..." - - # --------------------------------------------------------- - # AUTOMATIC INSTALLER: Install Windrose if missing - # --------------------------------------------------------- - if [ ! -f "$SERVER_EXE" ]; then - echo "Windrose server binary missing — installing via SteamCMD..." - mkdir -p "$WINDROSE_R5" - - /root/steamcmd/steamcmd.sh \ - +login anonymous \ - +force_install_dir "$WINDROSE_R5" \ - +app_update 4129620 validate \ - +quit - - echo "Windrose server installation complete." - fi - # --------------------------------------------------------- - - if [ ! -f "$SERVER_EXE" ]; then - echo "ERROR: Windrose server binary still not found after install:" - echo " $SERVER_EXE" - exit 1 - fi - - cd "$(dirname "$SERVER_EXE")" - - exec wine "$SERVER_EXE" \ - -log \ - -unattended \ - -nosteam \ - -savedir "$SAVED_DIR" - ;; - - bash) - exec /bin/bash - ;; - - *) - exec "$@" - ;; -esac +first_run_generate_config "$SERVER_EXE" +patch_server_config +start_server "$SERVER_EXE"