From afe583692cc874b67ae43dad0a1679deacb5a1cf Mon Sep 17 00:00:00 2001 From: Red-Thirten Date: Sun, 22 May 2022 18:32:49 -0700 Subject: [PATCH 1/3] Initial Upload Currently only works with experimental branch of DayZ Standalone. --- .github/workflows/games.yml | 1 + games/dayz/Dockerfile | 49 +++++++ games/dayz/README.md | 2 + games/dayz/entrypoint.sh | 284 ++++++++++++++++++++++++++++++++++++ games/dayz/passwd.template | 26 ++++ 5 files changed, 362 insertions(+) create mode 100644 games/dayz/Dockerfile create mode 100644 games/dayz/README.md create mode 100644 games/dayz/entrypoint.sh create mode 100644 games/dayz/passwd.template diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index d4ec133..fe85f0f 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -18,6 +18,7 @@ jobs: game: - altv - arma3 + - dayz - samp - source diff --git a/games/dayz/Dockerfile b/games/dayz/Dockerfile new file mode 100644 index 0000000..55d074e --- /dev/null +++ b/games/dayz/Dockerfile @@ -0,0 +1,49 @@ +FROM --platform=$BUILDPLATFORM debian:stable-slim + +LABEL author="David Wolfe (Red-Thirten)" maintainer="rehlmgaming@gmail.com" + +LABEL org.opencontainers.image.source="https://github.com/parkervcp/yolks" +LABEL org.opencontainers.image.licenses=MIT + +## Update base packages and install dependencies +ENV DEBIAN_FRONTEND=noninteractive +RUN dpkg --add-architecture i386 \ + && apt update \ + && apt upgrade -y \ + && apt install -y \ + curl \ + tzdata \ + locales \ + iproute2 \ + gettext-base \ + ca-certificates \ + libssl-dev \ + lib32gcc-s1 \ + libsdl2-2.0-0 \ + libsdl2-2.0-0:i386 \ + libstdc++6 \ + libstdc++6:i386 \ + lib32stdc++6 \ + libcap2 \ + libnss-wrapper + +## Configure locale +RUN update-locale lang=en_US.UTF-8 \ + && dpkg-reconfigure --frontend noninteractive locales + +## Prepare NSS Wrapper for the entrypoint as a workaround for Arma 3 requiring a valid UID +ENV NSS_WRAPPER_PASSWD=/tmp/passwd NSS_WRAPPER_GROUP=/tmp/group +RUN touch ${NSS_WRAPPER_PASSWD} ${NSS_WRAPPER_GROUP} \ + && chgrp 0 ${NSS_WRAPPER_PASSWD} ${NSS_WRAPPER_GROUP} \ + && chmod g+rw ${NSS_WRAPPER_PASSWD} ${NSS_WRAPPER_GROUP} +ADD passwd.template /passwd.template + +## Setup user and working directory +RUN useradd -m -d /home/container -s /bin/bash container +USER container +ENV USER=container HOME=/home/container +WORKDIR /home/container + +## Copy over and execute entrypoint.sh +COPY ./entrypoint.sh /entrypoint.sh +CMD [ "/bin/bash", "/entrypoint.sh" ] diff --git a/games/dayz/README.md b/games/dayz/README.md new file mode 100644 index 0000000..f13724e --- /dev/null +++ b/games/dayz/README.md @@ -0,0 +1,2 @@ +# DayZ +Docker container designed to run Bohemia Interactive's DayZ dedicated server. diff --git a/games/dayz/entrypoint.sh b/games/dayz/entrypoint.sh new file mode 100644 index 0000000..080b372 --- /dev/null +++ b/games/dayz/entrypoint.sh @@ -0,0 +1,284 @@ +#!/bin/bash + +## File: Pterodactyl DayZ SA Image - entrypoint.sh +## Author: David Wolfe (Red-Thirten) +## Contributors: Aussie Server Hosts (https://aussieserverhosts.com/), Stephen White (SilK) +## Date: 2022/05/22 +## License: MIT License + +## === CONSTANTS === +STEAMCMD_DIR="./steamcmd" # SteamCMD's directory containing steamcmd.sh +STEAMCMD_LOG="${STEAMCMD_DIR}/steamcmd.log" # Log file for SteamCMD +GAME_ID=221100 # SteamCMD ID for the DayZ SA GAME (not server). Only used for Workshop mod downloads. + +# Color Codes +CYAN='\033[0;36m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +## === ENVIRONMENT VARS === +# STARTUP, STARTUP_PARAMS, STEAM_USER, STEAM_PASS, SERVER_BINARY, MOD_FILE, MODIFICATIONS, SERVERMODS, UPDATE_SERVER, VALIDATE_SERVER, MODS_LOWERCASE, STEAMCMD_EXTRA_FLAGS, STEAMCMD_APPID, SERVER_PASSWORD, STEAMCMD_ATTEMPTS, DISABLE_MOD_UPDATES + +## === GLOBAL VARS === +# validateServer, extraFlags, updateAttempt, modifiedStartup, allMods, CLIENT_MODS + +## === DEFINE FUNCTIONS === + +# Runs SteamCMD with specified variables and performs error handling. +function RunSteamCMD { #[Input: int server=0 mod=1; int id] + # Clear previous SteamCMD log + if [[ -f "${STEAMCMD_LOG}" ]]; then + rm -f "${STEAMCMD_LOG:?}" + fi + + updateAttempt=0 + while (( $updateAttempt < $STEAMCMD_ATTEMPTS )); do # Loop for specified number of attempts + # Increment attempt counter + updateAttempt=$((updateAttempt+1)) + + if (( $updateAttempt > 1 )); then # Notify if not first attempt + echo -e "\t${YELLOW}Re-Attempting download/update in 3 seconds...${NC} (Attempt ${CYAN}${updateAttempt}${NC} of ${CYAN}${STEAMCMD_ATTEMPTS}${NC})\n" + sleep 3 + fi + + # Check if updating server or mod + if [[ $1 == 0 ]]; then # Server + ${STEAMCMD_DIR}/steamcmd.sh +force_install_dir /home/container "+login \"${STEAM_USER}\" \"${STEAM_PASS}\"" +app_update $2 $extraFlags $validateServer +quit | tee -a "${STEAMCMD_LOG}" + else # Mod + ${STEAMCMD_DIR}/steamcmd.sh "+login \"${STEAM_USER}\" \"${STEAM_PASS}\"" +workshop_download_item $GAME_ID $2 +quit | tee -a "${STEAMCMD_LOG}" + fi + + # Error checking for SteamCMD + steamcmdExitCode=${PIPESTATUS[0]} + if [[ -n $(grep -i "error\|failed" "${STEAMCMD_LOG}" | grep -iv "setlocal\|SDL") ]]; then # Catch errors (ignore setlocale and SDL warnings) + # Soft errors + if [[ -n $(grep -i "Timeout downloading item" "${STEAMCMD_LOG}") ]]; then # Mod download timeout + echo -e "\n${YELLOW}[UPDATE]: ${NC}Timeout downloading Steam Workshop mod: \"${CYAN}${modName}${NC}\" (${CYAN}${2}${NC})" + echo -e "\t(This is expected for particularly large mods)" + elif [[ -n $(grep -i "0x402\|0x6\|0x602" "${STEAMCMD_LOG}") ]]; then # Connection issue with Steam + echo -e "\n${YELLOW}[UPDATE]: ${NC}Connection issue with Steam servers." + echo -e "\t(Steam servers may currently be down, or a connection cannot be made reliably)" + # Hard errors + elif [[ -n $(grep -i "Password check for AppId" "${STEAMCMD_LOG}") ]]; then # Incorrect beta branch password + echo -e "\n${RED}[UPDATE]: ${YELLOW}Incorrect password given for beta branch. ${CYAN}Skipping download...${NC}" + echo -e "\t(Check your \"[ADVANCED] EXTRA FLAGS FOR STEAMCMD\" startup parameter)" + break + # Fatal errors + elif [[ -n $(grep -i "Invalid Password\|two-factor\|No subscription" "${STEAMCMD_LOG}") ]]; then # Wrong username/password, Steam Guard is turned on, or host is using anonymous account + echo -e "\n${RED}[UPDATE]: Cannot login to Steam - Improperly configured account and/or credentials" + echo -e "\t${YELLOW}Please contact your administrator/host and give them the following message:${NC}" + echo -e "\t${CYAN}Your Egg, or your client's server, is not configured with valid Steam credentials.${NC}" + echo -e "\t${CYAN}Either the username/password is wrong, or Steam Guard is not properly configured\n\taccording to this egg's documentation/README.${NC}\n" + exit 1 + elif [[ -n $(grep -i "Download item" "${STEAMCMD_LOG}") ]]; then # Steam account does not own base game for mod downloads, or unknown + echo -e "\n${RED}[UPDATE]: Cannot download mod - Download failed" + echo -e "\t${YELLOW}While unknown, this error is likely due to your host's Steam account not owning the base game.${NC}" + echo -e "\t${YELLOW}(Please contact your administrator/host if this issue persists)${NC}\n" + exit 1 + elif [[ -n $(grep -i "0x202\|0x212" "${STEAMCMD_LOG}") ]]; then # Not enough disk space + echo -e "\n${RED}[UPDATE]: Unable to complete download - Not enough storage" + echo -e "\t${YELLOW}You have run out of your allotted disk space.${NC}" + echo -e "\t${YELLOW}Please contact your administrator/host for potential storage upgrades.${NC}\n" + exit 1 + elif [[ -n $(grep -i "0x606" "${STEAMCMD_LOG}") ]]; then # Disk write failure + echo -e "\n${RED}[UPDATE]: Unable to complete download - Disk write failure" + echo -e "\t${YELLOW}This is normally caused by directory permissions issues,\n\tbut could be a more serious hardware issue.${NC}" + echo -e "\t${YELLOW}(Please contact your administrator/host if this issue persists)${NC}\n" + exit 1 + else # Unknown caught error + echo -e "\n${RED}[UPDATE]: ${YELLOW}An unknown error has occurred with SteamCMD. ${CYAN}Skipping download...${NC}" + echo -e "\t(Please contact your administrator/host if this issue persists)" + break + fi + elif [[ $steamcmdExitCode != 0 ]]; then # Unknown fatal error + echo -e "\n${RED}[UPDATE]: SteamCMD has crashed for an unknown reason!${NC} (Exit code: ${CYAN}${steamcmdExitCode}${NC})" + echo -e "\t${YELLOW}(Please contact your administrator/host for support)${NC}\n" + exit $steamcmdExitCode + else # Success! + if [[ $1 == 0 ]]; then # Server + echo -e "\n${GREEN}[UPDATE]: Game server is up to date!${NC}" + else # Mod + # Move the downloaded mod to the root directory, and replace existing mod if needed + mkdir -p ./@$2 + rm -rf ./@$2/* + mv -f ./Steam/steamapps/workshop/content/$GAME_ID/$2/* ./@$2 + rm -d ./Steam/steamapps/workshop/content/$GAME_ID/$2 + # Make the mods contents all lowercase + ModsLowercase @$2 + # Move any .bikey's to the keys directory + echo -e "\tMoving any mod ${CYAN}.bikey${NC} files to the ${CYAN}~/keys/${NC} folder..." + find ./@$2 -name "*.bikey" -type f -exec cp {} ./keys \; + echo -e "${GREEN}[UPDATE]: Mod download/update successful!${NC}" + fi + break + fi + if (( $updateAttempt == $STEAMCMD_ATTEMPTS )); then # Notify if failed last attempt + if [[ $1 == 0 ]]; then # Server + echo -e "\t${RED}Final attempt made! ${YELLOW}Unable to complete game server update. ${CYAN}Skipping...${NC}" + echo -e "\t(Please try again at a later time)" + sleep 3 + else # Mod + echo -e "\t${RED}Final attempt made! ${YELLOW}Unable to complete mod download/update. ${CYAN}Skipping...${NC}" + echo -e "\t(You may try again later, or manually upload this mod to your server via SFTP)" + sleep 3 + fi + fi + done +} + +# Takes a directory (string) as input, and recursively makes all files & folders lowercase. +function ModsLowercase { + echo -e "\n\tMaking mod ${CYAN}$1${NC} files/folders lowercase..." + for SRC in `find ./$1 -depth` + do + DST=`dirname "${SRC}"`/`basename "${SRC}" | tr '[A-Z]' '[a-z]'` + if [ "${SRC}" != "${DST}" ] + then + [ ! -e "${DST}" ] && mv -T "${SRC}" "${DST}" + fi + done +} + +# Removes duplicate items from a semicolon delimited string +function RemoveDuplicates { #[Input: str - Output: printf of new str] + if [[ -n $1 ]]; then # If nothing to compare, skip to prevent extra semicolon being returned + echo $1 | sed -e 's/;/\n/g' | sort -u | xargs printf '%s;' + fi +} + +## === ENTRYPOINT START === + +# Wait for the container to fully initialize +sleep 1 + +# Set environment variable that holds the Internal Docker IP +INTERNAL_IP=$(ip route get 1 | awk '{print $(NF-2);exit}') +export INTERNAL_IP + +# Switch to the container's working directory +cd /home/container || exit 1 + +# Collect and parse all specified mods +if [[ -n ${MODIFICATIONS} ]] && [[ ${MODIFICATIONS} != *\; ]]; then # Add manually specified mods to the client-side mods list, while checking for trailing semicolon + CLIENT_MODS="${MODIFICATIONS};" +else + CLIENT_MODS=${MODIFICATIONS} +fi +# If the mod list file exists and is valid, parse and add mods to the client-side mods list +if [[ -f ${MOD_FILE} ]] && [[ -n "$(cat ${MOD_FILE} | grep 'Created by DayZ Launcher')" ]]; then + CLIENT_MODS+=$(cat ${MOD_FILE} | grep 'id=' | cut -d'=' -f3 | cut -d'"' -f1 | xargs printf '@%s;') +elif [[ -n "${MOD_FILE}" ]]; then # If MOD_FILE is not null, warn user file is missing or invalid + echo -e "\n${YELLOW}[STARTUP_WARN]: DayZ Modlist file \"${CYAN}${MOD_FILE}${YELLOW}\" could not be found, or is invalid!${NC}" + echo -e "\tEnsure your uploaded modlist's file name matches your Startup Parameter." + echo -e "\tOnly files exported from a DayZ Launcher are permitted." + if [[ -n "${CLIENT_MODS}" ]]; then + echo -e "\t${CYAN}Reverting to the manual mod list...${NC}" + fi +fi +# Add server mods to the master mods list, while checking for trailing semicolon +if [[ -n ${SERVERMODS} ]] && [[ ${SERVERMODS} != *\; ]]; then + allMods="${SERVERMODS};" +else + allMods=${SERVERMODS} +fi +allMods+=$CLIENT_MODS # Add all client-side mods to the master mod list +CLIENT_MODS=$(RemoveDuplicates ${CLIENT_MODS}) # Remove duplicate mods from CLIENT_MODS, if present +allMods=$(RemoveDuplicates ${allMods}) # Remove duplicate mods from allMods, if present +allMods=$(echo $allMods | sed -e 's/;/ /g') # Convert from string to array + +# Update everything (server and mods), if specified +if [[ ${UPDATE_SERVER} == 1 ]]; then + echo -e "\n${GREEN}[STARTUP]: ${CYAN}Starting checks for all updates...${NC}" + echo -e "(It is okay to ignore any \"SDL\" errors during this process)\n" + + ## Update game server + echo -e "${GREEN}[UPDATE]:${NC} Checking for game server updates with App ID: ${CYAN}${STEAMCMD_APPID}${NC}..." + + if [[ ${VALIDATE_SERVER} == 1 ]]; then # Validate will be added as a parameter if specified + echo -e "\t${CYAN}File validation enabled.${NC} (This may take extra time to complete)" + validateServer="validate" + else + validateServer="" + fi + + # Determine what extra flags should be set + if [[ -n ${STEAMCMD_EXTRA_FLAGS} ]]; then + echo -e "\t(${YELLOW}Advanced${NC}) Extra SteamCMD flags specified: ${CYAN}${STEAMCMD_EXTRA_FLAGS}${NC}\n" + extraFlags=${STEAMCMD_EXTRA_FLAGS} + else + echo -e "" + extraFlags="" + fi + + RunSteamCMD 0 ${STEAMCMD_APPID} + + ## Update mods + if [[ -n $allMods ]] && [[ ${DISABLE_MOD_UPDATES} != 1 ]]; then + echo -e "\n${GREEN}[UPDATE]:${NC} Checking all ${CYAN}Steam Workshop mods${NC} for updates..." + for modID in $(echo $allMods | sed -e 's/@//g') + do + if [[ $modID =~ ^[0-9]+$ ]]; then # Only check mods that are in ID-form + # Get mod's latest update in epoch time from its Steam Workshop changelog page + latestUpdate=$(curl -sL https://steamcommunity.com/sharedfiles/filedetails/changelog/$modID | grep '

$(find @$modID | head -1 | xargs stat -c%Y) ) ]]; then + # Get the mod's name from the Workshop page as well + modName=$(curl -sL https://steamcommunity.com/sharedfiles/filedetails/changelog/$modID | grep 'workshopItemTitle' | cut -d'>' -f2 | cut -d'<' -f1) + if [[ -z $modName ]]; then # Set default name if unavailable + modName="[NAME UNAVAILABLE]" + fi + if [[ ! -d @$modID ]]; then + echo -e "\n${GREEN}[UPDATE]:${NC} Downloading new Mod: \"${CYAN}${modName}${NC}\" (${CYAN}${modID}${NC})" + else + echo -e "\n${GREEN}[UPDATE]:${NC} Mod update found for: \"${CYAN}${modName}${NC}\" (${CYAN}${modID}${NC})" + fi + if [[ -n $latestUpdate ]] && [[ $latestUpdate =~ ^[0-9]+$ ]]; then # Notify last update date, if valid + echo -e "\tMod was last updated: ${CYAN}$(date -d @${latestUpdate})${NC}" + fi + echo -e "\tAttempting mod update/download via SteamCMD...\n" + RunSteamCMD 1 $modID + fi + fi + done + echo -e "${GREEN}[UPDATE]:${NC} Steam Workshop mod update check ${GREEN}complete${NC}!" + fi +fi + +# Check if specified server binary exists. +if [[ ! -f ./${SERVER_BINARY} ]]; then + echo -e "\n${RED}[STARTUP_ERR]: Specified DayZ server binary could not be found in the root directory!${NC}" + echo -e "${YELLOW}Please do the following to resolve this issue:${NC}" + echo -e "\t${CYAN}- Double check your \"Server Binary\" Startup Variable is correct.${NC}" + echo -e "\t${CYAN}- Ensure your server has properly installed/updated without errors (reinstalling/updating again may help).${NC}" + echo -e "\t${CYAN}- Use the File Manager to check that your specified server binary file is not missing from the root directory.${NC}\n" + exit 1 +fi + +# Make mods lowercase, if specified +if [[ ${MODS_LOWERCASE} == "1" ]]; then + for modDir in $allMods + do + ModsLowercase $modDir + done +fi + +# Setup NSS Wrapper for use ($NSS_WRAPPER_PASSWD and $NSS_WRAPPER_GROUP have been set by the Dockerfile) +export USER_ID=$(id -u) +export GROUP_ID=$(id -g) +envsubst < /passwd.template > ${NSS_WRAPPER_PASSWD} +export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libnss_wrapper.so + +# Replace Startup Variables +modifiedStartup=`eval echo $(echo ${STARTUP} | sed -e 's/{{/${/g' -e 's/}}/}/g')` + +# Start the Server +echo -e "\n${GREEN}[STARTUP]:${NC} Starting server with the following startup command:" +echo -e "${CYAN}${modifiedStartup}${NC}\n" +${modifiedStartup} + +if [ $? -ne 0 ]; then + echo -e "\n${RED}PTDL_CONTAINER_ERR: There was an error while attempting to run the start command.${NC}\n" + exit 1 +fi diff --git a/games/dayz/passwd.template b/games/dayz/passwd.template new file mode 100644 index 0000000..2b0dd45 --- /dev/null +++ b/games/dayz/passwd.template @@ -0,0 +1,26 @@ +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false +systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false +systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false +systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false +syslog:x:104:108::/home/syslog:/bin/false +messagebus:x:106:109::/var/run/dbus:/bin/false +bind:x:108:112::/var/cache/bind:/bin/false +${USER}:x:${USER_ID}:${GROUP_ID}:${USER}:${HOME}:/bin/bash \ No newline at end of file From c13524ceab26a5cf34c3924f0d1416933f044d89 Mon Sep 17 00:00:00 2001 From: Red-Thirten Date: Sun, 22 May 2022 19:20:37 -0700 Subject: [PATCH 2/3] Add DayZ to root README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e0ddbbe..a65b567 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ is tagged correctly. * `ghcr.io/parkervcp/games:altv` * [`arma3`](/games/arma3) * `ghcr.io/parkervcp/games:arma3` +* [`dayz`](/games/dayz) + * `ghcr.io/parkervcp/games:dayz` * [`samp`](/games/samp) * `ghcr.io/parkervcp/games:samp` * [`source`](/games/source) From 84abe90d35e26ce519d8ab6ff82b8b6815ecc62a Mon Sep 17 00:00:00 2001 From: Red-Thirten Date: Sun, 22 May 2022 19:42:37 -0700 Subject: [PATCH 3/3] Fix Entrypoint contributors --- games/dayz/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/games/dayz/entrypoint.sh b/games/dayz/entrypoint.sh index 080b372..9460d30 100644 --- a/games/dayz/entrypoint.sh +++ b/games/dayz/entrypoint.sh @@ -2,7 +2,7 @@ ## File: Pterodactyl DayZ SA Image - entrypoint.sh ## Author: David Wolfe (Red-Thirten) -## Contributors: Aussie Server Hosts (https://aussieserverhosts.com/), Stephen White (SilK) +## Contributors: Aussie Server Hosts (https://aussieserverhosts.com/) ## Date: 2022/05/22 ## License: MIT License