Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deployment script #5

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,29 @@ The following default maps are available:

## Advanced

In order to use a custom content like maps or server config file, create a directory named `gamedir` and place your files there.
In order to use custom content like maps or server config file, create a directory named `deploy` and a subdirectory `copy-gamedir` and place your files there.
For an example of a custom `server.cfg` run:

```
mkdir gamedir && echo 'echo "Executing custom server.cfg"' > gamedir/server.cfg
mkdir -p deploy/copy-gamedir && echo 'echo "Executing custom server.cfg"' > deploy/copy-gamedir/server.cfg
```

Add your settings to the `server.cfg` and mount the directory as volume by running:

```
docker run -it --rm -d -p27015:27015 -p27015:27015/udp -v gamedir:/gamedir spezifanta/hldm
docker run -it --rm -d -p27015:27015 -p27015:27015/udp -v ${PWD}/deploy:/deploy spezifanta/hldm
```

You should see `Executing custom server.cfg` in the server log when starting the server.

You can add more content by creating the following directories under `deploy`:

- `install-assets`: You can put archive files here to be extracted into the root game directory (`valve`).
- `install-maps`: Put archive files or BSP, RES files here to be deployed into the `maps` subdirectory.
- `copy-gamedir`: The entire directory structure will be copied verbatim into the root game directory.

The following archive formats are supported: `zip`, `7z`, `rar`, `tar`, `gzip`, `bzip2`, `xz`, `zstd`, `arj`. To remove content, you can simply remove the files from `deploy` and then recreate your container.


## About this Docker image

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ services:
# - '27015:27015/udp'
# - '26900:26900/udp'
volumes:
- './gamedir:/gamedir'
- './docker/deploy:/deploy'
command: +maxplayers 12 +map crossfire +rcon_password "supersecret" +log on +logaddress 0.0.0.0 27500 +sys_ticrate 1000 -pingboost 2
network_mode: 'host'
30 changes: 19 additions & 11 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
FROM debian:bullseye-slim
FROM debian:bookworm-slim

ENV VERSION 2021.9
ENV RELEASE_DATE 2021-09-05
ENV VERSION 2023.12
ENV RELEASE_DATE 2023-12-01
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
ENV DEBIAN_FRONTEND noninteractive

# Update base image and install dependencies.
RUN dpkg --add-architecture i386 \
&& sed -i "s/^Components: main$/Components: main non-free/g" /etc/apt/sources.list.d/debian.sources \
&& apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
rsync \
file \
gzip \
bzip2 \
xz-utils \
zstd \
p7zip-rar \
libc6:i386 \
&& rm -rf /var/lib/apt/lists/* \
libstdc++6:i386 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& groupadd -r steam && useradd -r -g steam -m -d /opt/steam steam \
&& mkdir /gamedir
&& mkdir /deploy

USER steam
WORKDIR /opt/steam
COPY ./hldm.install /opt/steam

# Download SteamCMD and install HLDM.
# Download SteamCMD and install HLDM. Abort container build on failure.
RUN curl -sL media.steampowered.com/client/installer/steamcmd_linux.tar.gz | tar xzvf - \
&& ldd /opt/steam/linux32/steamcmd \
&& ./steamcmd.sh +runscript hldm.install \
&& rm -fr /opt/steam/hldm/cstrike \
&& rm -fr /opt/steam/hldm/siteserverui \
&& rm -fr /opt/steam/hldm/linux64
&& rm -fr /opt/steam/hldm/linux64 \
&& stat ./hldm/valve/gfx.wad

# Fix error that steamclient.so is missing.
RUN mkdir -p $HOME/.steam \
Expand All @@ -37,14 +45,14 @@ RUN mkdir -p $HOME/.steam \
WORKDIR /opt/steam/hldm

# Copy configs, Metamod, Stripper2 and AMX.
COPY --chown=steam:steam gamedir valve
COPY --chown=steam:steam ./entrypoint.sh ./entrypoint.sh
COPY --chown=steam:steam deploy/copy-gamedir valve
COPY --chown=steam:steam ./hlds_deploy ./hlds_deploy

EXPOSE 27015
EXPOSE 27015/udp

# Start server.
ENTRYPOINT ["./entrypoint.sh", "-timeout 3"]
ENTRYPOINT ["./hlds_deploy", "-timeout 15"]

# Default start parameters.
CMD ["+maxplayers 12", "+map crossfire"]
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion docker/hldm.install
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@ShutdownOnFailedCommand 0
@NoPromptForPassword 1
login anonymous
force_install_dir ./hldm
login anonymous
app_set_config 90 mod valve
app_update 90
app_update 90 validate
Expand Down
241 changes: 241 additions & 0 deletions docker/hlds_deploy
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#!/bin/sh
DEPLOY=/deploy
DEPLOY_MAPS=${DEPLOY}/install-maps
DEPLOY_ASSETS=${DEPLOY}/install-assets
DEPLOY_GAMEDIR=${DEPLOY}/copy-gamedir
TARGET_GAMEDIR=valve
TARGET_MAPS=${TARGET_GAMEDIR}/maps
MAPCYCLE=${TARGET_GAMEDIR}/mapcycle.txt

# announce text
# Print text with separator lines.
announce() {
sep="--------------------------------------------------------------------------------"
printf "\n\n%s\n %s\n%s\n\n" "${sep}" "$1" "${sep}"
}

# is_multiplayer_map bspfile
# Determines is given BSP file is a multiplayer map by searching for an
# info_player_deathmatch entity.
is_multiplayer_map() {
BSPFILE="$1"
grep -q "^\"classname\" \"info_player_deathmatch\"$" "${BSPFILE}"
}

# decompress_with command switches source target
# Use the specified command and its switches to decompress the source file
# to the given destination directory.
decompress_with() {
COMMAND="$1"
SWITCHES="$2"
SOURCE="$3"
TARGET="$4"

outfile="${TARGET}/${filename%.*}"
eval "${COMMAND}" "${SOURCE}" "${SWITCHES}" > "${outfile}"
touch -r "${SOURCE}" "${outfile}"
}

# deploy_archive source target
# Extracts an archive file (source) to the target directory.
deploy_archive() {
SOURCE="$1"
TARGET="$2"

filename="$(basename "${SOURCE}")"
filetype="$(file -b --mime-type "${SOURCE}")"
filetype="${filetype#"application/"}"

printf "\nConsidering %s, it is a(n) %s...\n" "${SOURCE}" "${filetype}"

# Check for tar compressed with gzip, bzip2, xz, zstd
if [ "${filetype}" = "gzip" ] || [ "${filetype}" = "x-bzip2" ] || [ "${filetype}" = "x-xz" ] || [ "${filetype}" = "zstd" ]
then
if [ "$(file -bz --mime-type "${SOURCE}")" = "application/x-tar" ]
then
printf "Found x-tar compressed with %s.\n" "${filetype}"
filetype="x-tar"
fi
fi

case "${filetype}" in
"gzip")
decompress_with gunzip -vc "${SOURCE}" "${TARGET}";;
"x-bzip2")
decompress_with bunzip2 -vc "${SOURCE}" "${TARGET}";;
"x-xz")
decompress_with unxz -vc "${SOURCE}" "${TARGET}";;
"zstd")
zstd -vdfo "${TARGET}/${filename%.*}" "${SOURCE}";;
"x-tar")
tar xvfC "${SOURCE}" "${TARGET}";;
"x-7z-compressed" | "zip" | "x-rar" | "x-arj")
7z x -bb1 "${SOURCE}" -y -o"${TARGET}/";;
*)
printf "ERROR! Skipping %s due to unknown file type.\n" "${SOURCE}" >&2
esac
}

# deploy_archive_map source target
# The source may be an archive, a BSP file, or a RES file. In case the source
# is an archive, extract it with deploy_archive. If it is a multiplayer map or
# accompanying resource file, copy it to the target directory.
deploy_archive_map() {
SOURCE="$1"
TARGET="$2"

filename="$(basename "${SOURCE}")"
fileext="$(printf "%s" "${filename##*.}" | tr 'BSPRE' 'bspre')"
filetype="$(file -b --mime-type "${SOURCE}")"

if [ "${fileext}" = "bsp" ]
then
if is_multiplayer_map "${SOURCE}"
then
printf "\nFound BSP %s...\n" "${SOURCE}"
cp -pv "${SOURCE}" "${TARGET}/"
else
printf "\nFound %s, but it is not a Half-Life multiplayer map!\n" "${SOURCE}" >&2
fi
elif [ "${fileext}" = "res" ]
then
if [ "$(file -b --mime-type "${SOURCE}")" = "text/plain" ]
then
printf "\nFound RES %s...\n" "${SOURCE}"
cp -pv "${SOURCE}" "${TARGET}/"
else
printf "\nFound %s, but it is not a valid resource file!\n" "${SOURCE}" >&2
fi
else
deploy_archive "${SOURCE}" "${TARGET}"
fi
}


cat << END


--------------------------------------------------------------------------------

# # # ###### #####
# # # # # # #
# # # # # #
####### # # # #####
# # # # # #
# # # # # # #
# # ####### ###### #####

((( Docker Deployment )))

--------------------------------------------------------------------------------

END


# This often breaks HLDS, rather not try
#announce "Updating HLDS via SteamCMD..."
#../steamcmd.sh +runscript hldm.update

# Deploy archives to the base game directory
announce "Deploying assets..."
if [ -d "${DEPLOY_ASSETS}" ]
then
find "${DEPLOY_ASSETS}" -mindepth 1 -maxdepth 1 | while read -r sourcefile
do
deploy_archive "${sourcefile}" "${TARGET_GAMEDIR}"
done
else
printf "No assets deploy found. You may add archive files to %s.\n" "${DEPLOY_ASSETS}"
fi

# Deploy archives to the maps directory
announce "Deploying maps..."
if [ -d "${DEPLOY_MAPS}" ]
then
find "${DEPLOY_MAPS}" -mindepth 1 -maxdepth 1 | while read -r sourcefile
do
deploy_archive_map "${sourcefile}" "${TARGET_MAPS}"
done
else
printf "No maps deploy found. You may add archive files and maps to %s.\n" "${DEPLOY_MAPS}"
fi

# Copy miscellaneous files to the game directory
announce "Deploying gamedir..."
if [ -d "${DEPLOY_GAMEDIR}" ]
then
find "${DEPLOY_GAMEDIR}" -mindepth 1 -maxdepth 1 -exec cp -vr {} "${TARGET_GAMEDIR}"/ \;
else
printf "No gamedir deploy found. You may add files to %s.\n" "${DEPLOY_GAMEDIR}"
fi

# Generate a mapcycle.txt by enumerating all multiplayer maps
# In case the server owner prefers to use a hand-made mapcycle.txt, this file
# might be overridden by specifying a different mapcycle file in server.cfg
announce "Generating mapcycle.txt..."
printf "" > "${MAPCYCLE}"
find "${TARGET_MAPS}" -mindepth 1 -maxdepth 1 -type f -iname \*.bsp -not -iname \*~\*.bsp | while read -r bspfile
do
mapname="$(basename "${bspfile}")"
mapname="${mapname%.*}"
printf "%-30s: " "${mapname}"
if is_multiplayer_map "${bspfile}"
then
printf "%s\n" "${mapname}" >> "${MAPCYCLE}"
printf "added\n"
else
printf "skipped (not a multiplayer map)\n"
fi
done

# Check for potentially misinstalled maps
tmplist="$(mktemp)"
find "${TARGET_GAMEDIR}" -mindepth 1 -maxdepth 1 -type f -iname \*.bsp -ls > "${tmplist}"
if [ "$(wc -l < "${tmplist}")" -gt 0 ]
then
announce "WARNING! Misinstalled maps in root game directory!"
printf "%s\n%s\n\n" \
"The following maps found in the root game directory, likely came from" \
"archive files you should have put into $(basename "${DEPLOY_MAPS}") instead:"
cat "${tmplist}"
fi

# First try to remove the bogus directory which will fail if it's not empty,
# so we avoid listing an empty directory
rmdir "${TARGET_MAPS}/maps" 2> /dev/null
if [ -d "${TARGET_MAPS}/maps" ]
then
announce "WARNING! Misinstalled maps in ${TARGET_MAPS}/maps directory!"
printf "%s\n%s\n\n" \
"The following files likely came from an archive you should have put" \
"into $(basename "${DEPLOY_ASSETS}") instead:"
ls -l "${TARGET_MAPS}/maps"
fi

# Find maps from various other subdirectories
find "${TARGET_GAMEDIR}" -mindepth 2 -type f -iname \*.bsp -not -ipath "${TARGET_MAPS}/*.bsp" -ls > "${tmplist}"
find "${TARGET_MAPS}" -mindepth 2 -type f -iname \*.bsp -ls >> "${tmplist}"

if [ "$(wc -l < "${tmplist}")" -gt 0 ]
then
announce "WARNING! Misinstalled maps!"
printf "%s\n%s\n\n" \
"The following maps found in subdirectories where they are not supposed to be," \
"likely came from incorrectly structured archive files:"
cat "${tmplist}"
fi

# Warn about maps with invalid filenames
find "${TARGET_MAPS}" -mindepth 1 -maxdepth 1 -iname \*~\*.bsp -ls > "${tmplist}"
if [ "$(wc -l < "${tmplist}")" -gt 0 ]
then
announce "WARNING! Maps with invalid filenames!"
printf "The '~' character is not allowed in map names, thus these maps will not be functional:\n\n"
cat "${tmplist}"
fi
rm "${tmplist}"

# Execute the hlds_run script, replacing the current process, thus this
# deployment script and its shell interpreter will be unloaded from memory
announce "Starting HLDS..."
exec ./hlds_run "$@"