mirror of
https://ceregatti.org/git/daniel/dayzdockerserver.git
synced 2025-05-06 22:31:18 +00:00

Make development work by setting an environment variable. Set the web container to restart instead of not, should the express server crash. Copy XML files that are merged when the server starts only when the mpmissions directory exists. Refactor XML functions for better naming. Handle display of lists when no mods are installed. Add support for adding mpmissions for Deer Isle and mpmissions in general via mod integrations. Add support for Red Falcon Watercraft XML files. WIP. Add a deer isle DayZ bicycle spawn file.
453 lines
13 KiB
Bash
Executable file
453 lines
13 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(s)..."
|
|
for dir in $(ls -d ${MPMISSIONS}/*)
|
|
do
|
|
#echo "dir: ${dir}, basename: $(basename ${dir})"
|
|
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=""
|
|
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
|
|
FOUND=1
|
|
echo "Adding ${WORKSHOP_DIR}/${ID}/${var,,}.xml to cfgeconomycore..."
|
|
for dir in $(ls ${MPMISSIONS})
|
|
do
|
|
mkdir -pv ${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..."
|
|
# 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
|
|
}
|
|
|
|
# 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="none"
|
|
# Map name
|
|
if [[ -f ${SERVER_CFG_DST} ]]
|
|
then
|
|
MAP=$(grep -E "template=" ${SERVER_CFG_DST} | grep -vE "^//")
|
|
fi
|
|
# 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}
|
|
cp -a "${i}" "${B}"
|
|
done
|
|
}
|
|
|
|
# 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
|