mirror of
https://ceregatti.org/git/daniel/dayzdockerserver.git
synced 2025-05-07 14:51:17 +00:00

Refine output to only show relevant info when starting the server. Filter mod integration by current map, even if other mpmissions directories should exist. Remove previous cfgeconomy directories when starting, to prevent previous files from possibly existing. Orchestrate the retrieval and normalization of XML files as 'dz x ID' now does both.
474 lines
14 KiB
Bash
Executable file
474 lines
14 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
source dz-common
|
|
|
|
# Server container base directories
|
|
MPMISSIONS="${SERVER_FILES}/mpmissions"
|
|
|
|
mkdir -p ${SERVER_PROFILE}/battleye
|
|
|
|
# Server configuration file
|
|
SERVER_CFG_FILE="serverDZ.cfg"
|
|
SERVER_CFG_DST="${SERVER_PROFILE}/${SERVER_CFG_FILE}"
|
|
SERVER_CFG_SRC="${FILES}/${SERVER_CFG_FILE}"
|
|
|
|
# Command line parameters except mod, as that is handled separately.
|
|
parameters="-config=${SERVER_CFG_DST} -port=${port} -freezecheck -BEpath=${SERVER_PROFILE}/battleye -profiles=${SERVER_PROFILE} -nologs"
|
|
|
|
# Where mods are installed.
|
|
WORKSHOP_DIR="/mods/${release_client_appid}"
|
|
|
|
# Backups
|
|
BACKUP_DIR="${HOME}/backup"
|
|
if [ ! -d "${BACKUP_DIR}" ]
|
|
then
|
|
mkdir -p "${BACKUP_DIR}"
|
|
fi
|
|
|
|
mod_command_line=""
|
|
|
|
# Functions
|
|
|
|
# Usage
|
|
usage(){
|
|
echo -e "
|
|
${red}Bad option or arguments! ${yellow}${*}${default}
|
|
|
|
Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ]
|
|
|
|
Options and arguments:
|
|
|
|
a|activate id - Activate an installed DayZ Workshop items by id or index
|
|
b|backup - Backup the mission storage files in all mission directories
|
|
c|config - Update the internal serverDZ.cfg file from files/serverDZ.cfg on the host. Presents a unified diff if the internal file doesn't match the host file
|
|
d|deactivate id - Deactivate an installed DayZ Workshop items by id or index - Keeps the mod files but excludes it from the mod parameter
|
|
f|force - Forcibly kill the server. Use only as a last resort if the server won't shut down
|
|
l|list - List Workshop items and their details
|
|
n|rcon - Connect to the server using a python RCON client
|
|
r|restart - Restart the server without restarting the container
|
|
s|status - Shows the server's status: Running, uptime, mods, parameters, mod parameter, etc.
|
|
stop - Stop the server
|
|
${default}"
|
|
exit 1
|
|
}
|
|
|
|
loadconfig(){
|
|
if [ ! -f "${SERVER_INSTALL_FILE}" ]
|
|
then
|
|
echo
|
|
echo -e "The DayZ server files are not installed. You need to do this first in the web UI."
|
|
echo
|
|
exit 1
|
|
fi
|
|
# Handle the initial server configuration file
|
|
if [ ! -f ${SERVER_CFG_DST} ]
|
|
then
|
|
echo "Creating initial server configuration file"
|
|
cp "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}"
|
|
fi
|
|
# battleye config and rconpassword setup
|
|
# The server creates a new file from this file, which it then uses.
|
|
# Let's make sure to delete it first
|
|
BE_SERVER_FILE="${SERVER_PROFILE}/battleye/beserver_x64.cfg"
|
|
ALT_BE_SERVER_FILE=$(find ${SERVER_PROFILE}/battleye -name "beserver_x64_active*")
|
|
if [ ! -f "${BE_SERVER_FILE}" ] && [ ! -f "${ALT_BE_SERVER_FILE}" ]
|
|
then
|
|
passwd=$(openssl rand -base64 8 | tr -dc 'A-Za-z0-9')
|
|
if [ "${passwd}" == "" ]
|
|
then
|
|
passwd=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c10)
|
|
fi
|
|
if [ "${passwd}" == "" ]
|
|
then
|
|
printf "[ ${red}FAIL${default} ] Could not generate a passwort for RCON!\nOpen the Battleye config with 'dayzserver rcon'."
|
|
exit 1
|
|
else
|
|
cat > "${BE_SERVER_FILE}" <<EOF
|
|
RConPassword ${passwd}
|
|
RestrictRCon 0
|
|
RConPort ${rcon_port}
|
|
EOF
|
|
fi
|
|
printf "[ ${cyan}INFO${default} ] New RCON password: ${yellow}${passwd}${default}\n"
|
|
else
|
|
if [ -f "${BE_SERVER_FILE}" ]
|
|
then
|
|
FILE="${BE_SERVER_FILE}"
|
|
elif [ -f "${ALT_BE_SERVER_FILE}" ]
|
|
then
|
|
FILE="${ALT_BE_SERVER_FILE}"
|
|
fi
|
|
passwd=$(grep RConPassword ${FILE} | awk '{print $2}')
|
|
# printf "[ ${cyan}INFO${default} ] Using existing RCON password: ${yellow}${passwd}${default}\n"
|
|
fi
|
|
cp /usr/local/py3rcon/configexample.json ~/py3rcon.config.json
|
|
jq --arg port 2303 --arg rcon_password b0fNIBVfkM \
|
|
'.logfile="py3rcon.log" | .loglevel=0 | .server.port=$port | .server.rcon_password=$rcon_password | del(.repeatMessage)' \
|
|
/usr/local/py3rcon/configexample.json \
|
|
> ~/py3rcon.config.json
|
|
}
|
|
|
|
# Make sure to clean up and report on exit, as these files remain in the container's volume
|
|
report() {
|
|
rm -f /tmp/mod_command_line /tmp/parameters
|
|
echo
|
|
echo -e "${yellow}========================================== error.log =========================================="
|
|
find "${SERVER_PROFILE}" -name error.log -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
|
|
echo
|
|
echo -e "========================================== script*.log ========================================"
|
|
find "${SERVER_PROFILE}" -name "script*.log" -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
|
|
echo
|
|
echo -e "========================================== *.RPT =============================================="
|
|
find "${SERVER_PROFILE}" -name "*.RPT" -exec ls -la {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
|
|
echo
|
|
echo -e "========================================== End log ======================================${default}"
|
|
}
|
|
|
|
mergexml(){
|
|
# First copy the pristine files from upstream
|
|
for i in cfgeconomycore.xml #cfgeventspawns.xml
|
|
do
|
|
echo "Copying pristine version of ${i} into local mpmission ${MAP}..."
|
|
for dir in $(ls -d ${MPMISSIONS}/*)
|
|
do
|
|
if [[ ${dir} != ${MAP} ]]
|
|
then
|
|
continue
|
|
fi
|
|
find /mpmissions/$(basename ${dir}) -name ${i} -exec cp -v {} ${SERVER_FILES}{} \;
|
|
done
|
|
done
|
|
|
|
# Follow https://community.bistudio.com/wiki/DayZ:Central_Economy_mission_files_modding and make a single XML
|
|
# file for the ones that can go into it.
|
|
for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null)
|
|
do
|
|
ID=$(readlink ${link} | awk -F/ '{print $NF}')
|
|
# Going to have to maintain a matrix of file names -> root node -> child node permutations
|
|
C=""
|
|
FOUND=0
|
|
for i in "CFGSPAWNABLETYPES:spawnabletypes:type" "EVENTS:events:event" "TYPES:types:type"
|
|
do
|
|
var=$(echo ${i} | cut -d: -f1)
|
|
CHECK=$(echo ${i} | cut -d: -f2)
|
|
CHILD=$(echo ${i} | cut -d: -f3)
|
|
if [ -f "${WORKSHOP_DIR}/${ID}/${var,,}.xml" ]
|
|
then
|
|
if [[ ${FOUND} = 0 ]]
|
|
then
|
|
MODNAME=$(get_mod_name ${ID})
|
|
echo " Adding mod ${MODNAME}"
|
|
FOUND=1
|
|
fi
|
|
echo -n " "
|
|
for dir in $(ls ${MPMISSIONS})
|
|
do
|
|
# Only copy for the current map
|
|
if [[ ${dir} != ${MAP} ]]
|
|
then
|
|
continue
|
|
fi
|
|
rm -rf ${MPMISSIONS}/${dir}/${ID}
|
|
mkdir -p ${MPMISSIONS}/${dir}/${ID}
|
|
cp -v ${WORKSHOP_DIR}/${ID}/${var,,}.xml ${MPMISSIONS}/${dir}/${ID}/${var,,}.xml
|
|
done
|
|
C+="-s / -t elem -n file -a /file -t attr -n name -v ${var,,}.xml -a /file -t attr -n type -v ${CHECK} -m /file /ce "
|
|
fi
|
|
done
|
|
if [[ ${C} != "" ]]
|
|
then
|
|
# Merge into every mpmissions file
|
|
find ${MPMISSIONS} -name cfgeconomycore.xml -exec \
|
|
xmlstarlet ed -L -s / -t elem -n ce \
|
|
-a /ce -t attr -n folder -v "${ID}" \
|
|
${C} \
|
|
-m /ce /economycore {} \;
|
|
fi
|
|
# These still have to be merged into the upstream file
|
|
# for i in "CFGEVENTSPAWNS:eventposdef:event"
|
|
# do
|
|
# var=$(echo ${i} | cut -d: -f1)
|
|
# CHECK=$(echo ${i} | cut -d: -f2)
|
|
# CHILD=$(echo ${i} | cut -d: -f3)
|
|
# if [ -f "${WORKSHOP_DIR}/${ID}/${var,,}.xml" ]
|
|
# then
|
|
# echo "Merging ${var,,}.xml (Not really...WIP)"
|
|
# xmlstarlet ed -L -s / -t name event
|
|
# fi
|
|
# done
|
|
done
|
|
}
|
|
|
|
# Start the server in the foreground
|
|
start(){
|
|
# Ensure mpmissions has at least one map. If not, copy it from the local read-only volume that stores pristine mpmissons directories
|
|
if [ ! -d "${MPMISSIONS}/dayzOffline.chernarusplus" ]
|
|
then
|
|
echo
|
|
echo "Performing one-time copy of Chernarus mpmissions..."
|
|
echo
|
|
cp -av /mpmissions/dayzOffline.chernarusplus ${MPMISSIONS}
|
|
fi
|
|
# If we're developing, just block the container
|
|
if [[ ${DEVELOPMENT} = "1" ]]
|
|
then
|
|
echo "DEVELOPMENT mode, blocking..."
|
|
tail -f /dev/null
|
|
exit 0
|
|
fi
|
|
# Do the report on exit. Set here so that it only happens once we're starting the server, and not for other actions.
|
|
trap '
|
|
report
|
|
' EXIT
|
|
get_mods
|
|
mergexml
|
|
cd ${SERVER_FILES}
|
|
# Run the server. Allow docker to restart the container if the script exits with a code other than 0. This is so we can
|
|
# safely shut the container down without killing the server within.
|
|
printf "[ ${green}DayZ${default} ] Server starting...\n"
|
|
# Save the mod command line and parameters that were used to start the server, so status reflects the running server's
|
|
# actual status with those
|
|
echo ${mod_command_line} > /tmp/mod_command_line
|
|
echo ${parameters} > /tmp/parameters
|
|
./DayZServer "${mod_command_line}" ${parameters}
|
|
EXIT_CODE=$?
|
|
if [ -f ${SERVER_FILES}/restart ]
|
|
then
|
|
rm -f ${SERVER_FILES}/restart
|
|
EXIT_CODE=42
|
|
fi
|
|
printf "\n[ ${yellow}DayZ${default} ] Server exited. Exit code: ${EXIT_CODE}\n"
|
|
exit ${EXIT_CODE}
|
|
}
|
|
|
|
# Restarts the server by forcing an exit code other than 0, causing docker to restart the container.
|
|
restart(){
|
|
touch "${SERVER_FILES}/restart"
|
|
echo "Restarting DayZ server..."
|
|
kill -TERM $(pidof DayZServer)
|
|
}
|
|
|
|
# Stops the server cleanly and exits 0, which will stop the container.
|
|
stop(){
|
|
echo "Stopping DayZ server..."
|
|
kill -TERM $(pidof DayZServer)
|
|
}
|
|
|
|
# Forcibly kill the server, should it be necessary.
|
|
force(){
|
|
echo "Forcibly stopping DayZ server..."
|
|
kill -KILL $(pidof DayZServer)
|
|
}
|
|
|
|
# Handle any changes in the server config file by allowing them to be merged after viewing a diff.
|
|
config(){
|
|
if ! diff -q "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}"
|
|
then
|
|
echo "========================================================================="
|
|
diff -Nau --color "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}" | more
|
|
echo "========================================================================="
|
|
if prompt_yn "The new server configuration file differs from what's installed. Use it?"
|
|
then
|
|
echo "Updating the server configuration file"
|
|
cp "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}"
|
|
else
|
|
echo "NOT updating the server configuration file"
|
|
fi
|
|
else
|
|
echo "No differences found between ${SERVER_CFG_SRC} and ${SERVER_CFG_DST}"
|
|
fi
|
|
}
|
|
|
|
# Activate / Deactivate a mod
|
|
activate(){
|
|
W=${1}
|
|
shift
|
|
WW=""
|
|
COLOR="${green}"
|
|
if [[ ${W} = 0 ]]
|
|
then
|
|
WW="de"
|
|
COLOR="${red}"
|
|
fi
|
|
ID=$(get_mod_id ${1} ${W})
|
|
MODNAME=$(get_mod_name ${ID})
|
|
# echo "ID: ${ID}, MODNAME: ${MODNAME}"
|
|
# exit 0
|
|
# Toggle state or report nothing burger
|
|
pushd "${SERVER_PROFILE}" > /dev/null
|
|
if [[ ${W} = 0 ]] && [ -L "${SERVER_PROFILE}/@${MODNAME}" ]
|
|
then
|
|
echo -n "Removing mod symlink: "
|
|
rm -vf "${SERVER_PROFILE}/@${MODNAME}"
|
|
elif [[ ${W} = 1 ]]
|
|
then
|
|
echo -n "Creating mod symlink: "
|
|
ln -sfv "${WORKSHOP_DIR}/${ID}" "${SERVER_PROFILE}/@${MODNAME}"
|
|
else
|
|
echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} - is already ${WW}active"
|
|
fi
|
|
copy_keys ${W} ${ID}
|
|
echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} ${WW}activated"
|
|
popd > /dev/null
|
|
status
|
|
}
|
|
|
|
# Our internal RCON
|
|
rcon(){
|
|
exec /usr/local/py3rcon/py3rcon.py --gui ~/py3rcon.config.json
|
|
}
|
|
|
|
# List mods
|
|
activelist(){
|
|
X=1
|
|
C="${green}"
|
|
spaces=" "
|
|
have=no
|
|
for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null)
|
|
do
|
|
if [[ ${have} = "no" ]]
|
|
then
|
|
have="yes"
|
|
echo -e "\n ID Name URL Size"
|
|
echo "------------------------------------------------------------------------------------------------------------------------"
|
|
fi
|
|
ID=$(readlink ${link} | awk -F/ '{print $NF}')
|
|
MODNAME=$(get_mod_name ${ID})
|
|
SIZE=$(du -sh "${WORKSHOP_DIR}/${ID}" | awk '{print $1}')
|
|
printf "${C}%.3d %s %.23s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${MODNAME}" "${spaces:${#MODNAME}+1}" ${ID} ${SIZE}
|
|
X=$((X+1))
|
|
done
|
|
if [[ ${have} = "no" ]]
|
|
then
|
|
echo -ne "${red}none${default}"
|
|
fi
|
|
}
|
|
|
|
get_map_name(){
|
|
MAP="none"
|
|
# Map name
|
|
if [[ -f ${SERVER_CFG_DST} ]]
|
|
then
|
|
MAP=$(grep -E "template=" ${SERVER_CFG_DST} | grep -vE "^//" | cut -d= -f2 | cut -d\; -f1 | tr -d '"')
|
|
fi
|
|
echo ${MAP}
|
|
}
|
|
|
|
# Display the status of everything
|
|
status(){
|
|
loadconfig
|
|
INSTALLED="${NO}"
|
|
RUNNING="${NO}"
|
|
|
|
# DayZ Server files installation
|
|
if [ -f "${SERVER_INSTALL_FILE}" ]
|
|
then
|
|
INSTALLED="${YES}"
|
|
fi
|
|
# Running or not
|
|
if pidof DayZServer > /dev/null
|
|
then
|
|
# Uptime
|
|
D=$(date +%s)
|
|
F=$(date +%s -r ${SERVER_PROFILE}/server_console.log)
|
|
DAYS=$(( (${D} - ${F}) / 86400 ))
|
|
# UPTIME=$(date --date="$(( ${D} - ${F} ))" +"${DAYS} days %H:%M:%S")
|
|
UPTIME="${DAYS} days "$(date -d@$(($(date +%s) - $(date +%s -r ${SERVER_PROFILE}/server_console.log))) -u +"%H hours %M minutes %S seconds")
|
|
|
|
RUNNING="${YES}\nUptime: ${green}${UPTIME}${default}"
|
|
# Current parameters
|
|
RUNNING="${RUNNING}\nRunning Parameters: $(cat /tmp/parameters)\nRunning mod parameter: $(cat /tmp/mod_command_line)"
|
|
fi
|
|
MAP=$(get_map_name)
|
|
# Number of mods plus the list denoting on or off
|
|
echo -ne "
|
|
Server files installed: ${INSTALLED}"
|
|
if [[ "${INSTALLED}" = "${NO}" ]]
|
|
then
|
|
echo
|
|
echo
|
|
exit 0
|
|
fi
|
|
get_mods
|
|
echo -ne "
|
|
Active mods: "
|
|
activelist
|
|
echo -e "${MODS}
|
|
Server running: ${RUNNING}
|
|
Working parameters: ${parameters}
|
|
Working mod parameter: ${mod_command_line}"
|
|
if [[ "${INSTALLED}" = "${YES}" ]]
|
|
then
|
|
MAP=$(grep template ${SERVER_CFG_DST} | grep -v "^//" | cut -d= -f2 | cut -d\; -f1)
|
|
echo "Map: ${MAP}"
|
|
fi
|
|
echo
|
|
}
|
|
|
|
backup(){
|
|
cd ${MPMISSIONS}
|
|
DATE=$(date +'%Y-%m-%d-%H-%M-%S')
|
|
for i in $(ls)
|
|
do
|
|
B="${BACKUP_DIR}/${DATE}/"
|
|
echo "Backing up ${i} to ${B}..."
|
|
mkdir -p ${B} 1> /dev/null
|
|
cp -a "${i}" "${B}"
|
|
done
|
|
}
|
|
|
|
MAP=$(get_map_name)
|
|
|
|
# Capture the first argument and shift it off so we can pass $@ to every function
|
|
C=${1}
|
|
shift || {
|
|
usage
|
|
}
|
|
|
|
case "${C}" in
|
|
a|activate)
|
|
activate 1 "${@}"
|
|
;;
|
|
add)
|
|
add "${@}"
|
|
;;
|
|
b|backup)
|
|
backup "${@}"
|
|
;;
|
|
c|config)
|
|
config "${@}"
|
|
;;
|
|
d|deactivate)
|
|
activate 0 "${@}"
|
|
;;
|
|
f|force)
|
|
force
|
|
;;
|
|
i|install)
|
|
install "${@}"
|
|
;;
|
|
l|list)
|
|
list "${@}"
|
|
;;
|
|
login)
|
|
login "${@}"
|
|
;;
|
|
n|rcon)
|
|
rcon "${@}"
|
|
;;
|
|
r|restart)
|
|
restart "${@}"
|
|
;;
|
|
start)
|
|
start "${@}"
|
|
;;
|
|
s|status)
|
|
status "${@}"
|
|
;;
|
|
stop)
|
|
stop "${@}"
|
|
;;
|
|
*)
|
|
usage "$*"
|
|
;;
|
|
esac
|