Refuge Game Server Panel (RGSP) is an open-source, self-hosted control plane for game servers. One controller orchestrates many agents; each agent runs Docker on its host and manages game-server containers. Modules describe how each game runs — image, ports, env vars, RCON dialect, ready-line patterns. The panel handles install, start/stop, console, files, configs, backups, schedules, and (where supported) mods + clusters.
Designed for the use case of "I run game servers for friends and want to stop SSH-ing into hosts to babysit them." RGSP doesn't sell you anything; everything ships in the binary. No telemetry, no phone-home, no license server.
RGSP is a distributed Controller / Target design. Two binaries:
| Component | What it does | Where it runs |
|---|---|---|
| Controller | HTTP/SSE web UI, gRPC server for agents, persists state in Postgres, embeds module manifests, dispatches commands. | Anywhere reachable on the LAN. Ports 8080 (HTTP, dev) or 8180 (HTTP, prod), 8443 (gRPC). |
| Agent | Dials the controller via mTLS, executes Docker operations locally (create / start / stop / delete / exec), streams logs + stats back. | On every host where you want to run servers. Docker daemon must be installed. |
| Modules | YAML files declaring how each game runs: image, ports, env, RCON adapter, browseable roots, backup paths, ready-line regex. | Loaded by the agent at startup from /modules/. |
Browser ──HTTP/SSE──▶ Controller ──gRPC/mTLS──▶ Agent ──docker.sock──▶ Container
│ │
▼ ▼
Postgres Docker volumes
panel.instance_id=<id> and survive every recreate unless the operator explicitly purges them via the Danger Zone.config_values column. The recreate dance always re-injects them so per-instance settings persist across container lifetimes.update_provider runs to download the game's binaries.
First-time installs can take 5–30 minutes per game (SteamCMD pacing). Subsequent restarts are seconds.
The main page is a flat dense grid of every server across every agent — no per-agent collapsible panes. Density is configurable via Settings → Card density.
The topbar's health pill shows the same running/total + online/total agents in compact form. The pill at the right-side ⚙ opens app settings.
Each server is a glass tile with the game's art as the background:
module · :port · 12/30 players, then thin CPU + RAM bars.Tile cards diff-render in place — the polling tick never causes flashing. CPU/RAM bars and the player count update live via SSE between polls.
Above the grid:
The per-instance modal has a head bar with the server name, status, primary actions, and a tab strip of up to ten tabs (game-specific tabs are hidden when not relevant).
| Tab | What you can do |
|---|---|
| Console | Live log + RCON send. History from the controller's ring buffer; live via SSE. |
| Players | RCON-driven roster (where the game supports it). |
| Config | Launch settings, ports, INI/properties editor — staged + single Apply. |
| Files | Multi-root file browser, edit any text file with a real search bar. |
| Backups | tar.gz snapshots; back up now / restore / delete. |
| Schedules | cron + event-CEL triggers per instance. |
| Updates | Manually trigger the module's update_provider (SteamCMD, etc.). |
| Settings | Overview, Maintenance (Rebuild container), Danger Zone (Delete + purge). |
| Mods (ARK / DayZ) | CurseForge / Workshop mod manager — staged + Apply. |
| Cluster (ARK) | One-click cluster creation + transfer permissions. |
Renders log/player/instance_state events from the controller's 500-event ring buffer, then opens an SSE stream for live tail. The line at the bottom is an RCON sender — protocol depends on the module (Source RCON, BattlEye, Telnet, Stdio).
Type the first token of an RCON command; a dropdown shows matching commands with short descriptions. ↑/↓ to navigate, Tab/Enter to insert, Esc to dismiss. Click the 📖 button next to Send for the full command reference, filterable by name/args/desc.
Supported registries: DayZ, ARK SA, 7DTD, Minecraft Java, Palworld, Rust, Conan Exiles, Valheim. Unregistered modules show a friendly "no reference yet" placeholder.
Console is a pure server-side log view — no panel-status chatter. Connection retries happen silently. When the server is stopped you'll see a subtle one-liner; nothing more.
Live roster derived from periodic RCON polls (every 30s by default — the agent runs ListPlayers / list / lp per the module's state_sources). Auto-refreshes every 5 seconds while the tab is open. Headline shows online / max.
Games that don't expose a programmatic roster show a "no data yet — RCON may still be connecting" hint until the first successful poll lands.
The Config tab is unified: a single navigation rail on the left, one focused pane on the right, and a sticky Apply footer that batches dirty changes across all three scopes:
SESSION_NAME, MAX_PLAYERS, BATTLEYE, etc.). Saving recreates the container. Volumes (world data, saves, configs) survive.GameUserSettings.ini). Effective on next start.Each setting renders as a card with a friendly name, the underlying config key as a ghost caption, an inline hint, and the value control. Empty fields show schema defaults as placeholder text so you can see "what good values look like" without us pre-filling them. Dirty cards get a yellow accent stripe + dot.
Top-right of each pane: type to filter cards on the current pane by name / key / hint. Useful when the INI has 20+ settings in one group.
One sticky footer button counts dirty fields across all panes, e.g. "3 changes pending (1 launch, 2 settings)". Click Apply N changes; the panel writes the INI first, then runs at most one recreate (for ports + env). If a launch setting declares a wipe path (e.g. changing MAP_NAME wipes SavedArks), you get a per-wipe-path checkbox in the confirm dialog before anything happens.
If you stage env and port changes together, Apply runs two recreates back-to-back (~2 min each on heavy games like ARK). Group port edits with each other when you can.
A multi-root file browser tailored to whatever the module declared in browseable_roots. For 7DTD: Saves & Configs + Game Files. For ARK: just ShooterGame/Saved. Single-root modules drop the picker.
panel-<id>-fs (a tiny debian:12-slim + sleep infinity sidecar) mounting the same volumes, so you can edit configs without starting the game.Large modal (up to 1280×920) with a real search bar:
0/N. Enter = next, Shift+Enter = previous, Esc = clear.tar.gz snapshots of whatever the module's backup.include covers (saves, configs, logs — not game install or shared cluster mounts). Click Back up now to create one. Restore on a snapshot overwrites live data — the panel warns first.
Backups are timestamped and per-instance; nothing is uploaded off-host. Files live under the panel's data dir.
The toolbar's 📅 Auto-backup dropdown installs a cron schedule that fires the same Back-up-now action on a regular cadence (every 6h / 12h / 24h / weekly). Pick Off to remove it. Each auto-created snapshot is a complete tar.gz, tagged with an Auto chip in the list so you can tell it from a manual one.
For ARK Survival Ascended a separate dropdown 🌍 SaveWorld RCON appears next to 📅 Auto-backup. Pick a cadence (every 15 min / 30 min / hour) and the panel installs a cron that fires the SaveWorld RCON command. This is not a backup — it just tells ARK to flush its current world to the in-place save files, useful between full backups on busy servers so a crash loses less progress.
RCON only answers while the server is running, so a fire while stopped just no-ops with an error in the schedule log.
Both dropdowns persist as regular schedules — you'll see them on the Schedules tab labeled Auto-backup … and Auto SaveWorld …. Editing or deleting them there is fine; the dropdown re-detects state on next open.
Per-instance schedules with two trigger kinds:
0 4 * * * = daily 4am).Each schedule has an action (Start / Stop / Restart / RCON command / Backup). Toggle enable/disable per row.
Lists every update_provider declared by the module (usually one — SteamCMD). Click Update to dispatch; the agent runs the provider in a sidecar container and streams progress to the Console.
Most modules also auto-update on Restart via the image's built-in updater (acekorneya's ARK image, etc.) — the Updates tab is for forcing a manual run.
Three sections inside the Manage modal's Settings tab (not to be confused with the app-wide Settings cog in the topbar):
Default delete preserves volumes. Only the Danger Zone purges. The "purge data" toggle was deliberately separated so a routine delete doesn't blow away worlds.
RGSP runs ARK SA on the acekorneya/asa_server image (Wine + Proton + the dedicated server). The image has a built-in CurseForge mod downloader and SteamCMD updater.
When viewing an ARK SA instance, two extra tabs appear in the Manage modal: 🧩 Mods and 🔗 Cluster.
A staged mod manager backed by the CurseForge API (via the api.curse.tools proxy — no key needed). Each mod ID lives in either the active (-mods=) or passive (-passivemods=) launch arg. ASA downloads the binaries on next start.
The big change from per-mod-recreate: nothing touches the container until you click Apply.
-mods= launch arg. Clients must subscribe; mod content is visible in-game.-passivemods= launch arg. Server-only balance / admin tools that don't force a client download.Tick the "Install as server-side (passive)" checkbox before staging to drop a mod into the passive list.
Clustering lets multiple ARK servers share a transfer directory so survivors / dinos / items can move between maps via obelisks. RGSP makes this one-click:
data/arkcluster/<cid>) or Join existing cluster.CLUSTER_ID on each member, bind-mounts the shared folder into each container's default cluster path, and recreates them. Members appear in the registry under data/ark-clusters.json.Per-server controls live in the Config tab → Cluster transfers group. Six toggles, all written to [ServerSettings] of GameUserSettings.ini:
Default is permissive (transfers in both directions). Restart the server after toggling for the new INI to take effect.
Smoke test for a working cluster: have a player upload a survivor at an obelisk. Within seconds a <cluster_id>/<steam64> file should appear under data/arkcluster/<cid> on the host. No file = transfers aren't actually writing to the bind mount.
DayZ has its own mod manager (Steam Workshop, asynchronous via SteamCMD) plus an XML integration tab that helps merge mod-provided types.xml / cfgspawnabletypes.xml / etc. into your live mission folder.
Search Workshop, click Install — a SteamCMD job queues; progress streams into a per-mod row. When the download finishes, the mod is added to -mod=. Existing mods can be uninstalled or reordered.
The XML tab fetches the mod's recommended XML files, shows you a 3-pane diff (current / mod's contribution / preview), and lets you commit the merge to the mission folder. The merger preserves comments and unknown elements so hand-edits aren't clobbered.
Each game RGSP supports is a module — a YAML manifest declaring how it runs. Currently shipped:
| Module | Image | RCON | Notes |
|---|---|---|---|
| ARK Survival Ascended | acekorneya/asa_server | Source RCON | CurseForge mods, clusters, BattlEye |
| 7 Days to Die | panel-7dtd | Telnet | Multi-root files (Saves + Game) |
| Minecraft Java | itzg/minecraft-server | Stdio | Vanilla / Forge / Fabric / Paper |
| Valheim | panel-valheim | Source RCON | — |
| V Rising | panel-v-rising | RCON optional | Full env-config; CLI-flag entrypoint |
| Empyrion | panel-empyrion | — | Wine + Unity; log-only ready signal |
| Palworld | panel-palworld | Source RCON | — |
| Rust | panel-rust | WebRCON | EAC handshake delays "ready" |
| DayZ | panel-dayz | BattlEye RCON | Workshop mods, XML merger |
| Conan Exiles | panel-conan-exiles | Source RCON | Match-state ready signal |
| Project Zomboid, Factorio, Satisfactory, Barotrauma, Enshrouded, Terraria, Core Keeper, Soulmask, Sons of the Forest, Windrose, Dragonwilds | Various — see each module's module.yaml for specifics. | ||
23 modules registered as of this writing. Adding a new game is a couple of YAML files + a Dockerfile.
An agent is a lightweight Go binary that connects to the controller over mTLS gRPC and executes Docker operations on its host. One controller can manage many agents — useful for distributing servers across multiple physical hosts.
The dashboard's filter strip shows a chip per agent — click to filter to that agent's servers. Each tile carries an agent chip in its top-left corner. + New server asks which agent to create on.
If you remove an agent that still has servers, those servers become orphan instances — their agent_id doesn't resolve anymore. They appear in the Manage agents drawer marked "orphaned" with a warning style. You can still delete them, but Start/Stop won't work until you re-pair the agent or migrate them.
Each module declares a list of ports in its YAML (game / RCON / query / etc.). When you create a server, the agent picks free host ports from the agent's configured range (default 10000–30000) and stores the assignments per-instance. The Config tab → Network ports lets you override any port; the panel detects collisions client-side as you type.
Ports marked internal in the manifest bind to 127.0.0.1 only and don't show up as configurable knobs on the tile or in the Network ports group. RCON usually rides an internal port — the agent dials it locally, no external exposure needed.
Forward each game's external port to the agent host. For multi-NAT setups (e.g. WireGuard over a VPS), make sure the destination-NAT rule's interface matches the path traffic actually arrives on, not just the WAN. The kernel UDP counter trick (/proc/net/snmp Udp:NoPorts) helps verify whether forwarded UDP is making it to the agent at all.
Click Steam Accounts in the sidebar to manage Steam linkage:
The cog button in the topbar (or the Settings link in the sidebar) opens the app-wide settings modal. All values persist to localStorage under rgsp.settings.v1.
RGSP ships a single CLI binary, panelctl, with two surfaces. The day-to-day surface talks to the controller's gRPC and covers normal lifecycle (start, stop, RCON, watch). The admin surface is HTTP and is meant for out-of-band repair — anything that requires a recreate of the underlying container, like changing an env-config knob or re-attaching an ARK cluster's bind mount, without needing to be logged into the dashboard.
You don't need this for normal use — the dashboard already does everything panelctl admin does. Reach for the CLI when the dashboard is down, when you're SSH'd into the controller host doing a recovery, or when you want to script a repair across several servers.
panelctl agents
panelctl instances [agent-id]
panelctl create <agent-id> <instance-id> <module-id>
panelctl start <agent-id> <instance-id>
panelctl stop <agent-id> <instance-id> [grace-seconds]
panelctl delete <agent-id> <instance-id> [--purge]
panelctl update <agent-id> <instance-id> [provider-id]
panelctl rcon <agent-id> <instance-id> <command...>
panelctl watch [instance-id]
panelctl backup create <agent-id> <instance-id> [description]
panelctl backup restore <instance-id> <backup-id>
Defaults to --controller localhost:8443. From a different host you'd point that at the controller's gRPC address.
panelctl admin token-show
panelctl admin status
panelctl admin instances
panelctl admin ark-rebind <instance-id>
panelctl admin set-config <instance-id> KEY=VALUE [KEY=VALUE...]
panelctl admin del-config <instance-id> KEY [KEY...]
panelctl admin recreate <instance-id> [--mount CONTAINER_PATH=HOST_PATH ...]
Three flags govern where the CLI looks:
--http URL — controller HTTP base. Default http://localhost:8080 (dev). The prod controller listens on :8180; pass --http http://localhost:8180 or set PANEL_HTTP.--admin-token TOK — explicit token. Defaults to $PANEL_ADMIN_TOKEN, then to reading <data-dir>/admin-token.--data-dir DIR — where the admin-token file lives. Default ./data; on prod, /home/refuge/panel/data.An admin request is admitted if either:
127.0.0.1 or ::1) — running the CLI on the controller host needs no token, ORX-Panel-Admin-Token header (constant-time compared) matches the on-disk token.The token is auto-generated on first controller start and written to <data-dir>/admin-token at mode 0600 — only the panel user can read it. There is no rotation API; to rotate, delete the file and restart the controller (a fresh token gets written on the next boot).
The admin token is an ambient bearer credential. Anyone who can read the file can drive the admin surface from anywhere. Treat it like an SSH private key — don't paste it into screenshots, chat, or commit it.
SSH into the controller host first; from there, loopback bypass means no token is needed.
# See everything the panel knows about, with config-key counts and cluster ids
panelctl admin --http http://localhost:8180 --data-dir /home/refuge/panel/data instances
# Change an env-driven config value (triggers a stop → recreate → start dance)
panelctl admin --http http://localhost:8180 --data-dir /home/refuge/panel/data \
set-config ragnarok MAP_NAME=Ragnarok_WP
# Remove an env override entirely (the manifest default re-applies)
panelctl admin --http http://localhost:8180 --data-dir /home/refuge/panel/data \
del-config ragnarok CUSTOM_SERVER_ARGS
# Re-run an ARK cluster's mount recreate (uses the cluster registry to find the right cluster id)
panelctl admin --http http://localhost:8180 --data-dir /home/refuge/panel/data \
ark-rebind ragnarok
# Generic recreate with no config change — cluster mount overrides preserved automatically
panelctl admin --http http://localhost:8180 --data-dir /home/refuge/panel/data \
recreate ragnarok
panelctl create / panelctl delete.systemctl restart panel-agent.panelctl watch <id> against the gRPC stream.| Where | Keys | Action |
|---|---|---|
| Anywhere | Esc | Close the topmost modal |
| File editor | Ctrl+S / Cmd+S | Save current file |
| File editor | Ctrl+F / Cmd+F | Focus the in-file search |
| File editor search | Enter | Next match |
| File editor search | Shift+Enter | Previous match |
| File editor search | Esc | Clear search + hide highlight |
| Console | ↑ / ↓ | Navigate autocomplete suggestions |
| Console | Tab / Enter | Insert selected suggestion |
Most games take a while to actually become joinable. The pill clears when either the game's RCON answers (most modules) or a known "ready" log line shows up (ARK, Empyrion, Windrose, Conan, Rust). If the server is running cleanly but the pill never clears, the module's READY_PATTERNS regex may be too strict — check the Console for the actual ready line; a 10-minute uptime fallback also clears it eventually.
Local art lives at /game-art/<module-id>.{jpg,png} embedded in the controller binary. If a module shows just an emoji + gradient, no art is bundled for that game yet. Fallback to Steam CDN works for most Steam games.
Steam's CDN serves at whatever rate it picks; the panel doesn't shape SteamCMD traffic. Slow first installs are normal — subsequent restarts pull only changed files (usually seconds).
The Config tab's Network ports pane catches duplicates client-side as you type and disables Apply until you fix them. If a collision sneaks through (e.g. another container outside the panel grabbed the port), the agent returns an error toast — pick a different host port.
ARK and DayZ both download mod binaries on next start after the env changes. Apply queues the change; the actual download happens during the boot sequence. Watch the Console — first-boot mod downloads can take several minutes.
The agent reconnects automatically with backoff. If it stays offline, check that the agent process is running (systemctl status panel-agent on the agent host) and that the controller's gRPC port is reachable from there. The agent dials the controller — controller never connects to the agent — so firewalls only need to permit outbound from the agent.
controller/cmd/controller/ # the Go binary that serves the UI + gRPC
controller/internal/... # DB, events, log buffer, etc.
agent/cmd/agent/ # the Go binary that runs on each host
agent/internal/... # dispatcher, docker driver, RCON adapters
proto/panel/v1/ # gRPC schema (buf-driven, remote plugins)
modules/<game>/ # YAML manifest + Dockerfile + entrypoint
pkg/ # shared utilities (dayzxml merger, etc.)
controller/cmd/controller/static/ # the UI: one big index.html
controller/cmd/controller/static/game-art/ # bundled hero art
# cross-compile for Linux from any host
GOOS=linux GOARCH=amd64 go build -o bin/controller-linux ./controller/cmd/controller
GOOS=linux GOARCH=amd64 go build -o bin/agent-linux ./agent/cmd/agent
Static assets (HTML/CSS/JS, art, module YAMLs) are embedded into the binary via //go:embed — there is no separate JS/CSS build step.
modules/<game>/module.yaml declaring image, ports, env, RCON, ready-line, browseable roots.Dockerfile + entrypoint.sh in the same dir.moduleAppearance in static/index.html (emoji + gradient + optional art).READY_PATTERNS regex if the module is log-only-ready (no usable RCON readiness signal).configSchemas entry for in-panel INI editing, and an ENV_CONFIG_SCHEMA entry for env-driven config.Refuge Game Server Panel — open-source, single-binary, multi-agent control plane for self-hosted game servers. No telemetry. No license server. No paid tier. Built because every existing alternative either (a) charges per server, (b) is closed-source, or (c) was clearly designed for a hosting company instead of a person who runs servers for friends.
RGSP is licensed MIT. PRs welcome.
Co-authored with Claude Opus.
{min} is replaced with remaining minutes):Generate a one-time token, then run this on the machine you want to add:
Run on the new host:
Then start the agent normally — it'll auto-detect the cert files and connect with mTLS.
This module uses a SteamCMD app that won't download anonymously. Provide a Steam account that owns the game in its Steam library — the panel stores your password encrypted at rest and only uses it for SteamCMD.
The password is encrypted with AES-GCM keyed on the panel's internal CA key before it hits disk. It's never logged, never echoed in the Console tab, and never returned to the browser once stored. SteamCMD caches an SSFN device-auth blob after a successful 2FA challenge so subsequent updates skip the code prompt.