Files
panel/modules/ark-sa/module.yaml
T
2026-07-14 19:22:07 -07:00

380 lines
19 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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"