{"id":48903095,"url":"https://github.com/uberdudepl/windrose-dedicated-server-docker","last_synced_at":"2026-05-09T13:06:08.191Z","repository":{"id":351402177,"uuid":"1210826257","full_name":"UberDudePL/windrose-dedicated-server-docker","owner":"UberDudePL","description":"Dockerized Windrose Dedicated Server (SteamCMD + Wine) with persistent saves and 24/7 hosting setup.","archived":false,"fork":false,"pushed_at":"2026-04-14T21:50:34.000Z","size":10,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-14T22:19:00.562Z","etag":null,"topics":["dedicated-server","docker","game-server","steamcmd","windrose","wine"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/UberDudePL.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"custom":["https://ko-fi.com/uberdudepl","https://paypal.me/uberdudepl","https://revolut.me/uberdudepl"]}},"created_at":"2026-04-14T19:48:21.000Z","updated_at":"2026-04-14T22:14:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/UberDudePL/windrose-dedicated-server-docker","commit_stats":null,"previous_names":["uberdudepl/windrose-dedicated-server-docker"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/UberDudePL/windrose-dedicated-server-docker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UberDudePL%2Fwindrose-dedicated-server-docker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UberDudePL%2Fwindrose-dedicated-server-docker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UberDudePL%2Fwindrose-dedicated-server-docker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UberDudePL%2Fwindrose-dedicated-server-docker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/UberDudePL","download_url":"https://codeload.github.com/UberDudePL/windrose-dedicated-server-docker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UberDudePL%2Fwindrose-dedicated-server-docker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31980783,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T17:30:12.329Z","status":"ssl_error","status_checked_at":"2026-04-18T17:29:59.069Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dedicated-server","docker","game-server","steamcmd","windrose","wine"],"created_at":"2026-04-16T17:00:37.461Z","updated_at":"2026-05-09T13:06:08.161Z","avatar_url":"https://github.com/UberDudePL.png","language":"Shell","funding_links":["https://ko-fi.com/uberdudepl","https://paypal.me/uberdudepl","https://revolut.me/uberdudepl"],"categories":[],"sub_categories":[],"readme":"# Windrose Dedicated Server — Docker\n\n![GitHub Stars](https://img.shields.io/github/stars/UberDudePL/windrose-dedicated-server-docker)\n![License](https://img.shields.io/github/license/UberDudePL/windrose-dedicated-server-docker)\n![Version](https://img.shields.io/github/v/release/UberDudePL/windrose-dedicated-server-docker)\n![Docker Pulls](https://img.shields.io/docker/pulls/uberdudepl/windrose-dedicated-server-docker)\n\nWindrose dedicated server for Linux using Docker, SteamCMD and Wine, with persistent saves, backups, diagnostics and optional Discord/Gotify notifications.\n\nSelf-hosted and production-friendly setup with first-time setup helper, world switching, health checks and 24/7 operation support.\n\n\u003e **No port forwarding required** — players join via **Invite Code** from `ServerDescription.json`.\n\n---\n\n## Table of contents\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [First-time setup (recommended)](#first-time-setup-recommended)\n- [Quick start](#quick-start)\n- [Configuration](#configuration)\n- [Volumes](#volumes)\n- [Multiple worlds](#multiple-worlds)\n- [How players join](#how-players-join)\n- [In-game visibility (official)](#in-game-visibility-official)\n- [Useful commands](#useful-commands)\n- [Quick diagnostics](#quick-diagnostics)\n- [Activity notifications: Discord, Gotify, or both](#activity-notifications-discord-gotify-or-both)\n- [Save transfer and world selection](#save-transfer-and-world-selection)\n- [Backup saves](#backup-saves)\n- [Directory structure](#directory-structure)\n- [Troubleshooting](#troubleshooting)\n- [Image versions](#image-versions)\n- [Technical notes](#technical-notes)\n- [FAQ](#faq)\n- [Issues and suggestions](#issues-and-suggestions)\n- [Support](#support)\n- [License](#license)\n\nAdditional documents:\n\n- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) — full symptom table, diagnostics playbooks, network debugging\n- [DEVELOPMENT.md](DEVELOPMENT.md) — local builds, image channels, CI workflows\n\n---\n\n## Features\n\n- Dockerized Windrose dedicated server on Linux (Wine + Xvfb, headless)\n- Automatic game install/update via SteamCMD with optional `UPDATE_ON_START` toggle\n- Persistent data by default (`./data`, `./steam-home`) for saves, config, and Steam/Wine state\n- Simple operator-first configuration through `.env` and optional JSON auto-patching\n- Stable helper commands for start/stop/restart/logs/diagnostics and world management\n- Save transfer workflow with explicit `WorldIslandId` mapping and versioned world paths\n- Built-in backup tooling (`./windrose backup`, cron installer, retention controls)\n- Optional Discord/Gotify activity notifications (or both at once) plus notifier test command\n- Multiple image channels (`stable`, `latest`, `staging`, `debug`) for operations and troubleshooting\n- Production-friendly defaults: host networking, restart policy, healthcheck, and log rotation\n\n---\n\n## Requirements\n\n| Component      | Minimum                                                   |\n| -------------- | --------------------------------------------------------- |\n| OS             | Ubuntu 22.04+ / Debian 12+ (Linux host)                   |\n| Docker         | 24.x+                                                     |\n| Docker Compose | v2.x (`docker compose`)                                   |\n| RAM            | 8 GB (2 players) · 12 GB (4 players) · 16 GB (10 players) |\n| Disk           | 35 GB SSD                                                 |\n\n---\n\n## First-time setup (recommended)\n\nIf this is your first run, use the interactive helper first. It creates `.env`, asks for key settings, optionally configures backup cron, and can start the server immediately.\n\n```bash\n# 1. Clone and enter the repository\ngit clone https://github.com/UberDudePL/windrose-dedicated-server-docker.git\ncd windrose-dedicated-server-docker\n\n# 2. Make helper scripts executable\nchmod +x ./windrose ./serverctl.sh\n\n# 3. Run interactive setup\n./windrose setup\n```\n\nWhat `./windrose setup` asks:\n\n1. Start automatically after setup (`Y/n`)\n2. Server name\n3. Invite code (optional, alphanumeric, minimum 6 chars)\n4. Optional server password\n5. Max players\n6. Enable automatic backup cron (`y/N`)\n7. Backup cron schedule (default: `0 6 * * *`, daily at 06:00)\n8. Backup format (`tar.gz` or `zip`)\n9. Backup scope (`full`, `save`, `both`)\n10. Discord upload for save backups (`y/N`)\n11. Discord webhook URL (only if upload is enabled)\n\nIf invite code is left empty, the server generates it automatically on first successful start.\nWhen setup starts the server automatically, it tries to show the generated code.\nWhen setup does not start the server, check the generated code later in `data/R5/ServerDescription.json`.\n\nBehavior and safety notes:\n\n- Setup is one-off by design: if `.env` already exists, setup exits with a clear message.\n- Setup runs a host precheck before questions: Docker in PATH, Docker Compose v2, RAM \u003e= 8 GB, free disk \u003e= 8 GB.\n- `PUID` and `PGID` are auto-detected from the current host user.\n- If backup upload is enabled and scope is `full`, scope is adjusted to `both`.\n- If `crontab` is missing, setup continues and warns instead of failing.\n- Before auto-start, setup runs preflight checks (`docker compose config`) and warns if `PORT` or `QUERYPORT` are already in use.\n\nAfter setup, use:\n\n```bash\n./windrose status\n./windrose logs\n```\n\n---\n\n## Quick start\n\nProduction mode uses the published GHCR image by default. Most users only need this mode and can ignore the development override file.\n\nIf this is your first run, prefer [First-time setup (recommended)](#first-time-setup-recommended).\n\n```bash\n# 1. Clone the repository\ngit clone https://github.com/UberDudePL/windrose-dedicated-server-docker.git\ncd windrose-dedicated-server-docker\n\n# 2. Copy the example environment file\ncp .env.example .env\n\n# 3. Edit basic values if needed\nnano .env\n\n# 4. Pull the published image\ndocker compose pull\n\n# 5. Start the server (downloads game files on first run ~3 GB)\ndocker compose up -d\n\n# 6. Follow logs\ndocker compose logs -f windrose\n```\n\nRecommended image tags:\n\n```text\nStable: ghcr.io/uberdudepl/windrose-dedicated-server-docker:v1.6.4\nLatest: ghcr.io/uberdudepl/windrose-dedicated-server-docker:latest\nStaging fallback: ghcr.io/uberdudepl/windrose-dedicated-server-docker:staging\nDebug tools: ghcr.io/uberdudepl/windrose-dedicated-server-docker:debug\n```\n\nSet the image version in `.env` with:\n\n```dotenv\nIMAGE_REPOSITORY=ghcr.io/uberdudepl/windrose-dedicated-server-docker\nIMAGE_TAG=v1.6.4\n```\n\n### Image variants\n\n- `latest` / version tags: stable Wine build for normal use.\n- `staging`: fallback image using Wine Staging plus `winetricks` prewarm (`win10`, `vcrun2022`) for host-specific Wine issues.\n- `debug`: stable Wine build plus extra diagnostic tools (`dnsutils`, `file`, `iproute2`, `lsof`, `strace`) and more verbose Wine logging.\n\nUse the stable channel unless you are actively diagnosing host-specific startup problems.\n\nFor local development, builds, and CI workflows, see [DEVELOPMENT.md](DEVELOPMENT.md).\n\n---\n\n## Configuration\n\n### Common server settings\n\nYou can set the most common values directly in `.env`:\n\n```dotenv\nSERVER_NAME=My Windrose Server\nSERVER_NOTE=Friendly co-op server\nSERVER_PASSWORD=\nMAX_PLAYERS=4\nINVITE_CODE=\n```\n\nIf you prefer manual editing, stop the server first and edit `data/R5/ServerDescription.json` directly.\n\n\u003e Important: edit JSON files only while the server is stopped, or your changes may be overwritten.\n\n### Environment variables (`.env`)\n\nCopy `.env.example` to `.env` and adjust to your needs. Use `.env.dev.example` for local development and notifier testing.\n\n```dotenv\nPUID=1000                    # Host user id for mounted files\nPGID=1000                    # Host group id for mounted files\nSTEAM_LOGIN=anonymous        # SteamCMD login\nSTEAM_PASS=                  # Leave empty for anonymous login\nWINDROSE_APP_ID=4129620      # Steam AppID for Windrose Dedicated Server\nUPDATE_ON_START=true         # Set false to skip update on container restart\nUPDATE_VERIFY_TIMEOUT=120    # Post-update runtime verification timeout in seconds\nGENERATE_SETTINGS=true       # Set false to skip env-based JSON patching\nINVITE_CODE=                 # Optional invite code\nSERVER_NAME=                 # Optional server name\nSERVER_NOTE=                 # Optional public server note/description\nSERVER_PASSWORD=             # Optional password\nMAX_PLAYERS=4                # Recommended for stability\nP2P_PROXY_ADDRESS=127.0.0.1  # Keep default unless players connect over LAN\n# Direct connection (alternative to invite code, requires port forwarding)\nUSE_DIRECT_CONNECTION=false\nDIRECT_CONNECTION_SERVER_PORT=7777\nDIRECT_CONNECTION_PROXY_ADDRESS=0.0.0.0\nUSER_SELECTED_REGION=        # Leave empty for auto-detect (SEA, CIS, EU)\nPORT=7777\nQUERYPORT=7778\nMULTIHOME=0.0.0.0\n```\n\nSet `NO_COLOR=1` to disable ANSI colors in helper/CLI output.\n\nIf your host is slow to start the container after `./windrose update`, increase `UPDATE_VERIFY_TIMEOUT` (for example to `180` or `300`).\n\n### `docker-compose.yml` overrides\n\n| Variable                          | Default     | Description                                                                                                                |\n| --------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- |\n| `CONTAINER_NAME`                  | `windrose`  | Change only if you run more than one server on the same host                                                               |\n| `HOSTNAME`                        | `localhost` | Internal container hostname used by ICE candidate discovery; keep `localhost` unless custom name resolves inside container |\n| `IMAGE_REPOSITORY`                | GHCR repo   | Published image repository                                                                                                 |\n| `IMAGE_TAG`                       | `v1.6.4`    | Stable image tag to run                                                                                                    |\n| `PUID`                            | `1000`      | User id used for mounted files                                                                                             |\n| `PGID`                            | `1000`      | Group id used for mounted files                                                                                            |\n| `UPDATE_ON_START`                 | `true`      | Update and validate server files on startup                                                                                |\n| `UPDATE_VERIFY_TIMEOUT`           | `120`       | Timeout in seconds for post-update runtime verification in `./windrose update`; increase on slower hosts                   |\n| `GENERATE_SETTINGS`               | `true`      | Auto-patch `ServerDescription.json` from env values                                                                        |\n| `INVITE_CODE`                     | empty       | Invite code shown to players. Leave empty to use direct connection instead                                                 |\n| `SERVER_NAME`                     | empty       | Display name of the server                                                                                                 |\n| `SERVER_NOTE`                     | empty       | Short public server note/description                                                                                       |\n| `SERVER_PASSWORD`                 | empty       | Leave empty for a public server                                                                                            |\n| `MAX_PLAYERS`                     | `4`         | Maximum number of simultaneous players                                                                                     |\n| `P2P_PROXY_ADDRESS`               | `127.0.0.1` | Internal socket proxy address. Change to LAN IP if players connect from the same network                                   |\n| `USE_DIRECT_CONNECTION`           | `false`     | Set to `true` to allow players to connect directly via IP instead of invite code. Requires port forwarding.                |\n| `DIRECT_CONNECTION_SERVER_PORT`   | `7777`      | Port used for direct connection (TCP and UDP). Only applies when `USE_DIRECT_CONNECTION=true`                              |\n| `DIRECT_CONNECTION_PROXY_ADDRESS` | `0.0.0.0`   | Proxy address for direct connection. Only applies when `USE_DIRECT_CONNECTION=true`                                        |\n| `USER_SELECTED_REGION`            | empty       | Connection service region: `SEA`, `CIS`, `EU`. Leave empty to auto-detect. `EU` covers both EU and NA regions              |\n| `PORT`                            | `7777`      | Game port (UDP)                                                                                                            |\n| `QUERYPORT`                       | `7778`      | Query port (UDP)                                                                                                           |\n| `WINDROSE_APP_ID`                 | `4129620`   | Steam AppID                                                                                                                |\n| `STEAM_LOGIN`                     | `anonymous` | SteamCMD login                                                                                                             |\n\n---\n\n## Volumes\n\n| Host path      | Container path | Contents                    |\n| -------------- | -------------- | --------------------------- |\n| `./data`       | `/data`        | Server files, saves, config |\n| `./steam-home` | `/home/steam`  | Wine prefix, SteamCMD cache |\n\n## Multiple worlds\n\nWindrose stores each world under the save database path:\n\n```text\ndata/R5/Saved/SaveProfiles/Default/RocksDB/\u003cGameVersion\u003e/Worlds/\u003cWorldIslandId\u003e\nor\ndata/R5/Saved/SaveProfiles/Default/RocksDB_v2/\u003cGameVersion\u003e/Worlds/\u003cWorldIslandId\u003e\n```\n\nThe active world is selected by `ServerDescription.json`:\n\n```json\nServerDescription_Persistent.WorldIslandId\n```\n\nUse the helper command to switch interactively:\n\n```bash\n./windrose switch\n```\n\nTo only list available worlds without changing anything:\n\n```bash\n./windrose worlds\n```\n\nTo detect orphan or broken world directories:\n\n```bash\n./windrose worlds-check\n```\n\nWhat it does:\n\n- Lists all worlds found under the current RocksDB save version.\n- Marks the currently selected world.\n- Lets you switch to an existing world or create a new one.\n- When creating a new world, it can store a display name and sync it into `WorldDescription.json` after the game creates the metadata file.\n- Stops the server first if it is running, updates `WorldIslandId`, then starts it again.\n- Hides stale placeholder entries (for example directories with only `.windrose-world-name`) unless that placeholder is currently selected.\n\nImportant:\n\n- Do not rename world folders. The save database relies on those IDs.\n- If you create a new world, the server initializes its data on the next start.\n- World discovery is version-specific, so the command uses the latest directory found under the auto-detected save root (`RocksDB_v2/` preferred, then `RocksDB/`).\n\n### Gameplay difficulty\n\nGameplay difficulty is stored per world in `WorldDescription.json` and is not controlled by `docker-compose.yml` environment variables.\n\n1. Stop the server:\n\n   ```bash\n   ./windrose stop\n   ```\n\n2. Find the active world ID from `data/R5/ServerDescription.json`:\n\n   ```text\n   ServerDescription_Persistent.WorldIslandId\n   ```\n\n3. Edit this file for that active world:\n\n   ```text\n   data/R5/Saved/SaveProfiles/Default/RocksDB/\u003cGameVersion\u003e/Worlds/\u003cWorldIslandId\u003e/WorldDescription.json\n   or\n   data/R5/Saved/SaveProfiles/Default/RocksDB_v2/\u003cGameVersion\u003e/Worlds/\u003cWorldIslandId\u003e/WorldDescription.json\n   ```\n\n4. Set the preset fields in `WorldDescription.json`. Reference values per preset:\n\n   **Easy**\n   - `WorldPresetType = \"Easy\"`\n   - `MobHealthMultiplier = 0.7`, `MobDamageMultiplier = 0.6`\n   - `ShipsHealthMultiplier = 0.7`, `ShipsDamageMultiplier = 0.6`\n   - `BoardingDifficultyMultiplier = 0.7`\n   - `CombatDifficulty = Easy`\n   - `EasyExplore = true` _(disables map markers — shown as \"Immersive exploration\" in-game; despite the name, this makes exploration harder)_\n\n   **Medium** (default)\n   - `WorldPresetType = \"Medium\"`\n   - All multipliers = `1.0`\n   - `CombatDifficulty = Normal`\n   - `EasyExplore = false`\n\n   **Hard**\n   - `WorldPresetType = \"Hard\"`\n   - `MobHealthMultiplier = 1.5`, `MobDamageMultiplier = 1.25`\n   - `ShipsHealthMultiplier = 1.5`, `ShipsDamageMultiplier = 1.25`\n   - `BoardingDifficultyMultiplier = 1.5`\n   - `CombatDifficulty = Hard`\n   - `EasyExplore = false`\n\n5. Start the server:\n\n   ```bash\n   ./windrose start\n   ```\n\nTip: if values do not apply, verify the edited world ID is the same as `ServerDescription_Persistent.WorldIslandId`.\n\n### Make one world persist across restarts\n\nTo keep the same game world instead of generating new ones:\n\n1. Keep persistent host binds for `/data` and `/home/steam` (do not change them between deployments).\n2. Always keep `ServerDescription_Persistent.WorldIslandId` set to an existing world folder name.\n3. Do not rename world folders.\n4. Stop the server before editing `ServerDescription.json` or `WorldDescription.json`.\n5. Restart after edits and verify logs.\n\nIf a new world keeps appearing:\n\n- Check that `WorldIslandId` points to a folder that exists under `.../RocksDB/\u003cGameVersion\u003e/Worlds/` or `.../RocksDB_v2/\u003cGameVersion\u003e/Worlds/`.\n- Run `./windrose worlds-check` to detect broken or placeholder entries.\n- Re-select the intended world with `./windrose switch`.\n\n### World consistency guardrails\n\nTo avoid accidental new-world generation and confusing config drift, keep these values aligned:\n\n1. `ServerDescription_Persistent.WorldIslandId`\n2. The selected world folder name under `.../Worlds/\u003cWorldIslandId\u003e`\n3. `WorldDescription.IslandId` inside that world's `WorldDescription.json`\n\nIf any of these mismatch, the server may generate a new world and rewrite IDs on startup.\n\n### Preset vs custom behavior\n\n- `WorldPresetType` should be one of `Easy`, `Medium`, or `Hard` for preset mode.\n- If you change individual `WorldSettings` values, the world can switch to `Custom` on next launch.\n- For predictable outcomes, either:\n  - Use preset values only, or\n  - Intentionally manage a full custom profile and treat `WorldPresetType` as `Custom`.\n\n### Custom preset parameters\n\n\u003e **Note:** It is generally easier to configure these settings in-game first, then copy the resulting values from your local save file to the server.\n\n| Parameter                          | Default  |         Range          | Description                                                                                        |\n| :--------------------------------- | :------: | :--------------------: | :------------------------------------------------------------------------------------------------- |\n| `CoopQuests`                       |  `true`  |           —            | Auto-completes co-op quests for all active players                                                 |\n| `EasyExplore`                      | `false`  |           —            | Disables map markers (\"Immersive exploration\" in-game). Despite the name, makes exploration harder |\n| `MobHealthMultiplier`              |  `1.0`   |      `0.2`–`5.0`       | Enemy health multiplier                                                                            |\n| `MobDamageMultiplier`              |  `1.0`   |      `0.2`–`5.0`       | Enemy damage multiplier                                                                            |\n| `ShipHealthMultiplier`             |  `1.0`   |      `0.4`–`5.0`       | Enemy ship health multiplier                                                                       |\n| `ShipDamageMultiplier`             |  `1.0`   |      `0.2`–`2.5`       | Enemy ship damage multiplier                                                                       |\n| `BoardingDifficultyMultiplier`     |  `1.0`   |      `0.2`–`5.0`       | Enemy sailors needed to win boarding                                                               |\n| `Coop_StatsCorrectionModifier`     |  `1.0`   |      `0.0`–`2.0`       | Scales enemy health by active player count                                                         |\n| `Coop_ShipStatsCorrectionModifier` |  `0.0`   |      `0.0`–`2.0`       | Scales enemy ship health by active player count                                                    |\n| `CombatDifficulty`                 | `Normal` | `Easy`/`Normal`/`Hard` | Boss aggression level                                                                              |\n\n### Safe config edit workflow\n\nUse this sequence every time you change server/world JSON files:\n\n1. Stop server.\n2. Back up config/save files.\n3. Edit files.\n4. Start server.\n5. Verify loaded values in logs and in active JSON.\n\nThis avoids partial writes, tool/UI overwrites, and startup-time regeneration surprises.\n\n---\n\n## How players join\n\n1. Start the server once and wait until it is healthy\n2. Open `data/R5/ServerDescription.json` and copy the `InviteCode` value\n3. Share that code with players — they use it in-game under **Join via Code**\n4. Invite codes are case-sensitive and should be at least 6 characters long\n5. No port forwarding is required for the normal invite-code flow\n\nThe server still binds internal game and query ports, mainly for local binding and advanced or multi-instance setups.\n\n---\n\n## In-game visibility (official)\n\nBased on official Windrose documentation and Steam announcements:\n\n- Players can join via invite code in-game: **Play -\u003e Connect to Server**.\n- There is a **Show Server Info** section in the in-game **Esc** menu.\n- `ServerName` is intended to help identify the correct server when invite codes are similar.\n\nWhat is not clearly documented as visible in dedicated-server UI:\n\n- Detailed world difficulty internals (for example `WorldPresetType`, combat tags, and multipliers).\n\nTreat those as file-based settings in `WorldDescription.json` and verify with logs/file values when needed.\n\nOfficial references:\n\n- https://playwindrose.com/dedicated-server-guide/\n- https://steamcommunity.com/app/3041230/announcements/\n\n---\n\n## Useful commands\n\n```bash\n# First-time interactive setup (.env, backup options, optional auto-start)\n./windrose setup\n\n# Start\ndocker compose up -d\n\n# Stop\ndocker compose stop\n\n# Restart helper flow\n./windrose restart\n\n# Helper status overview\n./windrose status\n\n# JSON snapshot for monitoring integrations\n./windrose status-json\n\n# Full operator preflight checks\n./windrose doctor\n\n# Create a diagnostics bundle (default: 300 log lines)\n./windrose diagnostics\n\n# View live logs\ndocker compose logs -f windrose\n\n# Helper log shortcut\n./windrose logs\n\n# Best-effort player activity lines from recent logs\n./windrose activity history\n\n# Structured join/leave events (JSONL)\n./windrose activity events\n\n# List worlds\n./windrose worlds\n\n# Detect orphan/broken world entries\n./windrose worlds-check\n\n# Switch to another world interactively\n./windrose switch\n\n# Start or inspect activity notifications\n./windrose notify\n./windrose notify status\n./windrose notify test\n\n# Create a backup or install the backup cron helper\n./windrose backup\n./windrose install-backup-cron\n\n# Pull the latest published image tag\n./windrose pull\n\n# Update helper flow (safe pull -\u003e up; use --force-down for full recreate)\n./windrose update\n\n# Show detailed update log (default: last 120 lines)\n./windrose update-log\n\n# Stop and remove the stack\n./windrose down\n\n# Check server process inside container\ndocker compose exec windrose pgrep -a WindroseServer\n\n# Container status + health\ndocker compose ps\n\n# Optional system-wide install target\n./windrose install /usr/local/bin/windrosectl\n```\n\n## Quick diagnostics\n\nUse these commands for a fast operational check:\n\n```bash\n# 1) Basic container and health status\n./windrose status\n\n# 2) Full host/runtime preflight\n./windrose doctor\n\n# 3) World integrity check (orphan/broken entries)\n./windrose worlds-check\n\n# 4) Recent critical network/auth errors from current log file\ndocker compose logs --no-color --tail 400 windrose | grep -Ei \"account verification failed|turn session was expired|p2pgate disconnected|server authorization failed|login finished with error\"\n\n# 5) Create diagnostics bundle for incident review\n./windrose diagnostics\n```\n\nIf command `3` returns lines repeatedly, check outbound connectivity and firewall/NAT behavior for `*.windrose.support` on UDP/TCP `3478`.\n\nFor a machine-readable snapshot, use `./windrose status-json`.\n\n`./windrose status` shows a compact operator dashboard: container state and health, currently online players (parsed from the last 24 hours of container logs), last activity event timestamp, backup age, and notifier status. It does not require the `notify` background process to be running — player data is read directly from container logs. Using a 24-hour log window means players active for many hours will still appear correctly.\n\n`./windrose activity status` is a focused diagnostic tool for player activity: it shows how many log lines were scanned, how many join/leave events were matched, and the full list of online players without a display cap. Use it when you want to verify the parser is working or diagnose a mismatch between expected and reported online counts. You can pass a custom line count: `./windrose activity status 8000`.\n\nFor quick player activity extraction from logs, use `./windrose activity history [lines]`.\n\nFor structured join/leave records, use `./windrose activity events [lines]`.\nEvents are appended as JSON lines to `./logs/player-events.log`.\nThe parser is best-effort and now prefers richer Windrose/UE markers such as `Login request`, prelogin/account verification, and account summary dumps when they are present.\nEntries may also include an optional `name` field when the server log exposes a human-readable player name.\nA persistent identity map is maintained in `./state/player-identities.tsv` and reused to improve name resolution for disconnect events.\n\nLegacy aliases are still supported for backward compatibility: `./windrose player-history`, `./windrose player-events`.\n\nFor deeper investigation, extended symptom table, and network playbooks, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md).\n\n---\n\n## Activity notifications: Discord, Gotify, or both\n\nA basic log watcher is included for best-effort player activity notifications.\n\n1. Choose a notification backend in `.env`:\n\n```dotenv\nNOTIFY_PROVIDER=auto\nDISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...\nGOTIFY_URL=https://gotify.example.com\nGOTIFY_TOKEN=your_app_token\nGOTIFY_PRIORITY=5\n```\n\nProvider modes:\n\n- `auto`: prefers Gotify when it is configured, otherwise falls back to Discord\n- `discord`: sends only to Discord\n- `gotify`: sends only to Gotify\n- `both`: sends to Discord and Gotify for every event\n\n1. Test the webhook once before long-term use:\n\n```bash\n./windrose notify test\n```\n\n1. Start the watcher:\n\n```bash\n./windrose notify\n```\n\n1. Check watcher status and effective backend:\n\n```bash\n./windrose notify status\n```\n\nThe helper asks whether to run in background mode. If you start it in background mode, running `./windrose notify` again detects the running watcher and offers to stop it.\n\nBackground logs are written to:\n\n```text\n./logs/notify.log\n```\n\nAt the moment this is log-based and best-effort. Disconnect events are easier to detect reliably than joins, so treat it as a lightweight helper rather than a perfect audit system.\nWhen available, the notifier also uses `./state/player-identities.tsv` to resolve player names for disconnect lines that do not contain a name directly.\n\n---\n\n## Save transfer and world selection\n\nWorld saves live under:\n\n```text\ndata/R5/Saved/SaveProfiles/Default/RocksDB/\u003cgame-version\u003e/Worlds/\nor\ndata/R5/Saved/SaveProfiles/Default/RocksDB_v2/\u003cgame-version\u003e/Worlds/\n```\n\nEach world is a folder named with its world ID (for example `EC10598E83A14ED04D9C44CBFBF3F4B1`). The server loads the world whose ID matches `WorldIslandId` in `ServerDescription.json`.\n\nOperator note: if both `RocksDB` and `RocksDB_v2` exist at the same time, `./windrose switch` still changes one global `WorldIslandId` value in `ServerDescription.json`, independent of layout. Save backups with scope `save` or `both` archive the whole `R5/Saved` tree, so both `RocksDB` and `RocksDB_v2` are included when present.\n\n### Transfer a save from singleplayer or another server\n\n⚠ Always back up your saves first. Also shut down both the dedicated server and the game client before copying files.\n\n1. **Stop the dedicated server**:\n\n   ```bash\n   ./windrose stop\n   ```\n\n2. **Locate the source world folder** on the machine that currently has the save:\n   - Steam: `C:\\Users\\{UserName}\\AppData\\Local\\R5\\Saved\\SaveProfiles\\{YourProfile}\\RocksDB\\{GameVersion}\\Worlds\\{WorldID}` or `...\\RocksDB_v2\\{GameVersion}\\Worlds\\{WorldID}`\n   - EGS: `C:\\Users\\{UserName}\\AppData\\Local\\R5\\Saved\\SaveProfiles\\{YourProfile}\\RocksDB\\{GameVersion}\\Worlds\\{WorldID}` or `...\\RocksDB_v2\\{GameVersion}\\Worlds\\{WorldID}`\n   - Stove: `C:\\Users\\{UserName}\\AppData\\Local\\R5\\Saved\\SaveProfiles\\StoveDefault\\RocksDB\\{GameVersion}\\Worlds\\{WorldID}` or `...\\RocksDB_v2\\{GameVersion}\\Worlds\\{WorldID}`\n   - Example: `C:\\Users\\YarrHarrPirate\\AppData\\Local\\R5\\Saved\\SaveProfiles\\76561199699067790\\RocksDB_v2\\0.8.0\\Worlds\\EC10598E83A14ED04D9C44CBFBF3F4B1`\n\n3. **Copy the entire world folder** to the dedicated server data directory, preserving the folder name exactly:\n\n   ```text\n   data/R5/Saved/SaveProfiles/Default/RocksDB/\u003cgame-version\u003e/Worlds/\n   or\n   data/R5/Saved/SaveProfiles/Default/RocksDB_v2/\u003cgame-version\u003e/Worlds/\n   ```\n\n   Example using `scp` from a local machine (copy folder as-is):\n\n   ```bash\n   scp -r \"./EC10598E83A14ED04D9C44CBFBF3F4B1\" user@yourserver:/windrose/data/R5/Saved/SaveProfiles/Default/RocksDB_v2/\u003cversion\u003e/Worlds/\n   ```\n\n   Use the copied folder name exactly. Do not rename world folders.\n\n   Restore note: copy the `WorldID` folder directly into `.../Worlds/`. Do not create nested `Worlds/Worlds/...` paths. Helper commands (`./windrose worlds`, `./windrose worlds-check`, `./windrose switch`, `./windrose worlds-prune`) auto-detect `RocksDB_v2` or `RocksDB`.\n\n4. **Set the world ID** in `data/R5/ServerDescription.json`:\n\n   ```json\n   \"WorldIslandId\": \"EC10598E83A14ED04D9C44CBFBF3F4B1\"\n   ```\n\n   Use the copied folder name exactly. Do not rename world folders.\n\n5. **Start the server:**\n\n   ```bash\n   ./windrose start\n   ```\n\n6. **Verify** — check logs to confirm the correct world loaded:\n\n   ```bash\n   ./windrose logs\n   ```\n\n7. **Server to client transfer**: reverse the same steps in the opposite direction. If the game asks, choose **local** saves.\n\n\u003e **Note:** The `\u003cgame-version\u003e` path segment is version-specific (for example `0.8.0`). Use the exact version directory that contains your world.\n\n---\n\n## Backup saves\n\nUse the built-in helper for a safer backup flow. It briefly stops the server, creates a timestamped archive, and starts it again if it was running. If the activity notifier (`./windrose notify`) was active before the backup, it is restarted automatically afterwards.\n\n```bash\n# Create a manual backup\n./windrose backup\n\n# Install a host cron job running daily at 06:00\n./windrose install-backup-cron\n\n# Or provide your own schedule\n./windrose install-backup-cron \"0 3 * * *\"\n```\n\nBackups are stored in `backups` by default and old archives are pruned after 7 days. You can change that in `.env` with `BACKUP_DIR` and `BACKUP_RETENTION_DAYS`. Relative paths in `BACKUP_DIR` are resolved relative to the repository directory, not the current working directory.\n\nYou can choose what gets archived in `.env`:\n\n```dotenv\nBACKUP_SCOPE=full\n```\n\nSupported values:\n\n- `full` (default): archive full `R5` directory\n- `save`: archive only save data (`R5/Saved` and `R5/ServerDescription.json` when present)\n- `both`: create both full and save archives in one run\n\nYou can choose the archive format in `.env`:\n\n```dotenv\nBACKUP_FORMAT=tar.gz\n```\n\nSupported values:\n\n- `tar.gz` (default)\n- `zip` (more convenient to open on Windows)\n\nAfter each archive is created, the script runs an integrity test (`tar -tzf` or `zip -T`) and fails fast if verification does not pass.\n\nIf you use `BACKUP_FORMAT=zip`, the script checks whether `zip` is available.\nIn an interactive shell it asks whether it should install `zip`; in cron/non-interactive mode it exits with a clear error.\n\nThe installed cron job appends logs to `backups/backup.log`.\n\nBefore creating an archive, the backup script checks whether any players are currently online by reading recent container logs. If players are detected, the backup is aborted and a notification is sent via the configured provider (Discord or Gotify). To skip this check (for example in a maintenance window where you know the state), set:\n\n```dotenv\nBACKUP_SKIP_ONLINE_CHECK=true\n```\n\nYou can also enable backup result notifications in `.env`:\n\n```dotenv\nBACKUP_NOTIFY_SUCCESS=false\nBACKUP_NOTIFY_FAIL=true\n```\n\nWhen enabled, backup status notifications use the same backend as `./windrose notify` (`NOTIFY_PROVIDER`, Discord, or Gotify).\n\nYou can also upload the backup archive directly to a Discord channel after each successful backup:\n\n```dotenv\nBACKUP_DISCORD_UPLOAD=false\n```\n\nWhen set to `true`, Discord upload depends on `BACKUP_SCOPE`:\n\n- `save` or `both`: upload the newest `windrose-backup-save-*` archive (`.tar.gz` or `.zip`)\n- `full`: skip upload intentionally\n\nFiles larger than 25 MB are skipped with a warning (Discord free tier limit).\n\nThe backup script also checks for available disk space before creating an archive. It estimates the required space as 1.5× the size of the data directory plus a 2 GB safety margin. If the target disk does not have enough free space, the backup is aborted with a clear error. The check runs against the filesystem where `BACKUP_DIR` is mounted.\n\n---\n\n## Directory structure\n\n```text\nwindrose/\n├── Dockerfile          # Ubuntu 22.04 + Wine + SteamCMD\n├── docker-compose.yml  # Service definition\n├── scripts/            # Canonical runtime scripts used by container\n├── .env                # Environment variables (do not commit with secrets)\n├── data/               # Persistent server files and saves (created on first run)\n│   └── R5/\n│       ├── ServerDescription.json\n│       └── Saved/\n├── steam-home/         # Wine prefix and SteamCMD state (created on first run)\n├── backups/            # Archive files only (tar.gz, zip) from backup operations\n├── logs/               # Log files (update, backup, player activity)\n├── state/              # Metadata (player identities, event deduplication)\n└── diagnostics/        # Diagnostics bundles (tar.gz archives)\n```\n\n**Migration note:** If you are upgrading from an older version with a combined `backups/` folder, run the included `migrate-folders.sh` script once to reorganize files:\n\n```bash\n./migrate-folders.sh\n```\n\nThis moves log files, state files, and diagnostics to their respective folders while keeping backup archives in `backups`. The script is safe to run multiple times.\n\n---\n\n## Troubleshooting\n\nFor the full symptom table, diagnostics playbooks, and network troubleshooting, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md).\n\nCommon quick fixes:\n\n| Symptom                                              | Fix                                                                   |\n| ---------------------------------------------------- | --------------------------------------------------------------------- |\n| `wine: '/home/steam' is not owned by you`            | Set `PUID` and `PGID` correctly in `.env`, then restart the container |\n| `Server is already active for display 99`            | Stale Xvfb lock — entrypoint removes it automatically on restart      |\n| Config reset after restart                           | Edit JSON only when container is stopped                              |\n| Server not visible to players                        | Share the `InviteCode` from `ServerDescription.json`                  |\n| Players have issues after a game patch               | Keep the dedicated server version updated to match the game version   |\n| Server fails to start or crashes silently in Proxmox | Set CPU type to `host` in the VM/LXC settings (see below)             |\n\n### Proxmox VM and LXC\n\nIf you are hosting this server inside a Proxmox VM or LXC container, set the CPU type to `host` in the Proxmox configuration for that VM or container.\n\nProxmox's default CPU types (for example `kvm64`) omit instruction sets that Wine and the server binary may depend on. This can cause the server to fail to start, crash at runtime, or fail silently with no useful log output.\n\nIn the Proxmox web UI: VM → Hardware → Processors → Type → `host`.\n\nUsing `host` CPU type passes the physical CPU's full instruction set through to the VM, which is required for Wine to run the dedicated server binary reliably.\n\n---\n\n## Image versions\n\n- Most users should keep `IMAGE_TAG=v1.6.4` for a stable server.\n- Use `latest` only for testing.\n- Use `staging` only as a fallback for Wine compatibility issues on a specific host.\n- Use `debug` when you need extra troubleshooting tools inside the image.\n- To upgrade later, change `IMAGE_TAG` in `.env`, then run:\n\n```bash\ndocker compose pull\ndocker compose up -d\n```\n\n---\n\n## Technical notes\n\n- Supports configurable `PUID` and `PGID` to align mounted volumes with the host\n- `network_mode: host` — no Docker NAT, direct network access\n- Xvfb provides a headless X display required by Wine\n- `stop_grace_period: 90s` — allows the server to save before shutdown\n- Optional env-based patching can update `ServerDescription.json` automatically\n- Healthcheck can fail on recent fatal runtime log patterns, not just missing process state\n- Canonical runtime scripts are under `/opt/windrose/scripts/*`; root-level script files are compatibility wrappers\n- Compatibility wrappers are kept for backward compatibility and may be removed in a future major release after deprecation notice\n\n---\n\n## FAQ\n\n### How do I transfer a savegame to the server?\n\nSee the [Save transfer and world selection](#save-transfer-and-world-selection) section. In short: back up first, stop both server and client, copy the full world folder into `data/R5/Saved/SaveProfiles/Default/RocksDB_v2/\u003cversion\u003e/Worlds/` (or `.../RocksDB/\u003cversion\u003e/Worlds/`), set `WorldIslandId` to the exact folder name, then start the server.\n\n### How do players join the server?\n\nStart the server once, wait until it is healthy, then open `data/R5/ServerDescription.json` and share the `InviteCode` value with players.\n\n### Why is the first start so slow?\n\nThe first launch needs to download and prepare SteamCMD, Wine runtime files, and the dedicated server files. This can take several minutes depending on your network and the upstream mirrors.\n\n### Why do I get permission denied errors?\n\nThis usually means the mounted host directories are owned by a different user than the container expects. Check `PUID` and `PGID` in your `.env`, then restart the container.\n\n### How do I test Discord or Gotify integration?\n\nUse the built-in test command before you start the watcher:\n\n```bash\n./windrose notify test\n```\n\n### How do I update safely on production?\n\nPull the latest repository changes first, then refresh the selected image tag and recreate the container:\n\n```bash\ngit pull\n./windrose update\n```\n\n`./windrose update` writes detailed command output to `backups/update.log` and keeps three rotated history files (`update.log.1`, `update.log.2`, `update.log.3`).\n\nUse `./windrose update-log [lines]` to quickly inspect recent update details from the active log file.\n\n### What is the difference between stable and latest?\n\nUse a pinned version tag such as `v1.6.4` for production stability. Use `latest` only when you want the newest changes for testing.\nFor developer image channels (dev, dev-staging, dev-debug), see [DEVELOPMENT.md](DEVELOPMENT.md).\n\n## Practical operator guides\n\n### Initial host setup and first launch\n\n1. Clone and enter the repository:\n\n   ```bash\n   git clone https://github.com/UberDudePL/windrose-dedicated-server-docker.git\n   cd windrose-dedicated-server-docker\n   ```\n\n2. Create `.env` and adjust only required values:\n\n   ```bash\n   cp .env.example .env\n   nano .env\n   ```\n\n   Set at least `PUID`, `PGID`, and optional server identity values (`SERVER_NAME`, `INVITE_CODE`).\n\n3. Run first launch:\n\n   ```bash\n   ./windrose setup\n   ```\n\n4. Verify running state before inviting players:\n\n   ```bash\n   ./windrose status\n   ./windrose logs\n   ```\n\n### Save migration and world switch safety\n\n1. Create a backup first:\n\n   ```bash\n   ./windrose backup\n   ```\n\n2. Stop server before any manual save copy/edit:\n\n   ```bash\n   ./windrose stop\n   ```\n\n3. Validate worlds and active world mapping:\n\n   ```bash\n   ./windrose worlds\n   ./windrose worlds-check\n   ```\n\n4. Switch world with helper (recommended):\n\n   ```bash\n   ./windrose switch\n   ```\n\n5. Use prune in safe order:\n\n   ```bash\n   ./windrose worlds-prune\n   ./windrose worlds-prune --apply\n   ```\n\n   Default mode is dry-run. `--apply` requires confirmation in interactive shell and never removes the active world.\n\n### Failed update recovery\n\n1. Check update status and details:\n\n   ```bash\n   ./windrose status\n   ./windrose update-log 200\n   ```\n\n2. Keep mounts and compose defaults unchanged (`./data`, `./steam-home`, ports, and network settings).\n\n3. Roll back to a known-good Git ref only if needed, then restart:\n\n   ```bash\n   git checkout \u003cknown-good-tag-or-commit\u003e\n   ./windrose update --force-down\n   ```\n\n4. If startup still fails, generate diagnostics bundle for review:\n\n   ```bash\n   ./windrose diagnostics\n   ```\n\n### Rollback for script path migration\n\nIf you need to roll back this script layout migration, use this short procedure:\n\n1. Check out the previous known-good ref and rebuild:\n\n   ```bash\n   git checkout \u003cknown-good-tag-or-commit\u003e\n   docker compose build --no-cache windrose\n   ```\n\n2. Recreate the service:\n\n   ```bash\n   docker compose up -d windrose\n   ```\n\n3. Verify health and status:\n\n   ```bash\n   ./windrose status\n   ./windrose doctor\n   ```\n\nThis rollback does not require data migration and keeps existing save paths unchanged (`./data`, `./steam-home`).\n\n## Release checklist\n\n1. Pick the new stable version (example: `v1.6.0`).\n2. Update version bump points before tagging:\n   - `.env.example`: set `IMAGE_TAG=v1.6.0`\n   - `README.md`: update all stable version references (`IMAGE_TAG` default examples, quick start snippets, stable guidance lines)\n3. Verify old stable version references are gone from `.env.example` and `README.md`.\n4. Verify behavior locally before publishing:\n\n   ```bash\n   bash -n serverctl.sh backup.sh notify.sh\n   ./windrose status\n   ./windrose worlds-prune\n   ./windrose notify status\n   ```\n\n5. Commit docs/version changes and push them to `main` first.\n6. Run a manual approval checkpoint for script layout migration changes before tagging:\n   - Confirm compose parity checks passed.\n   - Confirm rollback procedure was tested and documented.\n   - Confirm root compatibility wrappers delegate correctly.\n7. After `main` contains the version bump commit and manual approval is recorded, create and push the release tag.\n8. Publish the GitHub release notes for that tag.\n9. If a tag was created too early, move it to the latest `main` commit before publishing release notes.\n\n---\n\n## Issues and suggestions\n\nIf you hit a bug or want a new feature, please open an issue in the GitHub repository.\n\n---\n\n## Support\n\nIf this project saved you time and you want to support further maintenance, you can use:\n\n- Ko-fi: [https://ko-fi.com/uberdudepl](https://ko-fi.com/uberdudepl)\n- PayPal: [https://paypal.me/uberdudepl](https://paypal.me/uberdudepl)\n- Revolut: [https://revolut.me/uberdudepl](https://revolut.me/uberdudepl)\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuberdudepl%2Fwindrose-dedicated-server-docker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fuberdudepl%2Fwindrose-dedicated-server-docker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuberdudepl%2Fwindrose-dedicated-server-docker/lists"}