panel v0.9.2 — open-source game server manager
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
#!/bin/bash
|
||||
# panel-native DayZ entrypoint.
|
||||
#
|
||||
# Contract:
|
||||
# /game — SteamCMD-installed DayZ Server (app 223350). Populated
|
||||
# by the panel's SteamCMD updater sidecar, which uses the
|
||||
# operator-supplied Steam credentials (the module manifest
|
||||
# sets requires_steam_login: true).
|
||||
# /game-saves — serverDZ.cfg + profiles/ (logs, bans, RPTs) + optional
|
||||
# mpmissions/ override tree.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
log() { printf '[panel-dayz] %s\n' "$*"; }
|
||||
|
||||
# ----- stage 1: root-only bootstrap -----
|
||||
if [[ "$(id -u)" = "0" ]]; then
|
||||
chown -R panel:panel /game /game-saves /home/panel 2>/dev/null || true
|
||||
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 \
|
||||
SERVER_NAME="${SERVER_NAME:-panel DayZ}" \
|
||||
SERVER_PORT="${SERVER_PORT:-2302}" \
|
||||
SERVER_PASSWORD="${SERVER_PASSWORD:-}" \
|
||||
BE_PASSWORD="${BE_PASSWORD:-panel_be}" \
|
||||
CPU_COUNT="${CPU_COUNT:-2}" \
|
||||
LD_LIBRARY_PATH="/game:${LD_LIBRARY_PATH:-}" \
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
||||
/entrypoint.sh "$@"
|
||||
fi
|
||||
|
||||
GAME_DIR=/game
|
||||
SAVE_DIR=/game-saves
|
||||
DEDI_EXE="${GAME_DIR}/DayZServer"
|
||||
|
||||
if [[ ! -x "${DEDI_EXE}" ]]; then
|
||||
log "ERROR: ${DEDI_EXE} not found or not executable."
|
||||
log " Run 'Update' on this server in the panel — the modal will"
|
||||
log " prompt for Steam credentials (DayZ is paid content; the"
|
||||
log " anonymous SteamCMD login won't work)."
|
||||
exit 78 # EX_CONFIG
|
||||
fi
|
||||
|
||||
# --- persist profiles into /game-saves ---
|
||||
# DayZ writes RPT crash logs, ADM admin logs, and BattlEye bans to
|
||||
# <install>/profiles/. Move out to the save volume + symlink back on
|
||||
# first boot so SteamCMD validate can't wipe them.
|
||||
mkdir -p "${SAVE_DIR}/profiles"
|
||||
if [[ -d "${GAME_DIR}/profiles" && ! -L "${GAME_DIR}/profiles" ]]; then
|
||||
mv "${GAME_DIR}/profiles"/* "${SAVE_DIR}/profiles/" 2>/dev/null || true
|
||||
rm -rf "${GAME_DIR}/profiles"
|
||||
fi
|
||||
ln -sfn "${SAVE_DIR}/profiles" "${GAME_DIR}/profiles"
|
||||
|
||||
# --- seed serverDZ.cfg on first boot ---
|
||||
CFG="${SAVE_DIR}/serverDZ.cfg"
|
||||
if [[ ! -s "${CFG}" ]]; then
|
||||
log "seeding serverDZ.cfg (first boot)"
|
||||
cat > "${CFG}" <<EOF
|
||||
hostname = "${SERVER_NAME}";
|
||||
password = "${SERVER_PASSWORD}";
|
||||
passwordAdmin = "";
|
||||
enableWhitelist = 0;
|
||||
disableVoN = 0;
|
||||
vonCodecQuality = 7;
|
||||
disable3rdPerson = 0;
|
||||
disableCrosshair = 0;
|
||||
serverTime = "SystemTime";
|
||||
serverTimeAcceleration = 12;
|
||||
serverNightTimeAcceleration = 1;
|
||||
serverTimePersistent = 0;
|
||||
guaranteedUpdates = 1;
|
||||
loginQueueConcurrentPlayers = 5;
|
||||
loginQueueMaxPlayers = 500;
|
||||
instanceId = 1;
|
||||
storageAutoFix = 1;
|
||||
verifySignatures = 2;
|
||||
forceSameBuild = 1;
|
||||
timeStampFormat = "Short";
|
||||
logAverageFps = 1;
|
||||
logMemory = 1;
|
||||
logPlayers = 1;
|
||||
logFile = "server_console.log";
|
||||
adminLogPlayerHitsOnly = 0;
|
||||
adminLogPlacement = 1;
|
||||
adminLogBuildActions = 1;
|
||||
adminLogPlayerList = 1;
|
||||
disableBanlist = 0;
|
||||
BattlEye = 1;
|
||||
respawnTime = 5;
|
||||
steamQueryPort = ${SERVER_PORT};
|
||||
maxPing = 200;
|
||||
MissionTemplate = "dayzOffline.chernarusplus";
|
||||
class Missions
|
||||
{
|
||||
class DayZ
|
||||
{
|
||||
template = "dayzOffline.chernarusplus";
|
||||
};
|
||||
};
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Point /game/serverDZ.cfg at the save-volume copy so the binary's default
|
||||
# relative-path lookup finds it.
|
||||
ln -sf "${CFG}" "${GAME_DIR}/serverDZ.cfg"
|
||||
|
||||
# --- BattlEye RCON config ---
|
||||
# DayZ ships a `battleye/` dir that contains a NESTED `battleye/` subdir with
|
||||
# a duplicate `beserver_x64.so`. The DayZ launcher (`-BEpath=battleye`) loads
|
||||
# BE from there and expects `beserver_x64.cfg` to sit alongside the nested
|
||||
# lib — i.e. `battleye/battleye/beserver_x64.cfg`, NOT `battleye/beserver_x64.cfg`.
|
||||
# Writing to the outer dir (which is what most tutorials show) results in BE
|
||||
# initializing but never binding the RCON port, with no error in the log.
|
||||
#
|
||||
# We unconditionally overwrite both locations so operator env changes take
|
||||
# effect on the next boot. We also purge stale `beserver_x64_active_<token>.cfg`
|
||||
# files BE writes after first handshake — if the password has changed, the
|
||||
# active copy will mismatch and BE refuses incoming RCON packets.
|
||||
#
|
||||
# Reference: AMP's dayz-experimental.kvp uses `{{$FullBaseDir}}battleye` as
|
||||
# the BE path and their PreStartStages purge the active file the same way.
|
||||
BE_DIR="${GAME_DIR}/battleye"
|
||||
for CFGPATH in "${BE_DIR}/beserver_x64.cfg" "${BE_DIR}/battleye/beserver_x64.cfg"; do
|
||||
if [[ -d "$(dirname "${CFGPATH}")" ]]; then
|
||||
log "writing ${CFGPATH} (RCON port 2305, pw from BE_PASSWORD)"
|
||||
cat > "${CFGPATH}" <<EOF
|
||||
RConPassword ${BE_PASSWORD}
|
||||
RConPort 2305
|
||||
RConIP 0.0.0.0
|
||||
RestrictRCon 0
|
||||
RConLog 1
|
||||
UseLocationsToBan 0
|
||||
LogFile 0
|
||||
EOF
|
||||
fi
|
||||
done
|
||||
# Purge stale active-session files (both locations).
|
||||
rm -f "${BE_DIR}/beserver_x64_active_"*.cfg 2>/dev/null || true
|
||||
rm -f "${BE_DIR}/battleye/beserver_x64_active_"*.cfg 2>/dev/null || true
|
||||
|
||||
cd "${GAME_DIR}"
|
||||
log "starting DayZ dedicated server"
|
||||
log " name: ${SERVER_NAME}"
|
||||
log " game port: ${SERVER_PORT}/udp"
|
||||
log " rcon port: 2305/udp (internal, BattlEye)"
|
||||
log " cfg: ${CFG}"
|
||||
|
||||
trap 'log "SIGTERM received"; kill -TERM "${DZ_PID:-0}" 2>/dev/null || true; wait "${DZ_PID:-0}" 2>/dev/null || true; exit 0' TERM INT
|
||||
|
||||
# --- mod list dynamic build ---
|
||||
# Panel's mod manager maintains two plain-text files on the save volume:
|
||||
# /game-saves/panel-mods.txt — one @ModFolder per line → -mod=
|
||||
# /game-saves/panel-servermods.txt — one @ModFolder per line → -servermod=
|
||||
# Blank lines and lines starting with '#' are ignored. This lets the mod
|
||||
# manager edit a human-readable file and have the server pick up the new
|
||||
# load order on the next start without entrypoint code changes.
|
||||
build_mod_arg() {
|
||||
local file="$1" prefix="$2"
|
||||
[[ -s "$file" ]] || { echo ""; return; }
|
||||
local joined=""
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
line="${line%$'\r'}" # strip trailing CR (Windows edits)
|
||||
line="${line#"${line%%[![:space:]]*}"}" # ltrim
|
||||
line="${line%"${line##*[![:space:]]}"}" # rtrim
|
||||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||
if [[ -z "$joined" ]]; then joined="$line"
|
||||
else joined="${joined};${line}"
|
||||
fi
|
||||
done < "$file"
|
||||
if [[ -n "$joined" ]]; then
|
||||
echo "${prefix}${joined}"
|
||||
fi
|
||||
}
|
||||
MOD_ARG="$(build_mod_arg "${SAVE_DIR}/panel-mods.txt" '-mod=')"
|
||||
SERVERMOD_ARG="$(build_mod_arg "${SAVE_DIR}/panel-servermods.txt" '-servermod=')"
|
||||
[[ -n "${MOD_ARG}" ]] && log "loading mods: ${MOD_ARG}"
|
||||
[[ -n "${SERVERMOD_ARG}" ]] && log "loading server-mods: ${SERVERMOD_ARG}"
|
||||
|
||||
# DayZ's documented launch command:
|
||||
# ./DayZServer -config=serverDZ.cfg -port=2302 -BEpath=battleye \
|
||||
# -profiles=profiles -dologs -adminlog -netlog \
|
||||
# -freezecheck -doScriptLogs -cpuCount=2
|
||||
# [-mod=@A;@B] [-servermod=@X]
|
||||
DZ_ARGS=(
|
||||
-config=serverDZ.cfg
|
||||
-port="${SERVER_PORT}"
|
||||
"-BEpath=${GAME_DIR}/battleye"
|
||||
-profiles=profiles
|
||||
-dologs
|
||||
-adminlog
|
||||
-netlog
|
||||
-freezecheck
|
||||
-doScriptLogs
|
||||
"-cpuCount=${CPU_COUNT}"
|
||||
)
|
||||
[[ -n "${MOD_ARG}" ]] && DZ_ARGS+=("${MOD_ARG}")
|
||||
[[ -n "${SERVERMOD_ARG}" ]] && DZ_ARGS+=("${SERVERMOD_ARG}")
|
||||
|
||||
exec stdbuf -oL -eL "${DEDI_EXE}" "${DZ_ARGS[@]}" &
|
||||
DZ_PID=$!
|
||||
wait "${DZ_PID}"
|
||||
Reference in New Issue
Block a user