Start turning this project into a provisioning system: The main image will manage the base server files and mod files, then run servers as separate containers with these volumes mounted read-only, so they can be shared across server instances.

Add command line xml merge tool for when that time comes.
Add Red Falcon Heliz mod as the work-in-progress for getting a turnkey system that merges many different XML files that a full server mod installation will require.
Fix finding a mod by index and use that for all mod operations.
Start re-working how mods are added/removed/activated/deactivated. Split the script up into separate files with distinct functionality. WIP.
Add a template system for handling mod XML files.
Add an express web server to be the provisioning container frontend.
Add lots of comments.
Add TL;DR for turnkey release server install.
This commit is contained in:
Daniel Ceregatti 2023-05-13 20:58:35 -07:00
parent 169018665f
commit 71004b4fcc
15 changed files with 1702 additions and 326 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
.idea .idea
*.iml *.iml
node_modules/

View file

@ -18,6 +18,22 @@ This volume can get quite large. It will require at least 2G of disk space for t
Some map mods are as large as 10G. Make sure you have that much disk space in the location where docker stores its Some map mods are as large as 10G. Make sure you have that much disk space in the location where docker stores its
volumes, usually `/var/lib/docker/volumes`. volumes, usually `/var/lib/docker/volumes`.
## TL;DR for this branch and release DayZ server
```shell
git clone https://ceregatti.org/git/daniel/dayzdockerserver.git
cd dayzdockerserver
git checkout volume-refactor
docker compose up -d --build
docker compose exec main bash
dz login # Use a real login, as anonymous cannot download mods
dz install
cd /serverfiles
mv DayZServer DayZServer.release
wget https://cdn.discordapp.com/attachments/491622000935305217/1105089599983853698/DayZServer
chmod 755 DayZServer
dz start # Will start a vanilla Chernarus map
```
## Configure and Build ## Configure and Build
Ensure [Docker](https://docs.docker.com/engine/install/) and [Docker compose](https://docs.docker.com/compose/install/) Ensure [Docker](https://docs.docker.com/engine/install/) and [Docker compose](https://docs.docker.com/compose/install/)

View file

@ -2,7 +2,9 @@ version: "3.3"
volumes: volumes:
# For steamcmd files and resource files used by the scripts # For steamcmd files and resource files used by the scripts
homedir: homedir_main:
# For workshop.cfg, for now
homedir_server:
# Where the server files will be installed # Where the server files will be installed
serverfiles: serverfiles:
# Server profile files # Server profile files
@ -15,14 +17,28 @@ volumes:
services: services:
main: main:
build: . build: web
volumes: volumes:
- homedir:/home/user - homedir_main:/home/user
- serverfiles:/serverfiles - serverfiles:/serverfiles
- mods:/serverfiles/steamapps/workshop/content - mods:/serverfiles/steamapps/workshop/content
- mpmissions:/serverfiles/mpmissions - mpmissions:/serverfiles/mpmissions
- ./files:/files:ro
- ./web:/web:ro
ports:
- "8000:8000/tcp"
restart: no
server:
build: server
volumes:
- homedir_server:/home/user
- serverfiles:/serverfiles:ro
- mods:/serverfiles/steamapps/workshop/content:ro
- mpmissions:/serverfiles/mpmissions
- profiles:/profiles - profiles:/profiles
- ./files:/files - ./files:/files
- ./bin:/files/bin
# 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
@ -39,13 +55,10 @@ services:
# 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

View file

@ -1 +0,0 @@
dayzserver

170
files/dz-common Executable file
View file

@ -0,0 +1,170 @@
#!/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.
# But more importantly, if we have a release-compatible binary, the base files must be installed from this id.
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
# Main container base directories
CFG_SRC_FILES="/files"
SERVER_FILES="/serverfiles"
# Server container base directories
SERVER_PROFILE="/profiles"
mkdir -p ${SERVER_FILES}/battleye
# 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
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
}
# 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
}
# Assemble the workshop variables
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_id_by_index(){
# If we were passed a valid mod id, just return it
if [[ -d "${workshopdir}/${1}" ]]
then
echo ${1}
return
fi
X=1
# Loop over mod list
for i in "${workshopID[@]}"
do
ID=$(echo ${i} | cut -d: -f1)
if [[ ${X} = ${1} ]]
then
echo ${ID}
return
fi
X=$((X+1))
done
}
# Get mod name by ID or index
get_mod_name(){
# Check for an ID
if [ -d "${workshopfolder}/${1}" ]
then
ID=${1}
else
ID=$(get_mod_id_by_index ${1})
fi
if ! [ -d "${workshopfolder}/${ID}" ]
then
echo "Mod ID ${1} doesn't exist" >&2
exit 1
fi
NAME=$(grep name ${workshopfolder}/${ID}/meta.cpp | cut -d '"' -f2 | sed -r 's/\s+//g')
echo ${NAME}
}

View file

@ -6,7 +6,7 @@ then
echo "Creating .bashrc..." echo "Creating .bashrc..."
cat > .bashrc <<EOF cat > .bashrc <<EOF
alias ls='ls --color' alias ls='ls --color'
export PS1="${debian_chroot:+($debian_chroot)}\u@dayzdockerserver:\w\$ " export PS1="${debian_chroot:+($debian_chroot)}\u@dz-server:\w\$ "
EOF EOF
fi fi

15
files/webserver.sh Executable file
View file

@ -0,0 +1,15 @@
#!/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-main:\w\$ "
EOF
fi
cd /web
npm i
node index.js

View file

@ -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/'
@ -19,10 +16,6 @@ RUN apt-get update && apt-get -y upgrade && apt-get -y install --no-install-reco
git \ git \
gwenhywfar-tools \ gwenhywfar-tools \
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 \
@ -30,9 +23,7 @@ RUN apt-get update && apt-get -y upgrade && apt-get -y install --no-install-reco
nano \ nano \
procps \ procps \
python3-pip \ python3-pip \
wget \ wget
rename \
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
@ -43,18 +34,18 @@ 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:${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 -p /home/user /serverfiles/mpmissions /serverfiles/steamapps/workshop/content /profiles /mods && \ mkdir -p /home/user /serverfiles/mpmissions /serverfiles/steamapps/workshop/content /profiles /mods && \
chown -R user:user /home/user /serverfiles /profiles /mods chown -R user:user /home/user /serverfiles /profiles /mods
# Use our non-privileged user # Use our non-privileged user
USER user USER user

View file

@ -1,46 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -eEa source /files/dz-common
# If you want/need the server and rcon ports to be different, set them here. # Server container base directories
# 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="/serverfiles"
SERVER_PROFILE="/profiles" SERVER_PROFILE="/profiles"
mkdir -p ${SERVER_FILES}/battleye ${SERVER_PROFILE} mkdir -p ${SERVER_PROFILE}/battleye
# Server configuration file # Server configuration file
SERVER_CFG_FILE="serverDZ.cfg" SERVER_CFG_FILE="serverDZ.cfg"
SERVER_CFG_DST="${SERVER_FILES}/${SERVER_CFG_FILE}" SERVER_CFG_DST="${SERVER_PROFILE}/${SERVER_CFG_FILE}"
SERVER_CFG_SRC="${CFG_SRC_FILES}/${SERVER_CFG_FILE}" SERVER_CFG_SRC="${CFG_SRC_FILES}/${SERVER_CFG_FILE}"
# Command line parameters except mod, as that is handled separately. # Command line parameters except mod, as that is handled separately.
@ -49,10 +18,6 @@ parameters="-config=${SERVER_CFG_FILE} -port=${port} -freezecheck -BEpath=${SERV
# Used to check if dayZ is installed # Used to check if dayZ is installed
SERVER_INSTALL_FILE="${SERVER_FILES}/DayZServer" 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. This file will store metadata about what mods are installed.
WORKSHOP_CFG="${HOME}/workshop.cfg" WORKSHOP_CFG="${HOME}/workshop.cfg"
if [ ! -f "${WORKSHOP_CFG}" ] if [ ! -f "${WORKSHOP_CFG}" ]
@ -71,10 +36,6 @@ then
mkdir -p "${BACKUP_DIR}" mkdir -p "${BACKUP_DIR}"
fi fi
# Other stuff
YES="${green}yes${default}"
NO="${red}no${default}"
# Functions # Functions
# Usage # Usage
@ -87,25 +48,33 @@ Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ]
Options and arguments: Options and arguments:
a|activate id - Activate an installed DayZ Workshop items by id or index 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 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 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 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 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 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 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 restart - Restart the server without restarting the container
s|status - Shows the server's status: Running, uptime, mods, parameters, mod parameter, etc. s|status - Shows the server's status: Running, uptime, mods, parameters, mod parameter, etc.
stop - Stop the server stop - Stop the server
u|update - Update the server files
${default}" ${default}"
exit 1 exit 1
} }
# Manage the mod symlink
symlink(){
W=${1}
ID=${2}
NAME=${3}
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
}
# Make sure to clean up and report on exit, as these files remain in the container's volume # Make sure to clean up and report on exit, as these files remain in the container's volume
report() { report() {
rm -f /tmp/mod_command_line /tmp/parameters rm -f /tmp/mod_command_line /tmp/parameters
@ -122,30 +91,6 @@ report() {
echo -e "========================================== End log ======================================${default}" 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. # Ensures all is installed and ready before allowing operations that depends on things being ready.
# Installs the initial server config file from its template. # Installs the initial server config file from its template.
# Handles the importing of changes to that template. # Handles the importing of changes to that template.
@ -245,61 +190,6 @@ force(){
kill -KILL $(pidof DayZServer) 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. # Handle any changes in the server config file by allowing them to be merged after viewing a diff.
config(){ config(){
if ! diff -q "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}" if ! diff -q "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}"
@ -319,51 +209,6 @@ config(){
fi 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 variables # Assemble the workshop variables
get_mods(){ get_mods(){
mapfile -t workshopID < "${WORKSHOP_CFG}" mapfile -t workshopID < "${WORKSHOP_CFG}"
@ -414,87 +259,6 @@ get_mod_name(){
echo ${NAME} echo ${NAME}
} }
# Update mods
modupdate(){
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"
checkXML ${1} install
checkInstall ${1} install
}
# Remove a mod
remove(){
ID=$(get_mod_id_by_index ${1})
MODNAME=$(get_mod_name ${1})
checkXML ${ID} uninstall
checkInstall ${ID} uninstall
if [ -d "${workshopfolder}/${ID}" ]
then
echo "Removing directory ${workshopfolder}/${ID}"
rm -rf "${workshopfolder}/${ID}"
fi
if [ -L "${SERVER_FILES}/@${MODNAME}" ]
then
echo "Removing symlink ${SERVER_FILES}/@${MODNAME}"
rm -vf "${SERVER_FILES}/@${MODNAME}"
fi
if grep -q ${ID} "${WORKSHOP_CFG}"
then
echo "Removing workshop file entry"
sed -i "${WORKSHOP_CFG}" -e "/${ID}:/d"
fi
echo -e "Mod id ${ID} - ${red}${MODNAME}${default} - removed"
}
# Activate / Deactivate a mod # Activate / Deactivate a mod
activate(){ activate(){
W=${1} W=${1}
@ -513,9 +277,6 @@ activate(){
if [[ "${ACTIVE}" != "${W}" ]] if [[ "${ACTIVE}" != "${W}" ]]
then then
sed -i "${WORKSHOP_CFG}" -e "s/${ID}:${MODNAME}:[0-1]/${ID}:${MODNAME}:${W}/" sed -i "${WORKSHOP_CFG}" -e "s/${ID}:${MODNAME}:[0-1]/${ID}:${MODNAME}:${W}/"
symlink ${W} ${ID} "${MODNAME}"
copy_keys ${W} ${ID}
checkInstall ${ID} ${UU}install
echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} ${WW}activated" echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} ${WW}activated"
else else
echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} - is already ${WW}active" echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} - is already ${WW}active"
@ -552,30 +313,6 @@ list(){
done done
} }
# Copy mod keys
copy_keys(){
if [[ ${1} = 1 ]]
then
echo "Copying key files..."
find "${workshopfolder}/${2}" -name "*.bikey" -exec cp "{}" "${SERVER_FILES}/keys/" \;
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 # Assemble the mod command line
mod_cmd(){ mod_cmd(){
mod_command_line="" mod_command_line=""
@ -632,22 +369,6 @@ checkXML(){
fi fi
} }
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 # Our internal RCON
rcon(){ rcon(){
exec /usr/local/py3rcon/py3rcon.py --gui ~/py3rcon.config.json exec /usr/local/py3rcon/py3rcon.py --gui ~/py3rcon.config.json

82
web/Dockerfile Normal file
View file

@ -0,0 +1,82 @@
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
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:${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:/work/bin
# 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 ["webserver.sh"]

302
web/bin/dz Executable file
View file

@ -0,0 +1,302 @@
#!/usr/bin/env bash
source /files/dz-common
# Workshop. This file will store metadata about what mods are installed.
WORKSHOP_CFG="${SERVER_FILES}/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}"
# Functions
# Usage
usage(){
echo -e "
${red}Bad option or arguments! ${yellow}${*}${default}
Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ]
Options and arguments:
add id - Add a DayZ Workshop item by id. Added items become active by default
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
r|remove id - Remove all files and directories of a Workshop item by id
s|status - Shows the server's status: Running, uptime, mods, parameters, mod parameter, etc.
u|update - Update the server files
${default}"
exit 1
}
# Handle 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
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
}
# 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}"
}
# 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
}
# Update mods
modupdate(){
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
}
# List mods
list(){
# The state may have changed since we started
get_mods
if [[ "${workshopID[@]}" = "" ]]
then
return
fi
X=1
spaces=" "
echo -e "\n 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
}
# Display the status of the provisioning container
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
# 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="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 "
Logged in to Steam: ${LOGGED_IN} ${ANONYMOUS}
Server files installed: ${INSTALLED}"
if [[ "${INSTALLED}" = "${NO}" ]]
then
echo
echo
exit 0
fi
echo -ne "
Mods: "
MODS=$(list)
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
}
# Capture the first argument and shift it off so we can pass $@ to every function
C=${1}
shift || {
usage
}
get_mods
case "${C}" in
add)
add "${@}"
;;
i|install)
install "${@}"
;;
l|list)
list "${@}"
;;
login)
login "${@}"
;;
m|modupdate)
modupdate "${@}"
;;
r|remove)
remove "${@}"
;;
s|status)
status "${@}"
;;
u|update)
update "${@}"
;;
*)
usage "$*"
;;
esac

10
web/index.js Normal file
View file

@ -0,0 +1,10 @@
const express = require('express')
const path = require('path')
const app = express()
const port = 8000
app.use('/', express.static(path.join(__dirname, 'root')))
app.listen(port, () => {
console.log(`Listening on port ${port}`)
})

1018
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

15
web/package.json Normal file
View file

@ -0,0 +1,15 @@
{
"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"
}
}

23
web/root/index.html Normal file
View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DayZ Docker Server</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<style>
body {
padding-top: 50px;
background-color: darkgray;
}
</style>
</head>
<body>
<div class="container">
<div class="jumbotron">
<h1>DayZ Docker Server</h1>
</div>
</div>
</body>
</html>