mirror of
https://ceregatti.org/git/daniel/dayzdockerserver.git
synced 2025-05-07 14:51:17 +00:00
Merge pull request 'volume-refactor' (#1) from volume-refactor into main
Reviewed-on: https://ceregatti.org/git/daniel/dayzdockerserver/pulls/1
This commit is contained in:
commit
2ec01a0d7a
32 changed files with 3323 additions and 863 deletions
|
@ -1,2 +0,0 @@
|
||||||
**
|
|
||||||
!files
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
||||||
.idea
|
.idea
|
||||||
|
*.iml
|
||||||
|
node_modules/
|
||||||
|
|
|
@ -1,19 +1,53 @@
|
||||||
version: "3.3"
|
version: "3.3"
|
||||||
|
|
||||||
# This is where the server files, profile, mods, and logs will reside.
|
|
||||||
# The server script does its best to clean up the copious logs that the
|
|
||||||
# server creates, but with mods, this volume will grow rather large.
|
|
||||||
# It's best to keep an eye on its size.
|
|
||||||
volumes:
|
volumes:
|
||||||
homedir:
|
# For steamcmd files and resource files used by the scripts
|
||||||
|
homedir_main:
|
||||||
|
# For Steam, for now
|
||||||
|
homedir_server:
|
||||||
|
# Where the server files will be installed
|
||||||
|
serverfiles:
|
||||||
|
# Server profile files
|
||||||
|
profiles:
|
||||||
|
# Upstream mission files
|
||||||
|
servermpmissions:
|
||||||
|
# Server mission files
|
||||||
|
mpmissions:
|
||||||
|
# Mods
|
||||||
|
mods:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
main:
|
web:
|
||||||
build: .
|
build: web
|
||||||
volumes:
|
volumes:
|
||||||
- homedir:/home/user
|
- homedir_main:/home/user
|
||||||
|
- serverfiles:/serverfiles
|
||||||
|
- servermpmissions:/serverfiles/mpmissions
|
||||||
|
- mods:/serverfiles/steamapps/workshop/content
|
||||||
|
- mods:/mods
|
||||||
- ./files:/files
|
- ./files:/files
|
||||||
|
- ./web/bin/dz:/usr/local/bin/dz
|
||||||
|
- ./web:/web
|
||||||
|
ports:
|
||||||
|
- "8000:8000/tcp"
|
||||||
|
restart: no
|
||||||
|
environment:
|
||||||
|
# The use of the Steam API requires a key. Get yours here: https://steamcommunity.com/dev/apikey
|
||||||
|
- STEAMAPIKEY=YOUR_STEAM_API_KEY_HERE
|
||||||
|
|
||||||
|
server:
|
||||||
|
build: server
|
||||||
|
volumes:
|
||||||
|
- homedir_server:/home/user
|
||||||
|
- serverfiles:/serverfiles
|
||||||
|
- servermpmissions:/mpmissions:ro
|
||||||
|
- mods:/mods
|
||||||
|
- mpmissions:/serverfiles/mpmissions
|
||||||
|
- profiles:/profiles
|
||||||
|
- ./files:/files
|
||||||
|
- ./server:/server
|
||||||
|
- ./server/bin/dz:/usr/local/bin/dz
|
||||||
# To have the server show up in the LAN tab of the DayZ launcher,
|
# To have the server show up in the LAN tab of the DayZ launcher,
|
||||||
# it must run under host mode.
|
# it must run under host mode.
|
||||||
network_mode: host
|
network_mode: host
|
||||||
|
@ -21,20 +55,19 @@ services:
|
||||||
# the server to show up on the LAN, comment out the network_mode above
|
# the server to show up on the LAN, comment out the network_mode above
|
||||||
# and uncomment the port mappings below.
|
# and uncomment the port mappings below.
|
||||||
# ports:
|
# ports:
|
||||||
|
# # Game port
|
||||||
# - 2302:2302/udp
|
# - 2302:2302/udp
|
||||||
# - 2303:2303/udp
|
# # RCON port
|
||||||
# - 2304:2304/udp
|
# - 2302:2302/udp
|
||||||
|
# # Steam port
|
||||||
# - 27016:27016/udp
|
# - 27016:27016/udp
|
||||||
# Always restart, unless stopped
|
# Always restart, unless stopped
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
# Allows attaching a debugger from the host
|
# Allows attaching a debugger from the host
|
||||||
# cap_add:
|
cap_add:
|
||||||
# - SYS_PTRACE
|
- SYS_PTRACE
|
||||||
# Allows core files to be created within the container. These are VERY LARGE! Enable only for debugging!
|
# Allows core files to be created within the container. These are VERY LARGE! Enable only for debugging!
|
||||||
# ulimits:
|
ulimits:
|
||||||
# core:
|
core:
|
||||||
# soft: -1
|
soft: -1
|
||||||
# hard: -1
|
hard: -1
|
||||||
# Do nothing instead of starting the server, which is the default.
|
|
||||||
# Helpful for development or debugging.
|
|
||||||
# command: tail -f /dev/null
|
|
||||||
|
|
135
files/bin/dz-common
Executable file
135
files/bin/dz-common
Executable file
|
@ -0,0 +1,135 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eEa
|
||||||
|
|
||||||
|
# If you want/need the server and rcon ports to be different, set them here.
|
||||||
|
# The steam query port is set in serverDZ.cfg.
|
||||||
|
|
||||||
|
# Server port
|
||||||
|
port=2302
|
||||||
|
rcon_port=2303
|
||||||
|
|
||||||
|
# Don't change anything else.
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# DayZ release server Steam app ID. USE ONE OR THE OTHER!!
|
||||||
|
# Presumably once the Linux server is released, the binaries will come from this ID.
|
||||||
|
# But more importantly, if we have a release-compatible binary, the base files must be installed from this id,
|
||||||
|
# even if the server binary and accompanying shared object don't come from it.
|
||||||
|
#release_server_appid=223350
|
||||||
|
# Without a release binary, we must use the experimental server app id for everything.
|
||||||
|
release_server_appid=1042420
|
||||||
|
|
||||||
|
# DayZ release client SteamID. This is for mods, as only the release client has them.
|
||||||
|
release_client_appid=221100
|
||||||
|
|
||||||
|
# Common container base directories
|
||||||
|
FILES="/files"
|
||||||
|
SERVER_FILES="/serverfiles"
|
||||||
|
|
||||||
|
# Used to check if dayZ is installed
|
||||||
|
SERVER_INSTALL_FILE="${SERVER_FILES}/DayZServer"
|
||||||
|
|
||||||
|
# Steam files
|
||||||
|
STEAM_LOGIN="${HOME}/steamlogin"
|
||||||
|
STEAMCMD=steamcmd
|
||||||
|
|
||||||
|
# Workshop files (mods)
|
||||||
|
WORKSHOP_DIR="${SERVER_FILES}/steamapps/workshop/content/${release_client_appid}"
|
||||||
|
|
||||||
|
# Other stuff
|
||||||
|
YES="${green}yes${default}"
|
||||||
|
NO="${red}no${default}"
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
|
||||||
|
# Convenience function
|
||||||
|
prompt_yn(){
|
||||||
|
echo -n "${1} (y|N) " >&2
|
||||||
|
read -s -n 1 a
|
||||||
|
a=$(echo ${a} | tr A-Z a-z)
|
||||||
|
echo
|
||||||
|
if [[ "${a}" = "y" ]]
|
||||||
|
then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_mod_install(){
|
||||||
|
# See if this mod id exists in files/mods, and offer to install other server side files if an install.sh is found
|
||||||
|
if [ -f ${FILES}/mods/${1}/${2}.sh ]
|
||||||
|
then
|
||||||
|
echo "An ${2}.sh was found for mod id ${1}. Running..."
|
||||||
|
${FILES}/mods/${1}/${2}.sh
|
||||||
|
fi
|
||||||
|
# A generic map install script. Presumes a git repo as the source
|
||||||
|
if [ -f ${FILES}/mods/${1}/install.env ]
|
||||||
|
then
|
||||||
|
echo "An ${2}.env was found for mod id ${1}. Performing ${2}..."
|
||||||
|
source ${FILES}/mods/${1}/install.env
|
||||||
|
${FILES}/mods/install.sh ${1} ${2}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_mod_id_by_index2(){
|
||||||
|
# If we were passed a valid mod id, just return it
|
||||||
|
if [ -d "${WORKSHOP_DIR}/${1}" ]
|
||||||
|
then
|
||||||
|
echo -n ${1}
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
X=1
|
||||||
|
# Loop over mods
|
||||||
|
for dir in $(ls -tr ${WORKSHOP_DIR})
|
||||||
|
do
|
||||||
|
ID=${dir}
|
||||||
|
if [[ ${X} = ${1} ]]
|
||||||
|
then
|
||||||
|
echo -n ${ID}
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
X=$((X+1))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get mod name by ID or index
|
||||||
|
get_mod_name(){
|
||||||
|
ID=$(get_mod_id_by_index2 ${1})
|
||||||
|
if ! [ -d "${WORKSHOP_DIR}/${ID}" ]
|
||||||
|
then
|
||||||
|
echo "Mod ID ${1} doesn't exist" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
NAME=$(grep name ${WORKSHOP_DIR}/${ID}/meta.cpp | cut -d '"' -f2 | sed -r 's/\s+//g')
|
||||||
|
echo -n ${NAME}
|
||||||
|
}
|
||||||
|
|
||||||
|
# List mods
|
||||||
|
list(){
|
||||||
|
X=1
|
||||||
|
C="${green}"
|
||||||
|
spaces=" "
|
||||||
|
echo "Installed mods:"
|
||||||
|
echo -e " ID Name URL Size"
|
||||||
|
echo "------------------------------------------------------------------------------------------------------------------------"
|
||||||
|
for dir in $(ls -tr ${WORKSHOP_DIR})
|
||||||
|
do
|
||||||
|
ID=${dir}
|
||||||
|
NAME=$(grep name "${WORKSHOP_DIR}/${dir}/meta.cpp" | cut -d '"' -f2 | sed -r 's/\s+//g')
|
||||||
|
SIZE=$(du -sh "${WORKSHOP_DIR}/${dir}" | awk '{print $1}')
|
||||||
|
printf "${C}%.3d %s %.30s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${NAME}" "${spaces:${#NAME}+1}" ${ID} ${SIZE}
|
||||||
|
X=$((X+1))
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
}
|
|
@ -5,26 +5,26 @@ set -eE
|
||||||
source /files/mods/${1}/install.env
|
source /files/mods/${1}/install.env
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if echo ${0} | grep -q "uninstall.sh"
|
if [[ ${2} = "uninstall" ]]
|
||||||
then
|
then
|
||||||
echo "Backing up, as uninstalling will remove the ${MAP} mpmissions directory"
|
echo "Backing up, as uninstalling will remove the ${MAP} mpmissions directory"
|
||||||
dayzserver backup
|
dayzserver backup
|
||||||
echo "Uninstalling mpmissions..."
|
echo "Uninstalling mpmissions..."
|
||||||
echo
|
echo
|
||||||
rm -rf ${HOME}/serverfiles/mpmissions/${MPDIR}
|
rm -rf ${SERVER_FILES}/mpmissions/${MPDIR}
|
||||||
elif echo ${0} | grep -q "update.sh"
|
elif [[ ${2} = "update" ]]
|
||||||
then
|
then
|
||||||
echo "Updating mpmissions directory..."
|
echo "Updating mpmissions directory..."
|
||||||
echo
|
echo
|
||||||
cd /tmp
|
cd /tmp
|
||||||
git clone ${REPO} 2> /dev/null 1> /dev/null
|
git clone ${REPO} 2> /dev/null 1> /dev/null
|
||||||
cp -a ${DIR}/${MPDIR} ${HOME}/serverfiles/mpmissions
|
cp -a ${DIR}/${MPDIR} ${SERVER_FILES}/mpmissions
|
||||||
rm -rf ${DIR}
|
rm -rf ${DIR}
|
||||||
else
|
else
|
||||||
echo "Installing mpmissions files..."
|
echo "Installing mpmissions files..."
|
||||||
echo
|
echo
|
||||||
cd /tmp
|
cd /tmp
|
||||||
git clone ${REPO} 2> /dev/null 1> /dev/null
|
git clone ${REPO} 2> /dev/null 1> /dev/null
|
||||||
cp -a ${DIR}/${MPDIR} ${HOME}/serverfiles/mpmissions
|
cp -a ${DIR}/${MPDIR} ${SERVER_FILES}/mpmissions
|
||||||
rm -rf ${DIR}
|
rm -rf ${DIR}
|
||||||
fi
|
fi
|
26
files/bin/xml.sh
Executable file
26
files/bin/xml.sh
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# A generic script that retrieves XML files from mods, either upstream in remote endpoints, or
|
||||||
|
# locally from the downloaded mod directory
|
||||||
|
|
||||||
|
set -eE
|
||||||
|
|
||||||
|
ID=${1}
|
||||||
|
|
||||||
|
source ${FILES}/mods/${ID}/xml.env
|
||||||
|
|
||||||
|
# Iterate over the file names we can handle
|
||||||
|
for var in CFGEVENTSPAWNS CFGSPAWNABLETYPES EVENTS TYPES
|
||||||
|
do
|
||||||
|
if echo ${!var} | grep -q http
|
||||||
|
then
|
||||||
|
OUT="${WORKSHOP_DIR}/${ID}/${var,,}.xml"
|
||||||
|
echo "${var} is a URL, downloading to ${OUT}"
|
||||||
|
curl -so ${OUT} ${!var}
|
||||||
|
xmllint --noout ${OUT} 2> /dev/null || {
|
||||||
|
echo -e "${red}${var,,}.xml does not pass XML lint test!${default}"
|
||||||
|
} && {
|
||||||
|
echo -e "${green}${var,,}.xml passes XML lint test!${default}"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
done
|
743
files/dayzserver
743
files/dayzserver
|
@ -1,743 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -eEa
|
|
||||||
|
|
||||||
# If you want/need the server and rcon ports to be different, set them here.
|
|
||||||
# The steam query port is set in serverDZ.cfg.
|
|
||||||
|
|
||||||
# Server port
|
|
||||||
port=2302
|
|
||||||
rcon_port=2303
|
|
||||||
|
|
||||||
# Don't change anything else.
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
|
|
||||||
# DayZ release server Steam app ID. Presumably once the Linux server is released, the binaries will come
|
|
||||||
# from this ID. Let's find out!
|
|
||||||
#release_server_appid=223350
|
|
||||||
|
|
||||||
# For now, use the experimental server app id
|
|
||||||
release_server_appid=1042420
|
|
||||||
|
|
||||||
# DayZ release client SteamID. This is for mods, as only the release client has them.
|
|
||||||
release_client_appid=221100
|
|
||||||
|
|
||||||
# Base directories
|
|
||||||
CFG_SRC_FILES="/files"
|
|
||||||
SERVER_FILES="${HOME}/serverfiles"
|
|
||||||
SERVER_PROFILE="${HOME}/profiles"
|
|
||||||
|
|
||||||
mkdir -p ${SERVER_FILES}/battleye ${SERVER_PROFILE}
|
|
||||||
|
|
||||||
# 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}"
|
|
||||||
|
|
||||||
# Command line parameters except mod, as that is handled separately.
|
|
||||||
parameters="-config=${SERVER_CFG_FILE} -port=${port} -freezecheck -BEpath=${SERVER_FILES}/battleye -profiles=${SERVER_PROFILE} -nologs"
|
|
||||||
|
|
||||||
# Used to check if dayZ is installed
|
|
||||||
SERVER_INSTALL_FILE="${SERVER_FILES}/DayZServer"
|
|
||||||
|
|
||||||
# Steam files
|
|
||||||
STEAM_LOGIN="${HOME}/steamlogin"
|
|
||||||
STEAMCMD=steamcmd
|
|
||||||
|
|
||||||
# Workshop. This file will store metadata about what mods are installed.
|
|
||||||
WORKSHOP_CFG="${HOME}/workshop.cfg"
|
|
||||||
if [ ! -f "${WORKSHOP_CFG}" ]
|
|
||||||
then
|
|
||||||
touch "${WORKSHOP_CFG}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# An array to store Workshop items. Each element contains the mod's ID, name, and state (active or not).
|
|
||||||
declare -a workshopID
|
|
||||||
workshopfolder="${SERVER_FILES}/steamapps/workshop/content/${release_client_appid}"
|
|
||||||
|
|
||||||
# Backups
|
|
||||||
BACKUP_DIR="${HOME}/backup"
|
|
||||||
if [ ! -d "${BACKUP_DIR}" ]
|
|
||||||
then
|
|
||||||
mkdir -p "${BACKUP_DIR}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Other stuff
|
|
||||||
YES="${green}yes${default}"
|
|
||||||
NO="${red}no${default}"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
add id - Add a DayZ Workshop item by id. Added items become active by default
|
|
||||||
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
|
|
||||||
i|install - Install the DayZ server files
|
|
||||||
l|list - List Workshop items and their details
|
|
||||||
g|login - Login to Steam.
|
|
||||||
m|modupdate - Update the mod files
|
|
||||||
n|rcon - Connect to the server using a python RCON client
|
|
||||||
r|remove id - Remove all files and directories of a Workshop item by id
|
|
||||||
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
|
|
||||||
u|update - Update the server files
|
|
||||||
${default}"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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 "${HOME}" -name error.log -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
|
|
||||||
echo
|
|
||||||
echo -e "========================================== script*.log ========================================"
|
|
||||||
find "${HOME}" -name "script*.log" -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
|
|
||||||
echo
|
|
||||||
echo -e "========================================== *.RPT =============================================="
|
|
||||||
find "${HOME}" -name "*.RPT" -exec ls -la {} \; -exec tail -n 30 {} \; -exec rm -f {} \;
|
|
||||||
echo
|
|
||||||
echo -e "========================================== End log ======================================${default}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Convenience function
|
|
||||||
prompt_yn(){
|
|
||||||
echo -n "${1} (y|N) " >&2
|
|
||||||
read -s -n 1 a
|
|
||||||
a=$(echo ${a} | tr A-Z a-z)
|
|
||||||
echo
|
|
||||||
if [[ "${a}" = "y" ]]
|
|
||||||
then
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_install(){
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ensures all is installed and ready before allowing operations that depends on things being ready.
|
|
||||||
# Installs the initial server config file from its template.
|
|
||||||
# Handles the importing of changes to that template.
|
|
||||||
# Installs the initial Battleye RCON config.
|
|
||||||
loadconfig(){
|
|
||||||
# check_install
|
|
||||||
# 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
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start the server in the foreground
|
|
||||||
start(){
|
|
||||||
# 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
|
|
||||||
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"
|
|
||||||
# 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Hanle the Steam login information.
|
|
||||||
login(){
|
|
||||||
loadconfig
|
|
||||||
if [ -f "${STEAM_LOGIN}" ]
|
|
||||||
then
|
|
||||||
if 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
|
|
||||||
}
|
|
||||||
|
|
||||||
# "Perform" the Steam login. This just sources the file with the Steam login name.
|
|
||||||
dologin(){
|
|
||||||
loadconfig
|
|
||||||
if [ -f "${STEAM_LOGIN}" ]
|
|
||||||
then
|
|
||||||
source "${STEAM_LOGIN}"
|
|
||||||
else
|
|
||||||
echo "No cached Steam credentials. Please configure this now: "
|
|
||||||
login
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Perform the installation of the server files.
|
|
||||||
install(){
|
|
||||||
loadconfig
|
|
||||||
if [ ! -f "${SERVER_INSTALL_FILE}" ] || [[ ${1} = "force" ]]
|
|
||||||
then
|
|
||||||
mkdir -p "${SERVER_FILES}"
|
|
||||||
mkdir -p "${SERVER_PROFILE}"
|
|
||||||
printf "[ ${yellow}DayZ${default} ] Downloading DayZ Server-Files!\n"
|
|
||||||
dologin
|
|
||||||
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${release_server_appid}" validate +quit
|
|
||||||
else
|
|
||||||
printf "[ ${lightblue}DayZ${default} ] The server is already installed.\n"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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
|
|
||||||
}
|
|
||||||
|
|
||||||
# Update the server files.
|
|
||||||
update(){
|
|
||||||
dologin
|
|
||||||
appmanifestfile=${SERVER_FILES}/steamapps/appmanifest_"${release_server_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 "${release_server_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}" ] || [[ ${1} = "force" ]]
|
|
||||||
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/${release_server_appid}/\n"
|
|
||||||
printf "\nApplying update"
|
|
||||||
# run update
|
|
||||||
dologin
|
|
||||||
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${release_server_appid}" validate +quit
|
|
||||||
modupdate
|
|
||||||
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/${release_server_appid}/\n\n"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Assemble the workshop list variable
|
|
||||||
get_mods(){
|
|
||||||
mapfile -t workshopID < "${WORKSHOP_CFG}"
|
|
||||||
workshoplist=""
|
|
||||||
for i in "${workshopID[@]}"
|
|
||||||
do
|
|
||||||
ID=$(echo ${i} | cut -d: -f1)
|
|
||||||
workshoplist+=" +workshop_download_item "${release_client_appid}" "${ID}
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
get_mod_name(){
|
|
||||||
if [ -d "${workshopfolder}/${1}" ]
|
|
||||||
then
|
|
||||||
grep name ${workshopfolder}/${1}/meta.cpp | cut -d '"' -f2 | sed -r 's/\s+//g'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Update mods
|
|
||||||
modupdate(){
|
|
||||||
get_mods
|
|
||||||
echo "Updating mods..."
|
|
||||||
dologin
|
|
||||||
# echo ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" ${workshoplist} +quit
|
|
||||||
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" ${workshoplist} +quit
|
|
||||||
# Updated files come in with mixed cases. Fix that.
|
|
||||||
echo -ne "\nFixing file names..."
|
|
||||||
find "${workshopfolder}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;
|
|
||||||
echo "done"
|
|
||||||
echo
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add a mod
|
|
||||||
add(){
|
|
||||||
if [ -d "${workshopfolder}/${1}" ]
|
|
||||||
then
|
|
||||||
echo -e "${yellow}Warning: The mod directory ${workshopfolder}/${1} already exists!${default}"
|
|
||||||
MODNAME=$(get_mod_name ${1})
|
|
||||||
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}
|
|
||||||
dologin
|
|
||||||
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +workshop_download_item "${release_client_appid}" "${1}" +quit
|
|
||||||
# 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 mod
|
|
||||||
MODNAME=$(get_mod_name ${1})
|
|
||||||
symlink 1 ${1} "${MODNAME}"
|
|
||||||
# Lower case all the files in mod directories.
|
|
||||||
find "${workshopfolder}/${1}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;
|
|
||||||
# Copy the key files
|
|
||||||
copy_keys 1 ${1}
|
|
||||||
# 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"
|
|
||||||
checkTypesXML ${1} install
|
|
||||||
checkInstall ${1} install
|
|
||||||
}
|
|
||||||
|
|
||||||
# Remove a mod
|
|
||||||
remove(){
|
|
||||||
checkTypesXML ${1} uninstall
|
|
||||||
checkInstall ${1} uninstall
|
|
||||||
if [ -d "${workshopfolder}/${1}" ]
|
|
||||||
then
|
|
||||||
MODNAME=$(get_mod_name ${1})
|
|
||||||
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 -e "Mod id ${1} - ${red}${MODNAME}${default} - removed"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Activate / Deactivate a mod
|
|
||||||
activate(){
|
|
||||||
W=${1}
|
|
||||||
shift
|
|
||||||
WW=""
|
|
||||||
if [[ ${W} = 0 ]]
|
|
||||||
then
|
|
||||||
WW="de"
|
|
||||||
UU="un"
|
|
||||||
fi
|
|
||||||
get_mods
|
|
||||||
X=1
|
|
||||||
# Loop over mod list
|
|
||||||
for i in "${workshopID[@]}"
|
|
||||||
do
|
|
||||||
ID=$(echo ${i} | cut -d: -f1)
|
|
||||||
NAME=$(echo ${i} | cut -d: -f2)
|
|
||||||
ACTIVE=$(echo ${i} | cut -d: -f3)
|
|
||||||
# Find mod by ID or index
|
|
||||||
if [[ ${ID} = ${1} ]] || [[ ${X} = ${1} ]]
|
|
||||||
then
|
|
||||||
# Toggle state or report nothing burger
|
|
||||||
if [[ "${ACTIVE}" != "${W}" ]]
|
|
||||||
then
|
|
||||||
sed -i "${WORKSHOP_CFG}" -e "s/${ID}:${NAME}:[0-1]/${ID}:${NAME}:${W}/"
|
|
||||||
symlink ${W} ${ID} "${NAME}"
|
|
||||||
copy_keys ${W} ${ID}
|
|
||||||
checkTypesXML ${ID} ${UU}install
|
|
||||||
checkInstall ${ID} ${UU}install
|
|
||||||
echo "Mod id ${ID} - ${WW}activated"
|
|
||||||
else
|
|
||||||
echo -e "Mod id ${ID} - ${green}${NAME}${default} - is already ${WW}active"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
X=$((X+1))
|
|
||||||
done
|
|
||||||
list
|
|
||||||
}
|
|
||||||
|
|
||||||
# List mods
|
|
||||||
list(){
|
|
||||||
get_mods
|
|
||||||
X=1
|
|
||||||
spaces=" "
|
|
||||||
echo -e " ID Name Active URL Size"
|
|
||||||
echo "------------------------------------------------------------------------------------------------------------------------"
|
|
||||||
for i in "${workshopID[@]}"
|
|
||||||
do
|
|
||||||
ID=$(echo ${i} | cut -d: -f1)
|
|
||||||
NAME=$(echo ${i} | cut -d: -f2)
|
|
||||||
ACTIVE=$(echo ${i} | cut -d: -f3)
|
|
||||||
SIZE=$(du -sh ${SERVER_FILES}/steamapps/workshop/content/221100/${ID} | awk '{print $1}')
|
|
||||||
if [[ ${ACTIVE} = "1" ]]
|
|
||||||
then
|
|
||||||
C="${green}"
|
|
||||||
else
|
|
||||||
C="${red}"
|
|
||||||
fi
|
|
||||||
printf "${C}%.3d %s %.23s %s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${NAME}" "${spaces:${#NAME}+1}" ${ACTIVE} ${ID} ${SIZE}
|
|
||||||
X=$((X+1))
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Copy mod keys
|
|
||||||
copy_keys(){
|
|
||||||
if [[ ${1} = 1 ]]
|
|
||||||
then
|
|
||||||
echo "Copying key files..."
|
|
||||||
cp -v ${workshopfolder}/${2}/keys/* "${SERVER_FILES}/keys/" || \
|
|
||||||
cp -v ${workshopfolder}/${2}/key/* "${SERVER_FILES}/keys/" # Because mod authors can't stick to one way of doing things...
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Symlink mods
|
|
||||||
symlink(){
|
|
||||||
W=${1}
|
|
||||||
ID=${2}
|
|
||||||
NAME=${3}
|
|
||||||
# Symlink it
|
|
||||||
if [ ! -L "${SERVER_FILES}/@${NAME}" ] && [[ ${W} = 1 ]]
|
|
||||||
then
|
|
||||||
ln -sv ${workshopfolder}/${ID} "${SERVER_FILES}/@${NAME}"
|
|
||||||
elif [[ "${W}" = "0" ]]
|
|
||||||
then
|
|
||||||
rm -vf "${SERVER_FILES}/@${NAME}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Assemble the mod command line
|
|
||||||
mod_cmd(){
|
|
||||||
get_mods
|
|
||||||
mod_command_line=""
|
|
||||||
for i in "${workshopID[@]}"
|
|
||||||
do
|
|
||||||
NAME=$(echo ${i} | cut -d: -f2)
|
|
||||||
ACTIVE=$(echo ${i} | cut -d: -f3)
|
|
||||||
if [[ ${ACTIVE} = "1" ]]
|
|
||||||
then
|
|
||||||
mod_command_line="${mod_command_line}@${NAME};"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [[ ${mod_command_line} != "" ]]
|
|
||||||
then
|
|
||||||
mod_command_line='-mod='${mod_command_line::-1}
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTypesXML(){
|
|
||||||
# See if this mod has a types.xml. If so, manage it.
|
|
||||||
for path in "${workshopfolder}/${1}/extras" "${workshopfolder}/${1}" "/files/mods/${1}"
|
|
||||||
do
|
|
||||||
if [ -f "${path}/types.xml" ]
|
|
||||||
then
|
|
||||||
echo -n "The mod id ${1} has a types.xml: ${path}/types.xml. "
|
|
||||||
if [[ ${2} = "install" ]]
|
|
||||||
then
|
|
||||||
echo "Merging to missions..."
|
|
||||||
else
|
|
||||||
echo "Removing contents from missions..."
|
|
||||||
fi
|
|
||||||
/files/mods/types.sh ${1} ${2} ${path}/types.xml
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
checkInstall(){
|
|
||||||
# See if this mod id exists in files/mods, and offer to install other server side files if an install.sh is found
|
|
||||||
if [ -f /files/mods/${1}/${2}.sh ]
|
|
||||||
then
|
|
||||||
echo "An ${2}.sh was found for mod id ${1}. Running..."
|
|
||||||
/files/mods/${1}/${2}.sh
|
|
||||||
fi
|
|
||||||
# A generic map install script. Presumes a git repo as the source
|
|
||||||
if [ -f /files/mods/${1}/install.env ]
|
|
||||||
then
|
|
||||||
echo "An ${2}.env was found for mod id ${1}. Performing ${2}..."
|
|
||||||
source /files/mods/${1}/install.env
|
|
||||||
/files/mods/install.sh ${1} ${2}
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
# Our internal RCON
|
|
||||||
rcon(){
|
|
||||||
exec /usr/local/py3rcon/py3rcon.py --gui ~/py3rcon.config.json
|
|
||||||
}
|
|
||||||
|
|
||||||
# Display the status of everything
|
|
||||||
status(){
|
|
||||||
INSTALLED="${NO}"
|
|
||||||
LOGGED_IN="${NO}"
|
|
||||||
RUNNING="${NO}"
|
|
||||||
get_mods
|
|
||||||
|
|
||||||
# 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="${yellow}(as anonymous)${default}"
|
|
||||||
else
|
|
||||||
ANONYMOUS="${green}(not anonymous)${default}"
|
|
||||||
fi
|
|
||||||
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
|
|
||||||
mod_cmd
|
|
||||||
# Map name
|
|
||||||
# MAP=$(grep -E "template=" ${SERVER_CFG_DST} | grep -vE "^//")
|
|
||||||
# Number of mods plus the list denoting on or off
|
|
||||||
echo -e "
|
|
||||||
Logged in to Steam: ${LOGGED_IN} ${ANONYMOUS}
|
|
||||||
Server files installed: ${INSTALLED}
|
|
||||||
Mods:
|
|
||||||
"
|
|
||||||
|
|
||||||
list
|
|
||||||
|
|
||||||
echo -e "
|
|
||||||
Server running: ${RUNNING}
|
|
||||||
Working parameters: ${parameters}
|
|
||||||
Working mod parameter: ${mod_command_line}"
|
|
||||||
MAP=$(grep template ${SERVER_CFG_DST} | grep -v "^//" | cut -d= -f2 | cut -d\; -f1)
|
|
||||||
echo "Map: ${MAP}"
|
|
||||||
}
|
|
||||||
|
|
||||||
backup(){
|
|
||||||
cd "${SERVER_FILES}"/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 "${@}"
|
|
||||||
;;
|
|
||||||
m|modupdate)
|
|
||||||
modupdate "${@}"
|
|
||||||
;;
|
|
||||||
n|rcon)
|
|
||||||
rcon "${@}"
|
|
||||||
;;
|
|
||||||
r|remove)
|
|
||||||
remove "${@}"
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
restart "${@}"
|
|
||||||
;;
|
|
||||||
start)
|
|
||||||
start "${@}"
|
|
||||||
;;
|
|
||||||
s|status)
|
|
||||||
status "${@}"
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop "${@}"
|
|
||||||
;;
|
|
||||||
u|update)
|
|
||||||
update "${@}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage "$*"
|
|
||||||
;;
|
|
||||||
esac
|
|
1
files/dz
1
files/dz
|
@ -1 +0,0 @@
|
||||||
dayzserver
|
|
1
files/mods/1964490092/types.env
Normal file
1
files/mods/1964490092/types.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TYPES=types-v6.xml
|
|
@ -1 +0,0 @@
|
||||||
//home/user/serverfiles/steamapps/workshop/content/221100/1964490092/types-v6.xml
|
|
0
files/mods/2415195639/install.env
Executable file → Normal file
0
files/mods/2415195639/install.env
Executable file → Normal file
1
files/mods/2443122116/type.env
Normal file
1
files/mods/2443122116/type.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
xml_and_clasnames/snafu_types.xml
|
|
@ -1 +0,0 @@
|
||||||
/home/user/serverfiles/steamapps/workshop/content/221100/2443122116/xml_and_clasnames/snafu_types.xml
|
|
9
files/mods/2692979668/xml.env
Normal file
9
files/mods/2692979668/xml.env
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
CFGSPAWNABLETYPES=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/RFFSHelis_cfgspawnabletypes.xml
|
||||||
|
#CFGEVENTSPAWNS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/Banov/RFFSHelis_cfgeventspawns.xml
|
||||||
|
CFGEVENTSPAWNS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/Chernarus/RFFSHelis_cfgeventspawns.xml
|
||||||
|
#CFGEVENTSPAWNS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/DeerIsle/RFFSHelis_cfgeventspawns.xml
|
||||||
|
#CFGEVENTSPAWNS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/Namalsk/RFFSHelis_cfgeventspawns.xml
|
||||||
|
EVENTS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/RFFSHelis_events.xml
|
||||||
|
TYPES=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Types.XML/RFFSHelis_Types.xml
|
1
files/mods/2878980498/types.env
Normal file
1
files/mods/2878980498/types.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TYPES=extras/types/rag_baseitems.xml
|
|
@ -1 +0,0 @@
|
||||||
/home/user/serverfiles/steamapps/workshop/content/221100/2878980498/extras/types/rag_baseitems.xml
|
|
|
@ -1 +1 @@
|
||||||
@RaG_BaseItems
|
2878980498
|
1
files/mods/@RedFalconFlightSystemHeliz
Symbolic link
1
files/mods/@RedFalconFlightSystemHeliz
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
2692979668
|
|
@ -1,68 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# A generic script to manage a mod's types.xml against all installed missions
|
|
||||||
|
|
||||||
set -eE
|
|
||||||
|
|
||||||
ID=${1}
|
|
||||||
MODE=${2}
|
|
||||||
TYPES_FILE="${workshopfolder}/${ID}/extras/types.xml"
|
|
||||||
|
|
||||||
if [[ ${3} != "" ]]
|
|
||||||
then
|
|
||||||
TYPES_FILE="${3}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for file in $(find ${SERVER_FILES}/mpmissions -name types.xml -print -prune)
|
|
||||||
do
|
|
||||||
if [[ ${MODE} = "uninstall" ]]
|
|
||||||
then
|
|
||||||
# Remove the lines that were added by the mod's extras/types.xml from
|
|
||||||
# every db/types.xml in all mission directories
|
|
||||||
|
|
||||||
# Chop the top tag from the source file
|
|
||||||
tail -n+2 ${TYPES_FILE} > /tmp/types-tmp.xml
|
|
||||||
|
|
||||||
# Chop the bottom tag from the source file
|
|
||||||
head -n-1 /tmp/types-tmp.xml > /tmp/types-src.xml
|
|
||||||
|
|
||||||
# Remove that content from the original file
|
|
||||||
grep -qvxFf /tmp/types-src.xml ${file}
|
|
||||||
else
|
|
||||||
# Add the contents of extras/types.xml to every db/types.xml in all
|
|
||||||
# mission directories
|
|
||||||
xmllint --noout ${TYPES_FILE} 2> /dev/null && {
|
|
||||||
echo -e "${green}${TYPES_FILE} passes XML lint test!"
|
|
||||||
echo -e "Merging to $file...${default}"
|
|
||||||
# Chop the bottom tag from the destination file
|
|
||||||
head -n-1 ${file} > /tmp/types-dst.xml
|
|
||||||
|
|
||||||
# Chop the top 2 tags, xml and types, from the source file
|
|
||||||
tail -n+2 ${TYPES_FILE} > /tmp/types-src.xml
|
|
||||||
|
|
||||||
# Concatenate the two files back into the source file
|
|
||||||
cat /tmp/types-dst.xml /tmp/types-src.xml > /tmp/types.xml
|
|
||||||
|
|
||||||
xmllint --noout /tmp/types.xml 2> /dev/null && {
|
|
||||||
cp -v /tmp/types.xml ${file}
|
|
||||||
} || {
|
|
||||||
# Try again, but chop the top 3 tags, hopefully xml and types, from the source file...
|
|
||||||
echo "First merge attempt failed, trying again..."
|
|
||||||
tail -n+3 ${TYPES_FILE} > /tmp/types-src.xml
|
|
||||||
|
|
||||||
# Concatenate the two files back into the source file
|
|
||||||
cat /tmp/types-dst.xml /tmp/types-src.xml > /tmp/types.xml
|
|
||||||
|
|
||||||
# And lint again. This should probably be a recursive function...
|
|
||||||
xmllint --noout /tmp/types.xml && {
|
|
||||||
cp -v /tmp/types.xml ${file}
|
|
||||||
} || {
|
|
||||||
echo "XML lint check after merge failed! No files changed!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} || {
|
|
||||||
echo -e "${red}${TYPES_FILE} fails XML lint test!"
|
|
||||||
echo -e "This will have to be merged by hand!${default}"
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
done
|
|
|
@ -7,7 +7,7 @@ maxPlayers = 60; // Maximum amount of players
|
||||||
|
|
||||||
verifySignatures = 2; // Verifies .pbos against .bisign files. (only 2 is supported)
|
verifySignatures = 2; // Verifies .pbos against .bisign files. (only 2 is supported)
|
||||||
|
|
||||||
forceSameBuild = 1; // When enabled, the server will allow the connection only to clients with same the .exe revision as the server (value 0-1)
|
forceSameBuild = 0; // When enabled, the server will allow the connection only to clients with same the .exe revision as the server (value 0-1)
|
||||||
|
|
||||||
disableVoN = 0; // Enable/disable voice over network (value 0-1)
|
disableVoN = 0; // Enable/disable voice over network (value 0-1)
|
||||||
vonCodecQuality = 30; // Voice over network codec quality, the higher the better (values 0-30)
|
vonCodecQuality = 30; // Voice over network codec quality, the higher the better (values 0-30)
|
||||||
|
@ -85,8 +85,8 @@ class Missions
|
||||||
{
|
{
|
||||||
template="dayzOffline.chernarusplus"; // Chernarus
|
template="dayzOffline.chernarusplus"; // Chernarus
|
||||||
// template="dayzOffline.enoch"; // Livonia
|
// template="dayzOffline.enoch"; // Livonia
|
||||||
// template="empty.banov" // Banov
|
// template="empty.banov"; // Banov
|
||||||
// template="empty.deerisle" // Deer Isle
|
// template="empty.deerisle"; // Deer Isle
|
||||||
// template="serverMission.Pripyat" // Pripyat
|
// template="serverMission.Pripyat"; // Pripyat
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
FROM debian:bullseye
|
FROM debian:bullseye
|
||||||
|
|
||||||
# Set debconf to run non-interactively and agree to the SteamCMD EULA
|
# Set debconf to run non-interactively and agree to the SteamCMD EULA
|
||||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections \
|
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||||
&& echo steam steam/question select "I AGREE" | debconf-set-selections \
|
|
||||||
&& echo steam steam/license note '' | debconf-set-selections \
|
|
||||||
&& dpkg --add-architecture i386
|
|
||||||
|
|
||||||
# Add contrib and backports
|
# Add contrib and backports
|
||||||
RUN sed -i /etc/apt/sources.list -e 's/main/main contrib non-free/'
|
RUN sed -i /etc/apt/sources.list -e 's/main/main contrib non-free/'
|
||||||
|
@ -18,10 +15,6 @@ RUN apt-get update && apt-get -y upgrade && apt-get -y install --no-install-reco
|
||||||
gdb \
|
gdb \
|
||||||
git \
|
git \
|
||||||
jq \
|
jq \
|
||||||
lib32gcc-s1 \
|
|
||||||
lib32stdc++6 \
|
|
||||||
libcurl4:i386 \
|
|
||||||
libsdl2-2.0-0:i386 \
|
|
||||||
libsdl2-2.0-0 \
|
libsdl2-2.0-0 \
|
||||||
libcap2 \
|
libcap2 \
|
||||||
libxml2-utils \
|
libxml2-utils \
|
||||||
|
@ -29,9 +22,9 @@ RUN apt-get update && apt-get -y upgrade && apt-get -y install --no-install-reco
|
||||||
nano \
|
nano \
|
||||||
procps \
|
procps \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
|
strace \
|
||||||
wget \
|
wget \
|
||||||
rename \
|
xmlstarlet
|
||||||
steamcmd
|
|
||||||
|
|
||||||
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.9 1
|
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.9 1
|
||||||
RUN update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1
|
RUN update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1
|
||||||
|
@ -42,18 +35,17 @@ ENV LANG en_US.UTF-8
|
||||||
ENV LANGUAGE en_US:en
|
ENV LANGUAGE en_US:en
|
||||||
ENV LC_ALL en_US.UTF-8
|
ENV LC_ALL en_US.UTF-8
|
||||||
|
|
||||||
|
# Add our scripts directory to PATH
|
||||||
|
ENV PATH /files/bin:/server:${PATH}
|
||||||
|
|
||||||
# Add py3rcon
|
# Add py3rcon
|
||||||
RUN cd /usr/local && git clone https://github.com/indepth666/py3rcon.git
|
RUN cd /usr/local && git clone https://github.com/indepth666/py3rcon.git
|
||||||
|
|
||||||
# Steamcmd needs its path added, as it ends up in /usr/games.
|
|
||||||
# Our server script is bind mounted in /files in docker-compose.
|
|
||||||
ENV PATH /usr/games:/files:${PATH}
|
|
||||||
|
|
||||||
# Setup a non-privileged user
|
# Setup a non-privileged user
|
||||||
RUN groupadd user && \
|
RUN groupadd user && \
|
||||||
useradd -l -g user user && \
|
useradd -l -g user user && \
|
||||||
mkdir /home/user && \
|
mkdir -p /home/user /serverfiles/mpmissions /mods /mpmissions /profiles && \
|
||||||
chown user:user /home/user
|
chown -R user:user /home/user /serverfiles /mods /mpmissions /profiles
|
||||||
|
|
||||||
# Use our non-privileged user
|
# Use our non-privileged user
|
||||||
USER user
|
USER user
|
||||||
|
@ -62,4 +54,4 @@ USER user
|
||||||
WORKDIR /home/user
|
WORKDIR /home/user
|
||||||
|
|
||||||
# Run the server.
|
# Run the server.
|
||||||
CMD ["dayzserver", "start"]
|
CMD ["start.sh"]
|
480
server/bin/dz
Executable file
480
server/bin/dz
Executable file
|
@ -0,0 +1,480 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
source dz-common
|
||||||
|
|
||||||
|
# Server container base directories
|
||||||
|
SERVER_PROFILE="/profiles"
|
||||||
|
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}"
|
||||||
|
mod_command_line=""
|
||||||
|
|
||||||
|
# Backups
|
||||||
|
BACKUP_DIR="${HOME}/backup"
|
||||||
|
if [ ! -d "${BACKUP_DIR}" ]
|
||||||
|
then
|
||||||
|
mkdir -p "${BACKUP_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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
|
||||||
|
}
|
||||||
|
|
||||||
|
get_mods(){
|
||||||
|
workshoplist=""
|
||||||
|
mod_command_line=""
|
||||||
|
for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null)
|
||||||
|
do
|
||||||
|
ID=$(readlink ${link} | awk -F/ '{print $NF}')
|
||||||
|
MODNAME=$(get_mod_name ${ID})
|
||||||
|
workshoplist+=" +workshop_download_item "${release_client_appid}" "${ID}
|
||||||
|
mod_command_line+="@${MODNAME};"
|
||||||
|
done
|
||||||
|
# Remove the trailing semi-colon. This is necessary.
|
||||||
|
if [[ ${mod_command_line} != "" ]]
|
||||||
|
then
|
||||||
|
mod_command_line="-mod=${mod_command_line::-1}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
echo "Copying pristine versions of cfgeconomycore.xml and cfgeventspawns.xml..."
|
||||||
|
find /mpmissions -name cfgeconomycore.xml -exec cp {} ${SERVER_FILES}{} \;
|
||||||
|
# find /mpmissions -name cfgeventspawns.xml -exec cp {} ${SERVER_FILES}{} \;
|
||||||
|
|
||||||
|
# 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(){
|
||||||
|
# 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
|
||||||
|
}
|
||||||
|
|
||||||
|
get_mod_id_by_index(){
|
||||||
|
X=1
|
||||||
|
# Loop over mods
|
||||||
|
for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null)
|
||||||
|
do
|
||||||
|
ID=$(readlink ${link} | awk -F/ '{print $NF}')
|
||||||
|
if [[ ${X} = ${1} ]]
|
||||||
|
then
|
||||||
|
echo -n ${ID}
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
X=$((X+1))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get mod name by ID or index
|
||||||
|
get_mod_name(){
|
||||||
|
# Check for an ID
|
||||||
|
if ! [ -d "${WORKSHOP_DIR}/${ID}" ]
|
||||||
|
then
|
||||||
|
echo "Mod ID ${1} doesn't exist" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
NAME=$(grep name ${WORKSHOP_DIR}/${ID}/meta.cpp | cut -d '"' -f2 | sed -r 's/\s+//g')
|
||||||
|
echo -n ${NAME}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Activate / Deactivate a mod
|
||||||
|
activate(){
|
||||||
|
W=${1}
|
||||||
|
shift
|
||||||
|
WW=""
|
||||||
|
COLOR="${green}"
|
||||||
|
if [[ ${W} = 0 ]]
|
||||||
|
then
|
||||||
|
WW="de"
|
||||||
|
UU="un"
|
||||||
|
COLOR="${red}"
|
||||||
|
fi
|
||||||
|
ID=$(get_mod_id_by_index2 ${1})
|
||||||
|
MODNAME=$(get_mod_name ${ID})
|
||||||
|
# Toggle state or report nothing burger
|
||||||
|
pushd "${SERVER_PROFILE}" > /dev/null
|
||||||
|
if [ -L "${SERVER_PROFILE}/@${MODNAME}" ]
|
||||||
|
then
|
||||||
|
rm -vf "${SERVER_PROFILE}/@${MODNAME}"
|
||||||
|
else
|
||||||
|
ln -s "${WORKSHOP_DIR}/${ID}" "${SERVER_PROFILE}/@${MODNAME}"
|
||||||
|
# echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} - is already ${WW}active"
|
||||||
|
fi
|
||||||
|
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
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
if [[ ${MODS} == "" ]]
|
||||||
|
then
|
||||||
|
echo -n "none"
|
||||||
|
fi
|
||||||
|
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 "${@}"
|
||||||
|
;;
|
||||||
|
m|modupdate)
|
||||||
|
modupdate "${@}"
|
||||||
|
;;
|
||||||
|
n|rcon)
|
||||||
|
rcon "${@}"
|
||||||
|
;;
|
||||||
|
r|remove)
|
||||||
|
remove "${@}"
|
||||||
|
;;
|
||||||
|
r|restart)
|
||||||
|
restart "${@}"
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
start "${@}"
|
||||||
|
;;
|
||||||
|
s|status)
|
||||||
|
status "${@}"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop "${@}"
|
||||||
|
;;
|
||||||
|
u|update)
|
||||||
|
update "${@}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage "$*"
|
||||||
|
;;
|
||||||
|
esac
|
15
server/start.sh
Executable file
15
server/start.sh
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Set PS1 so we know we're in the container
|
||||||
|
cat > .bashrc <<EOF
|
||||||
|
alias ls='ls --color'
|
||||||
|
export PS1="${debian_chroot:+($debian_chroot)}\u@dz-server:\w\$ "
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Uncomment the line below to run things manually in the container, then run:
|
||||||
|
# docker compose exec main bash
|
||||||
|
tail -f /dev/null
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
# Otherwise, start the server normally
|
||||||
|
dz start
|
83
web/Dockerfile
Normal file
83
web/Dockerfile
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
FROM debian:bullseye
|
||||||
|
|
||||||
|
# Set debconf to run non-interactively and agree to the SteamCMD EULA
|
||||||
|
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections \
|
||||||
|
&& echo steam steam/question select "I AGREE" | debconf-set-selections \
|
||||||
|
&& echo steam steam/license note '' | debconf-set-selections \
|
||||||
|
&& dpkg --add-architecture i386
|
||||||
|
|
||||||
|
# Add contrib and backports
|
||||||
|
RUN sed -i /etc/apt/sources.list -e 's/main/main contrib non-free/'
|
||||||
|
|
||||||
|
RUN echo 'deb http://deb.debian.org/debian bullseye-backports main non-free' >> /etc/apt/sources.list
|
||||||
|
|
||||||
|
# Install _only_ the necessary packages
|
||||||
|
RUN apt-get update && apt-get -y upgrade && apt-get -y install --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
gdb \
|
||||||
|
git \
|
||||||
|
gwenhywfar-tools \
|
||||||
|
jq \
|
||||||
|
lib32gcc-s1 \
|
||||||
|
lib32stdc++6 \
|
||||||
|
libcurl4:i386 \
|
||||||
|
libsdl2-2.0-0:i386 \
|
||||||
|
libsdl2-2.0-0 \
|
||||||
|
libcap2 \
|
||||||
|
libxml2-utils \
|
||||||
|
locales \
|
||||||
|
nano \
|
||||||
|
procps \
|
||||||
|
python3-pip \
|
||||||
|
wget \
|
||||||
|
rename \
|
||||||
|
steamcmd \
|
||||||
|
xmlstarlet
|
||||||
|
|
||||||
|
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.9 1
|
||||||
|
RUN update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1
|
||||||
|
|
||||||
|
# Set the locale
|
||||||
|
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
ENV LANGUAGE en_US:en
|
||||||
|
ENV LC_ALL en_US.UTF-8
|
||||||
|
|
||||||
|
# Steamcmd needs its path added, as it ends up in /usr/games.
|
||||||
|
# Our server script is bind mounted in /files in docker-compose.
|
||||||
|
ENV PATH /usr/games:/files/bin:/web:${PATH}
|
||||||
|
|
||||||
|
# Install nodejs
|
||||||
|
RUN mkdir /usr/local/nvm
|
||||||
|
ENV NVM_DIR /usr/local/nvm
|
||||||
|
ENV NODE_VERSION 16.17.0
|
||||||
|
RUN echo $NODE_VERSION
|
||||||
|
|
||||||
|
# Install nvm with node and npm
|
||||||
|
RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash \
|
||||||
|
&& . $NVM_DIR/nvm.sh \
|
||||||
|
&& nvm install $NODE_VERSION \
|
||||||
|
&& nvm alias default $NODE_VERSION \
|
||||||
|
&& nvm use default
|
||||||
|
|
||||||
|
ENV NODE_PATH $NVM_DIR/versions/node/v$NODE_VERSION/lib/node_modules
|
||||||
|
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
|
||||||
|
|
||||||
|
# Add py3rcon
|
||||||
|
RUN cd /usr/local && git clone https://github.com/indepth666/py3rcon.git
|
||||||
|
|
||||||
|
# Setup a non-privileged user
|
||||||
|
RUN groupadd user && \
|
||||||
|
useradd -l -g user user && \
|
||||||
|
mkdir -p /home/user /serverfiles/mpmissions /serverfiles/steamapps/workshop/content /web && \
|
||||||
|
chown -R user:user /home/user /serverfiles /web
|
||||||
|
|
||||||
|
# Use our non-privileged user
|
||||||
|
USER user
|
||||||
|
|
||||||
|
# The dayzserver script expects a home directory to itself.
|
||||||
|
WORKDIR /home/user
|
||||||
|
|
||||||
|
# Run the web server
|
||||||
|
CMD ["start.sh"]
|
350
web/bin/dz
Executable file
350
web/bin/dz
Executable file
|
@ -0,0 +1,350 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
source dz-common
|
||||||
|
|
||||||
|
# An array to store Workshop items. Each element contains the mod's ID, name, and state (active or not).
|
||||||
|
WORKSHOP_DIR="/mods/${release_client_appid}"
|
||||||
|
|
||||||
|
workshoplist=""
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
usage(){
|
||||||
|
echo -e "
|
||||||
|
${red}Bad option or arguments! ${yellow}${*}${default}
|
||||||
|
|
||||||
|
Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ]
|
||||||
|
|
||||||
|
Options and arguments:
|
||||||
|
|
||||||
|
a|add id - Add a DayZ Workshop item by id. Added items become active by default
|
||||||
|
i|install - Install the DayZ server files
|
||||||
|
g|login - Login to Steam.
|
||||||
|
m|modupdate - Update the mod files
|
||||||
|
r|remove id - Remove all files and directories of a Workshop item by id
|
||||||
|
s|status - Shows Steam login status, if base files are installed, installed mods
|
||||||
|
u|update - Update the server files
|
||||||
|
x|xml id - Get and normalize XML files from a mod's template by id (Presumes template exists)
|
||||||
|
${default}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# "Manage" XML files.
|
||||||
|
xml(){
|
||||||
|
/files/mods/xml.sh ${1}
|
||||||
|
mergexml ${1}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy mod keys
|
||||||
|
copy_keys(){
|
||||||
|
if [[ ${1} = 1 ]]
|
||||||
|
then
|
||||||
|
echo "Copying key files..."
|
||||||
|
find ${WORKSHOP_DIR}/${2} -name "*.bikey" -exec cp -v {} "${SERVER_FILES}/keys/" \;
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Manage the mod symlink
|
||||||
|
symlink(){
|
||||||
|
W=${1}
|
||||||
|
ID=${2}
|
||||||
|
NAME=${3}
|
||||||
|
if [ ! -L "${SERVER_FILES}/@${NAME}" ] && [[ ${W} = 1 ]]
|
||||||
|
then
|
||||||
|
ln -sv ${WORKSHOP_DIR}/${ID} "${SERVER_FILES}/@${NAME}"
|
||||||
|
elif [[ "${W}" = "0" ]]
|
||||||
|
then
|
||||||
|
rm -vf "${SERVER_FILES}/@${NAME}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mergexml(){
|
||||||
|
ID=${1}
|
||||||
|
# Going to have to maintain a matrix of file names -> root node -> child node permutations
|
||||||
|
for i in "CFGEVENTSPAWNS:eventposdef:event" "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
|
||||||
|
echo "Normalizing ${WORKSHOP_DIR}/${ID}/${var,,}.xml..."
|
||||||
|
cp ${WORKSHOP_DIR}/${ID}/${var,,}.xml /tmp/x
|
||||||
|
if ! grep -q '<'${CHECK}'>' /tmp/x
|
||||||
|
then
|
||||||
|
echo " - has no root node <${CHECK}>. fixing..."
|
||||||
|
xmlstarlet ed -s / -t elem -n "${CHECK}" -m /${CHILD} /${CHECK} /tmp/x > /tmp/y
|
||||||
|
mv /tmp/y /tmp/x
|
||||||
|
fi
|
||||||
|
if ! grep -q '<?xml' /tmp/x
|
||||||
|
then
|
||||||
|
echo " - has no XML node, fixing..."
|
||||||
|
xmlstarlet fo /tmp/x > /tmp/y
|
||||||
|
mv /tmp/y /tmp/x
|
||||||
|
fi
|
||||||
|
xmllint --noout /tmp/x || {
|
||||||
|
echo "The final file failed lint tests, aborting..."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
# Keep the normalized version in the /mods directory, where they should have been from the start
|
||||||
|
cp /tmp/x ${WORKSHOP_DIR}/${ID}/${var,,}.xml
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add a mod
|
||||||
|
add(){
|
||||||
|
if [ -d "${WORKSHOP_DIR}/${1}" ]
|
||||||
|
then
|
||||||
|
echo -e "${yellow}Warning: The mod directory ${WORKSHOP_DIR}/${1} already exists!${default}"
|
||||||
|
MODNAME=$(get_mod_name ${1})
|
||||||
|
fi
|
||||||
|
if [ -L "${SERVER_FILES}/@${MODNAME}" ]
|
||||||
|
then
|
||||||
|
echo -e "${yellow}Warning: The mod symlink ${SERVER_FILES}/@${MODNAME} already exists!${default}"
|
||||||
|
fi
|
||||||
|
echo "Adding mod id ${1}"
|
||||||
|
dologin
|
||||||
|
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +workshop_download_item "${release_client_appid}" "${1}" +quit
|
||||||
|
# Make sure the install succeeded
|
||||||
|
if [ ! -d "${WORKSHOP_DIR}/${1}" ]
|
||||||
|
then
|
||||||
|
echo -e "${red}Mod installation failed: The mod directory ${WORKSHOP_DIR}/${1} was not created!${default}"
|
||||||
|
echo "Installation failed! See above (You probably need to use a real Steam login)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
# Get the name of the newly added mod
|
||||||
|
MODNAME=$(get_mod_name ${1})
|
||||||
|
symlink 1 ${1} "${MODNAME}"
|
||||||
|
# Lower case all the files in mod directories.
|
||||||
|
find "${WORKSHOP_DIR}/${1}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;
|
||||||
|
# Copy the key files
|
||||||
|
copy_keys 1 ${1}
|
||||||
|
echo -e "Mod id ${1} - ${green}${MODNAME}${default} - added"
|
||||||
|
mergexml ${ID}
|
||||||
|
# checkTypesXML ${1} install
|
||||||
|
# checkInstall ${1} install
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove a mod
|
||||||
|
remove(){
|
||||||
|
# checkTypesXML ${1} uninstall
|
||||||
|
# checkInstall ${1} uninstall
|
||||||
|
if [ -d "${WORKSHOP_DIR}/${1}" ]
|
||||||
|
then
|
||||||
|
MODNAME=$(get_mod_name ${1})
|
||||||
|
echo "Removing directory ${WORKSHOP_DIR}/${1}"
|
||||||
|
rm -rf "${WORKSHOP_DIR}/${1}"
|
||||||
|
fi
|
||||||
|
if [ -L "${SERVER_FILES}/@${MODNAME}" ]
|
||||||
|
then
|
||||||
|
echo "Removing symlink ${SERVER_FILES}/@${MODNAME}"
|
||||||
|
rm -f "${SERVER_FILES}/@${MODNAME}"
|
||||||
|
fi
|
||||||
|
echo -e "Mod id ${1} - ${red}${MODNAME}${default} - removed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle the Steam login information.
|
||||||
|
login(){
|
||||||
|
if [ -f "${STEAM_LOGIN}" ]
|
||||||
|
then
|
||||||
|
if 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
|
||||||
|
}
|
||||||
|
|
||||||
|
# "Perform" the Steam login. This just sources the file with the Steam login name.
|
||||||
|
dologin(){
|
||||||
|
if [ -f "${STEAM_LOGIN}" ]
|
||||||
|
then
|
||||||
|
source "${STEAM_LOGIN}"
|
||||||
|
else
|
||||||
|
echo "No cached Steam credentials. Please configure this now: "
|
||||||
|
login
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Perform the installation of the server files.
|
||||||
|
install(){
|
||||||
|
if [ ! -f "${SERVER_INSTALL_FILE}" ] || [[ ${1} = "force" ]]
|
||||||
|
then
|
||||||
|
printf "[ ${yellow}DayZ${default} ] Downloading DayZ Server-Files!\n"
|
||||||
|
dologin
|
||||||
|
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${release_server_appid}" validate +quit
|
||||||
|
else
|
||||||
|
printf "[ ${lightblue}DayZ${default} ] The server is already installed.\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update the server files.
|
||||||
|
update(){
|
||||||
|
dologin
|
||||||
|
appmanifestfile=${SERVER_FILES}/steamapps/appmanifest_"${release_server_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 "${release_server_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}" ] || [[ ${1} = "force" ]]
|
||||||
|
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/${release_server_appid}/\n"
|
||||||
|
printf "\nApplying update"
|
||||||
|
# run update
|
||||||
|
dologin
|
||||||
|
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${release_server_appid}" validate +quit
|
||||||
|
modupdate
|
||||||
|
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/${release_server_appid}/\n\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_mods(){
|
||||||
|
workshoplist=""
|
||||||
|
mod_command_line=""
|
||||||
|
for link in $(ls -tdr ${SERVER_FILES}/@* 2> /dev/null)
|
||||||
|
do
|
||||||
|
ID=$(readlink ${link} | cut -d/ -f7)
|
||||||
|
MODNAME=$(get_mod_name ${ID})
|
||||||
|
workshoplist+=" +workshop_download_item "${release_client_appid}" "${ID}
|
||||||
|
mod_command_line+="@${MODNAME};"
|
||||||
|
done
|
||||||
|
if [[ ${mod_command_line} != "" ]]
|
||||||
|
then
|
||||||
|
mod_command_line='-mod='${mod_command_line::-1}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Update mods
|
||||||
|
modupdate(){
|
||||||
|
echo "Updating mods..."
|
||||||
|
dologin
|
||||||
|
get_mods
|
||||||
|
${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" ${workshoplist} +quit
|
||||||
|
# Updated files come in with mixed cases. Fix that.
|
||||||
|
echo -ne "\nFixing file names..."
|
||||||
|
find "${WORKSHOP_DIR}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;
|
||||||
|
echo "done"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display the status of everything
|
||||||
|
status(){
|
||||||
|
INSTALLED="${NO}"
|
||||||
|
LOGGED_IN="${NO}"
|
||||||
|
RUNNING="${NO}"
|
||||||
|
|
||||||
|
# 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="${yellow}(as anonymous)${default}"
|
||||||
|
else
|
||||||
|
ANONYMOUS="${green}(not anonymous)${default}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo -ne "
|
||||||
|
Logged in to Steam: ${LOGGED_IN} ${ANONYMOUS}
|
||||||
|
Server files installed: ${INSTALLED}"
|
||||||
|
if [[ "${INSTALLED}" = "${NO}" ]]
|
||||||
|
then
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# Mods
|
||||||
|
echo -ne "
|
||||||
|
Mods: "
|
||||||
|
MODS=$(list)
|
||||||
|
if [[ ${MODS} == "" ]]
|
||||||
|
then
|
||||||
|
echo -n "none"
|
||||||
|
fi
|
||||||
|
echo -e "${MODS}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Capture the first argument and shift it off so we can pass $@ to every function
|
||||||
|
C=${1}
|
||||||
|
shift || {
|
||||||
|
usage
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${C}" in
|
||||||
|
a|add)
|
||||||
|
add "${@}"
|
||||||
|
;;
|
||||||
|
i|install)
|
||||||
|
install "${@}"
|
||||||
|
;;
|
||||||
|
g|login)
|
||||||
|
login "${@}"
|
||||||
|
;;
|
||||||
|
m|modupdate)
|
||||||
|
modupdate "${@}"
|
||||||
|
;;
|
||||||
|
r|remove)
|
||||||
|
remove "${@}"
|
||||||
|
;;
|
||||||
|
s|status)
|
||||||
|
status "${@}"
|
||||||
|
;;
|
||||||
|
u|update)
|
||||||
|
update "${@}"
|
||||||
|
;;
|
||||||
|
x|xml)
|
||||||
|
xml "${@}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage "$*"
|
||||||
|
;;
|
||||||
|
esac
|
1658
web/package-lock.json
generated
Normal file
1658
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
19
web/package.json
Normal file
19
web/package.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^2.0.22"
|
||||||
|
}
|
||||||
|
}
|
38
web/root/index.css
Normal file
38
web/root/index.css
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
body {
|
||||||
|
padding-top: 10px;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.green {
|
||||||
|
color: green;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
.yellow {
|
||||||
|
color: yellow;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
.darkgrey {
|
||||||
|
background-color: darkgray;
|
||||||
|
font-weight: bolder;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modInfo {
|
||||||
|
background-color: aliceblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background-color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simulink {
|
||||||
|
cursor: pointer;
|
||||||
|
text-underline: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding-right: 10px
|
||||||
|
}
|
21
web/root/index.html
Normal file
21
web/root/index.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>DayZ Docker Server</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="/index.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import app from '/index.js'
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
Vue.createApp(app).mount('#app')
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
231
web/root/index.js
Normal file
231
web/root/index.js
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
const template = `
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row jumbotron darkgrey">
|
||||||
|
<div class="col-7">
|
||||||
|
<h1>DayZ Docker Server</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 form-control-lg">
|
||||||
|
<form @submit="handleSubmit">
|
||||||
|
<input name="search" placeholder="Search mods..." autofocus>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<div>
|
||||||
|
Server files installed: {{ installed }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Version: {{ version }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="fetchError != ''"
|
||||||
|
class="row jumbotron text-center alert alert-danger"
|
||||||
|
>
|
||||||
|
{{ fetchError }}
|
||||||
|
</div>
|
||||||
|
<div class="row jumbotron darkgrey">
|
||||||
|
<div class="col-3">
|
||||||
|
<h2 class="text-center">Mods</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Steam Link</th>
|
||||||
|
<th>Mod Info</th>
|
||||||
|
</tr>
|
||||||
|
<template
|
||||||
|
v-for="mod in mods"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
:href="'https://steamcommunity.com/sharedfiles/filedetails/?id=' + mod.id"
|
||||||
|
>
|
||||||
|
{{ mod.id }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="simulink" @click="getModInfo(mod.id)">{{ mod.name }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-9 modInfo" v-if="searchResults != ''">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Steam Link</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Size</th>
|
||||||
|
<th>Last Updated</th>
|
||||||
|
<th>Subscriptions</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="result in searchResults">
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
:href="'https://steamcommunity.com/sharedfiles/filedetails/?id=' + result.publishedfileid"
|
||||||
|
>
|
||||||
|
<img data-bs-toggle="tooltip" data-bs-placement="left" :title="result.short_description" width="160" height="90" :src="result.preview_url">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ result.title }}</td>
|
||||||
|
<td>{{ BKMG(result.file_size) }}</td>
|
||||||
|
<td>{{ new Date(result.time_updated * 1000).toLocaleDateString("en-us") }}</td>
|
||||||
|
<td>{{ result.lifetime_subscriptions }}</td>
|
||||||
|
<td>
|
||||||
|
<button v-if="mods.find(o => o.id == result.publishedfileid)" @click="removeMod(result.publishedfileid)" type="button" class="btn btn-danger">Remove</button>
|
||||||
|
<button v-else @click="installMod(result.publishedfileid)" type="button" class="btn btn-success">Install</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-9 modInfo" v-if="modInfo != ''">
|
||||||
|
<div class="text-center col-12">
|
||||||
|
<h2>{{ modInfo.name }} mod info:</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2">
|
||||||
|
<div>
|
||||||
|
ID: {{ modInfo.id }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Size: {{ modInfo.size.toLocaleString("en-US") }}
|
||||||
|
</div>
|
||||||
|
<div v-if="modInfo.customXML.length > 0">
|
||||||
|
Custom XML files:
|
||||||
|
<ul>
|
||||||
|
<li v-for="info in modInfo.customXML">
|
||||||
|
<a
|
||||||
|
:class="'simulink xmlfile ' + info.name"
|
||||||
|
@click="getXMLInfo(modInfo.id,info.name)"
|
||||||
|
>
|
||||||
|
{{ info.name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<textarea cols="120" rows="15" v-if="this.XMLInfo != ''">{{ this.XMLInfo }}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DazDockerServer',
|
||||||
|
template: template,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetchError: "",
|
||||||
|
installed: false,
|
||||||
|
mods: [],
|
||||||
|
modInfo: "",
|
||||||
|
searchResults: [],
|
||||||
|
version: "Unknown",
|
||||||
|
XMLFile: "",
|
||||||
|
XMLInfo: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getModInfo(modId) {
|
||||||
|
fetch('/mod/' + modId)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
this.modInfo = response
|
||||||
|
this.XMLInfo = ""
|
||||||
|
this.searchResults = ""
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
this.fetchError = error.message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getXMLInfo(modId, file) {
|
||||||
|
for (const e of document.getElementsByClassName("selected")) e.classList.remove("selected")
|
||||||
|
fetch('/mod/' + modId + '/' + file)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(response => {
|
||||||
|
this.XMLFile = file
|
||||||
|
this.XMLInfo = response
|
||||||
|
for (const e of document.getElementsByClassName(file)) e.classList.add("selected")
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
this.fetchError = error.message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleSubmit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
fetch('/search/' + e.target.search.value)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
this.modInfo = ""
|
||||||
|
this.XMLInfo = ""
|
||||||
|
// const sortField = "time_updated"
|
||||||
|
const sortField = "lifetime_subscriptions"
|
||||||
|
response.response.publishedfiledetails.sort((a, b) =>
|
||||||
|
a[sortField] < b[sortField] ? 1 : -1
|
||||||
|
)
|
||||||
|
this.searchResults = response.response.publishedfiledetails
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Enable all tooltips
|
||||||
|
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||||
|
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
|
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
this.fetchError = error.message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
installMod(modId) {
|
||||||
|
fetch('/install/' + modId)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(response => {
|
||||||
|
console.log(response)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
this.fetchError = error.message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
BKMG(val) {
|
||||||
|
const units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||||
|
let l = 0, n = parseInt(val, 10) || 0
|
||||||
|
while(n >= 1024 && ++l){
|
||||||
|
n = n/1024
|
||||||
|
}
|
||||||
|
return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// Get the data
|
||||||
|
fetch('/status')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
this.installed = response.installed
|
||||||
|
this.version = response.version
|
||||||
|
this.mods = response.mods
|
||||||
|
if(response.error) {
|
||||||
|
this.fetchError = response.error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
this.fetchError = error.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
{ "result": 1, "publishedfileid": "2489240546", "creator": "76561199068873691", "creator_appid": 221100, "consumer_appid": 221100, "consumer_shortcutid": 0, "filename": "", "file_size": "276817803", "preview_file_size": "27678", "preview_url": "https://steamuserimages-a.akamaihd.net/ugc/2011465736408144669/A7137390FBB9F4F94E0BFE5389932F6DE7AB7B87/", "url": "", "hcontent_file": "4050838808220661564", "hcontent_preview": "2011465736408144669", "title": "LastDayZ_Helis", "short_description": "The author of the helicopter mod https://sibnic.info on the site you can download the latest version of free helicopters, If you need help with installation, go to discord https://sibnic.info/discord", "time_created": 1621186063, "time_updated": 1684985831, "visibility": 0, "flags": 5632, "workshop_file": false, "workshop_accepted": false, "show_subscribe_all": false, "num_comments_public": 0, "banned": false, "ban_reason": "", "banner": "76561197960265728", "can_be_deleted": true, "app_name": "DayZ", "file_type": 0, "can_subscribe": true, "subscriptions": 7935, "favorited": 3, "followers": 0, "lifetime_subscriptions": 22759, "lifetime_favorited": 5, "lifetime_followers": 0, "lifetime_playtime": "0", "lifetime_playtime_sessions": "0", "views": 535, "num_children": 0, "num_reports": 0, "tags": [ { "tag": "Animation", "display_name": "Animation" }, { "tag": "Environment", "display_name": "Environment" }, { "tag": "Sound", "display_name": "Sound" }, { "tag": "Vehicle", "display_name": "Vehicle" }, { "tag": "Mod", "display_name": "Mod" } ], "language": 0, "maybe_inappropriate_sex": false, "maybe_inappropriate_violence": false, "revision_change_number": "14", "revision": 1, "ban_text_check_result": 5 }
|
||||||
|
|
||||||
|
*/
|
16
web/start.sh
Executable file
16
web/start.sh
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Set PS1 so we know we're in the container
|
||||||
|
if ! [ -f .bashrc ]
|
||||||
|
then
|
||||||
|
echo "Creating .bashrc..."
|
||||||
|
cat > .bashrc <<EOF
|
||||||
|
alias ls='ls --color'
|
||||||
|
export PS1="${debian_chroot:+($debian_chroot)}\u@dz-web:\w\$ "
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd /web
|
||||||
|
npm i
|
||||||
|
export DEBUG='express:*'
|
||||||
|
npx nodemon web.js
|
165
web/web.js
Normal file
165
web/web.js
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import express from 'express'
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import https from 'https'
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
/*
|
||||||
|
The DayZ server Steam app ID. USE ONE OR THE OTHER!!
|
||||||
|
|
||||||
|
Presumably once the Linux server is officially released, the binaries will come from this ID.
|
||||||
|
Meanwhile, if we have a release-compatible binary, the base files must be installed from this id,
|
||||||
|
even if the server binary and required shared objects don't come from it. (They'd come from...elsewhere...)
|
||||||
|
*/
|
||||||
|
//const server_appid = "223350"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Without a release binary, we must use the experimental server app ID.
|
||||||
|
*/
|
||||||
|
const server_appid = "1042420"
|
||||||
|
|
||||||
|
/*
|
||||||
|
DayZ release client Steam app ID. This is for mods, as only the release client has them.
|
||||||
|
*/
|
||||||
|
const client_appid = "221100"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Base file locations
|
||||||
|
*/
|
||||||
|
const modDir = "/mods"
|
||||||
|
const serverFiles = "/serverfiles"
|
||||||
|
|
||||||
|
/*
|
||||||
|
File path delimiter
|
||||||
|
*/
|
||||||
|
const d = '/'
|
||||||
|
|
||||||
|
/*
|
||||||
|
XML config files the system can handle. These are retrieved from values in templates located in /files/mods/:modId
|
||||||
|
*/
|
||||||
|
const configFiles = [
|
||||||
|
'cfgeventspawns.xml',
|
||||||
|
'cfgspawnabletypes.xml',
|
||||||
|
'events.xml',
|
||||||
|
'types.xml',
|
||||||
|
]
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
installFile: serverFiles + "/DayZServer",
|
||||||
|
modDir: modDir + "/" + client_appid,
|
||||||
|
port: 8000,
|
||||||
|
steamAPIKey: process.env["STEAMAPIKEY"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDirSize = (dirPath) => {
|
||||||
|
let size = 0
|
||||||
|
const files = fs.readdirSync(dirPath)
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const filePath = path.join(dirPath, files[i])
|
||||||
|
const stats = fs.statSync(filePath)
|
||||||
|
if (stats.isFile()) {
|
||||||
|
size += stats.size
|
||||||
|
} else if (stats.isDirectory()) {
|
||||||
|
size += getDirSize(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCustomXML = (modId) => {
|
||||||
|
const ret = []
|
||||||
|
for(const file of configFiles) {
|
||||||
|
if (fs.existsSync(config.modDir + d + modId + d + file)) {
|
||||||
|
ret.push({name:file})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
const getModNameById = (id) => {
|
||||||
|
const files = fs.readdirSync(serverFiles, {encoding: 'utf8', withFileTypes: true})
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.isSymbolicLink()) {
|
||||||
|
const sym = fs.readlinkSync(serverFiles + d + file.name)
|
||||||
|
if(sym.indexOf(id) > -1) return file.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMods = () => {
|
||||||
|
const mods = []
|
||||||
|
fs.readdirSync(config.modDir).forEach(file => {
|
||||||
|
const name = getModNameById(file)
|
||||||
|
mods.push({name:name,id:file})
|
||||||
|
})
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(express.static('root'))
|
||||||
|
|
||||||
|
// Get mod metadata by ID
|
||||||
|
app.get('/mod/:modId', (req, res) => {
|
||||||
|
const modId = req.params["modId"]
|
||||||
|
const modDir = config.modDir + d + modId
|
||||||
|
const customXML = getCustomXML(modId)
|
||||||
|
const ret = {
|
||||||
|
id: modId,
|
||||||
|
name: getModNameById(modId),
|
||||||
|
size: getDirSize(modDir),
|
||||||
|
customXML: customXML
|
||||||
|
}
|
||||||
|
res.send(ret)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get a mod's XML file
|
||||||
|
app.get('/mod/:modId/:file', (req, res) => {
|
||||||
|
const modId = req.params["modId"]
|
||||||
|
const file = req.params["file"]
|
||||||
|
const contents = fs.readFileSync(config.modDir + d + modId + d + file)
|
||||||
|
res.send(contents)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Search for a mod
|
||||||
|
app.get(('/search/:searchString'), (req, res) => {
|
||||||
|
const searchString = req.params["searchString"]
|
||||||
|
const url = "https://api.steampowered.com/IPublishedFileService/QueryFiles/v1/?numperpage=1000&appid=221100&return_short_description=true&strip_description_bbcode=true&key=" + config.steamAPIKey + "&search_text=" + searchString
|
||||||
|
https.get(url, resp => {
|
||||||
|
let data = '';
|
||||||
|
resp.on('data', chunk => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
resp.on('end', () => {
|
||||||
|
res.send(JSON.parse(data))
|
||||||
|
})
|
||||||
|
}).on('error', err => {
|
||||||
|
console.log(err.message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Install a mod
|
||||||
|
app.get(('/install/:modId'), (req, res) => {
|
||||||
|
const modId = req.params["modId"]
|
||||||
|
// Shell out to steamcmd, monitor the process, and display the output as it runs
|
||||||
|
res.send(modId + " was installed")
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the status of things:
|
||||||
|
If the base files are installed, the version of the server, a list of mods, etc.
|
||||||
|
*/
|
||||||
|
app.get('/status', (req, res) => {
|
||||||
|
// FIXME Async/await this stuff...
|
||||||
|
const installed = fs.existsSync(config.installFile)
|
||||||
|
const mods = getMods()
|
||||||
|
const ret = {
|
||||||
|
"installed": installed,
|
||||||
|
"version": "1.20.bogus",
|
||||||
|
"mods": mods
|
||||||
|
}
|
||||||
|
res.send(ret)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(config.port, () => {
|
||||||
|
console.log(`Listening on port ${config.port}`)
|
||||||
|
})
|
Loading…
Add table
Reference in a new issue