diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d8c6dc6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Ensure the scripts use LF line endings, not CRLF +*.sh text eol=lf \ No newline at end of file diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..256d0e6 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1 @@ +external-sources=true \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ca734dd..961082f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,57 +5,82 @@ FROM cm2network/steamcmd:root LABEL maintainer="leandro.martin@protonmail.com" -ENV STEAMAPPID 1007 -ENV STEAMAPPID_TOOL 1963720 -ENV STEAMAPP core-keeper -ENV STEAMAPPDIR "${HOMEDIR}/${STEAMAPP}-dedicated" -ENV STEAMAPPDATADIR "${HOMEDIR}/${STEAMAPP}-data" -ENV DLURL https://raw.githubusercontent.com/escapingnetwork/core-keeper-dedicated - -COPY ./entry.sh ${HOMEDIR}/entry.sh -COPY ./launch.sh ${HOMEDIR}/launch.sh +ENV STEAMAPPID=1007 +ENV STEAMAPPID_TOOL=1963720 +ENV STEAMAPP=core-keeper +ENV STEAMAPPDIR="${HOMEDIR}/${STEAMAPP}-dedicated" +ENV STEAMAPPDATADIR="${HOMEDIR}/${STEAMAPP}-data" +ENV SCRIPTSDIR="${HOMEDIR}/scripts" +ENV MODSDIR="${STEAMAPPDATADIR}/StreamingAssets/Mods" +ENV DLURL=https://raw.githubusercontent.com/escapingnetwork/core-keeper-dedicated RUN dpkg --add-architecture i386 # Install Core Keeper server dependencies and clean up -# libx32gcc-s1 lib32gcc-s1 build-essential <- fixes tile generation bug (obsidian wall around spawn) without graphic cards mounted to server -# need all 3 + dpkg i do not know why but every other combination would run the server at an extreme speed - that combination worked for me. -# Thanks to https://www.reddit.com/r/CoreKeeperGame/comments/uym86p/comment/iays04w/?utm_source=share&utm_medium=web2x&context=3 RUN set -x \ - && apt-get update \ - && apt-get install -y --no-install-recommends --no-install-suggests \ - xvfb mesa-utils libx32gcc-s1 lib32gcc-s1 build-essential libxi6 x11-utils tini \ - && mkdir -p "${STEAMAPPDIR}" \ - && mkdir -p "${STEAMAPPDATADIR}" \ - && chmod +x "${HOMEDIR}/entry.sh" \ - && chmod +x "${HOMEDIR}/launch.sh" \ - && chown -R "${USER}:${USER}" "${HOMEDIR}/entry.sh" "${HOMEDIR}/launch.sh" "${STEAMAPPDIR}" "${STEAMAPPDATADIR}" \ - && rm -rf /var/lib/apt/lists/* + && apt-get update \ + && apt-get install -y --no-install-recommends --no-install-suggests \ + xvfb \ + libxi6 \ + tini \ + tzdata \ + gosu \ + jo \ + gettext-base \ + && rm -rf /var/lib/apt/lists/* +# Setup X11 Sockets folder RUN mkdir /tmp/.X11-unix \ - && chown -R "${USER}:${USER}" /tmp/.X11-unix - + && chmod 1777 /tmp/.X11-unix \ + && chown root /tmp/.X11-unix -ENV WORLD_INDEX=0 \ - WORLD_NAME="Core Keeper Server" \ - WORLD_SEED=0 \ - WORLD_MODE=0 \ - GAME_ID="" \ - DATA_PATH="${STEAMAPPDATADIR}" \ - MAX_PLAYERS=10 \ - SEASON=-1 \ - SERVER_IP="" \ - SERVER_PORT="" +# Setup folders +COPY ./scripts ${SCRIPTSDIR} +RUN set -x \ + && chmod +x -R "${SCRIPTSDIR}" \ + && mkdir -p "${STEAMAPPDIR}" \ + && mkdir -p "${STEAMAPPDATADIR}" \ + && chown -R "${USER}:${USER}" "${SCRIPTSDIR}" "${STEAMAPPDIR}" "${STEAMAPPDATADIR}" -# Switch to user -USER ${USER} +# Declare envs and their default values +ENV PUID=1000 \ + PGID=1000 \ + WORLD_INDEX=0 \ + WORLD_NAME="Core Keeper Server" \ + WORLD_SEED=0 \ + WORLD_MODE=0 \ + GAME_ID="" \ + DATA_PATH="${STEAMAPPDATADIR}" \ + MAX_PLAYERS=10 \ + SEASON="" \ + SERVER_IP="" \ + SERVER_PORT="" \ + DISCORD_WEBHOOK_URL="" \ + # Player Join + DISCORD_PLAYER_JOIN_ENABLED=true \ + DISCORD_PLAYER_JOIN_MESSAGE='${char_name} (${steamid}) has joined the server.' \ + DISCORD_PLAYER_JOIN_TITLE="Player Joined" \ + DISCORD_PLAYER_JOIN_COLOR="47456" \ + # Player Leave + DISCORD_PLAYER_LEAVE_ENABLED=true \ + DISCORD_PLAYER_LEAVE_MESSAGE='${char_name} (${steamid}) has disconnected. Reason: ${reason}.' \ + DISCORD_PLAYER_LEAVE_TITLE="Player Left" \ + DISCORD_PLAYER_LEAVE_COLOR="11477760" \ + # Server Start + DISCORD_SERVER_START_ENABLED=true \ + DISCORD_SERVER_START_MESSAGE='**World:** ${world_name}\n**GameID:** ${gameid}' \ + DISCORD_SERVER_START_TITLE="Server Started" \ + DISCORD_SERVER_START_COLOR="2013440" \ + # Server Stop + DISCORD_SERVER_STOP_ENABLED=true \ + DISCORD_SERVER_STOP_MESSAGE="" \ + DISCORD_SERVER_STOP_TITLE="Server Stopped" \ + DISCORD_SERVER_STOP_COLOR="12779520" # Switch to workdir WORKDIR ${HOMEDIR} -VOLUME ${STEAMAPPDIR} - # Use tini as the entrypoint for signal handling ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["bash", "entry.sh"] +CMD ["bash", "scripts/entry.sh"] diff --git a/README.md b/README.md index a756dc2..13eb701 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,7 @@ Create two directories where you want to run your server : ### Using Docker Compose Create a `docker-compose.yml` with the following content: -``` -version: "3" - +```yml services: core-keeper: container_name: core-keeper-dedicated @@ -45,19 +43,40 @@ volumes: ``` Create a `core.env` file, it should contain the environment variables for the dedicated server, see configuration for reference. Example: -``` +```env +PUID=1000 +PGID=1000 WORLD_INDEX=0 -WORLD_NAME=Core Keeper Server +WORLD_NAME="Core Keeper Server" WORLD_SEED=0 WORLD_MODE=0 -GAME_ID= -DATA_PATH=/home/steam/core-keeper-data +GAME_ID="" +DATA_PATH="${STEAMAPPDATADIR}" MAX_PLAYERS=10 -DISCORD=1 -DISCORD_HOOK=https://discord.com/api/webhooks/{id}/{token} -SEASON=-1 -SERVER_IP= -SERVER_PORT= +SEASON="" +SERVER_IP="" +SERVER_PORT="" +DISCORD_WEBHOOK_URL="" +# Player Join +DISCORD_PLAYER_JOIN_ENABLED=true +DISCORD_PLAYER_JOIN_MESSAGE="$${char_name} ($${steamid}) has joined the server." +DISCORD_PLAYER_JOIN_TITLE="Player Joined" +DISCORD_PLAYER_JOIN_COLOR="47456" +# Player Leave +DISCORD_PLAYER_LEAVE_ENABLED=true +DISCORD_PLAYER_LEAVE_MESSAGE="$${char_name} ($${steamid}) has disconnected. Reason: $${reason}." +DISCORD_PLAYER_LEAVE_TITLE="Player Left" +DISCORD_PLAYER_LEAVE_COLOR="11477760" +# Server Start +DISCORD_SERVER_START_ENABLED=true +DISCORD_SERVER_START_MESSAGE="**World:** $${world_name}\n**GameID:** $${gameid}" +DISCORD_SERVER_START_TITLE="Server Started" +DISCORD_SERVER_START_COLOR="2013440" +# Server Stop +DISCORD_SERVER_STOP_ENABLED=true +DISCORD_SERVER_STOP_MESSAGE="" +DISCORD_SERVER_STOP_TITLE="Server Stopped" +DISCORD_SERVER_STOP_COLOR="12779520" ``` On the folder which contains the files run `docker-compose up -d`. @@ -70,21 +89,39 @@ To query the game ID run: ## Configuration These are the arguments you can use to customize server behavior with default values. -``` -WORLD_INDEX Which world index to use. -WORLD_NAME The name to use for the server. -WORLD_SEED The seed to use for a new world. Set to 0 to generate random seed. -WORLD_MODE Sets the world mode for the world. Can be Normal (0), Hard (1), Creative (2), Casual (4). NOTE: Changing between Creative and non-Creative worlds not currently supported. -GAME_ID Game ID to use for the server. Need to be at least 28 characters and alphanumeric, excluding Y,y,x,0,O. Empty or not valid means a new ID will be generated at start. -DATA_PATH Save file location. If not set it defaults to a sub-folder named "DedicatedServer" at the default Core Keeper save location. -MAX_PLAYERS Maximum number of players that will be allowed to connect to server. -DISCORD Enables discord webhook features which sends GameID to a channel. -DISCORD_HOOK Webhook url (Edit channel > Integrations > Create Webhook). -DISCORD_PRINTF_STR The format string used to generate the content of the Discord webook. Default is `%s`, simply sending the GameID. -SEASON Overrides current season by setting to any of None (0), Easter (1), Halloween (2), Christmas (3), Valentine (4), Anniversary (5), CherryBlossom (6), LunarNewYear(7). -1 is default setting where it is set depending on system date. -SERVER_IP Only used if port is set. Sets the address that the server will bind to. -SERVER_PORT What port to bind to. If not set, then the server will use the Steam relay network. If set the clients will connect to the server directly and the port needs to be open. -``` + +| Argument | Default | Description | +| :---: | :---: | :---: | +| PUID | 1000 | The user ID on the host that the container should use for file ownership and permissions. | +| PGID | 1000 | The group ID on the host that the container should use for file ownership and permissions. | +| WORLD_INDEX | 0 | Which world index to use. | +| WORLD_NAME | "Core Keeper Server" | The name to use for the server. | +| WORLD_SEED | 0 | The seed to use for a new world. Set to 0 to generate random seed. | +| WORLD_MODE | 0 | Sets the world mode for the world. Can be Normal (0), Hard (1), Creative (2), Casual (4). | +| SEASON | No Default | Overrides current season by setting to any of None (0), Easter (1), Halloween (2), Christmas (3), Valentine (4), Anniversary (5), CherryBlossom (6), LunarNewYear(7).
**Do not set this env var if you want real date season.** | +| GAME_ID | "" | Game ID to use for the server. Need to be at least 28 characters and alphanumeric, excluding Y,y,x,0,O. Empty or not valid means a new ID will be generated at start. | +| MAX_PLAYERS | 10 | Maximum number of players that will be allowed to connect to server. | +| DATA_PATH | "/home/steam/core-keeper-data" | Save file location. | +| SERVER_IP | No Default | Only used if port is set. Sets the address that the server will bind to. | +| SERVER_PORT | No Default | What port to bind to. If not set, then the server will use the Steam relay network. If set the clients will connect to the server directly and the port needs to be open. | +| DISCORD_WEBHOOK_URL | "" | Webhook url (Edit channel > Integrations > Create Webhook). | +| DISCORD_PLAYER_JOIN_ENABLED | true | Enable/Disable message on player join | +| DISCORD_PLAYER_JOIN_MESSAGE | `"$${char_name} ($${steamid}) has joined the server."` | Embed message | +| DISCORD_PLAYER_JOIN_TITLE | "Player Joined" | Embed title | +| DISCORD_PLAYER_JOIN_COLOR | "47456" | Embed color | +| DISCORD_PLAYER_LEAVE_ENABLED | true | Enable/Disable message on player leave | +| DISCORD_PLAYER_LEAVE_MESSAGE | `"$${char_name} ($${steamid}) has disconnected. Reason: $${reason}."` | Embed message | +| DISCORD_PLAYER_LEAVE_TITLE | "Player Left" | Embed title | +| DISCORD_PLAYER_LEAVE_COLOR | "11477760" | Embed color | +| DISCORD_SERVER_START_ENABLED | true | Enable/Disable message on server start | +| DISCORD_SERVER_START_MESSAGE | `"**World:** $${world_name}\n**GameID:** $${gameid}"` | Embed message | +| DISCORD_SERVER_START_TITLE | "Server Started" | Embed title | +| DISCORD_SERVER_START_COLOR | "2013440" | Embed color | +| DISCORD_SERVER_STOP_ENABLED | true | Enable/Disable message on server stop | +| DISCORD_SERVER_STOP_MESSAGE | "" | Embed message | +| DISCORD_SERVER_STOP_TITLE | "Server Stopped" | Embed title | +| DISCORD_SERVER_STOP_COLOR | "12779520" | Embed color | + ### Contributors diff --git a/docker-compose-example/core.env b/docker-compose-example/core.env index 915b678..3e812ea 100644 --- a/docker-compose-example/core.env +++ b/docker-compose-example/core.env @@ -1,9 +1,33 @@ -GAME_ID= +PUID=1000 +PGID=1000 WORLD_INDEX=0 -WORLD_NAME=Core Keeper Server +WORLD_NAME="Core Keeper Server" WORLD_SEED=0 +WORLD_MODE=0 +GAME_ID="" +DATA_PATH="${STEAMAPPDATADIR}" MAX_PLAYERS=10 -DATA_PATH=/home/steam/core-keeper-data -DISCORD=1 -DISCORD_HOOK=https://discord.com/api/webhooks/id/token -SEASON=0 +SEASON="" +SERVER_IP="" +SERVER_PORT="" +DISCORD_WEBHOOK_URL="" +# Player Join +DISCORD_PLAYER_JOIN_ENABLED=true +DISCORD_PLAYER_JOIN_MESSAGE="$${char_name} ($${steamid}) has joined the server." +DISCORD_PLAYER_JOIN_TITLE="Player Joined" +DISCORD_PLAYER_JOIN_COLOR="47456" +# Player Leave +DISCORD_PLAYER_LEAVE_ENABLED=true +DISCORD_PLAYER_LEAVE_MESSAGE="$${char_name} ($${steamid}) has disconnected. Reason: $${reason}." +DISCORD_PLAYER_LEAVE_TITLE="Player Left" +DISCORD_PLAYER_LEAVE_COLOR="11477760" +# Server Start +DISCORD_SERVER_START_ENABLED=true +DISCORD_SERVER_START_MESSAGE="**World:** $${world_name}\n**GameID:** $${gameid}" +DISCORD_SERVER_START_TITLE="Server Started" +DISCORD_SERVER_START_COLOR="2013440" +# Server Stop +DISCORD_SERVER_STOP_ENABLED=true +DISCORD_SERVER_STOP_MESSAGE="" +DISCORD_SERVER_STOP_TITLE="Server Stopped" +DISCORD_SERVER_STOP_COLOR="12779520" diff --git a/docker-compose-example/docker-compose.yml b/docker-compose-example/docker-compose.yml index aa8d924..15fc80e 100644 --- a/docker-compose-example/docker-compose.yml +++ b/docker-compose-example/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3" - services: core-keeper: container_name: core-keeper-dedicated diff --git a/entry.sh b/entry.sh deleted file mode 100644 index f1ff2dd..0000000 --- a/entry.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -mkdir -p "${STEAMAPPDIR}" || true - -# Override SteamCMD launch arguments if necessary -# Used for subscribing to betas or for testing -if [ -z "$STEAMCMD_UPDATE_ARGS" ]; then - bash "${STEAMCMDDIR}/steamcmd.sh" +force_install_dir "$STEAMAPPDIR" +login anonymous +app_update "$STEAMAPPID" +app_update "$STEAMAPPID_TOOL" +quit -else - steamcmd_update_args=($STEAMCMD_UPDATE_ARGS) - bash "${STEAMCMDDIR}/steamcmd.sh" +force_install_dir "$STEAMAPPDIR" +login anonymous +app_update "$STEAMAPPID" +app_update "$STEAMAPPID_TOOL" "${steamcmd_update_args[@]}" +quit -fi - -bash "${STEAMCMDDIR}/steamcmd.sh" +force_install_dir "$STEAMAPPDIR" +login anonymous +app_update "$STEAMAPPID" +app_update "$STEAMAPPID_TOOL" +quit - -exec bash "./launch.sh" diff --git a/launch.sh b/launch.sh deleted file mode 100644 index 3b28673..0000000 --- a/launch.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash - -# Switch to workdir -cd "${STEAMAPPDIR}" - -xvfbpid="" -ckpid="" - -function kill_corekeeperserver { - if [[ ! -z "$ckpid" ]]; then - kill $ckpid - wait $ckpid - fi - if [[ ! -z "$xvfbpid" ]]; then - kill $xvfbpid - fi -} - -trap kill_corekeeperserver EXIT - -if ! (dpkg -l xvfb >/dev/null) ; then - echo "Installing xvfb dependency..." - sleep 1 - sudo apt-get update -yy && sudo apt-get install xvfb -yy -fi - -set -m - -rm -f /tmp/.X99-lock - -Xvfb :99 -screen 0 1x1x24 -nolisten tcp & -export DISPLAY=:99 -xvfbpid=$! - -# Wait for xvfb ready. -# Thanks to https://hg.mozilla.org/mozilla-central/file/922e64883a5b4ebf6f2345dfb85f04b487a0e714/testing/docker/desktop-build/bin/build.sh -retry_count=0 -max_retries=2 -xvfb_test=0 -until [ $retry_count -gt $max_retries ]; do - xvinfo - xvfb_test=$? - if [ $xvfb_test != 255 ]; then - retry_count=$(($max_retries + 1)) - else - retry_count=$(($retry_count + 1)) - echo "Failed to start Xvfb, retry: $retry_count" - sleep 2 - fi done - if [ $xvfb_test == 255 ]; then exit 255; fi - -rm -f GameID.txt - -chmod +x ./CoreKeeperServer - -#Build Parameters -declare -a params -params=(-batchmode -logfile "CoreKeeperServerLog.txt") -if [ ! -z "${WORLD_INDEX}" ]; then params=( "${params[@]}" -world "${WORLD_INDEX}" ); fi -if [ ! -z "${WORLD_NAME}" ]; then params=( "${params[@]}" -worldname "${WORLD_NAME}" ); fi -if [ ! -z "${WORLD_SEED}" ]; then params=( "${params[@]}" -worldseed "${WORLD_SEED}" ); fi -if [ ! -z "${WORLD_MODE}" ]; then params=( "${params[@]}" -worldmode "${WORLD_MODE}" ); fi -if [ ! -z "${GAME_ID}" ]; then params=( "${params[@]}" -gameid "${GAME_ID}" ); fi -if [ ! -z "${DATA_PATH}" ]; then params=( "${params[@]}" -datapath "${DATA_PATH}" ); fi -if [ ! -z "${MAX_PLAYERS}" ]; then params=( "${params[@]}" -maxplayers "${MAX_PLAYERS}" ); fi -if [ ! -z "${SEASON}" ]; then params=( "${params[@]}" -season "${SEASON}" ); fi -if [ ! -z "${SERVER_IP}" ]; then params=( "${params[@]}" -ip "${SERVER_IP}" ); fi -if [ ! -z "${SERVER_PORT}" ]; then params=( "${params[@]}" -port "${SERVER_PORT}" ); fi - -echo "${params[@]}" - -DISPLAY=:99 LD_LIBRARY_PATH="$LD_LIBRARY_PATH:../Steamworks SDK Redist/linux64/" ./CoreKeeperServer "${params[@]}"& - -ckpid=$! - -echo "Started server process with pid $ckpid" - -while [ ! -f GameID.txt ]; do - sleep 0.1 -done - -gameid=$(cat GameID.txt) -echo "Game ID: ${gameid}" - -if [ -z "$DISCORD" ]; then - DISCORD=0 -fi - -if [ $DISCORD -eq 1 ]; then - if [ -z "$DISCORD_HOOK" ]; then - echo "Please set DISCORD_WEBHOOK url." - else - format="${DISCORD_PRINTF_STR:-%s}" - curl -i -H "Accept: application/json" -H "Content-Type:application/json" -X POST --data "{\"content\": \"$(printf "${format}" "${gameid}")\"}" "${DISCORD_HOOK}" - fi -fi - -wait $ckpid -ckpid="" diff --git a/scripts/compile-parameters.sh b/scripts/compile-parameters.sh new file mode 100644 index 0000000..be1d213 --- /dev/null +++ b/scripts/compile-parameters.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# This scripts compiles parameters from an set of ENV variables to an array +# this should be run with source, so the params ENV becomes avaliable. + +# Function to add arguments to parameter array +# usage: add_param <$env_value> +add_param() { + local param_name="$1" + local param_value="$2" + + if [ -n "$param_value" ]; then + params+=("$param_name" "$param_value") + fi +} + +# Makes log file avaliable for other uses. +logfile="${STEAMAPPDIR}/logs/$(date '+%Y-%m-%d_%H-%M-%S').log" +params=( + "-batchmode" + "-logfile" "$logfile" +) + +add_param "-world" "${WORLD_INDEX}" +add_param "-worldname" "${WORLD_NAME}" +add_param "-worldseed" "${WORLD_SEED}" +add_param "-worldmode" "${WORLD_MODE}" +add_param "-gameid" "${GAME_ID}" +add_param "-datapath" "${DATA_PATH:-${STEAMAPPDATADIR}}" +add_param "-maxplayers" "${MAX_PLAYERS}" +add_param "-season" "${SEASON}" +add_param "-ip" "${SERVER_IP}" +add_param "-port" "${SERVER_PORT}" + +echo "${params[@]}" diff --git a/scripts/discord.sh b/scripts/discord.sh new file mode 100644 index 0000000..0181b16 --- /dev/null +++ b/scripts/discord.sh @@ -0,0 +1,14 @@ +#!/bin/bash +source "${SCRIPTSDIR}/helper-functions.sh" + +TITLE=$1 +MESSAGE=$2 +COLOR=$3 +URL=$4 + +DISCORD_URL="${URL:-${DISCORD_WEBHOOK_URL}}" + +JSON=$(jo embeds[]="$(jo title="$TITLE" description="$MESSAGE" color="$COLOR")") + +LogInfo "Sending Discord json: ${JSON}" +curl -sfSL -H "Content-Type: application/json" -d "$JSON" "$DISCORD_URL" diff --git a/scripts/entry.sh b/scripts/entry.sh new file mode 100644 index 0000000..115c929 --- /dev/null +++ b/scripts/entry.sh @@ -0,0 +1,39 @@ +#!/bin/bash +source "${SCRIPTSDIR}/helper-functions.sh" + +# From: https://github.com/thijsvanloef/palworld-server-docker/blob/32ffe489daecbc332701592f2facf0fe3237c65f/scripts/init.sh#L15 +# Checks for root, updates UID and GID of user steam +# and updates folders owners +if [[ "$(id -u)" -eq 0 ]] && [[ "$(id -g)" -eq 0 ]]; then + if [[ "${PUID}" -ne 0 ]] && [[ "${PGID}" -ne 0 ]]; then + LogAction "EXECUTING USERMOD" + usermod -o -u "${PUID}" "${USER}" + groupmod -o -g "${PGID}" "${USER}" + chown -R "${USER}:${USER}" "${HOMEDIR}" + else + LogError "Running as root is not supported, please fix your PUID and PGID!" + exit 1 + fi +elif [[ "$(id -u)" -eq 0 ]] || [[ "$(id -g)" -eq 0 ]]; then + LogError "Running as root is not supported, please fix your user!" + exit 1 +fi + +if ! [ -w "${STEAMAPPDIR}" ]; then + LogError "${STEAMAPPDIR} is not writable." + exit 1 +fi + +if ! [ -w "${STEAMAPPDATADIR}" ]; then + LogError "${STEAMAPPDATADIR} is not writable." + exit 1 +fi + +#Restart cleanup +if [ -f "/tmp/.X99-lock" ]; then rm /tmp/.X99-lock; fi + +if [[ "$(id -u)" -eq 0 ]]; then + exec gosu "${USER}" bash "${SCRIPTSDIR}/setup.sh" +else + exec bash "${SCRIPTSDIR}/setup.sh" +fi diff --git a/scripts/helper-functions.sh b/scripts/helper-functions.sh new file mode 100644 index 0000000..489885f --- /dev/null +++ b/scripts/helper-functions.sh @@ -0,0 +1,60 @@ +#!/bin/bash + + +## Logging Functions +declare -A COLORS=( + ["RESET"]='\033[0m' + ["WHITE"]='\033[0;37m' + ["RED_BOLD"]='\033[1;31m' + ["GREEN_BOLD"]='\033[1;32m' + ["YELLOW_BOLD"]='\033[1;33m' + ["CYAN_BOLD"]='\033[1;36m' + ["BLUE"]='\033[0;34m' +) + +# Generic logging function +Log() { + local message="$1" + local color="$2" + printf "${color}%s${COLORS["RESET"]}\n" "$message" +} + +# Specific logging functions +LogInfo() { + Log "$1" "${COLORS["WHITE"]}" +} +LogWarn() { + Log "$1" "${COLORS["YELLOW_BOLD"]}" +} +LogError() { + Log "$1" "${COLORS["RED_BOLD"]}" +} +LogSuccess() { + Log "$1" "${COLORS["GREEN_BOLD"]}" +} +LogAction() { + Log "$1" "${COLORS["CYAN_BOLD"]}" +} +LogDebug() { + if [[ "${DEBUG,,}" == true ]]; then + Log "$1" "${COLORS["BLUE"]}" + fi +} + +SendDiscordMessage() { + local title="$1" + local message="$2" + local color="$3" + local wait="$4" + + if [ -n "${DISCORD_WEBHOOK_URL}" ]; then + # printf is to solve issues with literal \n and jo + # shellcheck disable=SC2059 + "${SCRIPTSDIR}"/discord.sh "$title" "$(printf "$message")" "$color" & + waitpid=$! + fi + + if [[ "${wait,,}" == true ]]; then + wait "$waitpid" + fi +} diff --git a/scripts/launch.sh b/scripts/launch.sh new file mode 100644 index 0000000..aa4868f --- /dev/null +++ b/scripts/launch.sh @@ -0,0 +1,52 @@ +#!/bin/bash +source "${SCRIPTSDIR}/helper-functions.sh" + +# Switch to workdir +cd "${STEAMAPPDIR}" || exit + +### Function for gracefully shutdown +function kill_corekeeperserver { + if [[ -n "$ckpid" ]]; then + kill $ckpid + wait $ckpid + fi + if [[ -n "$xvfbpid" ]]; then + kill $xvfbpid + wait $xvfbpid + fi + + # Sends stop message + if [[ "${DISCORD_SERVER_STOP_ENABLED,,}" == true ]]; then + wait=true + SendDiscordMessage "$DISCORD_SERVER_STOP_TITLE" "$DISCORD_SERVER_STOP_MESSAGE" "$DISCORD_SERVER_STOP_COLOR" "$wait" + fi +} + +trap kill_corekeeperserver EXIT + +if [ -f "GameID.txt" ]; then rm GameID.txt; fi + +# Compile Parameters +# Populates `params` array with parameters. +# Creates `logfile` var with log file path. +source "${SCRIPTSDIR}/compile-parameters.sh" + +# Create the log file and folder. +mkdir -p "${STEAMAPPDIR}/logs" +touch "$logfile" + +# Start Xvfb +Xvfb :99 -screen 0 1x1x24 -nolisten tcp & +xvfbpid=$! + +# Start Core Keeper Server +DISPLAY=:99 LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${STEAMCMDDIR}/linux64/" ./CoreKeeperServer "${params[@]}" & +ckpid=$! + +LogDebug "Started server process with pid ${ckpid}" + +# Monitor server logs for player join/leave, server start, and server stop +source "${SCRIPTSDIR}/logfile-parser.sh" +tail --pid "$ckpid" -f "$logfile" | LogParser & + +wait $ckpid diff --git a/scripts/logfile-parser.sh b/scripts/logfile-parser.sh new file mode 100644 index 0000000..5127133 --- /dev/null +++ b/scripts/logfile-parser.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +declare -A characters + +LogParser() { + while IFS= read -r line; do + echo "$line" + + if [[ "$line" == *"is using new name"* ]]; then + + # Extract the steamid and character name + steamid=$(echo "$line" | awk -F'[[]|[ :]+|[]]' '{print $3}') + char_name=$(echo "$line" | awk -F'new name ' '{print $2}') + LogDebug "Character Name: $char_name ($steamid)" + + # Store character name for future use + characters[$steamid]=$char_name + + [[ "${DISCORD_PLAYER_JOIN_ENABLED,,}" == false ]] && return 0 + + # Build message from vars and send message + message=$(char_name="$char_name" steamid="$steamid" envsubst <<<"$DISCORD_PLAYER_JOIN_MESSAGE") + SendDiscordMessage "$DISCORD_PLAYER_JOIN_TITLE" "$message" "$DISCORD_PLAYER_JOIN_COLOR" + fi + + if [[ "$line" == *"Disconnected from userid:"* ]]; then + [[ "${DISCORD_PLAYER_LEAVE_ENABLED,,}" == false ]] && return 0 + + # Extract steamid and reason + steamid=$(echo "$line" | awk -F'[ :]+' '{print $4}') + reason=$(echo "$line" | awk -F'with reason ' '{print $2}') + char_name=${characters[$steamid]:-"Unknown"} + LogDebug "Character Name: $char_name ($steamid)" + + # Build message from vars and send message + message=$(char_name="$char_name" steamid="$steamid" reason="$reason" envsubst <<<"$DISCORD_PLAYER_LEAVE_MESSAGE") + SendDiscordMessage "$DISCORD_PLAYER_LEAVE_TITLE" "$message" "$DISCORD_PLAYER_LEAVE_COLOR" + fi + + if [[ "$line" == "Started session with Game ID "* ]]; then + [[ "${DISCORD_SERVER_START_ENABLED,,}" == false ]] && return 0 + + # Extract Game ID + gameid=$(echo "$line" | awk '{print $6}') + + # Build message from vars and send message + message=$(world_name="$WORLD_NAME" gameid="$gameid" envsubst <<<"$DISCORD_SERVER_START_MESSAGE") + SendDiscordMessage "$DISCORD_SERVER_START_TITLE" "$message" "$DISCORD_SERVER_START_COLOR" + fi + done +} diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100644 index 0000000..6f55d19 --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,24 @@ +#!/bin/bash +mkdir -p "${STEAMAPPDIR}" || true + +# Initialize arguments array +args=( + "+force_install_dir" "$STEAMAPPDIR" + "+login" "anonymous" + "+app_update" "$STEAMAPPID" "validate" + "+app_update" "$STEAMAPPID_TOOL" "validate" +) + +# Override SteamCMD launch arguments if necessary +# Used for subscribing to betas or for testing +if [ -n "$STEAMCMD_UPDATE_ARGS" ]; then + args+=("${STEAMCMD_UPDATE_ARGS[@]}") +fi + +# Add the quit command +args+=("+quit") + +# Run SteamCMD with the arguments +bash "${STEAMCMDDIR}/steamcmd.sh" "${args[@]}" + +exec bash "${SCRIPTSDIR}/launch.sh"