Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1858394
community feedback: safer saves download, shadow-stamp env mode, main…
seslly Apr 20, 2026
954acb2
entrypoint: fix shellcheck SC2034 + shadow-stamp preserves skip marker
seslly Apr 20, 2026
c5ff4a3
entrypoint: first-boot shadow-stamp treats live as operator-owned
seslly Apr 20, 2026
791d349
community feedback follow-ups: world-name shadow-stamp + review nits
seslly Apr 20, 2026
20446ab
entrypoint: skip SteamCMD when binary is already patched
seslly Apr 20, 2026
3693a3e
Revert "entrypoint: skip SteamCMD when binary is already patched"
seslly Apr 20, 2026
438a24b
entrypoint: idle-CPU patch uses a sibling .patched.exe instead of in-…
seslly Apr 20, 2026
3f7e43f
entrypoint: migrate from in-place patch to sibling on first boot
seslly Apr 20, 2026
6aaf856
entrypoint: drop upgrade revert; --idempotent handles it on the sibling
seslly Apr 20, 2026
eb782f3
entrypoint: tag-kill stale wine processes from prior boot
seslly Apr 20, 2026
cb30d27
shellcheck SC2155 + install.sh try-restart on re-run
seslly Apr 20, 2026
32c3c28
backups: age-based retention, pinned prefix, safety guards + test har…
seslly Apr 20, 2026
99a2305
ci fixes: shellcheck SC2034 + fake_stat kwarg compat
seslly Apr 21, 2026
a6f6f64
ui server: guard is_dir() calls against Python 3.13 OSError propagation
seslly Apr 21, 2026
d5b469a
tests: add restore round-trip + HTTP integration coverage
seslly Apr 21, 2026
7e60241
backups: auto-backup scheduler + Default_Backups tab + admin config
seslly Apr 21, 2026
16d3c91
refactor: flatten to /server.py + /ui + /tests
seslly Apr 21, 2026
b14bfcd
dockerignore: whitelist server.py + ui/ for flat layout
seslly Apr 21, 2026
9e8ffa2
server control: polkit rule + systemctl-backed stop/start/restart
seslly Apr 21, 2026
0543ace
direct ip: expose UseDirectConnection + friends across all surfaces
seslly Apr 21, 2026
739df0b
docs: add Recover Your World section
seslly Apr 21, 2026
8ae3f9b
install.sh: fix env merge silently wiping operator secrets on re-run
seslly Apr 21, 2026
8032db6
net tick rate: Engine.ini reconciler makes NET_SERVER_MAX_TICK_RATE a…
seslly Apr 21, 2026
c343e85
backups: full-backup download + upload endpoints (server migration)
seslly Apr 21, 2026
41459d1
ui: sort backups chronologically + responsive layout
seslly Apr 21, 2026
1e2b4b0
codex review fixes: shadow-stamp, set -u, maintenance bool
seslly Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Whitelist: only scripts/ feeds the build. Keeps the context small and
# prevents new top-level files (helm values, k8s manifests, docs) from
# silently bloating it.
# Whitelist: only the files the Dockerfile actually COPYs feed the
# build. Keeps the context small and prevents new top-level files
# (helm values, k8s manifests, docs, tests) from silently bloating it.
*
!scripts
!scripts/**
!ui
!ui/**
!server.py
!Dockerfile
**/__pycache__/
**/*.py[cod]
Expand Down
28 changes: 27 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,33 @@ jobs:
with:
python-version: "3.13"
- name: Syntax-check UI server
run: python3 -m py_compile scripts/ui/server.py scripts/patch-idle-cpu.py
run: python3 -m py_compile server.py scripts/patch-idle-cpu.py

python-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Backup retention tests
run: python3 tests/test_retention.py
- name: Backup create/restore round-trip tests
run: python3 tests/test_restore.py
- name: Auto-backup scheduler tests
run: python3 tests/test_auto_backup.py
- name: Server-control (systemctl dispatch) tests
run: python3 tests/test_server_control.py
- name: ServerDescription schema tests
run: python3 tests/test_schema.py
- name: HTTP integration tests
run: python3 tests/test_http.py
- name: install.sh env-merge regression
run: bash tests/test_install_env_merge.sh
- name: Engine.ini reconcile (NetServerMaxTickRate + t.MaxFPS)
run: bash tests/test_engine_ini_reconcile.sh
- name: ServerDescription shadow-stamp contract
run: bash tests/test_shadow_stamp.sh

yaml-lint:
runs-on: ubuntu-latest
Expand Down
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@ COPY scripts/WorldDescription_example.json /usr/local/share/WorldDescription_exa
# Optional idle-CPU patch — off by default, opt in with
# WINDROSE_PATCH_IDLE_CPU=1. The entrypoint only invokes it when set.
COPY --chmod=755 scripts/patch-idle-cpu.py /usr/local/bin/patch-idle-cpu.py
COPY --chmod=755 scripts/reconcile-engine-ini.sh /usr/local/bin/reconcile-engine-ini.sh

# Admin console (stdlib Python HTTP server + static assets). Baked into
# the same image as the game binary; the UI sidecar runs via a command
# override at /opt/windrose-ui/server.py.
COPY --chown=10000:10000 scripts/ui/ /opt/windrose-ui/
# override at /opt/windrose-ui/server.py. Backend lives at the repo
# root (server.py); frontend bundle is the sibling ui/ tree.
COPY --chown=10000:10000 server.py /opt/windrose-ui/server.py
COPY --chown=10000:10000 ui/ /opt/windrose-ui/ui/
RUN chmod 755 /opt/windrose-ui/server.py

USER steam
Expand Down
175 changes: 159 additions & 16 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ by hand.

Plan:
1. **Tagged artifact bundle.** On every `v*` tag, CI builds a
`windrose-ui-<version>.tar.gz` (just `scripts/ui/*` + `scripts/entrypoint.sh`
`windrose-ui-<version>.tar.gz` (`server.py` + `ui/*` + `scripts/entrypoint.sh`
+ `scripts/*_example.json`) and attaches it as a GitHub release
asset (or publishes as a GHCR OCI artifact alongside the chart).
2. **`install.sh --update [<version>]` flag.** Downloads the tagged
Expand Down
4 changes: 2 additions & 2 deletions bare-linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ the game's RSS spikes on world load + backend handshake.

```
/opt/windrose/scripts/entrypoint.sh # game launcher
/opt/windrose/scripts/ui/server.py # admin console
/opt/windrose/scripts/ui/{index.html,app.js,app.css}
/opt/windrose/server.py # admin console
/opt/windrose/ui/{index.html,app.js,app.css}
/etc/windrose/windrose.env # runtime env (root-rw, group-r for steam)
/home/steam/windrose/ # game data (WindowsServer/, saves, backups)
/home/steam/steamcmd/ # SteamCMD + GE-Proton compat data
Expand Down
124 changes: 91 additions & 33 deletions bare-linux/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,29 @@ fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SCRIPTS_SRC="${REPO_ROOT}/scripts"
UI_SRC="${REPO_ROOT}/ui"

WINDROSE_USER="${WINDROSE_USER:-steam}"
WINDROSE_INSTALL_DIR="${WINDROSE_INSTALL_DIR:-/opt/windrose}"
WINDROSE_ENV_DIR="${WINDROSE_ENV_DIR:-/etc/windrose}"
WINDROSE_ENV_FILE="${WINDROSE_ENV_FILE:-${WINDROSE_ENV_DIR}/windrose.env}"

# Safe-by-default: bind the admin UI to loopback. Exposing it publicly
# (UI_BIND=0.0.0.0) without UI_PASSWORD set is a foot-gun on any VPS,
# so the operator has to explicitly flip both knobs.
UI_BIND="${UI_BIND:-127.0.0.1}"
UI_PORT="${UI_PORT:-28080}"
UI_PASSWORD="${UI_PASSWORD:-}"
UI_ENABLE_ADMIN_WITHOUT_PASSWORD="${UI_ENABLE_ADMIN_WITHOUT_PASSWORD:-false}"

# Webhook knobs. All optional; if the URL vars are empty the
# EventDetector thread still runs but skips dispatch.
WINDROSE_WEBHOOK_URL="${WINDROSE_WEBHOOK_URL:-}"
WINDROSE_DISCORD_WEBHOOK_URL="${WINDROSE_DISCORD_WEBHOOK_URL:-}"
WINDROSE_WEBHOOK_EVENTS="${WINDROSE_WEBHOOK_EVENTS:-server.online,server.offline,player.join,player.leave,backup.created,backup.restored,config.applied}"
WINDROSE_WEBHOOK_POLL_SECONDS="${WINDROSE_WEBHOOK_POLL_SECONDS:-15}"
WINDROSE_WEBHOOK_TIMEOUT="${WINDROSE_WEBHOOK_TIMEOUT:-5}"
# DO NOT pre-assign defaults for managed keys here — the merge loop
# below pulls values from the existing env file, but only if the shell
# variable is *unset*. Assigning empty strings as defaults ahead of
# time would make "operator didn't pass a CLI override" (unset)
# indistinguishable from "operator explicitly passed empty" (set to
# ""), so merge would skip the existing env's non-empty value and the
# heredoc would write the empty default. Regression 2026-04-21:
# re-running install.sh silently wiped UI_PASSWORD on the VPS. Defaults
# now live inline in the heredoc (UI_PASSWORD=${UI_PASSWORD:-}) where
# they only apply if still unset post-merge.

# Warn if the operator is reaching for a publicly-exposed UI without
# a password. Not fatal — compose / bare-Linux have legitimate
# LAN-only deploys where this is fine — but loud so it's deliberate.
if [ "${UI_BIND}" = "0.0.0.0" ] && [ -z "${UI_PASSWORD}" ] && [ "${UI_ENABLE_ADMIN_WITHOUT_PASSWORD}" != "true" ]; then
# Uses inline ${:-} so the check works even with no pre-assignment.
if [ "${UI_BIND:-127.0.0.1}" = "0.0.0.0" ] && [ -z "${UI_PASSWORD:-}" ] && [ "${UI_ENABLE_ADMIN_WITHOUT_PASSWORD:-false}" != "true" ]; then
printf '\033[33m[install] WARN: UI_BIND=0.0.0.0 with no UI_PASSWORD.\n'
printf ' The admin console will refuse destructive routes\n'
printf ' without credentials, but anyone on the internet can\n'
Expand Down Expand Up @@ -190,21 +187,50 @@ install -m 0644 "${SCRIPTS_SRC}/ServerDescription_example.json" \
install -m 0644 "${SCRIPTS_SRC}/WorldDescription_example.json" \
/usr/local/share/WorldDescription_example.json

for f in server.py index.html app.js app.css; do
# Layout mirrors the repo: server.py at the install root, ui/ siblings.
# server.py resolves STATIC_DIR as parent/ui so both paths must be
# installed together. The legacy /opt/windrose-ui symlink points at
# ${WINDROSE_INSTALL_DIR} so any existing references keep resolving
# (server.py at /opt/windrose-ui/server.py, assets at /opt/windrose-ui/ui/).
install -d -o "${WINDROSE_USER}" -g "${WINDROSE_GROUP}" "${WINDROSE_INSTALL_DIR}/ui"
install -m 0755 -o "${WINDROSE_USER}" -g "${WINDROSE_GROUP}" \
"${REPO_ROOT}/server.py" "${WINDROSE_INSTALL_DIR}/server.py"
for f in index.html app.js app.css; do
install -m 0644 -o "${WINDROSE_USER}" -g "${WINDROSE_GROUP}" \
"${SCRIPTS_SRC}/ui/${f}" "${WINDROSE_INSTALL_DIR}/scripts/ui/${f}"
"${UI_SRC}/${f}" "${WINDROSE_INSTALL_DIR}/ui/${f}"
done
chmod 0755 "${WINDROSE_INSTALL_DIR}/scripts/ui/server.py"
# server.py expects /opt/windrose-ui/ when referenced from k8s; add a
# symlink so the UI path matches the container path. Harmless if it's
# already there from a previous install.
ln -snf "${WINDROSE_INSTALL_DIR}/scripts/ui" /opt/windrose-ui
# Old install may have left a scripts/ui/ tree behind. Remove it so
# operators don't stare at stale files that no longer participate.
rm -rf "${WINDROSE_INSTALL_DIR}/scripts/ui" 2>/dev/null || true
ln -snf "${WINDROSE_INSTALL_DIR}" /opt/windrose-ui

# Idle-CPU patch script — install at /usr/local/bin/ to match the
# Docker image layout so the entrypoint's _find_patch_script hits
# the fast-path candidate. Opt in via WINDROSE_PATCH_IDLE_CPU=1 in
# the env file; the UI Idle-CPU card toggles the runtime override.
install -m 0755 "${SCRIPTS_SRC}/patch-idle-cpu.py" /usr/local/bin/patch-idle-cpu.py
# Engine.ini reconciler — keeps NetServerMaxTickRate + t.MaxFPS in
# sync with NET_SERVER_MAX_TICK_RATE across boots. Same path as the
# Docker image so the entrypoint's _reconcile_script lookup hits.
install -m 0755 "${SCRIPTS_SRC}/reconcile-engine-ini.sh" /usr/local/bin/reconcile-engine-ini.sh

# --- Polkit rule ------------------------------------------------------
# Narrowly grant the steam user systemctl start/stop/restart access on
# the windrose-* units only — so the admin UI can do a clean systemd
# stop/restart without sudo. See bare-linux/polkit/50-windrose.rules
# for the rule source + scope notes. If polkit isn't installed on this
# host, skip — the UI transparently falls back to the SIGTERM-based
# stop path (which works cross-service as long as the game runs as
# the same user as the UI, which it does).
POLKIT_RULES_DIR="/etc/polkit-1/rules.d"
if [ -d "${POLKIT_RULES_DIR}" ]; then
install -m 0644 -o root -g root \
"${SCRIPT_DIR}/polkit/50-windrose.rules" \
"${POLKIT_RULES_DIR}/50-windrose.rules"
echo "[install] polkit rule installed at ${POLKIT_RULES_DIR}/50-windrose.rules"
else
echo "[install] polkit not detected (${POLKIT_RULES_DIR} missing) — skipping rule; UI will use SIGTERM fallback"
fi

# --- Xvfb socket dir --------------------------------------------------
install -d -m 1777 /tmp/.X11-unix
Expand Down Expand Up @@ -234,6 +260,9 @@ _MANAGED_KEYS=" \
DISPLAY WINDROSE_SERVER_SOURCE SERVER_NAME MAX_PLAYER_COUNT \
IS_PASSWORD_PROTECTED SERVER_PASSWORD WORLD_ISLAND_ID WORLD_NAME \
WORLD_PRESET_TYPE P2P_PROXY_ADDRESS DISABLE_SENTRY PROTON_USE_XALIA \
USE_DIRECT_CONNECTION DIRECT_CONNECTION_SERVER_ADDRESS \
DIRECT_CONNECTION_SERVER_PORT DIRECT_CONNECTION_PROXY_ADDRESS \
NET_SERVER_MAX_TICK_RATE \
FILES_WAIT_TIMEOUT_SECONDS WINDROSE_PATCH_IDLE_CPU \
UI_BIND UI_PORT UI_PASSWORD \
UI_ENABLE_ADMIN_WITHOUT_PASSWORD UI_SERVE_STATIC \
Expand Down Expand Up @@ -280,6 +309,17 @@ elif [ -f "${WINDROSE_ENV_FILE}" ]; then
done
fi

# Apply defaults NOW (post-merge) for managed keys whose shell var
# might still be unset: a fresh install with no existing env file and
# no CLI overrides leaves these empty, and the status echo at the end
# of the script dereferences them under `set -u`. The heredoc already
# uses ${:-} for the file contents, but that doesn't touch the shell
# var. Keep these defaults in sync with the heredoc's ${:-defaults}.
: "${UI_BIND:=127.0.0.1}"
: "${UI_PORT:=28080}"
: "${UI_PASSWORD:=}"
: "${UI_ENABLE_ADMIN_WITHOUT_PASSWORD:=false}"

log "writing env file ${WINDROSE_ENV_FILE}"
tmp_env="$(mktemp)"
cat > "${tmp_env}" <<EOF
Expand All @@ -302,17 +342,28 @@ WORLD_ISLAND_ID=${WORLD_ISLAND_ID:-default-world}
WORLD_NAME=${WORLD_NAME:-Default Windrose World}
WORLD_PRESET_TYPE=${WORLD_PRESET_TYPE:-Medium}
P2P_PROXY_ADDRESS=${P2P_PROXY_ADDRESS:-}
# Direct IP Connection (Windrose 2026-04). Leave USE_DIRECT_CONNECTION
# empty to keep the default backend-connectivity mode. See README for
# when Direct IP is the right choice + the port-forwarding caveat.
USE_DIRECT_CONNECTION=${USE_DIRECT_CONNECTION:-}
DIRECT_CONNECTION_SERVER_ADDRESS=${DIRECT_CONNECTION_SERVER_ADDRESS:-}
DIRECT_CONNECTION_SERVER_PORT=${DIRECT_CONNECTION_SERVER_PORT:-7777}
DIRECT_CONNECTION_PROXY_ADDRESS=${DIRECT_CONNECTION_PROXY_ADDRESS:-0.0.0.0}
# Server tick rate (stat srvfps) — stamped into Engine.ini's
# NetServerMaxTickRate + t.MaxFPS on every boot. 30 for weak hosts,
# 120 for beefy LAN setups. Shadow-stamp preserves hand-edits.
NET_SERVER_MAX_TICK_RATE=${NET_SERVER_MAX_TICK_RATE:-60}
DISABLE_SENTRY=${DISABLE_SENTRY:-1}
PROTON_USE_XALIA=${PROTON_USE_XALIA:-0}
FILES_WAIT_TIMEOUT_SECONDS=${FILES_WAIT_TIMEOUT_SECONDS:-0}
# Opt in to the idle-CPU binary patch (scripts/patch-idle-cpu.py).
# "1" -> entrypoint patches the EXE on every start (idempotent).
# The UI Idle-CPU card can flip this per-host without editing this file.
WINDROSE_PATCH_IDLE_CPU=${WINDROSE_PATCH_IDLE_CPU:-0}
UI_BIND=${UI_BIND}
UI_PORT=${UI_PORT}
UI_PASSWORD=${UI_PASSWORD}
UI_ENABLE_ADMIN_WITHOUT_PASSWORD=${UI_ENABLE_ADMIN_WITHOUT_PASSWORD}
UI_BIND=${UI_BIND:-127.0.0.1}
UI_PORT=${UI_PORT:-28080}
Comment on lines +363 to +364

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep UI bind vars initialized for strict-shell execution

On a fresh bare-linux install (no existing env file and no CLI overrides), this expansion only writes defaults into the generated env file; it does not assign shell variables in-process. Because the script runs with set -u, later status output that dereferences ${UI_BIND}/${UI_PORT} aborts with an unbound-variable error, causing install.sh to exit non-zero after doing the install work.

Useful? React with 👍 / 👎.

UI_PASSWORD=${UI_PASSWORD:-}
UI_ENABLE_ADMIN_WITHOUT_PASSWORD=${UI_ENABLE_ADMIN_WITHOUT_PASSWORD:-false}
UI_SERVE_STATIC=${UI_SERVE_STATIC:-true}

# Webhook notifications — Discord embed + generic JSON POST. Leave URLs
Expand All @@ -324,11 +375,11 @@ UI_SERVE_STATIC=${UI_SERVE_STATIC:-true}
# player.join / player.leave — AccountId appears in / drops from snapshot
# backup.created / backup.restored — /api/backups activity
# config.applied — admin console Apply + restart path
WINDROSE_DISCORD_WEBHOOK_URL=${WINDROSE_DISCORD_WEBHOOK_URL}
WINDROSE_WEBHOOK_URL=${WINDROSE_WEBHOOK_URL}
WINDROSE_WEBHOOK_EVENTS=${WINDROSE_WEBHOOK_EVENTS}
WINDROSE_WEBHOOK_POLL_SECONDS=${WINDROSE_WEBHOOK_POLL_SECONDS}
WINDROSE_WEBHOOK_TIMEOUT=${WINDROSE_WEBHOOK_TIMEOUT}
WINDROSE_DISCORD_WEBHOOK_URL=${WINDROSE_DISCORD_WEBHOOK_URL:-}
WINDROSE_WEBHOOK_URL=${WINDROSE_WEBHOOK_URL:-}
WINDROSE_WEBHOOK_EVENTS=${WINDROSE_WEBHOOK_EVENTS:-server.online,server.offline,player.join,player.leave,backup.created,backup.restored,config.applied}
WINDROSE_WEBHOOK_POLL_SECONDS=${WINDROSE_WEBHOOK_POLL_SECONDS:-15}
WINDROSE_WEBHOOK_TIMEOUT=${WINDROSE_WEBHOOK_TIMEOUT:-5}
EOF

# Preserve operator-added keys (anything NOT in _MANAGED_KEYS that
Expand Down Expand Up @@ -412,7 +463,7 @@ EnvironmentFile=${WINDROSE_ENV_FILE}
# works. The k8s side gets this via shareProcessNamespace; on bare
# Linux the UI already sees the whole host PID namespace by default,
# so no twist needed — this just confirms the expectation.
ExecStart=/usr/bin/python3 ${WINDROSE_INSTALL_DIR}/scripts/ui/server.py
ExecStart=/usr/bin/python3 ${WINDROSE_INSTALL_DIR}/server.py
Restart=always
RestartSec=5

Expand All @@ -423,6 +474,13 @@ WantedBy=multi-user.target
# --- Reload + enable --------------------------------------------------
systemctl daemon-reload
systemctl enable --now windrose-xvfb.service windrose-ui.service windrose-game.service
# `enable --now` is a no-op on services that are already running, so
# re-runs of install.sh (e.g. picking up new UI code) wouldn't restart
# them — the Python process would keep the old server.py in memory.
# try-restart bounces only the services that were already running, so
# fresh installs aren't double-started and upgrades actually pick up
# new code without the operator having to chase extra systemctl calls.
systemctl try-restart windrose-ui.service windrose-game.service

log "done."
echo
Expand Down
42 changes: 42 additions & 0 deletions bare-linux/polkit/50-windrose.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// /etc/polkit-1/rules.d/50-windrose.rules
//
// Narrowly grants the 'steam' user start/stop/restart on the windrose-*
// systemd units via systemctl, without requiring sudo or a password prompt.
// Installed by bare-linux/install.sh.
//
// Scope of this rule — intentionally tight:
// - Subject: user must be literally 'steam' (the system account the
// admin UI runs as). Root and other users are unaffected.
// - Action: only org.freedesktop.systemd1.manage-units (systemctl
// start/stop/restart/reload variants), NOT manage-unit-files (which
// would allow enable/disable/mask/daemon-reload — privileged).
// - Unit: only the three windrose-* unit names that install.sh writes.
// Any other unit (ssh.service, docker.service, etc.) falls through
// to polkit's default "ask for auth" policy.
//
// If you rename any of the units (see WINDROSE_UNIT_NAME env in
// server.py), update this list to match, or the systemctl dispatch path
// will silently fall back to the SIGTERM-based stop in server.py.
polkit.addRule(function(action, subject) {
if (action.id !== "org.freedesktop.systemd1.manage-units") {
return undefined;
}
if (subject.user !== "steam") {
return undefined;
}
var unit = action.lookup("unit") || "";
var allowed_units = {
"windrose-game.service": true,
"windrose-xvfb.service": true,
"windrose-ui.service": true,
};
if (!allowed_units[unit]) {
return undefined;
}
var verb = action.lookup("verb") || "";
if (verb === "start" || verb === "stop" || verb === "restart" ||
verb === "reload" || verb === "reload-or-restart") {
return polkit.Result.YES;
}
return undefined;
});
14 changes: 14 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ services:
# 0.0.0.0 breaks client-connect — the game advertises it verbatim as
# its ICE host candidate and Windows clients bounce to the main menu.
P2P_PROXY_ADDRESS: ${P2P_PROXY_ADDRESS:-}
# Direct IP Connection (Windrose 2026-04). Leave USE_DIRECT_CONNECTION
# empty to keep the default backend-connectivity mode. Set to true
# to advertise a raw IP:port instead — invite codes stop working in
# this mode, clients join only via DIRECT_CONNECTION_SERVER_ADDRESS.
# When enabled and the address is empty, entrypoint reuses
# P2P_PROXY_ADDRESS. Requires manual port-forwarding on your router.
USE_DIRECT_CONNECTION: ${USE_DIRECT_CONNECTION:-}
DIRECT_CONNECTION_SERVER_ADDRESS: ${DIRECT_CONNECTION_SERVER_ADDRESS:-}
DIRECT_CONNECTION_SERVER_PORT: ${DIRECT_CONNECTION_SERVER_PORT:-7777}
DIRECT_CONNECTION_PROXY_ADDRESS: ${DIRECT_CONNECTION_PROXY_ADDRESS:-0.0.0.0}
# Server tick rate (stat srvfps) — stamped into Engine.ini's
# NetServerMaxTickRate + t.MaxFPS on every boot. 30 for weak
# hosts, 120 for beefy LAN setups. See README for full context.
NET_SERVER_MAX_TICK_RATE: ${NET_SERVER_MAX_TICK_RATE:-60}
WINDROSE_CONFIG_MODE: ${WINDROSE_CONFIG_MODE:-env}
WINDROSE_LAUNCH_STRATEGY: ${WINDROSE_LAUNCH_STRATEGY:-shipping}
FILES_WAIT_TIMEOUT_SECONDS: ${FILES_WAIT_TIMEOUT_SECONDS:-0}
Expand Down
Loading
Loading