dayzdockerserver/files/dayzserver
2022-07-23 20:16:07 -07:00

609 lines
17 KiB
Bash
Executable file

#!/usr/bin/env bash
# Colors
default="\e[0m"
red="\e[31m"
green="\e[32m"
yellow="\e[93m"
lightblue="\e[94m"
blue="\e[34m"
magenta="\e[35m"
cyan="\e[36m"
# Make sure to report and clean up on exit, as these files remain in the container's volume
function report() {
echo
echo -e "${yellow}========================================== error.log =========================================="
find "${HOME}" -name error.log -exec head {} \; -exec tail {} \; -exec rm -f {} \;
echo
echo -e "${yellow}========================================== script*.log ========================================"
find "${HOME}" -name "script*.log" -exec head {} \; -exec tail {} \; -exec rm -f {} \;
echo
echo -e "${yellow}========================================== *.RPT =============================================="
find "${HOME}" -name "*.RPT" -exec ls -la {} \; -exec tail {} \; -exec rm -f {} \;
echo
echo -e "${yellow}========================================== End crash log ======================================"
}
# DayZ Experimental SteamID, because there is no release version of the Linux server yet
appid=1042420
# DayZ Release SteamID. This is for mods, as only the release has them.
dayz_id=221100
# Base directories
CFG_SRC_FILES="/files"
SERVER_FILES="${HOME}/serverfiles"
SERVER_PROFILE="${HOME}/profiles"
# Command line argument variables
# Server config file
config=serverDZ.cfg
# Server port
port=2302
rcon_port=2303
# Server profile argument
profile="-profiles=${SERVER_PROFILE}"
# To log or not to log.
# All possible logging on:
#logs="-dologs -adminlog -netlog"
# No logs? This should read "-somelogs"
logs="-nologs"
# Put them all together
dayzparameter=" -config=${config} -port=${port} -freezecheck -fps=60 -BEpath=${SERVER_FILES}/battleye ${profile} ${logs}"
# Server configuration file
SERVER_CFG_FILE="serverDZ.cfg"
SERVER_CFG_DST="${SERVER_FILES}/${SERVER_CFG_FILE}"
SERVER_CFG_SRC="${CFG_SRC_FILES}/${SERVER_CFG_FILE}"
# Used to check if dayZ is installed
SERVER_INSTALL_FILE="${SERVER_FILES}/DayZServer"
# Steam files
STEAM_LOGIN="${HOME}/steamlogin"
STEAMCMD=steamcmd
# Workshop
WORKSHOP_CFG="${HOME}/workshop.cfg"
if [ ! -f "${WORKSHOP_CFG}" ]
then
touch "${WORKSHOP_CFG}"
fi
declare -a workshopID
workshoplist=""
workshopfolder="${SERVER_FILES}/steamapps/workshop/content/${dayz_id}"
mod_command_line="-mod="
# Other stuff
YES="${green}yes${default}"
NO="${red}no${default}"
fn_usage(){
echo -e "
${red}Bad option or arguments! ${yellow}${*}${default}
Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ]
Options and arguments:
install - Install the DayZ server files
login - Login to Steam
rcon - Connect to the server using a python RCON client
status - Shows the server's status as well as the state of the server files and mods (Needs install, update, etc.)
stop - Stop the server
update - Update the DayZ server and configuration files
workshop - Workshop Features
activate id [id...] - Activate one or many installed DayZ Workshop item by id
add id [id...] - Add one or many DayZ Workshop items by id. Added items are become active by default.
deactivate id [id...] - Deactivate one or many installed DayZ Workshop items by id - Keeps the addon files but excludes from -mod=
list - List Workshop items and their states (Active vs. Non)
remove id [id...] - Remove one or many Workshop items by id
${default}"
exit 1
}
fn_prompt_yn(){
echo -n "${1} (y|N) " >&2
read -s -n 1 a
a=$(echo ${a} | tr A-Z a-z)
if [[ "${a}" = "y" ]]
then
echo
return 0
else
echo
return 1
fi
}
fn_loadconfig_dayz(){
if [ ! -f "${SERVER_INSTALL_FILE}" ]
then
echo
echo -e "The DayZ server files are not installed. Run '${green}docker-compose run --rm main dayzserver install${default}'"
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="${HOME}/serverfiles/battleye/beserver_x64.cfg"
ALT_BE_SERVER_FILE=$(find ${HOME}/serverfiles/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
}
fn_start_dayz(){
# Check for interactive shell
if [ -t 0 ]
then
echo
echo -e "Not starting the server in an interactive shell. Start the server using '${green}docker-compose up -d${default}'"
echo
exit
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
fn_mod_cmd
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"
./DayZServer "${mod_command_line}" ${dayzparameter}
EXIT_CODE=$?
if [ -f ${SERVER_FILES}/restart ]
then
rm -f ${SERVER_FILES}/restart
EXIT_CODE=42
fi
printf "[ ${yellow}DayZ${default} ] Server exited. Exit code: ${EXIT_CODE}\n"
exit ${EXIT_CODE}
}
fn_restart(){
touch "${SERVER_FILES}/restart"
echo "Restarting DayZ server..."
kill -TERM $(pidof DayZServer)
}
fn_stop_dayz(){
echo "Stopping DayZ server..."
kill -TERM $(pidof DayZServer)
}
fn_steam_login(){
if [ -f "${STEAM_LOGIN}" ]
then
if fn_prompt_yn "The steam login is already set. Reset it?"
then
rm -f "${STEAM_LOGIN}"
else
echo "Not reset."
exit 0
fi
fi
if [ ! -f "${STEAM_LOGIN}" ]
then
echo "Setting up Steam credentials"
echo -n "Steam Username (anonymous): "
read steamlogin
if [[ "${steamlogin}" = "" ]]
then
echo "Steam login set to 'anonymous'"
steamlogin="anonymous"
fi
echo "steamlogin=${steamlogin}" > "${STEAM_LOGIN}"
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +quit
fi
}
fn_steamlogin_dayz(){
if [ -f "${STEAM_LOGIN}" ]
then
source "${STEAM_LOGIN}"
else
echo "No cached Steam credentials. Please configure this now: "
fn_steam_login
fi
}
fn_runvalidate_dayz(){
# fn_loadconfig_dayz
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${appid}" validate +quit
}
fn_install_dayz(){
if [ ! -f "${SERVER_INSTALL_FILE}" ]; then
mkdir -p "${SERVER_FILES}"
mkdir -p "${SERVER_PROFILE}"
printf "[ ${yellow}DayZ${default} ] Downloading DayZ Server-Files!\n"
fn_steamlogin_dayz
fn_runvalidate_dayz
else
printf "[ ${lightblue}DayZ${default} ] The Server is already installed.\n"
fi
}
fn_runupdate_dayz(){
fn_steamlogin_dayz
if ! diff -q "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}"
then
echo "========================================================================="
diff -Nau --color "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}" || echo ""
echo "========================================================================="
if fn_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
fi
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${appid}" +quit
}
fn_update_dayz(){
fn_steamlogin_dayz
appmanifestfile=${SERVER_FILES}/steamapps/appmanifest_"${appid}".acf
printf "[ ... ] Checking for update:"
# gets currentbuild
currentbuild=$(grep buildid "${appmanifestfile}" | tr '[:blank:]"' ' ' | tr -s ' ' | cut -d \ -f3)
# Removes appinfo.vdf as a fix for not always getting up to date version info from SteamCMD
if [ -f "${HOME}/Steam/appcache/appinfo.vdf" ]; then
rm -f "${HOME}/Steam/appcache/appinfo.vdf"
fi
# check for new build
availablebuild=$(${STEAMCMD} +login "${steamlogin}" +app_info_update 1 +app_info_print "${appid}" +app_info_print \
"${appid}" +quit | sed -n '/branch/,$p' | grep -m 1 buildid | tr -cd '[:digit:]')
if [ -z "${availablebuild}" ]; then
printf "\r[ ${red}FAIL${default} ] Checking for update:\n"
printf "\r[ ${red}FAIL${default} ] Checking for update:: Not returning version info\n"
exit
else
printf "\r[ ${green}OK${default} ] Checking for update:"
fi
# compare builds
if [ "${currentbuild}" != "${availablebuild}" ]; then
printf "\r[ ${green}OK${default} ] Checking for update:: Update available\n"
printf "Update available:\n"
printf "\tCurrent build: ${red}${currentbuild}${default}\n"
printf "\tAvailable build: ${green}${availablebuild}${default}\n"
printf "\thttps://steamdb.info/app/${appid}/\n"
printf "\nApplying update"
# run update
fn_runupdate_dayz
fn_update_mods
else
printf "\r[ ${green}OK${default} ] Checking for update:: No update available\n"
printf "\nNo update available:\n"
printf "\tCurrent version: ${green}${currentbuild}${default}\n"
printf "\tAvailable version: ${green}${availablebuild}${default}\n"
printf "\thttps://steamdb.info/app/${appid}/\n\n"
fi
}
fn_get_mods(){
mapfile -t workshopID < "${WORKSHOP_CFG}"
for i in "${workshopID[@]}"
do
if [[ $i =~ ^[0-9] ]] && [ $(expr length $i) -gt 7 ]
then
workshoplist+=" +workshop_download_item "${dayz_id}" "$i""
fi
done
}
fn_update_mods(){
fn_get_mods
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" ${workshoplist} +quit
echo
}
fn_add_mod(){
if [ -d "${workshopfolder}/${1}" ]
then
echo -e "${yellow}Warning: The mod directory ${workshopfolder}/${1} already exists!${default}"
MODNAME=$(cut -d '"' -f 2 <<< $(grep name ${workshopfolder}/${1}/meta.cpp))
fi
if [ -L "${SERVER_FILES}/@${MODNAME}" ]
then
echo -e "${yellow}Warning: The mod symlink ${SERVER_FILES}/@${MODNAME} already exists!${default}"
fi
if grep -qP "\b${1}\b" "${WORKSHOP_CFG}"
then
echo "The mod with id ${1} is already in the workshop configuration."
return
fi
echo "Adding mod id ${1}"
echo "${1}:MODNAME:1" >> ${WORKSHOP_CFG}
fn_update_mods
# Make sure the install succeeded
if [ ! -d "${workshopfolder}/${1}" ]
then
echo -e "${red}Mod installation failed: The mod directory ${workshopfolder}/${1} was not created!${default}"
echo "Installation failed! See above (You probably need to use a real Steam login)"
# The mod is added temporarily into the workshop config. Since the installation failed, reemove it instead of updating it.
head -n-1 "${WORKSHOP_CFG}" > /tmp/workshop.cfg.tmp
mv /tmp/workshop.cfg.tmp "${WORKSHOP_CFG}"
return
fi
# Get the name of the newly added addon
MODNAME=$(cut -d '"' -f 2 <<< $(grep name ${workshopfolder}/${1}/meta.cpp))
# Symlink it
if [ ! -L "${SERVER_FILES}/@${MODNAME}" ]
then
ln -s ${workshopfolder}/${1} "${SERVER_FILES}/@${MODNAME}"
echo "Created symlink for mod id ${1}"
fi
# Lower case all the files in mod directories.
find "${workshopfolder}/${1}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;
# Copy the key files
if ls ${SERVER_FILES}/@* 1> /dev/null 2>&1
then
echo "Copying key files..."
cp -vu "${workshopfolder}/*/keys/*" "${SERVER_FILES}/keys/" 2> /dev/null
fi
# Set the mod name in the workshop config file, as we don't know this at the start.
sed -i "${WORKSHOP_CFG}" -e "s/${1}:MODNAME/${1}:${MODNAME}/"
echo -e "Mod id ${1} - ${green}${MODNAME}${default} - added"
}
fn_remove_mod(){
if [ -d "${workshopfolder}/${1}" ]
then
MODNAME=$(cut -d '"' -f 2 <<< $(grep name ${workshopfolder}/${1}/meta.cpp))
echo "Removing directory ${workshopfolder}/${1}"
rm -rf "${workshopfolder}/${1}"
fi
if [ -L "${SERVER_FILES}/@${MODNAME}" ]
then
echo "Removing symlink ${SERVER_FILES}/@${MODNAME}"
rm -f "${SERVER_FILES}/@${MODNAME}"
fi
if grep -q ${1} "${WORKSHOP_CFG}"
then
echo "Removing workshop file entry"
sed -i "${WORKSHOP_CFG}" -e "/${1}:/d"
fi
echo "Mod id ${1} - ${MODNAME} - removed"
}
fn_activate_mod(){
fn_get_mods
for i in "${workshopID[@]}"
do
ID=$(echo ${i} | cut -d: -f1)
NAME=$(echo ${i} | cut -d: -f2)
ACTIVE=$(echo ${i} | cut -d: -f3)
if [[ ${ID} = ${1} ]]
then
if [[ ${ACTIVE} = "0" ]]
then
sed -i "${WORKSHOP_CFG}" -e 's/'${1}':\(.*\):0/'${1}':\1:1/'
echo "Activcated mod id ${1}"
else
echo "Mod id ${1} was already active"
fi
fi
done
}
fn_deactivate_mod(){
fn_get_mods
for i in "${workshopID[@]}"
do
ID=$(echo ${i} | cut -d: -f1)
NAME=$(echo ${i} | cut -d: -f2)
ACTIVE=$(echo ${i} | cut -d: -f3)
if [[ ${ID} = ${1} ]]
then
if [[ ${ACTIVE} = "1" ]]
then
sed -i "${WORKSHOP_CFG}" -e 's/'${1}':\(.*\):1/'${1}':\1:0/'
echo "Dectivcated mod id ${1}"
else
echo "Mod id ${1} was already inactive"
fi
fi
done
}
fn_list_mods(){
fn_get_mods
spaces=" "
echo -e "ID\t\tName\t\t\tActive\tURL"
for i in "${workshopID[@]}"
do
ID=$(echo ${i} | cut -d: -f1)
NAME=$(echo ${i} | cut -d: -f2)
ACTIVE=$(echo ${i} | cut -d: -f3)
printf "%s\t%.23s%s\t%s\thttps://steamcommunity.com/sharedfiles/filedetails/?id=%s\n" ${ID} "${NAME}" "${spaces:${#NAME}+1}" ${ACTIVE} ${ID}
done
}
fn_mod_cmd(){
fn_get_mods
for i in "${workshopID[@]}"
do
ID=$(echo ${i} | cut -d: -f1)
NAME=$(echo ${i} | cut -d: -f2)
ACTIVE=$(echo ${i} | cut -d: -f3)
if [[ ${ACTIVE} = "1" ]]
then
modname=$(cut -d '"' -f 2 <<< $(grep name ${workshopfolder}/${ID}/meta.cpp))
mod_command_line="${mod_command_line}@${modname};"
fi
done
mod_command_line="${mod_command_line::-1}"
}
fn_workshop_mods(){
# Check usage
if [[ ${1} = "" ]]
then
fn_usage
fi
if [[ ${1} != "list" ]] && [[ ${2} = "" ]]
then
fn_usage
fi
# Most mods require a valid Steam login. Not all will work with the anonymous user.
fn_steamlogin_dayz
# Options
case "${1}" in
activate)
fn_activate_mod ${2}
;;
add)
fn_add_mod ${2}
;;
deactivate)
fn_deactivate_mod ${2}
;;
list)
fn_list_mods
;;
remove)
fn_remove_mod ${2}
;;
update)
echo "Updating mods..."
fn_update_mods
;;
**)
fn_usage
;;
esac
}
fn_rcon(){
exec /usr/local/py3rcon/py3rcon.py --gui ~/py3rcon.config.json
}
fn_status(){
INSTALLED="${NO}"
LOGGED_IN="${NO}"
RUNNING="${NO}"
fn_get_mods
MOD_INSTALLED="${green}${#workshopID[@]}${default}"
MOD_LIST=""
# DayZ Server files installation
if [ -f "${SERVER_INSTALL_FILE}" ]
then
INSTALLED="${YES}"
fi
# Logged into Steam
if [ -f "${STEAM_LOGIN}" ]
then
LOGGED_IN="${YES}"
if grep -q anonymous "${STEAM_LOGIN}"
then
ANONYMOUS="(as anonymous)"
else
ANONYMOUS="(not anonymous)"
fi
fi
# Running or not
if pidof DayZServer > /dev/null
then
RUNNING="${YES}"
fi
fn_mod_cmd
# Number of mods plus the list denoting on or off
echo -e "
Status:
Logged in to Steam: ${LOGGED_IN} ${ANONYMOUS}
Server files installed: ${INSTALLED}
Mods installed: ${MOD_INSTALLED}${MOD_LIST}
Server running: ${RUNNING}
Default parameters:${dayzparameter}
Mod parameter: ${mod_command_line}
"
}
case "${1}" in
cmd)
fn_mod_cmd
;;
install)
fn_loadconfig_dayz
fn_install_dayz
;;
login)
fn_loadconfig_dayz
fn_steam_login
;;
rcon)
fn_rcon "${2}"
;;
restart)
fn_restart
;;
start)
fn_loadconfig_dayz
fn_start_dayz
;;
status)
fn_status
;;
stop)
fn_stop_dayz
;;
update)
fn_runupdate_dayz
fn_loadconfig_dayz
fn_update_dayz
;;
workshop)
fn_loadconfig_dayz
fn_workshop_mods ${2} ${3} ${4}
;;
**)
fn_usage "$*"
;;
esac