5232609719
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
221 lines
9.8 KiB
Bash
221 lines
9.8 KiB
Bash
#!/bin/bash
|
|
# panel-native Empyrion entrypoint.
|
|
#
|
|
# Contract:
|
|
# /game — SteamCMD-installed Windows build of Empyrion (volume,
|
|
# populated by the panel's SteamCMD updater sidecar).
|
|
# If missing, exit with EX_CONFIG and a helpful message.
|
|
# /game-saves — dedicated.yaml + Saves/ tree (volume).
|
|
#
|
|
# Runs as root for its opening stage (fix up ownership after the SteamCMD
|
|
# sidecar wrote as root, nuke any stale X lock from a previous crash),
|
|
# then drops to the `panel` user for Wine + Xvfb + the game. Wine refuses
|
|
# to run as root, so the drop is load-bearing.
|
|
#
|
|
# On every start we verify the binary is there, start Xvfb, boot a minimal
|
|
# Wine prefix if needed, then exec EmpyrionDedicated.exe under Wine with
|
|
# stdout/stderr line-buffered so docker logs → agent → panel console
|
|
# delivers live output.
|
|
|
|
set -euo pipefail
|
|
|
|
log() { printf '[panel-empyrion] %s\n' "$*"; }
|
|
|
|
# ----- stage 1: root-only bootstrap (runs once per container start) -----
|
|
if [[ "$(id -u)" = "0" ]]; then
|
|
# SteamCMD writes as root in its sidecar; chown once so the panel user
|
|
# (UID 1000) can read/write the game dir without fiddling with perms.
|
|
chown -R panel:panel /game /game-saves /home/panel 2>/dev/null || true
|
|
|
|
# Nuke any stale Xvfb state from a previous boot. The container's /tmp
|
|
# persists across restarts (same writable layer), so /tmp/.X99-lock plus
|
|
# /tmp/.X11-unix/X99 left by a SIGKILL'd Xvfb make the next start die
|
|
# with "_XSERVTransMakeAllCOTSServerListeners: server already running".
|
|
# Single rm wasn't catching every variant (verified live 2026-04-29);
|
|
# nuking the whole dir + re-creating with the right mode is bulletproof.
|
|
pkill -9 Xvfb 2>/dev/null || true
|
|
rm -rf /tmp/.X11-unix /tmp/.X99-lock 2>/dev/null || true
|
|
mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix && chown root:root /tmp/.X11-unix 2>/dev/null || true
|
|
|
|
# Empyrion writes build-stamped logs to Z:\Logs\<buildnum> (= /Logs/...
|
|
# because Wine maps Z:\ to /). Without this symlink panel gets an
|
|
# UnauthorizedAccessException on first boot. Pointing it into the save
|
|
# volume keeps logs persistent + visible in the Files tab.
|
|
mkdir -p /game-saves/EmpyrionLogs
|
|
chown -R panel:panel /game-saves/EmpyrionLogs
|
|
if [[ ! -e /Logs || -L /Logs ]]; then
|
|
ln -sfn /game-saves/EmpyrionLogs /Logs
|
|
fi
|
|
|
|
# Re-exec as `panel` user. setpriv comes with util-linux in debian-slim.
|
|
log "dropping privileges to panel (UID 1000)"
|
|
exec setpriv --reuid=panel --regid=panel --clear-groups \
|
|
--inh-caps=-all --bounding-set=-all \
|
|
env \
|
|
HOME=/home/panel \
|
|
WINEPREFIX=/home/panel/.wine \
|
|
WINEDEBUG=-all \
|
|
SRV_PORT="${SRV_PORT:-30000}" \
|
|
EAC_PORT="${EAC_PORT:-30003}" \
|
|
CSW_PORT="${CSW_PORT:-30004}" \
|
|
EAH_API_PORT="${EAH_API_PORT:-30007}" \
|
|
SCENARIO="${SCENARIO:-}" \
|
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
|
/entrypoint.sh "$@"
|
|
fi
|
|
|
|
GAME_DIR=/game
|
|
SAVE_DIR=/game-saves
|
|
DEDI_DIR="${GAME_DIR}/DedicatedServer"
|
|
DEDI_EXE="${DEDI_DIR}/EmpyrionDedicated.exe"
|
|
CONFIG="${SAVE_DIR}/dedicated.yaml"
|
|
# Empyrion ships its default dedicated.yaml at the game root.
|
|
SHIPPED_CONFIG="${GAME_DIR}/dedicated.yaml"
|
|
|
|
# --- sanity: Windows build installed? ---
|
|
if [[ ! -f "${DEDI_EXE}" ]]; then
|
|
log "ERROR: ${DEDI_EXE} not found."
|
|
log " Game files haven't been downloaded yet."
|
|
log " Run 'Update' on this server in the panel — that launches a"
|
|
log " SteamCMD sidecar which installs Empyrion (Windows build) into"
|
|
log " this volume via +@sSteamCmdForcePlatformType windows."
|
|
exit 78 # EX_CONFIG
|
|
fi
|
|
|
|
# --- dedicated.yaml: copy shipped default on first run ---
|
|
if [[ ! -s "${CONFIG}" && -s "${SHIPPED_CONFIG}" ]]; then
|
|
log "no panel config found — seeding ${CONFIG} from ${SHIPPED_CONFIG}"
|
|
cp -f "${SHIPPED_CONFIG}" "${CONFIG}"
|
|
fi
|
|
if [[ ! -s "${CONFIG}" ]]; then
|
|
log "WARN: no dedicated.yaml found in game install. Empyrion will create"
|
|
log " one with defaults on first boot. Edit via the Files tab."
|
|
fi
|
|
|
|
# Symlink so the binary finds the config at its expected relative path.
|
|
# Empyrion reads dedicated.yaml from the game root (same dir as the .exe's
|
|
# -dedicated argument resolves).
|
|
ln -sf "${CONFIG}" "${GAME_DIR}/dedicated.yaml"
|
|
|
|
# --- patch dedicated.yaml ports from env on every boot ---
|
|
# Empyrion reads Srv_Port / EAC_Port / Tel_Port from dedicated.yaml. Without
|
|
# this, multiple instances on the same agent all bind 30000-30004 and only
|
|
# one is reachable. SRV_PORT comes from the panel allocator via the
|
|
# manifest's port→env mapping (`env: SRV_PORT` in module.yaml). yaml is
|
|
# whitespace-sensitive but these keys are a flat top-level "Key: NNN" so
|
|
# sed is safe.
|
|
if [[ -s "${CONFIG}" ]]; then
|
|
log "patching dedicated.yaml (Srv_Port=${SRV_PORT}, EAC_Port=${EAC_PORT})"
|
|
sed -i -E \
|
|
-e "s|^([[:space:]]*Srv_Port:[[:space:]]*)[0-9]+|\1${SRV_PORT}|" \
|
|
-e "s|^([[:space:]]*EAC_Port:[[:space:]]*)[0-9]+|\1${EAC_PORT}|" \
|
|
"${CONFIG}"
|
|
fi
|
|
|
|
# --- scenario selection ---
|
|
# SCENARIO env names a folder under /game/Content/Scenarios/. The empyrion-
|
|
# shipped default is "Default Random". Reforged Eden / Reforged Eden 2 /
|
|
# Project Eden need to be present in /game/Content/Scenarios/<name>/ first
|
|
# (drop via the panel's Files tab, or future workshop-subscribe feature).
|
|
# This block keeps dedicated.yaml's CustomScenario in sync with the env.
|
|
SCENARIO="${SCENARIO:-}"
|
|
if [[ -n "${SCENARIO}" && -s "${CONFIG}" ]]; then
|
|
if [[ ! -d "${GAME_DIR}/Content/Scenarios/${SCENARIO}" ]]; then
|
|
log "WARN: scenario folder '${SCENARIO}' not found at ${GAME_DIR}/Content/Scenarios/${SCENARIO}"
|
|
log " The panel UI Files tab can drop scenario contents there. For"
|
|
log " Reforged Eden 2 (Workshop ID 3041847672), unzip into:"
|
|
log " /game/Content/Scenarios/Reforged Eden 2/"
|
|
log " Booting with Default for now."
|
|
else
|
|
log "selecting scenario: ${SCENARIO}"
|
|
if grep -qE '^[[:space:]]*CustomScenario:' "${CONFIG}"; then
|
|
sed -i -E "s|^([[:space:]]*CustomScenario:[[:space:]]*).*|\1${SCENARIO}|" "${CONFIG}"
|
|
else
|
|
# Insert under GameConfig: at the top, ahead of dedicated game block.
|
|
printf '\nGameConfig:\n CustomScenario: %s\n' "${SCENARIO}" >> "${CONFIG}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# --- stage EPM mod (TCP relay for Eleon Mod API) on first boot ---
|
|
# Pre-staged at /opt/panel-epm in the image. Copy into the live game
|
|
# Content/Mods/EPM/ if it isn't there yet. This makes the panel-empyrion-
|
|
# bridge sidecar work out-of-the-box: every new empyrion instance comes
|
|
# with EPM listening on EAH_API_PORT.
|
|
EPM_SRC=/opt/panel-epm
|
|
EPM_DST="${GAME_DIR}/Content/Mods/EPM"
|
|
if [[ -d "${EPM_SRC}" && ! -f "${EPM_DST}/EmpyrionNetworkRelayMod.dll" ]]; then
|
|
log "staging EPM mod into ${EPM_DST}"
|
|
mkdir -p "${EPM_DST}"
|
|
cp -a "${EPM_SRC}/." "${EPM_DST}/"
|
|
# If a panel-allocated EAH_API_PORT is set, patch EPM's Settings.yaml
|
|
# so its TCP listener binds the right port for this instance.
|
|
if [[ -n "${EAH_API_PORT:-}" && -f "${EPM_DST}/Settings.yaml" ]]; then
|
|
sed -i -E "s|^GameServerApiPort:[[:space:]]*[0-9]+|GameServerApiPort: ${EAH_API_PORT}|" \
|
|
"${EPM_DST}/Settings.yaml"
|
|
fi
|
|
fi
|
|
# Re-patch port even if EPM was already staged, in case panel reallocated.
|
|
if [[ -f "${EPM_DST}/Settings.yaml" && -n "${EAH_API_PORT:-}" ]]; then
|
|
sed -i -E "s|^GameServerApiPort:[[:space:]]*[0-9]+|GameServerApiPort: ${EAH_API_PORT}|" \
|
|
"${EPM_DST}/Settings.yaml"
|
|
fi
|
|
|
|
# --- prep saves dir (Empyrion writes under /game/Saves by default) ---
|
|
# Redirect Saves into the save volume via a symlink so steam validate
|
|
# can't clobber world data.
|
|
mkdir -p "${SAVE_DIR}/Saves"
|
|
if [[ -d "${GAME_DIR}/Saves" && ! -L "${GAME_DIR}/Saves" ]]; then
|
|
# Steam shipped an empty Saves dir; replace with symlink.
|
|
rm -rf "${GAME_DIR}/Saves"
|
|
fi
|
|
ln -sfn "${SAVE_DIR}/Saves" "${GAME_DIR}/Saves"
|
|
|
|
# --- Wine + Xvfb via xvfb-run (auto-allocates a free display) ---
|
|
#
|
|
# We previously hand-started Xvfb on :99. That broke once the host had
|
|
# its OWN Xvfb on :99 — the empyrion container uses network_mode: host,
|
|
# which shares the network namespace, and X server abstract Unix sockets
|
|
# (`@/tmp/.X11-unix/X99`) are namespaced by network ns. Result: container
|
|
# Xvfb fails with "_XSERVTransMakeAllCOTSServerListeners: server already
|
|
# running" and Wine then dies "Authorization required, but no auth
|
|
# protocol specified" → Unity exits "Failed to create batch mode window:
|
|
# Success." (verified 2026-04-29).
|
|
#
|
|
# `xvfb-run -a` finds a free display number, starts Xvfb with proper
|
|
# xauth set up, exports DISPLAY to its child, and cleans up on exit.
|
|
# Bulletproof against host's existing Xvfb instances.
|
|
|
|
# --- Wine prefix bootstrap (first boot only). xvfb-run handles the X
|
|
# server for the actual run; for prefix init we don't strictly need
|
|
# X but wineboot complains less when one is available. ---
|
|
if [[ ! -d "${WINEPREFIX}" || ! -f "${WINEPREFIX}/system.reg" ]]; then
|
|
log "initializing Wine prefix at ${WINEPREFIX}"
|
|
xvfb-run -a wineboot --init 2>&1 | sed 's/^/ /' || true
|
|
wineserver -w || true
|
|
fi
|
|
|
|
# --- go ---
|
|
cd "${GAME_DIR}"
|
|
log "starting Empyrion Dedicated Server"
|
|
log " game dir: ${GAME_DIR}"
|
|
log " save dir: ${SAVE_DIR}"
|
|
log " config: ${CONFIG}"
|
|
log " binary: ${DEDI_EXE}"
|
|
|
|
# Shutdown handler: forward SIGTERM (xvfb-run forwards to wine; wine's
|
|
# Unity hook persists state on quit).
|
|
trap 'log "SIGTERM received; stopping server"; wineserver -k 2>/dev/null || true; exit 0' TERM INT
|
|
|
|
# -batchmode / -nographics make Unity skip the renderer; -logFile - pipes
|
|
# the Unity log to our stdout so the panel console sees everything.
|
|
# `xvfb-run -a` picks a free display, sets up xauth, and exports DISPLAY.
|
|
# `--server-args="-screen 0 1024x768x24"` matches our previous Xvfb args.
|
|
exec stdbuf -oL -eL xvfb-run -a --server-args="-screen 0 1024x768x24" \
|
|
wine "${DEDI_EXE}" \
|
|
-batchmode \
|
|
-nographics \
|
|
-logFile - \
|
|
-dedicated dedicated.yaml \
|
|
-startDedicated
|