feat: add mod downloading feature

This commit is contained in:
radiancex
2025-12-07 17:58:17 +02:00
parent 43f6aa8859
commit cfc3165aec
9 changed files with 143 additions and 59 deletions

8
.env
View File

@@ -1,2 +1,8 @@
# Steam account
STEAM_CMD_USER=
STEAM_CMD_PASSWORD=
STEAM_CMD_PASSWORD=
# Semicolon-separated list of mods to install (without quotes).
# You can find a mod ID in workshop content URL
# Example: MOD_IDS=2663169692;1559212036
MOD_IDS=

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# Folders
server/**
.github/**
# Files
.env

View File

@@ -9,33 +9,28 @@
## Configure credentials
To run the server you must login into account with purchased Dayz.
Docker-compose uses following variables from .env file to log into steam
Update .env with the account info
``` txt
STEAM_CMD_USER=<steam account login>
STEAM_CMD_PASSWORD=<steam account password>
```
## Configure server
## Select mods to install
Edit ./serverDZ.cfg
## Install mods
Follow this guide https://steamcommunity.com/app/221100/discussions/0/4794664409789522473/ to install mods
To install mods edit MOD_IDS in .env file. Scripts will automatically download mods when you start the container.
## Run the server
Execute following command in the terminal
```
docker compose up --build --force-recreate
docker compose up
```
## Connect from DayZ Launcher
- Run DayZ Launcher
- Go to Parameters > ALL PARAMETERS tab
- Go to tab Parameters > ALL PARAMETERS
- Go to Client section and set following parameters
- Server address: 127.0.0.1
- Server Port: 2306
- Server Password: 123456

View File

@@ -6,8 +6,8 @@ services:
FROM steamcmd/steamcmd:ubuntu-18
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y libcap-dev && \
rm -rf /var/lib/apt/lists/*
apt-get install -y libcap-dev rsync && \
rm -rf /var/lib/apt/lists/*
RUN addgroup --gid 1000 admin && \
adduser --no-create-home --uid 1000 --gid 1000 admin
ENV SERVER_DIR=/server
@@ -21,25 +21,40 @@ services:
- "27017:27017/udp"
volumes:
- steam_persist:/root/.steam/
- ./server:/server
- ./serverDZ.cfg:/server/serverDZ.cfg:ro
- ./scripts:/scripts:ro
user: "1000:1000"
env_file: ".env"
entrypoint: ["/bin/sh","-c"]
command:
entrypoint: ["/bin/sh", "-c"]
command:
- |
set -e
cd "$${SERVER_DIR}/"
echo "Updating DayZ Server..."
steamcmd \
+force_install_dir "$${SERVER_DIR}" \
+login "$${STEAM_CMD_USER}" "$${STEAM_CMD_PASSWORD}" \
+app_update 223350 \
+quit
cd $${SERVER_DIR}
echo "Installing mods..."
/scripts/install_mods.sh "$${MOD_IDS}" "$${SERVER_DIR}"
echo "Copying keys..."
/scripts/install_keys.sh "$${SERVER_DIR}" "$${SERVER_DIR}/keys"
echo "Starting server..."
./DayZServer \
-config="serverDZ.cfg" \
-mod="" \
-config="$${SERVER_DIR}/serverDZ.cfg" \
-mod="$${MOD_IDS}" \
-port=2306 \
-steamQueryPort=27017 \
-profiles="profiles"
-profiles="$${SERVER_DIR}/profiles"
volumes:
# Persistent steam folder to skip 2FA after first login and preserve downloaded content
steam_persist: {}

View File

@@ -1,2 +0,0 @@
# Copy all keys from mods located in "server"
find ./server/ -path "./server/keys" -prune -o -type f -name "*.bikey" -exec cp {} ./server/keys/ \;

39
scripts/install_keys.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
print_help() {
echo 'Copy .bikey files from source directory and subdirectories (excluding destination) to destination'
echo 'Usage: ./install_keys.sh "<source dir>" "<destination dir>"'
echo 'Example: ./install_keys.sh "/server" "/server/keys"'
}
# Get paths from args
SRC_DIR=${1:-}
DST_DIR=${2:-}
if [ -z "$SRC_DIR" ] || [ ! -d "$SRC_DIR" ] || [ -z "$DST_DIR" ] || [ ! -d "$DST_DIR" ]; then
print_help
exit 1
fi
echo "Extracting keys from mods in $SRC_DIR..."
FOUND=0
# Iterate source directories
for MOD_PATH in "$SRC_DIR"/*; do
if [ -d "$MOD_PATH" ] && [ "$(basename "$MOD_PATH")" != "$(basename "$DST_DIR")" ]; then
while IFS= read -r -d $'\0' file; do
echo "Found key: $file" >&2
cp -u -- "$file" "$DST_DIR/"
FOUND=1
done < <(find "$MOD_PATH" -type f -name "*.bikey" -print0)
fi
done
if [ "$FOUND" -eq 0 ]; then
echo "No .bikey files found"
else
echo "Keys installed successfully"
fi
exit 0

64
scripts/install_mods.sh Executable file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
print_help() {
echo 'DayZ mod installer — downloads mods via steamcmd and rsyncs them to destination directory'
echo 'Usage: ./install_mods.sh "<semicolon-separated mod ids> "<destination directory>"'
echo 'Env expected: STEAM_CMD_USER, STEAM_CMD_PASSWORD'
}
WORKSHOP_APP_ID=${WORKSHOP_APP_ID:-221100}
# Accept mod IDs and installation root as arguments
RAW_IDS=${1:-}
DST_ROOT=${2:-}
# Skip if no mods to install
if [ -z "$RAW_IDS" ]; then
echo "No mods to install. Skipping..."
exit 0
fi
# Fail if target dir does not exist
if [ ! -d "$DST_ROOT" ]; then
print_help
exit 1
fi
# Normalize separators: turn semicolons into spaces, then split into array
RAW_IDS="${RAW_IDS//;/ }"
read -ra MOD_IDS <<< "$RAW_IDS"
echo "Installing ${#MOD_IDS[@]} mods..."
# Build steamcmd arguments
STEAMCMD_CMD=(+login "${STEAM_CMD_USER}")
for MOD_ID in "${MOD_IDS[@]}"; do
STEAMCMD_CMD+=(+workshop_download_item ${WORKSHOP_APP_ID} ${MOD_ID})
done
STEAMCMD_CMD+=(+quit)
# Run steamcmd
steamcmd ${STEAMCMD_CMD[*]}
# Rsync downloaded mods into the destination root
mkdir -p "$DST_ROOT"
for MOD_ID in "${MOD_IDS[@]}"; do
SRC="/root/.steam/SteamApps/workshop/content/${WORKSHOP_APP_ID}/${MOD_ID}"
if [ ! -d "$SRC" ]; then
echo "Error: Mod directory not found: $SRC"
exit 1
fi
DEST="$DST_ROOT/$MOD_ID"
mkdir -p "$DEST"
rsync -a --delete -- "$SRC/" "$DEST/"
# Set ownership for container user
chown -R 1000:1000 "$DEST" 2>/dev/null || true
done
echo "Mods installed successfully"
exit 0

View File

@@ -1,34 +0,0 @@
hostname = "LOCAL SERVER"; // Server name
password = "123456"; // Password to connect to the server
passwordAdmin = "123456"; // Password to become a server admin
description = "LOCAL SERVER"; // Description of the server. Gets displayed to users in client server browser.
enableWhitelist = 0; // Enable/disable whitelist (value 0-1)
maxPlayers = 60; // Maximum amount of players
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)
disableVoN = 0; // Enable/disable voice over network (value 0-1)
vonCodecQuality = 20; // Voice over network codec quality, the higher the better (values 0-30)
shardId = "123abc"; // Six alphanumeric characters for Private server
disable3rdPerson=0; // Toggles the 3rd person view for players (value 0-1)
disableCrosshair=0; // Toggles the cross-hair (value 0-1)
disablePersonalLight = 1; // Disables personal light for all clients connected to server
lightingConfig = 1; // 0 for brighter night setup, 1 for darker night setup
serverTime="SystemTime"; // Initial in-game time of the server. "SystemTime" means the local time of the machine. Another possibility is to set the time to some value in "YYYY/MM/DD/HH/MM" format, f.e. "2015/4/8/17/23" .
serverTimeAcceleration=12; // Accelerated Time (value 0-24)// This is a time multiplier for in-game time. In this case, the time would move 24 times faster than normal, so an entire day would pass in one hour.
serverNightTimeAcceleration=1; // Accelerated Nigh Time - The numerical value being a multiplier (0.1-64) and also multiplied by serverTimeAcceleration value. Thus, in case it is set to 4 and serverTimeAcceleration is set to 2, night time would move 8 times faster than normal. An entire night would pass in 3 hours.
serverTimePersistent=0; // Persistent Time (value 0-1)// The actual server time is saved to storage, so when active, the next server start will use the saved time value.
guaranteedUpdates=1; // Communication protocol used with game server (use only number 1)
loginQueueConcurrentPlayers=5; // The number of players concurrently processed during the login process. Should prevent massive performance drop during connection when a lot of people are connecting at the same time.
loginQueueMaxPlayers=500; // The maximum number of players that can wait in login queue
instanceId = 1; // DayZ server instance id, to identify the number of instances per box and their storage folders with persistence files
storageAutoFix = 1; // Checks if the persistence files are corrupted and replaces corrupted ones with empty ones (value 0-1)
class Missions
{
class DayZ
{
template="dayzOffline.chernarusplus"; // Mission to load on server startup. <MissionName>.<TerrainName>
// Vanilla mission: dayzOffline.chernarusplus
// DLC mission: dayzOffline.enoch
};
};