4cf3471398
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
205 lines
7.1 KiB
Bash
205 lines
7.1 KiB
Bash
#!/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}"
|