- Go 89.4%
- PowerShell 9.5%
- Dockerfile 0.6%
- Makefile 0.5%
chore: Configure Renovate See merge request software/windrose-windows-agent!1 |
||
|---|---|---|
| .forgejo/workflows | ||
| cmd/windrose-agent | ||
| docs | ||
| internal | ||
| scripts | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
| renovate.json | ||
windrose-windows-agent
Tiny Go REST agent that runs on a native Windows host alongside the
Steam Windrose Dedicated Server. It is consumed by
windrose-webgui-server (Linux Docker)
over Tailscale + a static Bearer token.
Designed against ADR-005 (multi-backend abstraction) and ADR-006 (Windows agent stack).
CI / runner setup: see docs/CI-SETUP.md.
What it does
- Wraps
windrose-server.exeas a Windows service (WindroseServer) via NSSM. - Exposes a small HTTP API the panel calls to start / stop / restart the server, query status, and stream logs (SSE).
- Listens only on a Tailscale IP (recommended bind:
100.x.y.z:7878). Tailscale provides transport encryption — agent serves plain HTTP.
API (v0.2)
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /api/v1/health |
no | liveness |
| GET | /api/v1/status |
yes | service state |
| POST | /api/v1/start |
yes | start service |
| POST | /api/v1/stop |
yes | stop (90s timeout) |
| POST | /api/v1/restart |
yes | stop+start |
| GET | /api/v1/logs/stream |
yes | SSE tail of server log |
| GET | /api/v1/config/{server|world} |
yes | read JSON config |
| PUT | /api/v1/config/{server|world} |
yes | write JSON config (409 while running) |
| GET | /api/v1/backups |
yes | list tar.gz archives |
| POST | /api/v1/backups |
yes | create backup of Saves dir |
| DELETE | /api/v1/backups/{name} |
yes | delete archive |
| GET | /api/v1/mods |
yes | list .pak mods |
| POST | /api/v1/mods |
yes | upload mod (multipart, max 200 MiB) |
| DELETE | /api/v1/mods/{name}?confirm=true |
yes | delete mod |
| POST | /api/v1/rcon |
yes | proxy wp.* to Windrose+ (F9, gated) |
Auth header: Authorization: Bearer <WINDROSE_AGENT_TOKEN>.
Examples
# Status
curl -H "Authorization: Bearer $TOKEN" http://$AGENT/api/v1/status
# Read server config
curl -H "Authorization: Bearer $TOKEN" http://$AGENT/api/v1/config/server
# Write server config (service must be stopped)
curl -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
--data @ServerDescription.json http://$AGENT/api/v1/config/server
# List backups
curl -H "Authorization: Bearer $TOKEN" http://$AGENT/api/v1/backups
# Create backup (service must be stopped if BACKUP_REQUIRE_STOPPED=true)
curl -X POST -H "Authorization: Bearer $TOKEN" http://$AGENT/api/v1/backups
# Delete backup
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
http://$AGENT/api/v1/backups/windrose-20260510T120000Z.tar.gz
# Upload mod
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "X-File-Name: MyMod.pak" \
-F file=@./MyMod.pak \
http://$AGENT/api/v1/mods
# Delete mod
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"http://$AGENT/api/v1/mods/MyMod.pak?confirm=true"
Install (Windows host)
- Build the binary on a Linux/macOS box:
This producesmake windowsbin/windrose-agent.exe. - Copy the repo (or just
bin/windrose-agent.exeandscripts/install.ps1) to the Windows host. - Open an elevated PowerShell and run:
The script:.\scripts\install.ps1 -InstallPath "C:\Path\To\WindroseDedicatedServer"- downloads NSSM,
- registers
WindroseServerandWindroseAgentservices, - generates a 32-byte Bearer token,
- adds Defender exclusions,
- prints the agent URL + token at the end.
Configure the panel
Set these in the panel's environment:
WINDROSE_BACKEND=winagent
WINDROSE_AGENT_URL=http://<tailscale-ip>:7878
WINDROSE_AGENT_TOKEN=<token from install.ps1 output>
Environment variables (agent)
| Var | Default | Required | Notes |
|---|---|---|---|
WINDROSE_AGENT_TOKEN |
— | yes | min 32 chars |
WINDROSE_AGENT_BIND |
127.0.0.1:7878 |
no | use Tailscale IP in prod |
WINDROSE_INSTALL_PATH |
— | yes | server install dir |
WINDROSE_AGENT_LOG_PATH |
C:\Windrose\logs\server.log |
no | NSSM stdout target |
WINDROSE_CONFIG_DIR |
C:\Windrose\config |
no | reserved |
WINDROSE_BACKUP_DIR |
C:\Windrose\backups |
no | tar.gz archive root |
WINDROSE_MODS_DIR |
<InstallPath>\Mods |
no | .pak directory |
WINDROSE_SAVES_DIR |
<InstallPath>\R5\Saved |
no | what backups archive |
WINDROSE_SERVER_CONFIG_PATH |
<InstallPath>\R5\Saved\Config\WindowsServer\ServerDescription.json |
no | served by /config/server |
WINDROSE_WORLD_CONFIG_PATH |
<InstallPath>\R5\Saved\Config\WindowsServer\WorldDescription.json |
no | served by /config/world |
WINDROSE_SERVICE_NAME |
WindroseServer |
no | NSSM service name |
WINDROSE_MAX_UPLOAD_MB |
200 |
no | upload cap |
BACKUP_RETENTION_DAYS |
7 |
no | auto-prune older archives |
BACKUP_REQUIRE_STOPPED |
true |
no | refuse backup if running |
WINDROSE_AGENT_LOG_LEVEL |
info |
no | debug/info/warn/error |
WINDROSE_RCON_ENABLED |
false |
no | F9 — gate /api/v1/rcon |
WINDROSE_RCON_URL |
http://127.0.0.1:8780/api/rcon/command |
no | Windrose+ HTTP RCON |
WINDROSE_RCON_PASSWORD |
— | no | sent as X-Auth + ?password= |
RCON (F9)
When WINDROSE_RCON_ENABLED=true, the agent exposes POST /api/v1/rcon
accepting { "command": "wp.players" }. The handler validates the command
against ^wp\.[a-z]+(\s+[\w._-]+)*$, forwards it to WINDROSE_RCON_URL
with both X-Auth header and ?password= query string (Windrose+ builds
vary), and returns { "output": "..." } to the panel. Disabled by default —
operators opt in during install.ps1 or by editing agent.env.
Build
make build # native (Linux/macOS) — Windows code stubbed out
make windows # cross-compile windrose-agent.exe
make tidy
Repo layout
cmd/windrose-agent/ entry point
internal/config/ env loading + validation
internal/server/ HTTP + middleware (auth, logging)
internal/handlers/ health, status, lifecycle, logs
internal/svc/ Windows SCM wrapper (build-tagged)
internal/fs/ path-traversal-safe filesystem helpers
scripts/install.ps1 Windows bootstrap
Dockerfile Linux cross-compile for CI