diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5b5050d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -services: - - docker - -script: - - docker build -t "jusito/docker-ttt:develop" . - - docker volume create TTT - - docker run -d -P -e INSTALL_CSS=true -e WORKSHOP_COLLECTION_ID=899062542 --name "TTT" -p 27015:27015/tcp -p 27015:27015/udp -v "TTT:/home/steam/server:rw" \ - "jusito/docker-ttt:develop" "testing" \ No newline at end of file diff --git a/README.md b/README.md index 69909c7..8013440 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,101 @@ -# docker-ttt -Garry's mod, gamemode TTT as docker image: https://hub.docker.com/r/jusito/ +# GMOD TTT +GMOD TTT server image, https://hub.docker.com/r/jusito/ + +## TODO +* sv_password not working +* rcon not working +* scrds doesn't like different internal / external ports (thats why no ports are exposed) +* replacer config in other repo u2d? +* health check -> details +* volume for steam workshop +* volume for other games +* volume for gmod config +* AppArmor Profile ## Getting Started -1. Create 2 workshop collections. One containing Maps and a subcollection. Subcollection containing all non-map elements. -2. Do you want forced auto download? If no go 3., if yes use `-e WORKSHOP_COLLECTION_ID=*SubCollectionID*`. -3. Let Gmod server know which collection should be used. `[...]jusito/docker-ttt:beta +host_workshop_collection *MainCollectionID* [...]` -4. Do you want CSS or other game content installed and mounted(CSS recommended)? If no go 5., if yes `-e INSTALL_CSS=true` or see environment variables. -5. Choose your ports. Default is 27015. `docker run [...] -p 27015:27015/tcp jusito/docker-ttt:beta -port 27015 [...]` +1. Create public workshop collection without maps, dummy ID:=123456. Use `jusito/docker-ttt [...] +host_workshop_collection 123456`. Create a collection with maps ID:=7891011 and add/link it to 123456. If you want the clients to automatically load the collection from the workshop when connecting without subscribing, use `-e WORKSHOP_COLLECTION_ID=123456`. The users loading all non-maps at startup and the map if needed, but you don't need to point them to the collection in the workshop. +2. Do this elements need CSS, HL2, HLDM, TF2? Use `-e INSTALL_CSS=true` or `-e INSTALL_HL2=true` aso. +3. Which ports? 27015/udp is default for game traffic x/tcp for rcon, for 27016 you would use `-p 27016:27016/udp [...] jusito/docker-ttt [...] -port 27016`. +4. Set environment variables like servername `-e SERVER_NAME="My Server"`, password `-e SERVER_PASSWORD="securepw"` and timezone for cron `-e TZ="Europe/Berlin"`, default short downtime at Sunday 10 o'clock. +5. Add options for server like startmap `+map ttt_rooftops_2016_v1` and max players `-maxplayers 10` -### run example +### run example without rcon ``` -docker run -dit -p 27015:27015/tcp -p 27015:27015/udp -e WORKSHOP_COLLECTION_ID=123456 -e INSTALL_CSS=true "jusito/docker-ttt:beta" -port 27015 +host_workshop_collection 123456 +map ttt_rooftops_2016_v1 -maxplayers 16 +docker run -d \ + -e INSTALL_CSS=true \ + -p 27015:27015/udp \ + -e SERVER_NAME="My Server" \ + -e SERVER_PASSWORD="securepw" \ + -e TZ="Europe/Berlin" \ + jusito/docker-ttt:gmod_ttt_debian \ + -port 27015 \ + +map ttt_rooftops_2016_v1 \ + -maxplayers 10 ``` - * _-it_ needed for seeing all output if attached - * _27015/tcp_(optional) - rcon port, you will need this too: -usercon +rcon_password "yourPW" - * _27015/udp_ - udp port for game traffic - * _-port 27015_ - only needed if you want to use non-default port, docker -p 27016:27015 will _not_ work. - * _-e WORKSHOP_COLLECTION_ID_(optional) - add every element to forced - * _-e INSTALL_CSS_(optional) - download CSS and mount it - * _+host_workshop_collection_ - Garry's Mod will load this collection - * _+map_(optional) - default map to start - * _-maxplayers_(optional) - max count of players - -### whats missing, why tag beta: -1. cron service needs to be started otherwise the server will get only updates on restart -2. force cleanup of downloaded elements => removed workshop elements are otherwise used -3. I failed to create a proper volume, mount /home/steam/serverfiles. + +### run example with rcon +``` +docker run -d \ + -e WORKSHOP_COLLECTION_ID=123456 \ + -e INSTALL_CSS=true \ + -p 27015:27015/udp -p 27015:27015/tcp \ + -e SERVER_NAME="My Server" \ + -e SERVER_PASSWORD="securepw" \ + -e TZ="Europe/Berlin" \ + jusito/docker-ttt:gmod_ttt_debian \ + -port 27015 \ + +map ttt_rooftops_2016_v1 \ + -maxplayers 10 \ + -usercon +rcon_password "yourPW" +``` + +## Tags +* lgsm\_debian - Linux Game Server Manager in Debian +* gmod\_debian - Garrys Mod with Debian and LGSM +* gmod\_ttt\_debian + +## Environment Variables + +### Server Properties +### additional config +### Internal Used (don't change please) + +## GMOD Parameters + +## LGSM Usage +docker exec -it CONTAINER ./home/steam/gmodserver console + +## File Locations +### Volumes + +### Other + ## environment variables -| Variable | Default | Description | Example | -|----------|---------|-------------|---------| -|WORKSHOP_COLLECTION_ID|""(empty)|Every element on this list is set to forced download. The users don't need to subscribe to your collection. Don't add maps here, they are already forced by default.|1358835428| -||||| -|SERVER_NAME|""(empty)|overwrite server.cfg value|"[TTT] dockerized"| -|SERVER_PASSWORD|""(empty)|overwrite server.cfg value|"SecurePW"| -|SERVER_VOICE_ENABLE|1|overwrite server.cfg value|0 (disabling ingame voice)| -||||| -|INSTALL_CSS|false|Install & Mount CSS. Most of the time you will set this to true.|true| -|INSTALL_HL2|false|Install & Mount HL2.|true| -|INSTALL_HLDM|false|Install & Mount HLDM.|true| -|INSTALL_TF2|false|Install & Mount TF2.|true| +If set every workshop item at the collection is added as forced, that means its automatically downloaded on connecting. Don't add collections with maps here just like weapons aso. +WORKSHOP_COLLECTION_ID= + +This variables are used to write the value to the server.cfg: +SERVER_NAME="" +SERVER_PASSWORD="" +SERVER_VOICE_ENABLE="1" + +If set to "true" the game is installed and mounted, most of the time you want to add the css content. +INSTALL_CSS=false +INSTALL_HL2=false +INSTALL_HLDM=false +INSTALL_TF2=false ## server config -* [TTT config variables](http://ttt.badking.net/config-and-commands/convars) -* [Server.cfg variables](https://wiki.garrysmod.de/server.cfg) +http://ttt.badking.net/config-and-commands/convars +https://wiki.garrysmod.de/server.cfg + +Path in container is: +docker cp "your server.cfg path" CONTAINER:/home/steam/serverfiles/garrysmod/cfg/server.cfg ## Additional - * Attach to console after start, `docker exec -it _CONTAINER_ /home/steam/gmodserver console` - * Copy your server.cfg in, `docker cp "your server.cfg path" CONTAINER:/home/steam/serverfiles/garrysmod/cfg/server.cfg` - - +- Debian Buster, one dependency is missing: https://packages.debian.org/search?keywords=lib32tinfo5 +- Alpine, steamcmd doesn't like musl +### Debian Buster +Currently one dependency is missing: https://packages.debian.org/search?keywords=lib32tinfo5 diff --git a/TTT/Dockerfile b/TTT/Dockerfile new file mode 100644 index 0000000..a120c8a --- /dev/null +++ b/TTT/Dockerfile @@ -0,0 +1,9 @@ +FROM jusito/docker-ttt:gmod_debian + +ENV SERVER_GAMEMODE="terrortown" + +COPY "server.cfg.default" "/home/server.cfg.default" + +USER "$USER_ID:$GROUP_ID" + +VOLUME "$SERVER_PATH" \ No newline at end of file diff --git a/server.cfg b/TTT/server.cfg.default similarity index 100% rename from server.cfg rename to TTT/server.cfg.default diff --git a/apparmor/JusitoGmod b/apparmor/JusitoGmod new file mode 100644 index 0000000..a6335b1 --- /dev/null +++ b/apparmor/JusitoGmod @@ -0,0 +1,69 @@ +// +build linux + +package apparmor // import "github.com/docker/docker/profiles/apparmor" + +// baseTemplate defines the default apparmor profile for containers. +const baseTemplate = ` +{{range $value := .Imports}} +{{$value}} +{{end}} + +profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { +{{range $value := .InnerImports}} + {{$value}} +{{end}} + + network, + capability, + file, + umount, +{{if ge .Version 208096}} +{{/* Allow 'docker kill' to actually send signals to container processes. */}} + signal (receive) peer={{.DaemonProfile}}, +{{/* Allow container processes to send signals amongst themselves. */}} + signal (send,receive) peer={{.Name}}, +{{end}} + + deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir) + # deny write to files not in /proc//** or /proc/sys/** + deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w, + deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel) + deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/ + deny @{PROC}/sysrq-trigger rwklx, + deny @{PROC}/kcore rwklx, + + deny mount, + + deny /sys/[^f]*/** wklx, + deny /sys/f[^s]*/** wklx, + deny /sys/fs/[^c]*/** wklx, + deny /sys/fs/c[^g]*/** wklx, + deny /sys/fs/cg[^r]*/** wklx, + deny /sys/firmware/** rwklx, + deny /sys/kernel/security/** rwklx, + + deny /bin/*/** w, + deny /boot/*/** w, + deny /dev/*/** w, + deny /etc/*/** w, + deny /home/* w, + deny /lib/*/** w, + deny /lib64/*/** w, + deny /media/*/** w, + deny /mnt/*/** w, + deny /opt/*/** w, + deny /proc/*/** w, + deny /root/*/** w, + deny /run/*/** w, + deny /sbin/*/** w, + deny /srv/*/** w, + deny /sys/*/** w, + deny /tmp/*/** w, + deny /usr/*/** w, + deny /var/*/** w, + +{{if ge .Version 208095}} + # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container + ptrace (trace,read,tracedby,readby) peer={{.Name}}, +{{end}} +} \ No newline at end of file diff --git a/apparmor/generateAppArmorProfile.sh b/apparmor/generateAppArmorProfile.sh new file mode 100644 index 0000000..822b801 --- /dev/null +++ b/apparmor/generateAppArmorProfile.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o pipefail +set -o nounset + +wget -qO "apparmor.profile" 'https://raw.githubusercontent.com/moby/moby/master/profiles/apparmor/template.go' + diff --git a/forceWorkshopDownload.sh b/forceWorkshopDownload.sh deleted file mode 100644 index 6e2c130..0000000 --- a/forceWorkshopDownload.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -set -e - -#using WORKSHOP_COLLECTION_ID -LUA_PATH="${SERVER_PATH}/garrysmod/lua/autorun/server" -LUA_FILE="${LUA_PATH}/workshop_autoload.lua" - -#remove old file -if [ -e "$LUA_FILE" ]; then - rm "$LUA_FILE" -else - mkdir -p "$LUA_PATH" -fi - -if [ "$WORKSHOP_COLLECTION_ID" = "0" ] || [ "$WORKSHOP_COLLECTION_ID" = "" ]; then - echo "given ID is default, no workshop download" -else - touch "$LUA_FILE" - arr="$(wget -q -O - https://steamcommunity.com/sharedfiles/filedetails/?id=${WORKSHOP_COLLECTION_ID} | tr '\n' ' ' | grep -Po '"workshopItem"[^"]+"https://steamcommunity.com/sharedfiles/filedetails/\?id=(\d+)' | grep -Po '\d\d\d+' )" - str="" - for i in ${arr[@]} - do - str=${str}"resource.AddWorkshop( \"${i}\" )"$'\n' - done - echo "$str" > "$LUA_FILE" -fi - diff --git a/gmod/Dockerfile b/gmod/Dockerfile new file mode 100644 index 0000000..08b477a --- /dev/null +++ b/gmod/Dockerfile @@ -0,0 +1,83 @@ +FROM jusito/docker-ttt:lgsm_debian + +# Const \\ Overwrite Env \\ Configs optional +ENV CSS_PATH="/home/steam/addons/css" \ + HL2_PATH="/home/steam/addons/hl2" \ + HLDM_PATH="/home/steam/addons/hldm" \ + TF2_PATH="/home/steam/addons/tf2" \ + \ + \ + SERVER_EXECUTABLE="gmodserver" \ + SERVER_GAME="gmodserver" \ + \ + \ + WORKSHOP_COLLECTION_ID="" \ + WORKSHOP_API_KEY="" \ + WORKSHOP_AUTOLOAD="true" \ + SERVER_NAME="" \ + SERVER_PASSWORD="" \ + SERVER_VOICE_ENABLE="1" \ + SERVER_IP="0.0.0.0" \ + SERVER_PORT="27015" \ + SERVER_CLIENTPORT="27005" \ + SERVER_SOURCETVPORT="27020" \ + SERVER_DEFAULT_MAP="gm_construct" \ + SERVER_MAX_PLAYERS="16" \ + SERVER_TICKRATE="66" \ + SERVER_GAMEMODE="sandbox" \ + SERVER_LOGIN_TOKEN="" \ + SERVER_ADDITIONAL_PARAMETERS="-disableluarefresh" \ + LGSM_DISPLAYIP="" \ + LGSM_POSTALERT="off" \ + LGSM_POSTDAYS="7" \ + LGSM_POSTTARGET="https://hastebin.com" \ + LGSM_DISCORDALERT="off" \ + LGSM_DISCORDWEBHOOK="webhook" \ + LGSM_EMAILALERT="off" \ + LGSM_EMAIL="email@example.com" \ + LGSM_EMAILFROM="" \ + LGSM_IFTTTALERT="off" \ + LGSM_IFTTTTOKEN="accesstoken" \ + LGSM_IFTTTEVENT="linuxgsm_alert" \ + LGSM_MAILGUNALERT="off" \ + LGSM_MAILGUNTOKEN="accesstoken" \ + LGSM_MAILGUNDOMAIN="example.com" \ + LGSM_MAILGUNEMAILFROM="alert@example.com" \ + LGSM_MAILGUNEMAIL="email@myemail.com" \ + LGSM_PUSHBULLETALERT="off" \ + LGSM_PUSHBULLETTOKEN="accesstoken" \ + LGSM_CHANNELTAG="" \ + LGSM_PUSHOVERALERT="off" \ + LGSM_PUSHOVERTOKEN="accesstoken" \ + LGSM_TELEGRAMALERT="off" \ + LGSM_TELEGRAMTOKEN="accesstoken" \ + LGSM_TELEGRAMCHATID="" \ + LGSM_CURLCUSTOMSTRING="" \ + LGSM_UPDATEONSTART="off" \ + LGSM_MAXBACKUPS="4" \ + LGSM_MAXBACKUPDAYS="30" \ + LGSM_STOPONBACKUP="on" \ + LGSM_CONSOLELOGGING="on" \ + LGSM_LOGDAYS="7" \ + LGSM_QUERYDELAY="5" \ + LGSM_BRANCH="" \ + LGSM_STEAMMASTER="true" \ + \ + INSTALL_CSS=false \ + INSTALL_HL2=false \ + INSTALL_HLDM=false \ + INSTALL_TF2=false \ + \ + USE_MY_REPLACER_CONFIG=false + + +COPY ["prepareServer.sh", "initConfig.sh", "forceWorkshopDownload.sh", "installAndMountAddons.sh", "common.cfg", "/home/"] + +RUN chown "$DOCKER_USER:$DOCKER_USER" /home/prepareServer.sh && \ + chown "$DOCKER_USER:$DOCKER_USER" /home/initConfig.sh && \ + chown "$DOCKER_USER:$DOCKER_USER" /home/forceWorkshopDownload.sh && \ + chown "$DOCKER_USER:$DOCKER_USER" /home/installAndMountAddons.sh && \ + chmod a=rx /home/prepareServer.sh && \ + chmod a=rx /home/initConfig.sh && \ + chmod a=rx /home/forceWorkshopDownload.sh && \ + chmod a=rx /home/installAndMountAddons.sh diff --git a/gmod/common.cfg b/gmod/common.cfg new file mode 100644 index 0000000..dcf08f5 --- /dev/null +++ b/gmod/common.cfg @@ -0,0 +1,162 @@ +#!/bin/sh + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +#set -o errexit +#set -o pipefail +#travistest: unbound variable +#set -o nounset + +## Server Start Settings | https://docs.linuxgsm.com/configuration/start-parameters +ip="$SERVER_IP" +port="$SERVER_PORT" +clientport="$SERVER_CLIENTPORT" +sourcetvport="$SERVER_SOURCETVPORT" +defaultmap="gm_construct" +maxplayers="$SERVER_MAX_PLAYERS" +tickrate="$SERVER_TICKRATE" +gamemode="$SERVER_GAMEMODE" + +## Workshop Parameters | https://wiki.garrysmod.com/page/Workshop_for_Dedicated_Servers +# To get an API key visit - https://steamcommunity.com/dev/apikey +wsapikey="$WORKSHOP_API_KEY" +wscollectionid="$WORKSHOP_COLLECTION_ID" + +## Custom Start Parameters +# Default -disableluarefresh, disables lua autorefresh reducing server lag. Auto refresh only useful for developers. +customparms="$SERVER_ADDITIONAL_PARAMETERS" + +## Optional: Game Server Login Token +# GSLT can be used for running a public server. +# More info: https://linuxgsm.com/gslt +gslt="$SERVER_LOGIN_TOKEN" + +## Server Start Command | https://docs.linuxgsm.com/configuration/start-parameters#additional-parameters +fn_parms(){ +#shellcheck disable=SC2034,SC2154 +parms="-game garrysmod -strictportbind -ip ${ip} -port ${port} -tickrate ${tickrate} +host_workshop_collection ${wscollectionid} -authkey ${wsapikey} +clientport ${clientport} +tv_port ${sourcetvport} +gamemode ${gamemode} +map ${defaultmap} +sv_setsteamaccount ${gslt} +servercfgfile ${servercfg} -maxplayers ${maxplayers} ${customparms}" +} + +#### LinuxGSM Settings #### + +## Notification Alerts +# (on|off) + +# Display IP | https://docs.linuxgsm.com/alerts#display-ip +#shellcheck disable=SC2034 +displayip="$LGSM_DISPLAYIP" + +# More info | https://docs.linuxgsm.com/alerts#more-info +#shellcheck disable=SC2034 +postalert="$LGSM_POSTALERT" +#shellcheck disable=SC2034 +postdays="$LGSM_POSTDAYS" +#shellcheck disable=SC2034 +posttarget="$LGSM_POSTTARGET" + +# Discord Alerts | https://docs.linuxgsm.com/alerts/discord +#shellcheck disable=SC2034 +discordalert="$LGSM_DISCORDALERT" +#shellcheck disable=SC2034 +discordwebhook="$LGSM_DISCORDWEBHOOK" + +# Email Alerts | https://docs.linuxgsm.com/alerts/email +#shellcheck disable=SC2034 +emailalert="$LGSM_EMAILALERT" +#shellcheck disable=SC2034 +email="$LGSM_EMAIL" +#shellcheck disable=SC2034 +emailfrom="$LGSM_EMAILFROM" + +# IFTTT Alerts | https://docs.linuxgsm.com/alerts/ifttt +#shellcheck disable=SC2034 +iftttalert="$LGSM_IFTTTALERT" +#shellcheck disable=SC2034 +ifttttoken="$LGSM_IFTTTTOKEN" +#shellcheck disable=SC2034 +iftttevent="$LGSM_IFTTTEVENT" + +# Mailgun Email Alerts | https://docs.linuxgsm.com/alerts/mailgun +#shellcheck disable=SC2034 +mailgunalert="$LGSM_MAILGUNALERT" +#shellcheck disable=SC2034 +mailguntoken="$LGSM_MAILGUNTOKEN" +#shellcheck disable=SC2034 +mailgundomain="$LGSM_MAILGUNDOMAIN" +#shellcheck disable=SC2034 +mailgunemailfrom="$LGSM_MAILGUNEMAILFROM" +#shellcheck disable=SC2034 +mailgunemail="$LGSM_MAILGUNEMAIL" + +# Pushbullet Alerts | https://docs.linuxgsm.com/alerts/pushbullet +#shellcheck disable=SC2034 +pushbulletalert="$LGSM_PUSHBULLETALERT" +#shellcheck disable=SC2034 +pushbullettoken="$LGSM_PUSHBULLETTOKEN" +#shellcheck disable=SC2034 +channeltag="$LGSM_CHANNELTAG" + +# Pushover Alerts | https://docs.linuxgsm.com/alerts/pushover +#shellcheck disable=SC2034 +pushoveralert="$LGSM_PUSHOVERALERT" +#shellcheck disable=SC2034 +pushovertoken="$LGSM_PUSHOVERTOKEN" + +# Telegram Alerts | https://docs.linuxgsm.com/alerts/telegram +# You can add a custom cURL string eg proxy (useful in Russia) or else in "curlcustomstring". +# like a "--socks5 ipaddr:port" for socks5 proxy see more in "curl --help", if you not need +# any custom string in curl - simple ignore this parameter. +#shellcheck disable=SC2034 +telegramalert="$LGSM_TELEGRAMALERT" +#shellcheck disable=SC2034 +telegramtoken="$LGSM_TELEGRAMTOKEN" +#shellcheck disable=SC2034 +telegramchatid="$LGSM_TELEGRAMCHATID" +#shellcheck disable=SC2034 +curlcustomstring="$LGSM_CURLCUSTOMSTRING" + +## Updating | https://docs.linuxgsm.com/commands/update +#shellcheck disable=SC2034 +updateonstart="$LGSM_UPDATEONSTART" + +## Backup | https://docs.linuxgsm.com/commands/backup +#shellcheck disable=SC2034 +maxbackups="$LGSM_MAXBACKUPS" +#shellcheck disable=SC2034 +maxbackupdays="$LGSM_MAXBACKUPDAYS" +#shellcheck disable=SC2034 +stoponbackup="$LGSM_STOPONBACKUP" + +## Logging | https://docs.linuxgsm.com/features/logging +#shellcheck disable=SC2034 +consolelogging="$LGSM_CONSOLELOGGING" +#shellcheck disable=SC2034 +logdays="$LGSM_LOGDAYS" + +## Monitor | https://docs.linuxgsm.com/commands/monitor +# Query delay time +#shellcheck disable=SC2034 +querydelay="$LGSM_QUERYDELAY" + +#### LinuxGSM Advanced Settings #### + +# ANSI Colors +#shellcheck disable=SC2034 +ansi="on" + +# Message Display Time +#shellcheck disable=SC2034 +sleeptime="0.5" + +## SteamCMD Settings +# Server appid +#shellcheck disable=SC2034 +appid="4020" +# SteamCMD Branch | https://docs.linuxgsm.com/steamcmd/branch +#shellcheck disable=SC2034 +branch="$LGSM_BRANCH" +# Master Server | https://docs.linuxgsm.com/steamcmd/steam-master-server +#shellcheck disable=SC2034 +steammaster="$LGSM_STEAMMASTER" \ No newline at end of file diff --git a/gmod/forceWorkshopDownload.sh b/gmod/forceWorkshopDownload.sh new file mode 100644 index 0000000..edb132f --- /dev/null +++ b/gmod/forceWorkshopDownload.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o nounset +set -o pipefail + +#using WORKSHOP_COLLECTION_ID +LUA_PATH="${SERVER_PATH}/garrysmod/lua/autorun/server" +LUA_FILE="${LUA_PATH}/workshop_autoload.lua" + +#remove old file +if [ -e "$LUA_FILE" ]; then + rm "$LUA_FILE" +else + mkdir -p "$LUA_PATH" +fi + +if [ "$WORKSHOP_COLLECTION_ID" = "0" ] || [ "$WORKSHOP_COLLECTION_ID" = "" ] || [ "$WORKSHOP_AUTOLOAD" != "true" ]; then + echo "No auto workshop download" +else + touch "$LUA_FILE" + arr=$(wget -q -O - https://steamcommunity.com/sharedfiles/filedetails/?id="${WORKSHOP_COLLECTION_ID}" | tr '\n' ' ' | grep -Po '"workshopItem"[^"]+"https://steamcommunity.com/sharedfiles/filedetails/\?id=(\d+)' | grep -Po '\d\d\d+' ) + str="" + # resplitting needed here, otherwise one string with complete ids + # shellcheck disable=SC2068 + for i in ${arr[@]} + do + str=${str}"resource.AddWorkshop( \"${i}\" )"$'\n' + done + echo "$str" > "$LUA_FILE" +fi + diff --git a/gmod/initConfig.sh b/gmod/initConfig.sh new file mode 100644 index 0000000..0b9a103 --- /dev/null +++ b/gmod/initConfig.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o nounset +set -o pipefail + +function configReplace() { + source="$1" + target="$source \"$2\"" + + count=$(grep -Poc "($source).+" "${SERVER_PATH}/garrysmod/cfg/server.cfg") + + echo "Request for replacing $source to $target, source is found $count times" + + if [ "$count" == "1" ]; then + source=$(grep -Po "($source).+" "${SERVER_PATH}/garrysmod/cfg/server.cfg" | sed 's/\\/\\\\/g' | sed 's/\//\\\//g') + target=$(echo "$target" | sed 's/\\/\\\\/g' | sed 's/\//\\\//g') + sed -i "s/$source/$target/g" "${SERVER_PATH}/garrysmod/cfg/server.cfg" + + elif [ "$count" == "0" ]; then + echo "" >> "${SERVER_PATH}/garrysmod/cfg/server.cfg" + echo "$target" >> "${SERVER_PATH}/garrysmod/cfg/server.cfg" + + else + echo "can't set $1 because there are multiple in" + fi +} + +#create default server.config +# not empty: grep -q '[^[:space:]]' < 'server.cfg' && echo "not empty" +if [ ! -e "${SERVER_PATH}/garrysmod/cfg/server.cfg" ] || [ "0" = "$(grep -oc '[^[:space:]]' "${SERVER_PATH}/garrysmod/cfg/server.cfg")" ]; then + mkdir -p "${SERVER_PATH}/garrysmod/cfg" || true + cp -f "/home/server.cfg.default" "${SERVER_PATH}/garrysmod/cfg/server.cfg" + chown "$USER_ID:$GROUP_ID" "${SERVER_PATH}/garrysmod/cfg/server.cfg" + chmod u+rw "${SERVER_PATH}/garrysmod/cfg/server.cfg" +fi + +#set hostname & password, working if only one entry is in +if [ -n "${SERVER_NAME}" ]; then + configReplace "hostname" "$SERVER_NAME" +fi +if [ -n "${SERVER_PASSWORD}" ]; then + configReplace "sv_password" "$SERVER_PASSWORD" +fi +if [ -n "${SERVER_VOICE_ENABLE}" ]; then + configReplace "sv_voiceenable" "$SERVER_VOICE_ENABLE" +fi + + + +#this is a simple option for myself, but you can use it too +if [ "$USE_MY_REPLACER_CONFIG" = "true" ] && [ ! -e "${SERVER_PATH}/garrysmod/data/jusito_ttt_entity_replace" ]; then + mkdir -p "${SERVER_PATH}/garrysmod/data/jusito_ttt_entity_replace" + wget -O "${SERVER_PATH}/garrysmod/data/jusito_ttt_entity_replace/config.txt" "https://raw.githubusercontent.com/jusito/ttt_entity_replace/master/config.txt.example_fas2" +fi + diff --git a/installAndMountAddons.sh b/gmod/installAndMountAddons.sh similarity index 94% rename from installAndMountAddons.sh rename to gmod/installAndMountAddons.sh index 121308a..d62e460 100644 --- a/installAndMountAddons.sh +++ b/gmod/installAndMountAddons.sh @@ -1,6 +1,12 @@ #!/bin/bash -set -e +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o nounset +set -o pipefail cd "$STEAM_CMD" mount='"mountcfg"'$'\n{\n' if [ "$INSTALL_CSS" = "true" ]; then diff --git a/gmod/prepareServer.sh b/gmod/prepareServer.sh new file mode 100644 index 0000000..8ecba73 --- /dev/null +++ b/gmod/prepareServer.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o nounset +#./prepareServer.sh: 9: set: Illegal option -o pipefail +#set -o pipefail + +mkdir -p "/home/steam/lgsm/config-lgsm/gmodserver/" +cp -f "/home/common.cfg" "/home/steam/lgsm/config-lgsm/gmodserver/common.cfg" + +cd "/home" +echo "check configurations" +./initConfig.sh +echo "force workshop download" +./forceWorkshopDownload.sh +echo "install & mount gamefiles" +./installAndMountAddons.sh +cd "$STEAM_PATH" + +#docker args -> lgsm args +temp="" +temp=$(printf "%s " "$@") || true +export parms="-game garrysmod $SERVER_GAMEMODE $temp" +if [ -e "${STEAM_PATH}/lgsm/config-lgsm/gmodserver/gmodserver.cfg" ]; then + rm -f "${STEAM_PATH}/lgsm/config-lgsm/gmodserver/gmodserver.cfg" +fi +mkdir -p "${STEAM_PATH}/lgsm/config-lgsm/gmodserver/" +touch "${STEAM_PATH}/lgsm/config-lgsm/gmodserver/gmodserver.cfg" +echo "fn_parms(){" > "${STEAM_PATH}/lgsm/config-lgsm/gmodserver/gmodserver.cfg" +echo "parms="'"'"$parms"'"' >> "${STEAM_PATH}/lgsm/config-lgsm/gmodserver/gmodserver.cfg" +echo "}" >> "${STEAM_PATH}/lgsm/config-lgsm/gmodserver/gmodserver.cfg" +echo "starting with $parms" \ No newline at end of file diff --git a/lgsm/Dockerfile b/lgsm/Dockerfile new file mode 100644 index 0000000..6456419 --- /dev/null +++ b/lgsm/Dockerfile @@ -0,0 +1,67 @@ +FROM debian:stretch-slim + +# Const \\ Overwrite Env \\ Configs possible \\ Configs needed +ENV STEAM_PATH="/home/steam" \ + SERVER_PATH="/home/steam/serverfiles" \ + STEAM_CMD="/home/steam/steamcmd" \ + GROUP_ID=10000 \ + USER_ID=10000 \ + DOCKER_USER=steam \ + SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.1.9/supercronic-linux-amd64 \ + SUPERCRONIC=supercronic-linux-amd64 \ + SUPERCRONIC_SHA1SUM=5ddf8ea26b56d4a7ff6faecdd8966610d5cb9d85 \ + \ + \ + DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + TERM=xterm \ + \ + \ + DEBUGGING=false \ + CRON_MONITOR="*/5 * * * *" \ + CRON_UPDATE="*/30 * * * *" \ + CRON_FORCE_UPDATE="0 10 * * 0" \ + CRON_LOG_ROTATE="0 0 * * 0" \ + \ + \ + SERVER_EXECUTABLE="" \ + SERVER_GAME="" \ + TZ="Europe/Berlin" +#https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + +ENTRYPOINT ["./home/entrypoint.sh"] + +#WORKDIR "$STEAM_PATH" + +COPY ["entrypoint.sh", "initCron.sh", "/home/"] + +# procps needed for ps command +# iproute2 needed because of "-slim" +RUN dpkg --add-architecture i386 && \ + apt-get update -y && \ + apt-get install -y mailutils postfix curl wget file bzip2 gzip unzip bsdmainutils python util-linux ca-certificates \ + binutils bc jq tmux lib32gcc1 libstdc++6 libstdc++6:i386 lib32tinfo5 \ + procps iproute2 && \ + \ + groupadd -g $GROUP_ID $DOCKER_USER && \ + useradd -d "$STEAM_PATH" -g $GROUP_ID -u $USER_ID -m $DOCKER_USER && \ + chown "$DOCKER_USER:$DOCKER_USER" /home/entrypoint.sh && \ + chown "$DOCKER_USER:$DOCKER_USER" /home/initCron.sh && \ + mkdir -p "$SERVER_PATH" && \ + chown -R "$DOCKER_USER:$DOCKER_USER" "$STEAM_PATH" && \ + chmod a=rx /home/entrypoint.sh && \ + chmod a=rx /home/initCron.sh && \ + \ + ulimit -n 2048 && \ + \ + wget -O "$STEAM_PATH/linuxgsm.sh" "https://linuxgsm.sh" && \ + chown "$DOCKER_USER:$DOCKER_USER" "$STEAM_PATH/linuxgsm.sh" && \ + chmod +x "$STEAM_PATH/linuxgsm.sh" && \ + \ + \ + wget -O "${SUPERCRONIC}" "$SUPERCRONIC_URL" && \ + echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - && \ + chmod +x "$SUPERCRONIC" && \ + mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" && \ + ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic + diff --git a/lgsm/entrypoint.sh b/lgsm/entrypoint.sh new file mode 100644 index 0000000..4888c51 --- /dev/null +++ b/lgsm/entrypoint.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o nounset +set -o pipefail + +echo "starting entrypoint.sh" +set -e + + + + + + +# --- Install / Update --- +cd "$STEAM_PATH" +if [ -n "$SERVER_EXECUTABLE" ] && [ -e "${STEAM_PATH}/$SERVER_EXECUTABLE" ]; then + ./"$SERVER_EXECUTABLE" update-lgsm + ./"$SERVER_EXECUTABLE" update +else + bash linuxgsm.sh "$SERVER_GAME" + ./"$SERVER_EXECUTABLE" auto-install +fi + +if [ -e "/home/prepareServer.sh" ]; then + cd /home + ./prepareServer.sh "$@" + cd "$STEAM_PATH" +fi + + + + + + +# --- Start Server --- +#start server +IS_RUNNING="true" +function stopServer() { + echo "stopping server..." + cd "${STEAM_PATH}" + pid=$(pidof "$SERVER_EXECUTABLE") + kill -2 "$pid" || true + echo "server stopped!" + echo "stopping entrypoint..." + IS_RUNNING="false" + echo "done!" +} +./"$SERVER_EXECUTABLE" start & +trap stopServer SIGTERM + +#start cron +bash "/home/initCron.sh" + + + + + +# --- Wait for Shutdown --- +echo "Server is running, waiting for SIGTERM" +while [ "$IS_RUNNING" = "true" ] +do + sleep 1s +done +echo "entrypoint stopped" +exit 0 \ No newline at end of file diff --git a/lgsm/initCron.sh b/lgsm/initCron.sh new file mode 100644 index 0000000..218cd10 --- /dev/null +++ b/lgsm/initCron.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o pipefail +set -o nounset + +LOG_PATH="$STEAM_PATH/logs" +CRON="$LOG_PATH/lgsm.cron" +CRON_LOG="$LOG_PATH/cron.log" + +#set up cronjob +mkdir "$LOG_PATH" || true +rm -f "$CRON" || true +touch "$CRON" +# false positive +# shellcheck disable=SC2129 +echo "$CRON_MONITOR $STEAM_PATH/gmodserver monitor > '$LOG_PATH/monitor.log' 2>&1" >> "$CRON" +# shellcheck disable=SC2129 +echo "$CRON_UPDATE $STEAM_PATH/gmodserver update > '$LOG_PATH/update.log' 2>&1" >> "$CRON" +# shellcheck disable=SC2129 +echo "$CRON_FORCE_UPDATE $STEAM_PATH/gmodserver force-update >'$LOG_PATH/force-update.log' 2>&1" >> "$CRON" +# shellcheck disable=SC2129 +echo "$CRON_LOG_ROTATE mv -f '$CRON_LOG' '${CRON_LOG}.old'" >> "$CRON" +echo "" >> "$CRON" + +if [ -e "$CRON_LOG" ]; then + mv -f "$CRON_LOG" "${CRON_LOG}.old" +fi + +supercronic "$CRON" 2> "$LOG_PATH/cron.log" & \ No newline at end of file diff --git a/test/testCaseQuick.sh b/test/testCaseQuick.sh new file mode 100644 index 0000000..85876d5 --- /dev/null +++ b/test/testCaseQuick.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +else + DEBUGGING="false" +fi + +set -o errexit +set -o nounset +set -o pipefail + +#bash test/testStyle.sh + +echo "[testBuild][INFO]build" + +docker rmi "jusito/docker-ttt:lgsm_debian" || true +docker build -t "jusito/docker-ttt:lgsm_debian" "./lgsm/" + +docker rmi "jusito/docker-ttt:gmod_debian" || true +docker build -t "jusito/docker-ttt:gmod_debian" "./gmod/" + +docker rmi "jusito/docker-ttt:gmod_ttt_debian" || true +docker build -t "jusito/docker-ttt:gmod_ttt_debian" "./TTT/" + + +echo "[testRun][INFO]running" +if ! docker run -ti --name "JusitoTesting" --rm -e TEST_MODE=true -e DEBUGGING="$DEBUGGING" -e SERVER_PASSWORD="testpw" -e SERVER_MAX_PLAYERS="10" "jusito/docker-ttt:gmod_ttt_debian"; then + echo "[testRun][ERROR]run test failed for docker-ttt:ubuntu" + exit 1 +fi +docker stop "JusitoTesting" || true +docker rm "JusitoTesting" || true + +#docker run -ti --name "JusitoTesting" -p 27015:27015/udp -p 27015:27015/tcp --rm -e TEST_MODE=true -e DEBUGGING="$DEBUGGING" -e SERVER_PASSWORD="testpw" -e SERVER_MAX_PLAYERS="10" "jusito/docker-ttt:gmod_ttt_debian" \ No newline at end of file diff --git a/test/testHealth.sh b/test/testHealth.sh new file mode 100644 index 0000000..a09c63f --- /dev/null +++ b/test/testHealth.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ "${DEBUGGING:?}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o nounset +set -o pipefail + +docker exec -it CONTAINER ./home/steam/gmodserver details + +Status: OFFLINE -> fail Health \ No newline at end of file diff --git a/test/testStyle.sh b/test/testStyle.sh new file mode 100644 index 0000000..8d26c9f --- /dev/null +++ b/test/testStyle.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +if [ "${DEBUGGING}" = "true" ]; then + set -o xtrace +fi + +set -o errexit +set -o nounset +set -o pipefail + +# test sha3sums + +#if ! printf '%s %s' "$(grep -Eo "grep -Eq '\^[^\\]+" Dockerfile | sed 's/...........//')" "checkHealth.sh" | sha3sum -c ; then +# echo "[testStyle][ERROR]Sha3sum of checkHealth.sh in Dockerfile invalid" +# exit 2 +#fi + +directory="$PWD" +echo "[testStyle][INFO]workdir $directory" + +check() { + file="$1" + exclude="" + if [ -n "$2" ]; then + exclude="--exclude=$2" + fi + + echo "[testStyle][INFO]processing $file with extra arg: $exclude" + # shellcheck disable=SC2086 + if shellcheck $exclude "$file"; then + return 0 + else + echo "[testStyle][ERROR]style is bad" + return 1 + fi +} + + +find "${directory}" -maxdepth 1 -type f -iname '*.sh' | +while read -r filename +do + if ! check "$filename" ''; then + exit 1 + fi +done + +# shellcheck disable=SC2181 +if [ "$?" = "0" ]; then + echo "[testStyle][INFO]all elements passed style check" + exit 0 +else + echo "[testStyle][ERROR]style in at least one element looks bad" + exit 1 +fi \ No newline at end of file