# Architecture This repository is WARHOST: an Unraid template and a settings wrapper around Eugen Systems' official WARNO dedicated-server image. The game server itself stays in `eugensystems/warno`. Checked against the files in this repo on 2026-10-09, the Docker Hub page [eugensystems/warno](https://hub.docker.com/r/eugensystems/warno), and anonymous registry reads of `eugensystems/warno:latest` and `ghcr.io/suchamoneypit/warhost:latest`. Digests below are from that day and will change when the images are rebuilt. The rebuild workflow publishes `ghcr.io/suchamoneypit/warhost:latest` only. ## Layout | Path | Role | | --- | --- | | `templates/warhost.xml` | Unraid container template. Container name `WARHOST`. `` and `` are the raw `README.md` and `LICENSE` URLs on `main`. | | `ca_profile.xml` | Community Apps repository profile | | `LICENSE` | MIT license for the repository files. Copyright 2026 suchamoneypit. | | `Dockerfile` | `FROM eugensystems/warno:latest`, installs a pinned DepotDownloader, then replaces the entrypoint | | `entrypoint-unraid.sh` | Writes settings, then execs Eugen's entrypoint. With WebUI on, it may start `webui/launch.sh` first | | `webui/` | WebUI. Default off. It replaces `variables.ini` and does not signal the game server | | `samples/` | Placeholder shapes for the three generated files | | `tests/entrypoint_test.sh` | Runs the wrapper against a fake upstream entrypoint, and against fake `wget` and DepotDownloader for Workshop ids | | `scripts/upstream_digest.py` | Prints the Eugen image manifest digest | | `scripts/check_repo.sh` | Local XML and repository checks | | `scripts/next_version.py` | Prints the next `MAJOR.MINOR` release number | | `scripts/print_template_fetch.sh` | Prints one Unraid terminal command per branch, saving `my-.xml` or `my--NAME.xml`. `--clean` removes those containers, their `/mnt/user/appdata/warno//settings` folders when no other container still mounts them, and the image only when unused | | `.github/workflows/rebuild-image.yml` | Rebuilds the GHCR image when Eugen's digest changes | | `.github/workflows/release.yml` | Tags each `main` push and opens a GitHub Release | | `.github/workflows/check-repo.yml` | Runs `scripts/check_repo.sh` | | `docs/INSTALL-UNRAID.md` | First-install walkthrough for Unraid 7 with verification and troubleshooting | | `docs/UNRAID-TEMPLATE-GUIDE.md` | Generic Unraid template guidance, reusable for other game servers | | `docs/CURSOR-QUICKSTART.md` | How the Cursor rules, subagents, and commands in `.cursor/` are meant to be used | | `.cursor/rules/`, `.cursor/agents/`, `.cursor/commands/` | Agent rules (generic workflow, generic Unraid conventions, WARNO project), six subagents, eight slash commands | `.dockerignore` keeps templates, tests, samples, Markdown, and `.github` out of the image build context. The Dockerfile installs DepotDownloader, copies `entrypoint-unraid.sh` and `webui/`, and adds the `warhost-web` user. ## Template behavior Unraid installs `ghcr.io/suchamoneypit/warhost:latest` from `templates/warhost.xml`. The template is ``. `` is `WARHOST`. `` is `WARNO WARNO server dedicated server game server mods modded`. Paths and variables are `` entries. The default host folder is `/mnt/user/appdata/warno/10400/settings`. `` is `http://[IP]:[PORT:11400]/`. WebUI (`WEB_UI`) defaults to `false`, so that button connects only while the page is on. With the page off, the log says `WebUI is off. Nothing is listening on port N.`, or `WebUI is off. Port N is already in use.` when that port is already a local socket. Host networking has no published port, so [DockerClient.php](https://github.com/unraid/webgui/blob/master/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php) `getControlURL`, read on 2026-10-10, leaves `[PORT:11400]` as 11400. That button matches the container whose game port is `10400`. Another container uses its own game port plus 1000. On start, `WRITE_CONFIG` defaults to true. The wrapper then rewrites these files under the settings directory: - `login.ini` from `EUGEN_LOGIN` and `EUGEN_DEDICATED_KEY`. The startup line names the last 4 characters of the key. Those characters are not a separate setting. - `variables.ini` from the form plus the match defaults hardcoded in `entrypoint-unraid.sh`. When `WEB_UI=true` and this file already exists as a regular file, the start leaves it in place. - `params_for_ai.json` as two empty deck lists, `0` and `1` - `warhost-workshop-versions.txt`, only when Workshop Mod List holds a bare id (see Workshop Version at start) Those files are written mode `600`. The wrapper creates a private mode-`700` directory named `.warhost-stage.*` in the settings folder, writes the three files there, and renames them into place. A symlink at a final name is removed instead of followed. A directory at a final name stops the start. It removes that directory on exit and on `HUP`, `INT`, and `TERM`. After `warhost.lock` is held, it removes leftover real directories with that prefix and leaves symlinks with that prefix in place. A crash, `SIGKILL`, or power loss can leave one until the next locked start. `WARHOST_STAGING_HOLD` is a test seam, not a form field: when it names an existing file, the wrapper waits before that rename. `WRITE_CONFIG=false` leaves existing copies in place, sets `login.ini` to mode `600`, and exits if any of the three is missing. When `WEB_UI=true`, after that write the wrapper runs `webui/launch.sh` if it is executable, ignores a failure or a taken page port, and still execs `/server/entrypoint2.sh`. The launcher prints `WebUI listening on port N.` after a connection to `127.0.0.1` succeeds and `/proc/net/tcp` shows that port in state `0A` on `0.0.0.0`. The page listens on the game port plus 1000, replaces `variables.ini` only, and does not signal the game. The map list shows base-game maps. A modded list appears when `ModList` already contains a catalog pack id. A save keeps the existing `ModList` and `ModTagList`, and rejects a modded map whose pack id is not already in `ModList`. The ready file is written after the privilege drop is attempted. `WARHOST_WEBUI_DROP_FAIL` is a test seam: the drop fails, the process stays root, the container log warns that it stayed root, and the page still serves. While that page process is running, `variables.ini` is mode `644` so the page user can read it. `login.ini` stays mode `600` and the page does not read it. The wrapper does not mention `admins.ini` or `banned_clients.ini`. The README says the official server creates those two and that this wrapper leaves them alone. That server behavior was not observed here. The form exposes login, key, public IP, port, server name, map, player counts, team size, combat rule, mod list, the write-config switch, WebUI, and mod tags. The login and the dedicated key use `Mask="false"`. Map, Max Players, Minimum Players, Team Size, and Combat Rule use `Display="always"`. Map follows Server Name. Workshop Mod List, Write Config From Form, WebUI, and Workshop Mod Tags use `Display="advanced"`, in that order. Field descriptions are one or two sentences. Base-game scenario IDs and the named workshop mods are in `README.md`, not in the form. Unraid's [CreateDocker.php](https://github.com/unraid/webgui/blob/master/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php) `templateDisplayConfig`, read on 2026-10-09, prints each field description under the input, and appends `Display="advanced"` fields to the Show more settings block. The wrapper also writes fixed match values: `GameType = 0`, `MapRotationType = 0`, `InitMoney = 750`, `TimeLimit = 1200`, `ScoreLimit = 2000`, `WarmupCountdown = 60`, `LoadingTimeMax = 120`, `DeploiementTimeMax = 120`, `DebriefingTimeMax = 60`, `DeltaMaxTeamSize = 0`, `IncomeRate = 3`, `Upkeep = 0`, `AllowObservers = 1`, and `ObserverDelay = 120`. The preset in the template and sample is a 2v2 Conquest server (`NB_MAX_PLAYER=4`, `MAX_TEAM_SIZE=2`, `COMBAT_RULE=2`) on the base-game map `_2x2_Hesse_2vs2_CONQ`, with Server Name `WARHOST - Hesse 2v2`. `MOD_LIST` and `MOD_TAG_LIST` are empty in both the `Default` attribute and the element text, so the sample has no `ModList` or `ModTagList` line. Unraid's [Helpers.php](https://github.com/unraid/webgui/blob/master/emhttp/plugins/dynamix.docker.manager/include/Helpers.php), read on 2026-10-10, fills a blank `` value from its `Default` in `xmlToVar` (the edit form) and in `xmlToCommand` (the `docker run` variables), so a field with a non-empty `Default` cannot be cleared from the form. A container added from an earlier template keeps that template's `Default` in its saved `my-.xml`. The wrapper treats an unset variable and `none`, in any case, as empty for both fields. The Map and Workshop Mod List descriptions give the Red Dragon example: Map `RDPort_JungleLaw_2v2_CONQ` with `3811913066`, and tags `Maps-Scenarios`. A bare id is resolved to `id/version` before the files are written (see Workshop Version at start). `Jungle Law` and `YOUR_*` placeholders are rejected. When `ModList` is set, the wrapper prints that clients compare each `id/version` with `Version` in that mod's `Config.ini`, and that the client message does not appear in this log. When the list contains `3811913066` and Map does not start with `RDPort_`, it prints one warning that players without that mod cannot join, skips the version warning, and still writes the list. On a Red Dragon map, the typed pairs `3811913066/0` and `3811913066/15` print a warning and are still written through. A `Version` read from Steam never prints that warning. When Workshop Mod List is empty and Map is not one of the 102 base-game scenario IDs copied into the wrapper from the README table, it prints one warning and still starts. When Workshop Mod Tags is set and Workshop Mod List is empty, it prints a note that the tags are written and do not select a mod. Public WAN IP stays empty. Unraid's [CreateDocker.php](https://github.com/unraid/webgui/blob/master/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php), read on 2026-10-09, fills the Add Container form from the template XML and only rewrites the `/config` and `/unraid` path defaults. The install guide points at . ## Paths | Location | Path | | --- | --- | | Container settings | `/server/settings` | | Default Unraid host path | `/mnt/user/appdata/warno/10400/settings` | | Wrapper entrypoint | `/server/entrypoint-unraid.sh` | | Upstream entrypoint | `/server/entrypoint2.sh` (`UPSTREAM_ENTRYPOINT` can override it) | A second server is another container from the same template, with its own name, Game Port (the previous port plus 1), Server Name, and settings folder. The first host folder is `/mnt/user/appdata/warno/10400/settings`. An added server uses that same path with its own Game Port, for example `/mnt/user/appdata/warno/10401/settings`. Those folders hold the ini files. The wrapper takes an exclusive `flock` on `warhost.lock` in that directory and holds the fd across `exec` until the server process exits. If `flock` is missing, it warns and continues. Eugen's Docker Hub page says one login and key pair can start a maximum of five servers: [Obtaining access key](https://hub.docker.com/r/eugensystems/warno). Fifteen servers need three pairs, fifty need ten, and one hundred needs twenty. The published image is one shared copy. There is no game-library volume. ## Networking The template sets `host` and defines no port mapping. `EXPOSEDIP` and `EXPOSEDPORT` are environment variables read on each start. The default port in the template is `10400`. Eugen's start example uses a placeholder port, so `10400` is a choice of this repository. Editing the container and applying a new `EXPOSEDPORT` changes the listen port; the router forward has to use the same number. The next container's Game Port is that number plus 1. One server uses that one port: forward it as TCP and UDP, not as the port and the port plus 1. Players join by Server Name. Eugen's page does not say the port is fixed after the first start. Before exec, the wrapper reads `tcp`, `tcp6`, `udp`, and `udp6` under `WARHOST_PORT_TABLE` (default `/proc/net`, which host networking shares with the host). It stops if this Game Port is a local TCP socket in state `0A` (LISTEN) or a local UDP socket in any state. TIME_WAIT and the other TCP states do not count, so a port left behind after a stop does not force a new folder or Server Name. The first line of each file is skipped as a header. Only the local-address column is compared. `WARHOST_PORT_TABLE` is not a template field. Two containers started in the same moment can both pass this check. Eugen recommends host networking and says to forward both TCP and UDP to the exposed port: [Starting docker container](https://hub.docker.com/r/eugensystems/warno). The image config read on 2026-10-09 has no `ExposedPorts`. The README says outbound internet is required to reach Eugen except for LAN use. Docker Hub does not state that LAN exception. Treat it as a statement in this repository. RCON is unset. Eugen documents `-rcon_password` and `-rcon_port` as arguments to the official container. The template leaves `PostArgs` empty. The wrapper forwards any arguments it receives to the upstream entrypoint. ## Image chain `Dockerfile` bases the Unraid image on `eugensystems/warno:latest` and sets the entrypoint to the wrapper. The wrapper then execs the upstream script. The Dockerfile leaves `org.opencontainers.image.licenses` unset. `LICENSE` is the MIT license for the files in this repository. The Docker Hub repository API for `eugensystems/warno`, fetched 2026-10-09 from , has no license field, and the description text does not state one. Community Apps treats that image license as separate from `LICENSE`: [submission help](https://ca.unraid.net/submit/help), fetched the same day. Registry config for `eugensystems/warno:latest` on 2026-10-09: - Manifest digest `sha256:fb498aa82d7b15e3978bac51240e1243be3e19232ba0e66a6d947ec4b4364fe5` - Config created `2026-09-17T09:35:31Z` - Working directory `/server` - Entrypoint `./entrypoint2.sh` That entrypoint plus the working directory is the absolute path `/server/entrypoint2.sh` used by this wrapper. The script body was read from the image layer; its `warno-server` command line is quoted under External dependencies below. The wrapper image manifest was readable without credentials on 2026-10-09 at `ghcr.io/suchamoneypit/warno-unraid:latest` (anonymous token, HTTP 200). The rebuild workflow no longer publishes that name. It publishes `ghcr.io/suchamoneypit/warhost:latest`. Values below are from that `warno-unraid` read, the rebuild that followed commit `848a5a4`: - Manifest digest `sha256:67ea75b2542f2ed924958ba087b6598d5a3b2632885655d523a9187e39138095` - Created `2026-10-09T08:12:08Z` - Entrypoint `/server/entrypoint-unraid.sh`, working directory `/server`, no `ExposedPorts` - Label `warno.upstream.digest` equal to the Eugen digest above A later anonymous read of `ghcr.io/suchamoneypit/warhost:latest`, after [Actions run 37992605391](https://github.com/suchamoneypit/WARHOST/actions/runs/37992605391) for commit `e005c64` finished green, gave manifest digest `sha256:156858f3939a977e0329f56fe78c960fd3e39d41cb9486a2868ef1efb4c01847`, created `2026-10-09T21:48:01Z`. The `entrypoint-unraid.sh` layer from that manifest does not contain `EUGEN_KEY_LAST4` or `Set Key last 4`. The same run's first attempt failed in Build and push with Docker Hub `429 Too Many Requests` and left the previous image in place (created `2026-10-09T20:23:25Z`). That image's entrypoint still died when `EUGEN_KEY_LAST4` was empty. The template on `main` had already dropped that field, so a fresh install during the gap stopped with `Set Key last 4 to the last 4 characters of the Eugen dedicated key.` On 2026-10-10, [Actions run 38092461247](https://github.com/suchamoneypit/WARHOST/actions/runs/38092461247) rebuilt `ghcr.io/suchamoneypit/warhost:latest` for commit `eb4d65d` and finished green. That commit is tag `v1.02`. The manifest digest was not read. A later push of commit `431e662` to `main` is tag `v1.03` ([Actions run 38093029678](https://github.com/suchamoneypit/WARHOST/actions/runs/38093029678)). That commit does not change the Dockerfile or the wrapper, and no image rebuild ran for it. `VERSION` is still `1.02`. Both GitHub workflows (`Check repository`, `Rebuild WARNO image`) completed successfully for commit `848a5a4`. The workflow file is now named `Rebuild WARHOST image`. The repository is public with Issues enabled. The GitHub repository is `WARHOST`. `https://github.com/suchamoneypit/WARNO-Dedicated-Server-Unraid` returned HTTP 301 to that name on 2026-10-09. `.github/workflows/rebuild-image.yml` rebuilds that GHCR tag on a daily schedule, on manual dispatch, and on pushes to `main` that change the Dockerfile, wrapper, or workflow. Those pushes always rebuild. The daily schedule, and a manual run with force left false, skip the build when the published image's `warno.upstream.digest` label already matches Eugen's digest. `.github/workflows/release.yml` runs on every push to `main`. It reads git tags and the optional `VERSION` file. With no version tags the first release is `v0.90`. Otherwise it adds one to the minor part (`v0.90` then `v0.91`, `v0.99` then `v1.00`, `v1.00` then `v1.01`). A `VERSION` file higher than the latest tag is used as written (`1.0` is `v1.00`). A file that still matches the latest tag increments. A file below the latest tag is left from an earlier publish, so the workflow ignores it and increments the latest tag. The workflow creates an annotated tag when that tag is missing, then opens a GitHub Release. A later run on a commit that already has a version tag keeps the lowest of those tags and does not mint another number. It does not push a commit. Daily and manual image rebuilds do not bump this number. The template keeps installing `ghcr.io/suchamoneypit/warhost:latest`. ## Workshop Version at start A Workshop Mod List entry without `/` is a bare id. After the port and lock checks and before any settings file is written, the wrapper resolves each bare id, one at a time in list order, and writes `id/Version`. A typed `id/version` is a pin: it is written as typed and does not contact Steam. `WRITE_CONFIG=false` resolves nothing. The check runs once per start. There is no loop and no restart during a match. `warhost-workshop-versions.txt` in the settings folder holds one line per bare id from the last start: `id time_updated Version`. It is staged and renamed like the other files, and it is not followed when it is a symlink. A directory at that name is left in place with a warning, and the start continues without saving. Steam's `GetPublishedFileDetails` (`POST https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/` with `itemcount=1&publishedfileids%5B0%5D=`, no key) returns one line of JSON with `time_updated`, `file_size`, and `hcontent_file`, and no `Version`. When `time_updated` equals the saved time, the saved `Version` is written and nothing is downloaded. When Steam gives no time and a line exists, the saved `Version` is written with a warning. Otherwise the wrapper runs `timeout 300 DepotDownloader -app 1611600 -pubfile -filelist -dir /item` with no Steam account. The list holds only `Config.ini`. `` is a new mode-`700` `.warhost-stage.*` directory in the settings folder and is also `HOME`, `TMPDIR`, and the working directory. DepotDownloader starts under `env -i` with only `PATH`, `HOME`, `TMPDIR`, and two .NET settings, so it never receives the Eugen login or key. The wrapper reads the first `Version = ` line and removes `` before the next id, also after a failure and on `HUP`, `INT`, and `TERM`. When the fetch fails and a line exists, the saved `Version` is written with a warning and the saved time is kept, so the next start tries again. With no line, the start stops after printing the last 5 lines of DepotDownloader's output and the reason. Measured on 2026-10-10 with DepotDownloader 3.4.0 and no Steam account: | Item | `file_size` | `Config.ini` transferred | Manifest saved | `Version` | | --- | --- | --- | --- | --- | | `3811913066` Red Dragon | 9,059,324,422 bytes | 416 bytes (551 uncompressed) | 419,989 bytes | 27 | | `3474588989` Highway to Oslo | 148,918,317 bytes | 384 bytes (485 uncompressed) | 13,745 bytes | 14 | The manifest IDs DepotDownloader requested, `3167211120192437934` and `7803733855267144859`, equal `hcontent_file` from the details call. For published file `1`, DepotDownloader printed `Unable to locate manifest ID for published file 1` and exited `0`, so the wrapper requires `Config.ini` on disk and does not trust the exit status alone. With networking removed it printed `Could not connect to Steam after 10 tries` and exited `1` after about 55 seconds. In [ContentDownloader.cs](https://raw.githubusercontent.com/SteamRE/DepotDownloader/DepotDownloader_3.4.0/DepotDownloader/ContentDownloader.cs) at that tag, read 2026-10-10, `DownloadPubfileAsync` passes `hcontent_file` to the depot download as a manifest ID, and the manifest's files are filtered through `TestIsFileIncluded`, which applies the file list, before any chunk is queued. In [Program.cs](https://raw.githubusercontent.com/SteamRE/DepotDownloader/DepotDownloader_3.4.0/DepotDownloader/Program.cs) at that tag, `-filelist` turns the filter on with an empty list before reading the file, so a list that cannot be read downloads nothing. The Dockerfile downloads `DepotDownloader-linux-x64.zip` from release [DepotDownloader_3.4.0](https://github.com/SteamRE/DepotDownloader/releases/tag/DepotDownloader_3.4.0) (33,442,357 bytes, SHA-256 `a999dec66b4850fc961bd50366696d23c2d0fad7b18790e6a5647b2f19097a53`), checks the hash, and unpacks it with `python3 -m zipfile` into `/opt/depotdownloader`, with its GPL-2.0 `LICENSE`. `/usr/local/bin/DepotDownloader` links to the 78,977,777-byte self-contained binary. `DepotDownloader -V` printed `DepotDownloader v3.4.0+c553ef4d60c00a4f5fd16c9fe017f569001589ff` and `Runtime: .NET 9.0.0 on Debian GNU/Linux 13 (trixie)`. Eugen's image at digest `sha256:fb498aa82d7b15e3978bac51240e1243be3e19232ba0e66a6d947ec4b4364fe5` has six layers totaling 84,020,395 bytes compressed. It has `wget`, `sha256sum`, `python3`, `timeout`, and `flock`, has no `curl`, `unzip`, or ICU library, and its `/bin/sh` is `dash`. The wrapper sets `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1` because ICU is absent. The .NET runtime creates an empty `/tmp/.dotnet/shm` in the container regardless of `TMPDIR`. On 2026-10-10 those layers were unpacked into a directory. The Dockerfile's `RUN` line, `tests/entrypoint_test.sh` under `dash`, and the wrapper with the real DepotDownloader and Steam ran inside it under `unshare` and `chroot`, with a stub in place of `entrypoint2.sh`. `3811913066-3474588989` became `3811913066/27-3474588989/14` in 7 seconds. The next start used the cache, and with networking removed the start after that wrote the saved numbers with two warnings. Nothing outside the settings folder changed except `/tmp/.dotnet/shm`. No `docker build` ran, and no container has run the rebuilt image. `ModGenVersion = 201602 ; ModGen revision, do not modify` is the same line in all nine `Config.ini` files in the local Steam library on 2026-10-10, while `Version` differs per mod. The comment on `Version` says to increment it when an update is incompatible. `3811913066/15` joined while `Version` was 15, and `3811913066/0` was refused, so the ModList number is `Version`. ## External dependencies - Eugen image and settings reference: https://hub.docker.com/r/eugensystems/warno - DepotDownloader, GPL-2.0: https://github.com/SteamRE/DepotDownloader - Dedicated login and key: Docker Hub says to email `eugsupport@eugensystems.com` with the EugNet account email, and "We will send you back a login and apikey pair." An existing dedicated key from another Eugen game can be reused. The docs therefore tell users to enter the login from Eugen's reply rather than assuming it equals the email. - Game: Steam app `1611600`, https://store.steampowered.com/app/1611600/WARNO/ - Map pack: Steam Workshop item `3811913066`, https://steamcommunity.com/sharedfiles/filedetails/?id=3811913066 - Other packs named in `README.md`: [3363584349](https://steamcommunity.com/sharedfiles/filedetails/?id=3363584349) `Version` 1023, [3474588989](https://steamcommunity.com/sharedfiles/filedetails/?id=3474588989) `Version` 14, [3705706772](https://steamcommunity.com/sharedfiles/filedetails/?id=3705706772) `Version` 9, [3415339374](https://steamcommunity.com/sharedfiles/filedetails/?id=3415339374) `Version` 20, [3762638679](https://steamcommunity.com/sharedfiles/filedetails/?id=3762638679) `Version` 36. `Config.ini` and `Scenarios/` were read on 2026-10-09. Their pages do not publish either fact. - Conversions named in `README.md`: [Galactic Divide](https://steamcommunity.com/sharedfiles/filedetails/?id=3595948209) (`3595948209`) `Version` 16 and [A World in Flames](https://steamcommunity.com/sharedfiles/filedetails/?id=3388575848) (`3388575848`) `Version` 7, read from `Config.ini` on 2026-10-09. Both `Scenarios/` folders are empty. Pages fetched on 2026-10-09. Galactic Divide's page calls it a total conversion and links the tags Gameplay, Interface, Sound, Scenarios, and Maps. A World in Flames's page calls it an overhaul for the modern day and links Gameplay and Interface. Neither page states a scenario ID or a `Version`. - Workshop files on a subscribed PC: `steamapps/workshop/content/1611600/` - Community Apps submission: https://ca.unraid.net/submit — help fetched 2026-10-09: https://ca.unraid.net/submit/help , https://ca.unraid.net/submit/help/repository-xml , https://ca.unraid.net/submit/help/repository-info-xml , https://ca.unraid.net/submit/help/xml-field-reference , https://ca.unraid.net/submit/help/builders . Starter repository: https://github.com/unraid/unraid-community-apps-starter . GitHub license detection: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository . The GitHub API for this repository reported `license.spdx_id` `MIT` on 2026-10-09. The linked `Squidly271/community.applications` README URL returned HTTP 404 the same day; the Unraid org copy of that plugin has no root README. - Support: https://github.com/suchamoneypit/WARHOST/issues The workshop page lists Jungle Law as a 2v2 Conquest scenario name and describes the pack as an unofficial conversion. It does not publish scenario IDs. The 102 base-game IDs in `README.md` are Eugen's Map Base Id column. They were copied from the template text that quoted the Docker Hub table read on 2026-10-09, then removed from the form. The 28 Red Dragon IDs in `README.md` are the file names in the mod's `Scenarios/` folder (`_Assets.dat`, `_Definition.dat`, `_Details.dat`, `_GameData.dat`) as downloaded by Steam to `steamapps/workshop/content/1611600/3811913066/`; they were re-read from those files on 2026-10-10. The page description, fetched the same day, still lists 18 map names and "Wonsan Harbour" where the file is `RDPort_WonsanNative_2v2_CONQ`. The author's change note for that update says Mod Version = 27 and names the 10 added layouts: https://steamcommunity.com/sharedfiles/filedetails/changelog/3811913066. The two Strait to the Point Land & Sea rows take their size from the `2v2` and `4v4` tokens in the file names. The mod's `Config.ini` shows `ID = 3811913066` and `Version = 27`, re-read on 2026-10-10; the comment says to increment `Version` when an update is incompatible. The template's Workshop Mod List description gives `3811913066` as the Red Dragon example, and each start reads `Version`; the field itself is empty. Eugen's Docker Hub page says the `ModList` version "is usually always 0". On 2026-10-09 the operator started with `3811913066/0`: the log showed `Variable ModList set to "3811913066/0"` and `Connection to match making server validated`, and no line about the rejected join. The operator reported that `3811913066/15` is what let a client join that day. The page showed a download size of 9.059 GB when fetched on 2026-10-10. On 2026-10-09 the same Steam library contained the other mods named in `README.md`, under `steamapps/workshop/content/1611600//`. Each `Config.ini` `Version` is the number in that mod's Workshop Mod List: Galactic Divide 16, A World in Flames 7, WEST FULDA 1.0 1023, Highway to Oslo 14, Ramstein Air Base 9, Arsenal 20, Helbe 36. The comment on `Version` says to increment it when an update is incompatible. Galactic Divide and A World in Flames have an empty `Scenarios/` directory. The other packs' scenario IDs in `README.md` are the file names in each `Scenarios/` folder (`_Assets.dat`, `_Definition.dat`, `_Details.dat`, `_GameData.dat`). The Workshop pages do not publish those IDs. Oslo's files are `_2x2_Oslo_Conquest`, `_2x2_Oslo_Destruction`, and the same two with `NS`; the page calls them 1v1 and calls the `NS` pair no sidespawn. Ramstein's files are `_2x2_Ramstein_Conquest` and `_2x2_Ramstein_Destruction`; the page calls them 3v3. Arsenal's files are `_2x3_TwoHills_Conquest`, `_2x3_TwoHills_Destruction`, and the same two with `_6P`; the page lists 2v2 and 3v3 in both modes. Helbe Norden is `_5x3_HelbeNorden_2v2_CONQ`; the page says NORDEN 3v3. West Fulda includes `_3x3_WFuldaNeuen_1v1_CONQ`. Grossenluder is `_3x3_WFuldaGrossen_1v1_CONQ` and `_3x3_WFuldaGrossen_2v2_DEST`. Files named `SM_FO_`, `SM_PL_`, or `SM_SU_` are listed in the README as Army General file names. This document has not seen those files used as a lobby `Map`. `TagList` in `Config.ini` is `Scenarios,Maps` for each map pack, `Gameplay,Interface,Sound,Scenarios,Maps` for Galactic Divide, and `Gameplay,Interface` for A World in Flames. The README keeps the map-pack string `Maps-Scenarios`. Eugen's Docker Hub text documents `login.ini`, `variables.ini`, `params_for_ai.json`, and `maps.ini` (map rotation only). It names the `variables.ini` keys but never defines a `Map` key; the base-game table column is "Map Base Id". The `eugensystems/warno:latest` image read on 2026-10-09 is about 80 MB compressed: Debian base, a 27 MB `warno-server`, default `settings/variables.ini` and `settings/params_for_ai.json`, and no game data layer. What the server fetches at first start was not observed. `ModList` on that same Docker Hub page tells joining clients to download and enable a missing workshop mod. `entrypoint2.sh` in image digest `sha256:fb498aa82d7b15e3978bac51240e1243be3e19232ba0e66a6d947ec4b4364fe5` does not download files. It runs `warno-server -listenport=$EXPOSEDPORT -ip=$EXPOSEDIP -port=$EXPOSEDPORT -ipmms 178.32.126.73 -portmms 10002`. Eugen's `variables.ini` reference gives `GameType` `0` as NATO vs PACT. A later sentence on the same page calls that mode Allies vs Axis. The enumerated table is the one this wrapper follows (`GameType = 0`). `NbMaxPlayer` maximum is 20, which the wrapper also enforces. `MaxTeamSize` is only "Maximum number of players in each team." That page never says either value must match the size token in the scenario ID. Separate Map Base Ids exist for sizes such as `_2vs2_` and `_4vs4_`. Source: [Configuration file `variables.ini`](https://hub.docker.com/r/eugensystems/warno), registry `full_description` fetched 2026-10-09 (`last_updated` 2026-09-17) from . ## Unverified - On 2026-10-11 the operator opened the Docker WebUI button. Brave reported `10.0.1.105 refused to connect` (`ERR_CONNECTION_REFUSED`). The docker log from that boot is `warno-server` through matchmaking on `71.241.212.203:10400` for `WARHOST - Hesse 2v2` on `_2x2_Hesse_2vs2_CONQ`, with no lobby line. `ghcr.io/suchamoneypit/warhost:latest` that day was the image from [Actions run 38097617038](https://github.com/suchamoneypit/WARHOST/actions/runs/38097617038) for commit `9dfec47`. Its `entrypoint-unraid.sh` layer, blob `sha256:a3eaed4635aa`, contains `Wrote WARNO settings` and does not contain `WEB_UI` or `webui/launch.sh` (read 2026-10-11). The template button is `http://[IP]:[PORT:11400]/`. [DockerClient.php](https://github.com/unraid/webgui/blob/master/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php) `getControlURL`, re-read the same day, leaves that port as 11400 on host networking. Nothing in that image listens there. A local root run of `webui/server.py` did accept `http://127.0.0.1/` after dropping to `warhost-web`, and again when `WARHOST_WEBUI_DROP_FAIL` kept it root. That drop has still not run inside the published image. Whether `warno-server` keeps the match it already loaded after `variables.ini` is replaced, and whether `Upkeep` 5 and 7 and `IncomeRate` 0 through 5 match the dropdown labels, was not observed. - No completed Unraid install is recorded here. On 2026-10-09 the operator added a container from `my-WARHOST.xml` on Unraid 7.3.3. A fresh install while the 20:23 image was still published stopped on the old Key last 4 check. After the 21:48 image, the operator reported that stop was gone. This document did not see the healthy `warno-server` log, the settings files, or a Jungle Law lobby. Community Apps acceptance is untested. - Whether the official server creates `admins.ini` and `banned_clients.ini` was not observed. - The exact client error for a sixth server was not observed. The documented limit is five. - The README's LAN exception for Eugen connectivity was not checked against Eugen. - A bare id on a running WARHOST container. The published image has no DepotDownloader until the Dockerfile change reaches `main` and the rebuild finishes. Anonymous access to app `1611600` Workshop manifests worked on 2026-10-10; Steam can change that. - Whether `warno-server` downloads workshop item `3811913066` onto the server was not observed. The binary contains `GetModPackMountingPoint` and `ModDownloadCanceled`, and it contains no `workshop` string. There is no shared game-data mount until a running server shows a large directory and its path. RAM per `warno-server` is not documented. - On 2026-10-09 a second container that shared `/mnt/user/appdata/warno/settings` with a running container stopped with the settings-folder message. Separate folders `/mnt/user/appdata/warno/10400/settings` and `/mnt/user/appdata/warno/10401/settings` then both showed online. `tests/entrypoint_test.sh` holds the lock on a local file. - The Overview no longer contains `
`. [CreateDocker.php](https://github.com/unraid/webgui/blob/master/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php), read on 2026-10-09, converts `[` `]` to tags, replaces `
` with a newline, runs `strip_tags`, and the basic view then turns newlines into line breaks. The operator had seen the letters `br` in the previous Overview. Whether the new paragraphs break correctly on their Unraid screen has not been checked. The [template schema thread](https://forums.unraid.net/topic/38619-docker-template-xml-schema/) says Community Applications ignores `` when `` is present. - Whether `Map=RDPort_JungleLaw_2v2_CONQ` with `NbMaxPlayer=8` and `MaxTeamSize=4` opens an 8-player lobby. Eugen's page does not forbid it and does not confirm it. The server binary was not run with that combination. - The install route in `docs/INSTALL-UNRAID.md` follows the Community Applications author's [statement](https://forums.unraid.net/topic/112170-allow-template-repositories-to-be-hosted-from-other-sources/) that Unraid 6.10 removed the Template Repositories field. `scripts/print_template_fetch.sh` prints the download. The Template dropdown label is the flash filename with `my-` removed ([CreateDocker.php](https://github.com/unraid/webgui/blob/master/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php)); Unraid's own save path is `my-.xml` and keeps that case ([DockerClient.php](https://github.com/unraid/webgui/blob/master/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php) `getUserTemplatePath`). Both files were read from webgui `master` on 2026-10-09. The operator reported that `warno-dedicated-server.xml` showed up lowercase. On 2026-10-09 Unraid 7.3.3 opened Add Container at `user:/boot/config/plugins/dockerMan/templates-user/my-WARHOST.xml` and the dropdown label was WARHOST. That file kept the previous form, including Key last 4, until it was deleted and the curl from `main` was run again. Apply on an Add Container tab that was already open writes that tab back over the file.