panel v0.9.1 — open-source game server manager

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 23:18:05 -07:00
commit 4cf3471398
2161 changed files with 300831 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
# ARK: Survival Ascended — panel module
A panel-compatible module for running ARK: Survival Ascended (SA) dedicated
servers on any agent with Docker available.
## What this is
A thin manifest wrapper around `mschnitzer/ark-survival-ascended-server`
the most actively-maintained community image for ASA on Linux. That image
handles the Proton runtime (ASA has no native Linux server) + SteamCMD +
the Windows binary wrapper.
## First-run flow
1. **Create** the server in the panel (pick `ARK: Survival Ascended` from
the module picker).
2. Click **Update** on its card → our generic SteamCMD sidecar downloads
~18 GB into the instance's `install` volume.
3. Open **Config** → under *Identity*, set the server name and admin
password. Save.
4. Click **Start**. First map load takes 35 minutes — watch the **Console**
tab; when you see `Server started on port 7777`, it's ready.
## RAM
ASA is a hog. Plan for:
- **12 GB** minimum for `TheIsland_WP` with up to ~30 players
- **16 GB** comfortable
- **24 GB** if you're running a popular community
## Changing maps
Set `SERVER_MAP` in the container env (the mschnitzer image reads it at
boot). Valid values include:
- `TheIsland_WP`
- `TheCenter_WP`
- `ScorchedEarth_WP`
- `Aberration_WP`
- `Extinction_WP`
- `Astraeos_WP`
- `ClubArk_WP`
After changing the map, click **Stop → Start**.
## Using your own image
Replace the `image:` field in `module.yaml` with your Docker image. Keep
the volume mounts (`/home/steam/ASA/AppFiles` and
`/home/steam/ASA/AppFiles/ShooterGame/Saved`) so SteamCMD's output lands
where ASA expects and saves persist across updates.
## Known quirks
- **First start is slow** (35 min). This is Proton loading + ARK doing
its world init. Subsequent starts are ~90 seconds.
- **Windows file locks** — don't run Update while the server is running;
SteamCMD will conflict with ARK's open file handles.
- **EAC is enabled by default.** If you want to run mods that EAC blocks,
disable via `-NoBattlEye` in the server args (community image exposes
an env for this).
+379
View File
@@ -0,0 +1,379 @@
# ARK: Survival Ascended — dedicated server module.
#
# ASA has no native Linux server binary — it runs the Windows build under
# Proton. Rather than bundle our own 500 MB+ Wine/Proton image, this module
# uses `acekorneya/asa_server:latest`, the most actively-maintained
# community image for ASA on Linux. It packages:
#
# - Proton + Wine runtime
# - SteamCMD (handles install + auto-update on start)
# - An entrypoint that reads env vars and assembles the ASA command line
# - BattlEye / RCON / cluster flags
#
# So: the SteamCMD-style "download game via our sidecar" pattern we use for
# 7DTD doesn't apply here — the image downloads ASA itself on first boot
# via its internal SteamCMD. No external update provider needed, which is
# why this module has NO `update_providers:` section (the `Update` button
# won't do anything useful for ASA; just restart the server to pull game
# patches — the image auto-updates when UPDATE_SERVER=true is set).
#
# Operators who want to own the stack can publish their own image (Proton
# + SteamCMD + ASA) and swap `image:` below. Keep the env vars the same —
# the community entrypoint reads `SESSION_NAME`, `SERVER_ADMIN_PASSWORD`,
# `SERVER_MAP` etc.
#
# First-run flow:
# 1. Create the server in the panel → empty container + volume.
# 2. Click Start → the image's entrypoint runs SteamCMD inside the
# container and downloads ~18 GB of ASA into /home/pok/arkserver.
# First boot takes 1530 minutes depending on bandwidth.
# 3. After the install finishes the server actually starts. Open Console
# and wait for "Server started on port 7777".
#
# Subsequent starts are quick (<= 2 min) since the files are cached on the
# named volume.
id: ark-sa
name: "ARK: Survival Ascended"
version: 0.3.0
authors:
- panel contributors
supported_modes:
- docker
# The acekorneya image handles its own SteamCMD download on first Start
# (UPDATE_SERVER=TRUE env). Opt out of the panel's auto-update-on-create
# sidecar — its install path doesn't match the image's layout and would
# just waste ~20 minutes creating files the server won't use.
auto_update_on_create: false
# But DO auto-start after create — the image's entrypoint runs its own
# SteamCMD on first boot and then launches the server, so Create → Start
# is the one-gesture flow operators expect. (True is the default across
# all modules; declared here so the interaction with auto_update_on_create
# being false is explicit to anyone reading this file.)
auto_start_on_create: true
runtime:
docker:
image: acekorneya/asa_server:latest
# Host networking -- container shares the host net namespace, so
# any port the game/mod binds is directly on the LAN. AMP-like
# model. Multi-instance safety comes from env-driven per-instance
# ports (panel allocator + env: mapping on each ports entry).
network_mode: host
env:
TZ: "Etc/UTC"
# acekorneya's entrypoint expects these. They're defaults — operators
# override per-instance once the config-values layer is wired.
INSTANCE_NAME: "panel-asa"
SERVER_MAP: "TheIsland_WP"
SESSION_NAME: "Panel ARK SA Server"
SERVER_ADMIN_PASSWORD: "changeme-please"
SERVER_PASSWORD: ""
ASA_PORT: "7777"
# Steam query port. The agent rewrites this to the actually-allocated
# host port at create time (see resolve.go's port-env mapping) — for
# the first ARK on an agent this stays 27015; the second gets 27016
# or whatever the allocator picked. The acekorneya image translates
# this into the launch arg `?QueryPort=<n>` on its end.
QUERY_PORT: "27015"
RCON_PORT: "27020"
MAX_PLAYERS: "70"
# Pull any ASA patches from Steam on every start. Cheap when up to
# date (SteamCMD validate is a few seconds) and means the server is
# always current.
UPDATE_SERVER: "TRUE"
CHECK_FOR_UPDATE_INTERVAL: "24"
UPDATE_WINDOW_MINIMUM_TIME: "12:00 AM"
UPDATE_WINDOW_MAXIMUM_TIME: "11:59 PM"
RESTART_NOTICE_MINUTES: "30"
ENABLE_MOTD: "FALSE"
MOTD: "Welcome!"
MOTD_DURATION: "30"
MAP_NAME: "TheIsland"
NOTIFY_ADMIN_COMMANDS_IN_CHAT: "FALSE"
RANDOM_STARTUP_DELAY: "FALSE"
# BattlEye under Wine/Proton on Linux containers silently crashes the
# server ~2-3 minutes after "Server has successfully started!" (no
# log output between the success line and the process exit — classic
# UE5 silent death on BE handshake failure). Operators can enable it
# per-instance once BE for ASA/Linux gets sorted upstream; for now
# the default is off so servers actually stay up.
BATTLEEYE: "FALSE"
RCON_ENABLED: "TRUE"
DISPLAY_POK_MONITOR_MESSAGE: "FALSE"
MOD_IDS: ""
PASSIVE_MODS: ""
CUSTOM_SERVER_ARGS: ""
CLUSTER_ID: ""
# ----- EOS / session-browser stability knobs (see post_start) -----
# ASA reports the host's auto-detected adapter IP to Epic Online
# Services when it registers the server session for the in-game
# browser. Under Docker (host-networking, multiple bridges, IPv6
# scopes) EOS sometimes picks the wrong adapter on a re-registration
# and advertises an unreachable IP. The server stays up + RCON works
# + connect-by-IP works, but it vanishes from the browser. ASA
# exposes `-PublicIPForEpic=<ipv4>` to override the auto-detect.
# Leave empty to keep ASA's default behavior; set to the WAN IPv4
# players reach (the WG-edge VPS for this fleet) to pin EOS to it.
# Reference: forums.unrealengine.com EOS-DS browser-visibility thread.
PUBLIC_IP_FOR_EPIC: ""
# Run as UID/GID 1000 so writes land on the volume as a normal user.
PUID: "1000"
PGID: "1000"
# The acekorneya image runs ARK under Wine — the live install lives at
# the Wine-virtualized Windows path below. Early module versions mounted
# /home/pok/arkserver thinking it was a symlink; it isn't — that dir
# doesn't exist at image build time, so the volume was essentially
# unused, and saves/configs lived only in the container's ephemeral
# overlay FS. Fixed by mounting DIRECTLY at the Wine path, matching
# acekorneya's own docker-compose example. Saves, configs, logs, and
# admin lists all live under this Saved/ dir.
browseable_root: "/usr/games/.wine/drive_c/POK/Steam/steamapps/common/ARK Survival Ascended Dedicated Server/ShooterGame/Saved"
volumes:
# Whole ASA install (game binaries + ShooterGame/Saved/{configs,
# world data, logs}). One volume keeps things simple and mirrors
# the upstream docker-compose pattern.
- { type: volume, name: "panel-$INSTANCE_ID-server",
container: "/usr/games/.wine/drive_c/POK/Steam/steamapps/common/ARK Survival Ascended Dedicated Server" }
# Cluster transfer dir — may be overridden at instance create time
# to point at a shared `panel-ark-cluster-<id>` volume for
# multi-server clustering (see the Cluster button in the ARK modal).
- { type: volume, name: "panel-$INSTANCE_ID-cluster",
container: "/usr/games/.wine/drive_c/POK/Steam/steamapps/common/ARK Survival Ascended Dedicated Server/ShooterGame/Saved/clusters" }
ports:
# `env:` tells the agent which container env var to set with the
# allocated host port. The acekorneya entrypoint reads ASA_PORT /
# QUERY_PORT / RCON_PORT and assembles ARK's launch args from them
# (?Port=N ?QueryPort=N ?RCONPort=N). Without env-mirror, the second
# ARK instance on the same agent gets distinct host bindings but
# ARK still advertises 7777/27015 to Steam, so the browser shows
# only one of them.
- { name: game, proto: udp, default: 7777, required: true, env: ASA_PORT }
- { name: raw, proto: udp, default: 7778, required: true }
# Steam server-browser query port. ASA registers itself with the Steam
# master at this port, and `?QueryPort=<n>` on the ASA launch command
# tells the engine which port to bind + advertise. Without this entry
# in the manifest, the allocator can't pick distinct host ports for
# multiple instances on the same agent — they all clobber 27015 and
# only one shows up in the Steam server list.
- { name: query, proto: udp, default: 27015, required: true, env: QUERY_PORT }
- { name: rcon, proto: tcp, default: 27020, internal: true, env: RCON_PORT }
# post_start: shell commands executed inside the container by the agent
# after a successful Start. Best-effort — failures log a warning but don't
# fail the Start.
#
# Why this hook exists: ASA writes RCONPort=<n> under [ServerSettings] of
# GameUserSettings.ini at first boot, which is enough for the port to bind
# but the server silently refuses to respond to Source-RCON AUTH packets.
# Adding a duplicate RCONPort line under [SessionSettings] fixes it — a
# real, reproducible ASA quirk (hit with raw Python + the image's bundled
# rcon-cli, not panel-specific). Verified working on princess 2026-04-22.
#
# The hook: (a) waits up to 3 min for the ini to exist (cold first-boot
# SteamCMD install can take 15-30 min but the ini is written well before
# ASA launches), (b) bails out if the [SessionSettings] RCONPort already
# exists (idempotent — subsequent starts are no-ops), (c) injects the
# line, (d) signals the monitor_ark_server.sh watchdog to relaunch ASA so
# the server re-reads the patched config on its next boot cycle. The
# relaunch is the clean way — pkill is safe because the image's
# monitor_ark_server.sh is designed to re-spawn ArkAscendedServer.exe
# whenever it dies.
lifecycle:
post_start:
- |
set -e
LAUNCH="/usr/games/scripts/launch_ASA.sh"
INI="/usr/games/.wine/drive_c/POK/Steam/steamapps/common/ARK Survival Ascended Dedicated Server/ShooterGame/Saved/Config/WindowsServer/GameUserSettings.ini"
WINEPREFIX_DIR="/usr/games/.wine"
CA_MARKER="$WINEPREFIX_DIR/.panel-amazon-roots-installed-v1"
changed=0
# ----- Fix 1: launch_ASA.sh URL is missing ?QueryPort= -----
# The acekorneya image hard-codes "?Port=${ASA_PORT}" in the launch
# URL but never appends "?QueryPort=", so every sibling ARK instance
# defaults to 27015 — only the first to bind wins, the rest have
# NO query port. Without a query port, EOS / Steam server browser
# can't advertise the server, so clients can't connect.
# Idempotent: a second run sees the marker comment and no-ops.
if [ -f "$LAUNCH" ] && ! grep -q 'QueryPort=\${QUERY_PORT' "$LAUNCH"; then
echo "[panel-hook] patching $LAUNCH to add ?QueryPort=\${QUERY_PORT}"
sed -i 's|?Port=${ASA_PORT}|?Port=${ASA_PORT}?QueryPort=${QUERY_PORT}|' "$LAUNCH"
changed=1
fi
# ----- Fix 2: GameUserSettings.ini RCONPort under [SessionSettings] -----
# ARK ASA's RCON server silently rejects AUTH unless RCONPort also
# appears under [SessionSettings] (verified on princess 2026-04-22).
# ASA accepts the launch-URL ?RCONPort=N for binding, but the
# adapter handshake also reads from this INI key.
for i in $(seq 1 180); do [ -f "$INI" ] && break; sleep 1; done
if [ -f "$INI" ] && ! awk '/^\[SessionSettings\]/{f=1; next} /^\[/{f=0} f && /^RCONPort=/{found=1} END{exit !found}' "$INI"; then
echo "[panel-hook] injecting RCONPort=${RCON_PORT:-27020} under [SessionSettings]"
awk -v port="${RCON_PORT:-27020}" '/^\[SessionSettings\]/{print; print "RCONPort=" port; next} {print}' "$INI" > "${INI}.new"
mv "${INI}.new" "$INI"
changed=1
fi
# ----- Fix 3: pin EOS-advertised IP via -PublicIPForEpic ----------------
# WHY: ASA's EOS session registration auto-detects the host's outbound
# IP each time it re-registers. Under Docker host-net with multiple
# interfaces (docker0, br-*, IPv6 link-local) EOS sometimes picks the
# wrong adapter on a re-register and advertises an unreachable IP to
# the in-game browser → "server invisible but RCON works" + "restart
# fixes it" pattern. ASA exposes -PublicIPForEpic=<ipv4> to override.
# Reference: Epic dev-forum confirmation (multihome → wrong-EOS-IP).
# Idempotent: marker on the wine binary line means we only inject once.
if [ -n "$PUBLIC_IP_FOR_EPIC" ] && [ -f "$LAUNCH" ] && ! grep -q -- '-PublicIPForEpic=' "$LAUNCH"; then
echo "[panel-hook] injecting -PublicIPForEpic=${PUBLIC_IP_FOR_EPIC} into $LAUNCH"
# Insert the flag right after the ArkAscendedServer.exe binary path.
# The launch line ends with a backslash continuation, so the next
# line (the URL + extra args) is unaffected.
sed -i 's|ArkAscendedServer.exe" \\$|ArkAscendedServer.exe" -PublicIPForEpic='"${PUBLIC_IP_FOR_EPIC}"' \\|' "$LAUNCH"
changed=1
fi
# ----- Fix 4: ensure system CA bundle is up-to-date (idempotent) -------
# WHY: EOS endpoints (*.epicgames.dev, *.on.epicgames.com) are on AWS
# CloudFront and chain to Amazon Root CA 1-4 + Starfield Services G2.
# Whether the EOS SDK under Wine uses Wine's HKLM root store or the
# statically-linked openssl/curl bundle inside the game, missing CAs
# in the system bundle have been correlated with hours-later EOS
# token-refresh failures (session ages out → server vanishes from
# in-game browser → container restart fixes by replaying cached auth).
# Reference: acekorneya/Ark-Survival-Ascended-Server#8 (open).
# The acekorneya image's Debian base lists the Amazon PEMs as enabled
# in /etc/ca-certificates.conf but may have a stale bundle if
# update-ca-certificates didn't run during image build. Marker file
# makes this once-per-prefix-lifetime — the run itself is ~1s if
# nothing needs rebuilding, so the marker is just for log noise.
if [ ! -f "$CA_MARKER" ]; then
echo "[panel-hook] running update-ca-certificates to refresh system CA bundle"
if update-ca-certificates >/dev/null 2>&1; then
echo "[panel-hook] system CA bundle refreshed"
touch "$CA_MARKER" 2>/dev/null || true
else
echo "[panel-hook] warn: update-ca-certificates failed (non-fatal)"
fi
fi
if [ "$changed" = "1" ]; then
# Relaunch ASA so it picks up the new launch script + INI. The
# image's monitor_ark_server.sh spots the missing PID within ~60s
# and respawns ArkAscendedServer.exe with the fixed config.
pkill -f ArkAscendedServer.exe 2>/dev/null || true
echo "[panel-hook] fixups applied; ASA will relaunch via image monitor"
else
echo "[panel-hook] all fixups already in place — no-op"
fi
resources:
min_ram_mb: 12288
recommended_ram_mb: 16384
# What gets archived when an operator clicks Back-up-now. Without this
# the agent tarballs the whole 18 GB ASA install (game binaries +
# Proton runtime + saves), which is wasteful — the only files that
# actually need preserving are under ShooterGame/Saved/. Cluster
# transfers live in a sibling clusters/ dir which IS bind-mounted to
# a shared volume on cluster setup, so we exclude that — restoring
# saves shouldn't clobber cluster data the other servers are using.
backup:
# Paths are RELATIVE to browseable_root (which is already
# ".../ShooterGame/Saved"), NOT relative to the container root. The
# earlier "ShooterGame/Saved/SavedArks" form pointed at a doubled path
# that doesn't exist, so every include filtered out and busybox tar
# got an empty stdin → exited non-zero → "sidecar exited 1".
include:
- "SavedArks" # world saves (the main thing)
- "Config" # GameUserSettings.ini, Game.ini
- "Logs" # tribe logs, server logs
exclude:
- "clusters" # shared volume; restoring it would clobber siblings
- "SavedArks/*/Mods" # mod cache; rebuilds on next start
- "Crashes" # crash dumps — never useful in a restore
notes: "Saves + configs + tribe logs (~250 MB typical). Game install + Proton runtime are preserved by SteamCMD on next start; no need to back them up."
rcon:
adapter: docker_exec_rcon
host_port: rcon
auth: password
# Source-of-truth precedence (highest first, per agent's resolveRCONAddress + tracker):
# 1. config_values["rcon_password"] — explicit override (rare)
# 2. config_values["SERVER_ADMIN_PASSWORD"] via password_secret —
# the env we set at create time. THIS is what actually works
# for the acekorneya image.
# 3. password_from_ini lazy-read at dial time — only effective
# AFTER the operator changes the password via the Config tab
# and the server has restarted to pick it up.
#
# Why both: the acekorneya image takes SERVER_ADMIN_PASSWORD and
# passes it on the launch command line as `?ServerAdminPassword=...`.
# ARK SA accepts the launch-arg version BUT does NOT write it back
# into GameUserSettings.ini's [ServerSettings]ServerAdminPassword
# — that key stays empty until the operator edits it. So the env
# value is the working credential. The INI fallback is for after
# the operator has changed the password via the Config tab (which
# writes to the INI but doesn't propagate back into the env).
password_secret: SERVER_ADMIN_PASSWORD
password_from_ini:
file: "/usr/games/.wine/drive_c/POK/Steam/steamapps/common/ARK Survival Ascended Dedicated Server/ShooterGame/Saved/Config/WindowsServer/GameUserSettings.ini"
section: ServerSettings
key: ServerAdminPassword
commands:
list_players: "ListPlayers"
say: "ServerChat {msg}"
broadcast: "Broadcast {msg}"
kick: "KickPlayer {player}"
ban: "BanPlayer {player}"
save: "SaveWorld"
shutdown: "DoExit"
give_item: "GiveItemToPlayer {player} {item} {qty} 1 0"
set_time: "SetTimeOfDay {time}"
state_sources:
- type: rcon
command: "ListPlayers"
every: 30s
parse:
kind: regex
pattern: '^(?P<index>\d+)\.\s+(?P<name>.+?),\s+(?P<steamid>\d+)'
fields:
players_online: index
- type: log_tail
files:
- "$DATA_PATH/logs/*.log"
- "/home/pok/arkserver/ShooterGame/Saved/Logs/ShooterGame.log"
events:
join:
pattern: "(?P<name>[^ ]+) joined"
kind: join
leave:
pattern: "(?P<name>[^ ]+) left"
kind: leave
chat:
pattern: "Chat: \\[(?P<name>[^\\]]+)\\] (?P<msg>.+)"
kind: chat
# The panel manifest validator requires at least one update_provider. For
# ARK: SA the *real* update path is the image's built-in SteamCMD loop
# (env UPDATE_SERVER=TRUE). The stub below exists so the manifest loads;
# don't rely on it — the install path won't match the image's expected
# layout so running the panel's SteamCMD sidecar against it is a no-op at
# best. Click "Restart" instead; that triggers the image's update cycle.
update_providers:
- id: builtin
kind: steamcmd
app_id: "2430930"
install_path: "/usr/games/.wine/drive_c/POK/Steam/steamapps/common/ARK Survival Ascended Dedicated Server"
# --- Manifest-driven UI metadata (WI-07) --------------------------
# Consumed by the CONTROLLER/dashboard only (the controller loads its
# own ./modules copy at startup); the agent ignores these at runtime.
# ready_pattern is a JavaScript regex (slash-delimited when it needs
# flags) moved verbatim from the dashboard's READY_PATTERNS map.
ready_pattern: '/Server has completed startup and is now advertising/i'
appearance:
emoji: "🦖"
grad: "linear-gradient(135deg,#3a2e1e,#d97706)"
art: "/game-art/ark-sa.jpg"
steam: "2430930"