├── custom-maps └── .gitignore ├── custom-mods └── .gitignore ├── harmony-config └── .gitignore ├── harmony-mods └── .gitignore ├── mod-configs └── .gitignore ├── .gitignore ├── admin ├── logs │ ├── list-backups.sh │ ├── create-backup.sh │ └── clean.sh ├── shell.sh ├── get-or-update-oxide-plugins.sh ├── bugfix-oxide-plugins.sh ├── get-rcon-pass.sh ├── backup │ ├── create-backup.sh │ ├── list-backups.sh │ └── restore-backup.sh └── regenerate-map.sh ├── LICENSE ├── utils ├── monitor-rust-server.sh ├── custom-rust-server.sh ├── get-or-update-plugins.sh └── apply-settings.sh ├── rust-environment.sh ├── docker-compose.yml └── README.md /custom-maps/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /custom-mods/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /harmony-config/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /harmony-mods/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /mod-configs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.tgz 2 | /backups 3 | /plugins.txt 4 | .venv 5 | -------------------------------------------------------------------------------- /admin/logs/list-backups.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./admin/backup/list-backups.sh lgsm-logs-backup.tgz "$@" 4 | -------------------------------------------------------------------------------- /admin/shell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker exec -itu "${1:-linuxgsm}" $(docker compose ps -q lgsm) /bin/bash 4 | -------------------------------------------------------------------------------- /admin/get-or-update-oxide-plugins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker compose exec -Tu linuxgsm lgsm /utils/get-or-update-plugins.sh 4 | -------------------------------------------------------------------------------- /admin/bugfix-oxide-plugins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker compose exec -T lgsm /bin/bash -ec 'unix2dos serverfiles/oxide/plugins/*.cs' 4 | -------------------------------------------------------------------------------- /admin/get-rcon-pass.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -n 'RCON password: ' 4 | docker compose exec -T lgsm cat rcon_pass 2> /dev/null || ( 5 | # Could not find rcon random password file so falling back to auto detection. 6 | docker compose exec -T lgsm pgrep RustDedicated | \ 7 | xargs -n1 -I'{}' -- docker compose exec -T lgsm cat '/proc/{}/cmdline' | \ 8 | tr '\0' '\n' | \ 9 | awk '$1 == "+rcon.password" { x="1"; next}; x == "1" {print $0; exit}' 10 | ) 11 | 12 | echo ' 13 | Visit one of the following web RCON clients: 14 | 15 | - http://facepunch.github.io/webrcon address 127.0.0.1:28016 16 | - http://rcon.io/login address 127.0.0.1 port 28016 17 | ' 18 | -------------------------------------------------------------------------------- /admin/logs/create-backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function cleanup_on() { 4 | if [ "$1" -ne 0 ]; then 5 | # backup failed so remove the bad backup file 6 | [ ! -f "backups/${BACKUP_FILE:-dummy.txt}" ] || 7 | rm -f "backups/${BACKUP_FILE:-dummy.txt}" 8 | fi 9 | } 10 | trap 'cleanup_on $?' EXIT 11 | 12 | set -e 13 | 14 | # set the working directory to repository root 15 | # only when this script is called by full path; e.g. from cron job 16 | if grep '^/' <<< "$0" > /dev/null; then 17 | echo "Changing working directory to: ${0%admin/*}" 18 | cd "${0%admin/*}" 19 | fi 20 | 21 | BACKUP_FILE="$(date +%Y-%d-%m-%s)"_lgsm-logs-backup.tgz 22 | export BACKUP_FILE 23 | 24 | [ -d backups ] || mkdir backups 25 | docker compose exec -Tu linuxgsm lgsm tar -czv log > backups/"$BACKUP_FILE" 26 | ( 27 | echo 28 | echo -n 'Created backup file: ' 29 | ls ./backups/"$BACKUP_FILE" 30 | echo 31 | ) 32 | -------------------------------------------------------------------------------- /admin/backup/create-backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function cleanup_on() { 4 | if [ "$1" -ne 0 ]; then 5 | # backup failed so remove the bad backup file 6 | [ ! -f "backups/${BACKUP_FILE:-dummy.txt}" ] || 7 | rm -f "backups/${BACKUP_FILE:-dummy.txt}" 8 | fi 9 | } 10 | trap 'cleanup_on $?' EXIT 11 | 12 | set -e 13 | 14 | # set the working directory to repository root 15 | # only when this script is called by full path; e.g. from cron job 16 | if grep '^/' <<< "$0" > /dev/null; then 17 | echo "Changing working directory to: ${0%admin/*}" 18 | cd "${0%admin/*}" 19 | fi 20 | 21 | BACKUP_FILE="$(date +%Y-%d-%m-%s)"_lgsm-rustserver-backup.tgz 22 | export BACKUP_FILE 23 | 24 | [ -d backups ] || mkdir backups 25 | 26 | docker compose exec -Tu linuxgsm lgsm /bin/bash -ex > backups/"$BACKUP_FILE" <<'EOF' 27 | BACKUP_DIRS=( 28 | lgsm 29 | serverfiles/server 30 | ) 31 | if [ -d serverfiles/oxide ]; then 32 | BACKUP_DIRS+=( serverfiles/oxide ) 33 | fi 34 | tar -czv "${BACKUP_DIRS[@]}" 35 | EOF 36 | 37 | ( 38 | echo 39 | echo -n 'Created backup file: ' 40 | ls ./backups/"$BACKUP_FILE" 41 | echo 42 | ) 43 | -------------------------------------------------------------------------------- /admin/regenerate-map.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set the working directory to repository root 4 | # only when this script is called by full path; e.g. from cron job 5 | if grep '^/' <<< "$0" > /dev/null; then 6 | echo "Changing working directory to: ${0%admin/*}" 7 | cd "${0%admin/*}" 8 | fi 9 | 10 | if [ -n "${1:-}" ]; then 11 | echo 'Skipping user prompt.' 12 | else 13 | cat <<'EOF' 14 | WARNING: This is a permanent action. 15 | All maps will be destroyed including backups and a new map will be generated. 16 | EOF 17 | read -erp 'Do you wish to delete the current map? (y/N) ' response 18 | 19 | if [ ! "$response" = y -a ! "$response" = Y ]; then 20 | echo 'Operation aborted. Respond "y" next time if you want to proceed.' 21 | exit 22 | fi 23 | fi 24 | 25 | docker compose exec -T lgsm \ 26 | find serverfiles/server/rustserver/ \ 27 | -maxdepth 1 \ 28 | -type f \( -name '*.map' -o -name '*.sav*' \) \ 29 | -exec rm -f {} + 30 | docker compose exec -T lgsm sed -i '/^ *seed=/d' lgsm/config-lgsm/rustserver/rustserver.cfg 31 | docker compose down 32 | docker compose up -d 33 | echo 'The server has been rebooted with docker compose up -d.' 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Sam Gleske 2 | https://github.com/samrocketman/docker-compose-lgsm-rust-dedicated-server 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /utils/monitor-rust-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LOGFILE=/home/linuxgsm/log/autoheal.log 4 | [ ! -r /rust-environment.sh ] || source /rust-environment.sh 5 | 6 | function wait-for-rust() { 7 | until pgrep "$@"; do 8 | sleep 1 9 | done 10 | } 11 | function graceful-kill() { 12 | echo "$(date) - auto-heal graceful kill" >> "$LOGFILE" 13 | pgrep tail | xargs -- kill 14 | } 15 | 16 | function hard-kill() { 17 | echo "$(date) - auto-heal HARD kill" >> "$LOGFILE" 18 | pgrep tail | xargs -- kill -9 19 | } 20 | 21 | function kill-container-on-absence-of() { 22 | local retry=0 23 | while true; do 24 | sleep 10 25 | if pgrep "$@"; then 26 | retry=0 27 | else 28 | (( retry=retry+1 )) 29 | fi 30 | if [ "$retry" -ge 6 ]; then 31 | hard-kill 32 | elif [ "$retry" -ge 3 ]; then 33 | graceful-kill 34 | fi 35 | done 36 | } 37 | 38 | if [ ! "${uptime_monitoring:-}" = true ]; then 39 | echo 'RustDedicated will not be monitored.' 40 | echo 'Set uptime_monitoring=true in rust-environment.sh to enable autoheal.' 41 | exit 42 | else 43 | echo 'Monitoring RustDedicated for automatic restart.' 44 | fi 45 | # discard further output 46 | exec &> /dev/null 47 | wait-for RustDedicated 48 | kill-container-on-absence-of RustDedicated 49 | -------------------------------------------------------------------------------- /admin/logs/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -d .git -a ! -d admin ]; then 4 | echo 'ERROR: must run this command from the root of the git repository.' >&2 5 | exit 1 6 | fi 7 | 8 | echo 'List of log files:' 9 | docker compose exec -T lgsm find log -type f -name '*[0-9]*' -exec du -ch {} + | 10 | awk ' 11 | $2 == "total" { 12 | total=$1; 13 | next 14 | }; 15 | { 16 | print " "$2 17 | }; 18 | END { 19 | print "Log size to be removed: "total 20 | }' 21 | 22 | cat < /dev/null; then 21 | ./rustserver mods-install <<< $'rustoxide\nyes\n' 22 | fi 23 | ./rustserver mods-update 24 | if [ ! -f 'serverfiles/RustDedicated_Data/Managed/Oxide.Ext.RustEdit.dll' ]; then 25 | curl -fLo serverfiles/RustDedicated_Data/Managed/Oxide.Ext.RustEdit.dll \ 26 | https://github.com/k1lly0u/Oxide.Ext.RustEdit/raw/master/Oxide.Ext.RustEdit.dll 27 | fi 28 | 29 | # remove passwordless sudo access since setup is complete 30 | sudo rm -f /etc/sudoers.d/lgsm 31 | 32 | lgsm_cfg=lgsm/config-lgsm/rustserver/rustserver.cfg 33 | grep -F -- /utils/apply-settings.sh "$lgsm_cfg" || 34 | echo 'if [ ! "$1" = docker ]; then /utils/apply-settings.sh; source lgsm/config-lgsm/rustserver/rustserver.cfg docker; fi' >> "$lgsm_cfg" 35 | /utils/get-or-update-plugins.sh 36 | /utils/monitor-rust-server.sh & 37 | 38 | # start rust server 39 | ./rustserver start 40 | echo Sleeping for 30 seconds... 41 | sleep 30 42 | tail -f log/console/rustserver-console.log \ 43 | log/script/rustserver-steamcmd.log \ 44 | log/script/rustserver-script.log 45 | -------------------------------------------------------------------------------- /admin/backup/list-backups.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | backup_name="lgsm-rustserver-backup.tgz" 4 | list_all=false 5 | 6 | function python() { 7 | if type -P python3 > /dev/null; then 8 | command python3 "$@" 9 | elif type -P python > /dev/null; then 10 | command python "$@" 11 | else 12 | echo 'Python 2 or 3 could not be found. You may make it available via virtualenv.' >&2 13 | echo ' python3 -m venv .venv' >&2 14 | echo ' source .venv/bin/activate' >&2 15 | echo 'Note: you can run "deactivate" to unload venv.' >&2 16 | exit 1 17 | fi 18 | } 19 | 20 | while [ $# -gt 0 ]; do 21 | if [ "$1" = "--all" ]; then 22 | list_all=true 23 | fi 24 | if grep -- 'tgz$' >& /dev/null <<< "$1"; then 25 | backup_name="$1" 26 | fi 27 | shift 28 | done 29 | 30 | if ! ls backups/*"$backup_name" &> /dev/null; then 31 | echo 'No backups found.' 32 | exit 33 | fi 34 | 35 | function print_file() { 36 | local date="$(grep -o '[0-9]\+_' <<< "$1" | sed 's/_$//')" 37 | date="$(date -d @"$date")" 38 | echo "| $date | $1 |" 39 | } 40 | 41 | function limit_output() { 42 | if [ "$1" = 'true' ]; then 43 | cat 44 | else 45 | cat | tail -n5 46 | fi 47 | } 48 | 49 | if [ "$list_all" = 'true' ]; then 50 | echo 'ALL BACKUP FILES' 51 | else 52 | echo 'LAST 5 BACKUP FILES (use --all option to show all)' 53 | fi 54 | 55 | python -c 'print("="*95)' 56 | 57 | for x in backups/*"$backup_name"; do 58 | print_file "$x" 59 | done | limit_output "$list_all" 60 | python -c 'print("="*95)' 61 | 62 | if [ "$backup_name" = 'lgsm-rustserver-backup.tgz' ]; then 63 | echo 64 | echo 'Restore your backup with the following command.' 65 | echo ' ./admin/backup/restore-backup.sh backups/file.tgz' 66 | echo 67 | fi 68 | -------------------------------------------------------------------------------- /rust-environment.sh: -------------------------------------------------------------------------------- 1 | ###################### 2 | # SERVER BOOT SETTINGS 3 | ###################### 4 | 5 | # settings take effect every time the server boots 6 | 7 | #maxplayers=50 8 | #servername="Rust" 9 | 10 | # uncomment this to enable EAC, for Linux clients this must be commented out 11 | #ENABLE_RUST_EAC=1 12 | 13 | ####################### 14 | # GENERATED MAP SUPPORT 15 | ####################### 16 | 17 | # range: 1-2147483647, used to reproduce a procedural map. 18 | # default: random seed 19 | # If you change this value, then a new map will be generated on next boot. 20 | # The old map will still persist unless `./admin-actions/regenerate-map.sh` 21 | # is called which deletes all maps 22 | #seed=1 23 | 24 | # range: unknown, used to recover a known setting from an existing map. 25 | #salt= 26 | 27 | # default: 3000, range: 1000-6000, map size in meters. 28 | #worldsize=3000 29 | 30 | #################### 31 | # CUSTOM MAP SUPPORT 32 | #################### 33 | # If using a custom map, then generated map settings are ignored. 34 | 35 | # When self-hosting a map for multiplayer, MAP_BASE_URL is for providing public 36 | # IP address in the URL where clients will connect to download your map. 37 | #MAP_BASE_URL=http://localhost:8000/ 38 | 39 | # CUSTOM_MAP_URL is for posting a link to a publicly available custom map such 40 | # as a Dropbox download link. 41 | # Overrides MAP_BASE_URL unless SELF_HOSTING_CUSTOM_MAP=true 42 | #CUSTOM_MAP_URL=https://example.com/some-map.map 43 | 44 | # Download the CUSTOM_MAP_URL for self hosting. Set to true to force 45 | # self-hosting. The map will be downloaded to the custom-maps/ directory. 46 | # Overrides CUSTOM_MAP_URL 47 | #SELF_HOST_CUSTOM_MAP=false 48 | 49 | ################### 50 | # ADVANCED SETTINGS 51 | ################### 52 | 53 | # Rust server will be monitored. If the server is down, then the container 54 | # will be killed and automatically restarted by docker-compose. If you do not 55 | # want the container to die when rust shuts down or crashes, then disable this 56 | # monitoring. 57 | uptime_monitoring=true 58 | -------------------------------------------------------------------------------- /admin/backup/restore-backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ ! -d .git -a ! -d admin ]; then 6 | echo 'ERROR: must run this command from the root of the git repository.' >&2 7 | exit 1 8 | fi 9 | 10 | if [ "$#" -ne 1 ]; then 11 | echo 'ERROR: No backup file argument given. For example,' >&2 12 | echo ' ./admin/backup/restore-backup.sh backups/file.tgz' >&2 13 | exit 1 14 | fi 15 | 16 | ls "$1" > /dev/null 17 | 18 | if [ ! "$(tar -tzf "$1" | head -n1)" = 'lgsm/' ]; then 19 | echo "File: $1" 20 | echo 'ERROR: File exists but not a valid backup.' >&2 21 | exit 1 22 | fi 23 | 24 | cat < /dev/null; then 58 | echo 'Stopping uptime monitor.' 59 | kill "\$(pgrep -f monitor-rust-server.sh)" 60 | fi 61 | ./rustserver stop || true 62 | 63 | REMOVE_DIRS=( 64 | lgsm 65 | serverfiles/server 66 | ) 67 | if [ -d serverfiles/oxide ]; then 68 | REMOVE_DIRS+=( serverfiles/oxide ) 69 | fi 70 | 71 | find "\${REMOVE_DIRS[@]}" \\( ! -type d \\) -exec rm -f {} + 72 | tar -xzvf '${backup_file}' 73 | 74 | rm -f '${backup_file}' 75 | 76 | cat <<'EOT' 77 | Rebooting the server in 3 seconds... 78 | Watch restart logs with the following command. 79 | 80 | docker compose logs -f 81 | 82 | EOT 83 | echo '' 84 | sleep 3 85 | pgrep tail | xargs kill 86 | EOF 87 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | volumes: 2 | lgsm: 3 | services: 4 | lgsm: 5 | # Limiting server resources 6 | cpu_count: 2 7 | mem_limit: 8gb 8 | # other stuf 9 | init: true 10 | user: root 11 | image: gameservermanagers/linuxgsm-docker 12 | # Do not change this from restart: always. Maintenance scripts assume 13 | # docker-compose auto restarts the rust server. 14 | restart: always 15 | command: 16 | - /bin/bash 17 | - -exc 18 | - | 19 | # MAIN 20 | apt-get update 21 | apt-get install -y dos2unix rsync sudo vim nano libgdiplus python3.8-venv lib32z1 22 | #grant access to video card for direct rendering (not used by rust ds) 23 | #function get_video_gid() { 24 | # find /dev/dri -maxdepth 1 -type c | head -n1 | xargs stat -c %g 25 | #} 26 | #function get_video_group() { 27 | # local gid="$$(get_video_gid)" 28 | # awk -v gid="$$gid" -F: '$$3 == gid { print $$1 }' /etc/group 29 | #} 30 | #vid="$$(get_video_group)" 31 | #if [ -z "$$vid" ]; then 32 | # groupadd -g "$$(get_video_gid)" videocard 33 | # vid=videocard 34 | #fi 35 | #usermod -a -G "$$vid" linuxgsm 36 | 37 | # grant temporary sudo access for initial setup 38 | echo 'linuxgsm ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/lgsm 39 | lgsm_uid="$$(id -u linuxgsm)" 40 | lgsm_gid="$$(id -g linuxgsm)" 41 | if [ ! "$$lgsm_uid" = 1000 ]; then 42 | sed -i "s/:$$lgsm_uid:$$lgsm_gid:/:1000:1000:/" /etc/passwd 43 | sed -i "s/:$$lgsm_gid:/:1000:/" /etc/group 44 | fi 45 | if [ ! "$$(stat -c '%U' /home/linuxgsm)" = linuxgsm ]; then 46 | chown -R linuxgsm: /home/linuxgsm 47 | fi 48 | if [ ! "$$(stat -c '%U' /custom-maps)" = linuxgsm ]; then 49 | chown -R linuxgsm: /custom-maps 50 | fi 51 | python3 -m venv /home/linuxgsm/.venv 52 | grep -F .venv ~linuxgsm/.bash_profile || echo 'source /home/linuxgsm/.venv/bin/activate' > ~linuxgsm/.bash_profile 53 | grep -F .venv ~linuxgsm/.bashrc || echo 'source /home/linuxgsm/.venv/bin/activate' > ~linuxgsm/.bashrc 54 | if [ ! "$$(stat -c '%U' /home/linuxgsm/.venv)" = linuxgsm ]; then 55 | chown -R linuxgsm: /home/linuxgsm/.venv 56 | fi 57 | chown linuxgsm: /home/linuxgsm /home/linuxgsm/serverfiles /home/linuxgsm/serverfiles/oxide 58 | chown -R linuxgsm: /home/linuxgsm/serverfiles/oxide/config 59 | rm -f ~linuxgsm/linuxgsm.sh 60 | su - linuxgsm -c "LINUX_GSM_VERSION=\"${LINUX_GSM_VERSION:-v20.4.1}\" /utils/custom-rust-server.sh" 61 | volumes: 62 | - lgsm:/home/linuxgsm 63 | - ./mod-configs/:/home/linuxgsm/serverfiles/oxide/config/:rw 64 | - ./custom-mods/:/custom-plugins/:ro 65 | - ./custom-maps/:/custom-maps/:rw 66 | - ./harmony-mods:/home/linuxgsm/serverfiles/HarmonyMods 67 | - ./harmony-config:/home/linuxgsm/serverfiles/HarmonyConfig 68 | - ./utils/:/utils/:ro 69 | - ./rust-environment.sh:/rust-environment.sh:ro 70 | ports: 71 | - ${RUST_RCON_INTERFACE:-127.0.0.1}:28016:28016 72 | - 0.0.0.0:28015:28015/udp 73 | - 0.0.0.0:8000:8000/tcp 74 | healthcheck: 75 | test: ["CMD", "pgrep", "RustDedicated"] 76 | interval: 10s 77 | retries: 3 78 | start_period: 15m 79 | # devices: 80 | # - /dev/dri:/dev/dri 81 | -------------------------------------------------------------------------------- /utils/get-or-update-plugins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # DESCRIPTION: 3 | # Install, remove, and upgrade Oxide plugins from the Rust dedicated server. 4 | # Supports logging into the server. 5 | 6 | set -e 7 | plugin_dir=/home/linuxgsm/serverfiles/oxide/plugins 8 | plugin_txt=/home/linuxgsm/serverfiles/oxide/config/plugins.txt 9 | export TMP_DIR="$(mktemp -d)" 10 | trap '[ ! -d "$TMP_DIR" ] || rm -rf "$TMP_DIR"' EXIT 11 | if [ ! -f "$plugin_txt" ]; then 12 | plugin_txt=/dev/null 13 | fi 14 | if [ ! -d "$plugin_dir" ]; then 15 | mkdir -p "$plugin_dir" 16 | fi 17 | 18 | # state variables 19 | custom_plugins=() 20 | upgraded_plugins=() 21 | added_plugins=() 22 | removed_plugins=() 23 | 24 | function hash_stdin() { 25 | sha256sum 26 | } 27 | 28 | # 29 | # Check for plugin updates 30 | # 31 | function get_plugin_name_by_class() { 32 | awk ' 33 | $0 ~ /namespace/ {nextclass=1}; 34 | nextclass == 1 && $0 ~ /class [a-zA-Z]+/ { 35 | for(i=1; i "$plugin"; then 57 | plugin_name="${plugin##*/}" 58 | plugin_name="${plugin_name%.cs}" 59 | upgraded_plugins+=( "$plugin_name" ) 60 | fi 61 | fi 62 | done <<< "$(find "$plugin_dir" -type f -name '*.cs')" 63 | } 64 | 65 | # 66 | # Add new plugins 67 | function add_new_plugins() { 68 | retry_limit=5 69 | retry_delay=5 70 | while IFS= read -r plugin || [[ -n "$plugin" ]]; do 71 | # Trim leading/trailing whitespace and remove any special characters that might break the URL 72 | plugin=$(echo "$plugin" | xargs | sed 's/[^a-zA-Z0-9_-]//g') 73 | found_plugin="$(find "$plugin_dir" -type f -iname "${plugin}.cs")" 74 | if [ -z "$plugin" ] || [ -n "${found_plugin}" ]; then 75 | continue 76 | fi 77 | 78 | echo "Checking plugin: $plugin" 79 | function download_plugin() { 80 | local retries=0 81 | local success=false 82 | while [ $retries -lt $retry_limit ]; do 83 | http_status=$(curl -sI -w "%{http_code}" -o /dev/null "https://umod.org/plugins/${plugin}.cs") 84 | if [ "$http_status" -eq 200 ]; then 85 | if curl -sSfLo "${TMP_DIR}/${plugin}.cs" "https://umod.org/plugins/${plugin}.cs" && \ 86 | plugin_name="$(get_plugin_name_by_class < "${TMP_DIR}/${plugin}.cs")" && \ 87 | mv "${TMP_DIR}/${plugin}.cs" "${plugin_dir}/${plugin_name}.cs"; then 88 | added_plugins+=( "$plugin_name" ) 89 | echo "Successfully added plugin: $plugin_name" 90 | success=true 91 | break 92 | else 93 | echo "Error: Failed to download plugin: $plugin" >&2 94 | break 95 | fi 96 | elif [ "$http_status" -eq 429 ]; then 97 | # Rate limit encountered 98 | echo "Rate limit reached. Waiting for $retry_delay seconds before retrying..." >&2 99 | sleep $retry_delay 100 | retries=$((retries + 1)) 101 | retry_delay=$((retry_delay * 2)) # Exponential backoff 102 | else 103 | echo "Error: Plugin $plugin not found on the server or could not connect (HTTP $http_status)." >&2 104 | break 105 | fi 106 | done 107 | 108 | if [ "$success" = false ]; then 109 | echo "Failed to add plugin: $plugin after $retries retries." >&2 110 | fi 111 | } 112 | 113 | download_plugin 114 | 115 | done <<< "$(grep -v '^ *$\|^ *#' "$plugin_txt")" 116 | if [ "${#added_plugins[@]}" -gt 0 ]; then 117 | echo "The following plugins were added successfully: ${added_plugins[*]}" 118 | else 119 | echo "No new plugins were added." 120 | fi 121 | } 122 | 123 | 124 | # 125 | # Remove old plugins 126 | # 127 | function remove_plugins() { 128 | while read plugin; do 129 | if [ -z "$plugin" ]; then 130 | continue 131 | fi 132 | plugin_name="${plugin##*/}" 133 | plugin_name="${plugin_name%.cs}" 134 | if ! grep -v '^ *$\|^ *#' "$plugin_txt" | grep -i "${plugin_name}" > /dev/null && 135 | [ ! -f "/custom-plugins/${plugin_name}.cs" ]; then 136 | rm -f "${plugin}" 137 | removed_plugins+=( "$plugin_name" ) 138 | fi 139 | done <<< "$(find "$plugin_dir" -type f -name '*.cs')" 140 | } 141 | 142 | function copy_custom_plugins() { 143 | if ls /custom-plugins/*.cs &> /dev/null; then 144 | rsync -a /custom-plugins/*.cs "${plugin_dir}/" 145 | fi 146 | } 147 | 148 | copy_custom_plugins 149 | upgrade_plugins 150 | add_new_plugins 151 | remove_plugins 152 | 153 | if [ "${#upgraded_plugins[@]}" -gt 0 ]; then 154 | echo 'Upgraded plugins:' 155 | for x in "${upgraded_plugins[@]}"; do 156 | echo " $x" 157 | done 158 | else 159 | echo 'No plugins upgraded.' 160 | fi 161 | if [ "${#added_plugins[@]}" -gt 0 ]; then 162 | echo 'New plugins installed:' 163 | for x in "${added_plugins[@]}"; do 164 | echo " $x" 165 | done 166 | else 167 | echo 'No new plugins.' 168 | fi 169 | if [ "${#removed_plugins[@]}" -gt 0 ]; then 170 | echo 'Plugins deleted:' 171 | for x in "${removed_plugins[@]}"; do 172 | echo " $x" 173 | done 174 | else 175 | echo 'No plugins deleted.' 176 | fi 177 | 178 | if ls /custom-plugins/*.cs &> /dev/null; then 179 | echo 'Custom plugins:' 180 | ( 181 | cd /custom-plugins/ 182 | ls -1 *.cs | sed 's/\(.*\)\.cs/ \1/' 183 | ) 184 | for x in "${custom_plugins[@]}"; do 185 | echo " $x" 186 | done 187 | else 188 | echo 'No custom plugins found.' 189 | fi 190 | -------------------------------------------------------------------------------- /utils/apply-settings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if ! type -p python && type -p python3; then 6 | python() { python3 "$@"; } 7 | fi 8 | 9 | [ ! -r /rust-environment.sh ] || source /rust-environment.sh 10 | export ENABLE_RUST_EAC CUSTOM_MAP_URL MAP_BASE_URL SELF_HOST_CUSTOM_MAP 11 | export seed salt worldsize maxplayers servername apply_settings_debug_mode 12 | if [ "${apply_settings_debug_mode:-false}" = true ]; then 13 | echo 'docker compose apply config debug enabled.' >&2 14 | set -x 15 | fi 16 | 17 | echo 'Applying server settings from rust-environment.sh:' 18 | 19 | server_cfg=serverfiles/server/rustserver/cfg/server.cfg 20 | lgsm_cfg=lgsm/config-lgsm/rustserver/rustserver.cfg 21 | 22 | # disable EAC allowing Linux clients 23 | sed -i '/^ *server\.secure/d' "$server_cfg" 24 | sed -i '/^ *server\.encryption/d' "$server_cfg" 25 | if [ -z "${ENABLE_RUST_EAC:-}" ]; then 26 | echo server.secure 0 >> "$server_cfg" 27 | echo server.encryption 0 >> "$server_cfg" 28 | echo ' EAC Disabled.' 29 | else 30 | echo ' EAC Enabled.' 31 | fi 32 | 33 | # Checks for minimum version of python. Will check the minor or higher. 34 | # * 2.7 will look for python 2.7 or higher but only for python 2. 35 | # * 3.2 will look for python 3.2 or higher but only for python 3. 36 | # Example: minimum python 3.8 37 | function minimum() ( 38 | exec &> /dev/null 39 | local major="${2%.*}" 40 | local minor="${2#*.}" 41 | if ! type -P "$1"; then 42 | return false 43 | fi 44 | python -c 'import platform,sys; check=lambda x,y,z: x.startswith(y) and int(x.split(".")[0:2][-1]) >= z; sys.exit(0) if check(platform.python_version(), sys.argv[1], int(sys.argv[2])) else sys.exit(1)' \ 45 | "${major}" "${minor}" 46 | ) 47 | 48 | function rand_password() { 49 | tr -dc -- '0-9a-zA-Z' < /dev/urandom | head -c12;echo 50 | } 51 | 52 | # Map generation settings 53 | function check-range() { 54 | # Usage: check-range NUMBER MIN MAX 55 | # exits nonzero if outside of range or not a number 56 | python -c " 57 | import sys; 58 | i=int(sys.stdin.read()); 59 | exit(0) if i >= $2 and i <= $3 else exit(1)" &> /dev/null <<< "$1" 60 | } 61 | function apply-setting() { 62 | echo " $3" 63 | sed -i "/^ *$2/d" $1 64 | echo "$3" >> "$1" 65 | } 66 | 67 | function apply-generated-map-settings() { 68 | if [ -z "$worldsize" ] || ! check-range "$worldsize" 1000 6000; then 69 | worldsize=3000 70 | fi 71 | # apply user-customized settings from rust-environment.sh 72 | apply-setting "$lgsm_cfg" worldsize "worldsize=$worldsize" 73 | if [ -n "$seed" ]; then 74 | apply-setting "$lgsm_cfg" seed "seed=$seed" 75 | fi 76 | if ( [ -z "$seed" ] || ! check-range "${seed:-invalid}" 1 2147483647 ) && 77 | ! grep -F -- 'seed=' "$lgsm_cfg" > /dev/null; then 78 | # random seed; if seed is unset or invalid 79 | seed="$(python -c 'from random import randrange;print(randrange(2147483647))')" 80 | apply-setting "$lgsm_cfg" seed "seed=$seed" 81 | else 82 | echo -n ' ' 83 | grep -F -- 'seed=' "$lgsm_cfg" 84 | fi 85 | if [ -n "$salt" ]; then 86 | apply-setting "$lgsm_cfg" salt "salt=$salt" 87 | else 88 | sed -i '/^ *salt/d' "$lgsm_cfg" 89 | fi 90 | } 91 | 92 | servername="${servername:-Rust}" 93 | apply-setting "$lgsm_cfg" servername "servername=\"$servername\"" 94 | if [ -z "$maxplayers" ] || ! check-range "$maxplayers" 1 1000000; then 95 | maxplayers=50 96 | fi 97 | apply-setting "$lgsm_cfg" maxplayers "maxplayers=$maxplayers" 98 | 99 | # Custom Map Support 100 | function start-custom-map-server() ( 101 | cd /custom-maps/ 102 | if ! pgrep -f SimpleHTTPServer > /dev/null; then 103 | if minimum python '3.2'; then 104 | python -m http.server & 105 | elif minimum python3 '3.2'; then 106 | python3 -m http.server & 107 | elif minimum python '2.7'; then 108 | python -m SimpleHTTPServer & 109 | else 110 | echo 'ERROR: could not find suitable python version.' >&2 111 | exit 1 112 | fi 113 | fi 114 | echo ' Custom map server started on port 8000.' >&2 115 | ) 116 | function get-custom-map-url() { 117 | MAP_BASE_URL="${MAP_BASE_URL:-http://localhost:8000/}" 118 | until curl -sIfLo /dev/null "http://localhost:8000/"; do sleep 1; done 119 | local map_url="$(curl -sfL "http://localhost:8000/" | grep -o 'href="[^"]\+.map"' | sed 's/.*"\([^"]\+\)"/\1/' | head -n1)" 120 | echo "${MAP_BASE_URL%/}/${map_url}" 121 | } 122 | function unquote-url() { 123 | python3 -c 'from urllib.parse import unquote;import sys;print(unquote(sys.stdin.read()).strip())' 124 | } 125 | function download-custom-map() { 126 | local custom_map="$(echo "${CUSTOM_MAP_URL}" | grep -o '[^/]\+\.map' | unquote-url)" 127 | if [ -z "${custom_map:-}" ]; then 128 | custom_map='custom-map.map' 129 | fi 130 | [ -f /custom-maps/"${custom_map}" ] || ( 131 | echo ' Downloading custom map: '"${CUSTOM_MAP_URL}" >&2 132 | curl --retry 3 --retry-delay 10 -sfLo /custom-maps/"${custom_map}" "${CUSTOM_MAP_URL}" 133 | ) 134 | } 135 | sed -i '/^fn_parms/d' "$lgsm_cfg" 136 | 137 | 138 | if [ -n "${CUSTOM_MAP_URL:-}" ] || ls -1 /custom-maps/*.map &> /dev/null; then 139 | if [ -n "${CUSTOM_MAP_URL:-}" -a "${SELF_HOST_CUSTOM_MAP:-}" = true ]; then 140 | download-custom-map 141 | unset CUSTOM_MAP_URL 142 | fi 143 | start-custom-map-server 144 | if [ -z "${CUSTOM_MAP_URL:-}" ]; then 145 | export CUSTOM_MAP_URL="$(get-custom-map-url)" 146 | fi 147 | # custom map found so disabling map settings. 148 | cat >> "$lgsm_cfg" < rcon_pass 158 | fi 159 | ( 160 | grep "$(> "$lgsm_cfg" 161 | ) &> /dev/null 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dockerized LGSM Rust Dedicated Server 2 | This project combines Docker, [Rust][rust] Dedicated Server, and [Linux 3 | GSM][lgsm] all in one! Self-hosted Rust dedicated server management made easy. 4 | 5 | - [Play on your server](#play-on-your-server) 6 | - [Playing multiplayer](#playing-multiplayer) 7 | - [Prerequisites](#prerequisites) 8 | - [Getting started](#getting-started) 9 | - [Server power management](#server-power-management) 10 | - [Starting the server](#starting-the-server) 11 | - [Graceful shutdown](#graceful-shutdown) 12 | - [Uninstallation](#uninstallation) 13 | - [Game Server Administration](#game-server-administration) 14 | - [Login shell](#login-shell) 15 | - [RCON: Remote Admin Console](#rcon-remote-admin-console) 16 | - [RCON remote access](#rcon-remote-access) 17 | - [Limiting server resources](#limiting-server-resources) 18 | - [Backup and restore from backup](#backup-and-restore-from-backup) 19 | - [Log management](#log-management) 20 | - [Easy Anti-Cheat](#easy-anti-cheat) 21 | - [Server Mods](#server-mods) 22 | - [Oxide mods](#oxide-mods) 23 | - [Custom mods](#custom-mods) 24 | - [Troubleshooting: Mods failing to load](#troubleshooting-mods-failing-to-load) 25 | - [Updating mod configuration](#updating-mod-configuration) 26 | - [Customize Map](#customize-map) 27 | - [Generated Maps](#generated-maps) 28 | - [Custom Maps](#custom-maps) 29 | - [Self-hosted custom maps](#self-hosted-custom-maps) 30 | - [Remotely hosted custom maps](#remotely-hosted-custom-maps) 31 | - [Observer Island](#observer-island) 32 | 33 | # Play on your server 34 | 35 | By default your server is forwarding port `28015/UDP` to all interfaces so that 36 | you can use router port forwarding to play multiplayer. 37 | 38 | If you're playing on the same machine from Proton on Linux, then press F1 to 39 | open console and connect with: 40 | 41 | client.connect 127.0.0.1:28015 42 | 43 | You may need to enter a domain name or alternate IP address if you're playing 44 | Rust from a different computer. 45 | 46 | # Playing multiplayer 47 | 48 | Enable port forwarding on your router for the following ports. 49 | 50 | - `28015/udp` (required for game clients) 51 | - `8000/tcp` (optional: only required if self-hosting a custom map) 52 | 53 | # Prerequisites 54 | 55 | - 4GB of RAM if the server is remotely hosted (total memory including OS). 56 | Alternately, 16GB of RAM if you're going to host a dedicated server and play 57 | on the same machine. These memory recommendations are just estimates and 58 | minimum requirements for memory could be much lower. 59 | - You have [Git installed][git] and cloned this repository to work with locally. 60 | 61 | ``` 62 | git clone https://github.com/samrocketman/docker-compose-lgsm-rust-dedicated-server 63 | ``` 64 | 65 | - Install [Docker on Linux][docker]. Docker on Windows or Mac would probably 66 | work but is entirely untested. Docker for Mac has known performance issues 67 | unrelated to Rust. 68 | - Install [docker compose][compose]. This typically comes separate from Docker. 69 | 70 | # Getting started 71 | 72 | If you don't want to customize anything, then start the server. It will 73 | generate a 3K map using a random seed which will persist when restarting the 74 | server. 75 | 76 | docker compose up -d 77 | 78 | It may take 15 minutes or longer for the server to start the first time 79 | depending on your internet connection. This is because it has to download Rust 80 | among other server setup tasks. You can monitor the server logs at any time (to 81 | see progress) with the following command. 82 | 83 | docker compose logs -f 84 | 85 | Press `CTRL+C` to exit logs. 86 | 87 | # Server power management 88 | 89 | ### Starting the server 90 | 91 | docker compose up -d 92 | 93 | It may take at 5 minutes or longer to start depending on your internet 94 | connection. 95 | 96 | See logs with the following command (`CTRL+C` to cancel). 97 | 98 | docker compose logs -f 99 | 100 | ### Graceful shutdown 101 | 102 | docker compose down 103 | 104 | ### Uninstallation 105 | 106 | To completely uninstall and delete all Rust data run the following command. 107 | 108 | docker compose down -v --rmi all 109 | 110 | Remove this Git repository for final cleanup. 111 | 112 | # Game Server Administration 113 | 114 | ### Login shell 115 | 116 | If you want a shell login to your server, then run the following command from 117 | the root of this repository. 118 | 119 | ```bash 120 | ./admin/shell.sh 121 | 122 | # alternately if you need root shell access 123 | ./admin/shell.sh root 124 | ``` 125 | 126 | ### RCON: Remote Admin Console 127 | 128 | You can access the Rust RCON interface using any RCON client. I recommend one 129 | of the following clients. 130 | 131 | - https://facepunch.github.io/webrcon Facepunch official client 132 | - http://rcon.io/login community RCON client 133 | 134 | The RCON interface is password protected. Reveal the password using the 135 | following command. 136 | 137 | ./admin/get-rcon-pass.sh 138 | 139 | The script will output your RCON password as well as additional instructions for 140 | your web browser to access the RCON console. 141 | 142 | ##### RCON remote access 143 | 144 | Rust RCON connection uses `ws://` websocket protocol. It does not support 145 | secure websockets (`wss://`). Because of this, it is not fit for secure 146 | internet communication. All of your passwords would be sent over plain text. 147 | 148 | The recommended way to access RCON remotely from Linux is to forward the RCON 149 | port to your machine over SSH and connecting to the remote host over localhost 150 | on your own machine. The following SSH command makes RCON available on 151 | localhost. 152 | 153 | ssh -vNL 127.0.0.1:28016:127.0.0.1:28016 user@example.com 154 | 155 | If you still want your RCON interface to be publicy available (I don't recommend 156 | this), then you can set the `RUST_RCON_INTERFACE` variable before starting the 157 | server. 158 | 159 | ```bash 160 | docker compose down 161 | export RUST_RCON_INTERFACE=0.0.0.0 162 | docker compose up -d 163 | ``` 164 | 165 | ### Limiting server resources 166 | 167 | In the [`docker-compose.yml`](docker-compose.yml) file, there's two settings you 168 | can adjust to limit how much CPU and memory the dedicated server is allowed. By 169 | default, it is set to dedicated server recommended values for extremly high 170 | populations: 171 | 172 | ```yaml 173 | cpu_count: 2 174 | mem_limit: 8gb 175 | ``` 176 | 177 | You can adust the resources to your liking. Generally, I recommend to not set 178 | the server below `2` CPUs and `2gb` of memory (RAM). These policies ensure the 179 | server can't use more than these limits. 180 | 181 | You can inspect server resource usage with the following command. 182 | 183 | docker stats 184 | 185 | If you see heavy resource usage and your server is performing poorly, then you 186 | might need to allocate more resources. 100% CPU usage is fine but if you start 187 | seeing 600% usage, then that's an indicator you need to start increasing the 188 | `cpu_count` limit for Rust. 189 | 190 | ### Backup and restore from backup 191 | 192 | The following command is compatible with cron jobs and running from the root of 193 | the repository. 194 | 195 | ./admin/backup/create-backup.sh 196 | 197 | If you run `create-backup.sh` from a cron job, then be sure to reference it by 198 | its full path. 199 | 200 | List backups, 201 | 202 | ./admin/backup/list-backups.sh 203 | 204 | Will show you what backup files have been created along with their date and time 205 | of creation. You could then restore a backup of your choice which would 206 | destroy the running server in order to restore it from a backup. 207 | 208 | ./admin/backup/restore-backup.sh ./backups/file.tgz 209 | 210 | ### Log management 211 | 212 | There's a few scripts to help you manage logs and log size on disk in the Rust 213 | server. The following script will create a backup of all LGSM and server logs. 214 | 215 | ./admin/logs/create-backup.sh 216 | 217 | To list known log file backups run the following command. 218 | 219 | ./admin/logs/list-backups.sh 220 | 221 | Logs over time can take up a lot of disk storage. To reclaim disk space and 222 | deleted unused logs, run the following command. 223 | 224 | ./admin/logs/clean.sh 225 | 226 | ### Easy Anti-Cheat 227 | 228 | By default, EAC is disabled for Linux clients. Enable EAC with the following 229 | shell variable in [`rust-environment.sh`](rust-environment.sh). 230 | 231 | 232 | ```bash 233 | export ENABLE_RUST_EAC=1 234 | ``` 235 | 236 | If EAC is enabled, Linux clients will not be able to connect and your server 237 | will be listed in the in-game server browser. 238 | 239 | # Server Mods 240 | 241 | [Oxide plugins](https://umod.org/) and custom mods are supported. I use the 242 | term mods and plugins interchangeably. 243 | 244 | Oxide mods are installed and updated automatically on every server start. 245 | However, if you have a custom mod that has a name conflict with an official 246 | Oxide mod, then the custom mod will be used. 247 | 248 | ### Oxide mods 249 | 250 | To automatically install mods, create a new text file: 251 | [`mod-configs/plugins.txt`](mod-configs). Add what plugins you would like 252 | in your rust server; one plugin per line. 253 | 254 | For example, let's say you want the following plugins: 255 | 256 | * [Backpacks](https://umod.org/plugins/backpacks) 257 | * [Chest Stacks](https://umod.org/plugins/chest-stacks) 258 | 259 | The download links for both of those plugins would be `Backpacks.cs` and 260 | `ChestStacks.cs`. Your `mod-configs/plugins.txt` would need to have the 261 | following contents. 262 | 263 | ```bash 264 | # example mod-configs/plugins.txt 265 | # code comments are supported along with blank lines 266 | Backpacks 267 | ChestStacks 268 | ``` 269 | 270 | When the server boots, the mods will be automatically downloaded from uMod. If 271 | they already exist, then updates will be checked instead. 272 | 273 | If you edit `mod-configs/plugins.txt`, then you can reload plugins without 274 | restarting the server. Run the following command. 275 | 276 | ./admin/get-or-update-oxide-plugins.sh 277 | 278 | If you remove a plugin from `mod-configs/plugins.txt`, then it will be 279 | deleted from your server automatically. 280 | 281 | You can also remove mods from `mod-configs/plugins.txt` by starting the line 282 | with a `#`. This allows you to delete the plugin from the server but also keep 283 | it around in case you want to re-enable it later. 284 | 285 | ### Custom mods 286 | 287 | If you're writing your own custom mod, then the file name must end with `.cs`. 288 | For example, `MyCustomMod.cs`. Place any `.cs` files into the 289 | [`custom-mods/`](custom-mods) directory. Next time your server 290 | 291 | 292 | You can edit your mods as much as you want without affecting the mod used by the 293 | server. You can copy and update to your custom mod to the server using the 294 | following command. 295 | 296 | ./admin/get-or-update-oxide-plugins.sh 297 | 298 | If you delete a custom mod from `custom-mods/` folder, then it will be removed 299 | from the server automatically. 300 | 301 | ### Harmony mods 302 | 303 | Add Harmony mods to [`harmony-mods`](harmony-mods) folder. It will be available 304 | to your Rust maps such as [Observer Island][map-obs-isle]. 305 | 306 | ### Troubleshooting: Mods failing to load 307 | 308 | Sometimes, mods will fail to load into Oxide because of differences between 309 | Windows and Linux. An example is the following error message. 310 | 311 | ``` 312 | FurnaceSplitter was compiled successfully in 2345ms 313 | Unable to find main plugin class: FurnaceSplitter 314 | No previous version to rollback plugin: FurnaceSplitter 315 | ``` 316 | 317 | To fix this error run the following command. 318 | 319 | ./admin/bugfix-oxide-plugins.sh 320 | 321 | Then, in the RCON console try reloading the oxide plugin. 322 | 323 | oxide.reload FurnaceSplitter 324 | 325 | ### Updating mod configuration 326 | 327 | Oxide Mods automatically generate plugin config which is accessible by editing 328 | files in the [`mod-configs/`](mod-configs/) directory. If you edit a JSON 329 | config you can open up the web management RCON console to reload the plugin (See 330 | [RCON: Remote Admin Console](#rcon-remote-admin-console) for how to access RCON 331 | console). 332 | 333 | Use console command: 334 | 335 | oxide.reload plugin_name 336 | 337 | Or to reload all plugins: 338 | 339 | oxide.reload * 340 | 341 | Use the uMod download name of the plugin. For example, if you download from 342 | uMod `Backpacks.cs`, then you need only add `Backpacks` to 343 | `mod-configs/plugins.txt`. 344 | 345 | # Customize Map 346 | 347 | You can have a randomly generated map with a seed or a custom map. 348 | 349 | ### Generated Maps 350 | 351 | > Note: Generated Map settings are completely ignored if you've configured a 352 | > Custom Map. 353 | 354 | You can uncomment and change the following variables in 355 | [`rust-environment.sh`](rust-environment.sh). 356 | 357 | - seed 358 | - salt 359 | - worldsize 360 | 361 | If you want to wipe the map and start from another random see, then use 362 | [`./admin/regenerate-map.sh`](./admin/regenerate-map.sh). You can also 363 | reference this script by its full path from a cron job (it will not wipe 364 | blueprints. 365 | 366 | Weekly map wipe (but not BP wipe) cron job: 367 | 368 | @weekly /path/to/admin/regenerate-map.sh skip-prompt 369 | 370 | # or specify time of cron; e.g. every sunday at 1am 371 | 0 1 * * 7 /path/to/admin/regenerate-map.sh skip-prompt 372 | 373 | 374 | ### Custom Maps 375 | 376 | Please note: 377 | 378 | > _Your map file should be hosted on a public web-site that works 24/7, since 379 | > new players of your server will download the map from that URL, not from your 380 | > Rust server. If your URL link doesn't work then players that haven't 381 | > downloaded the map yet won't be able to join the server._ 382 | > - [facepunch Wiki: Hosting a custom map][fp-custom-maps] 383 | 384 | There's two ways a custom map is supported. 385 | 386 | 1. Self-hosted 387 | 2. Remotely hosted 388 | 389 | ##### Self-hosted custom maps 390 | 391 | If you're playing multi-player and self hosting your Map, then there's two extra 392 | configuration items you must toggle. 393 | 394 | - Port forward port `8000/tcp` to your router. 395 | - Set `MAP_BASE_URL` variable in [`rust-environment.sh`](rust-environment.sh) to 396 | your public IP where clients can download the map. 397 | 398 | Your custom map file name must end with `.map`. Download your custom map 399 | locally to your computer and place it in the [`custom-maps/`](custom-maps/) 400 | directory. 401 | 402 | If you have more than one map, then the first map alphabetically is used. If 403 | you would like to have multiple custom maps and change the map, then it is 404 | recommended you prefix all maps with a 4 digit number. For example, 405 | 406 | ``` 407 | 0001_custom-map.map 408 | 0002_custom-map.map 409 | 0003_custom-map.map 410 | ... etc 411 | ``` 412 | 413 | If you wish to use a Generated Map, then all files ending with `.map` must be 414 | removed from `custom-maps/` directory. Just having the custom map file in that 415 | directory is what enables the Custom Map logic. 416 | 417 | ##### Remotely hosted custom maps 418 | 419 | If you're using a public file serving service separate from your game server 420 | (such as Dropbox), then set `CUSTOM_MAP_URL` variable in 421 | [`rust-environment.sh`](rust-environment.sh). No extra configuration is 422 | required. 423 | 424 | ##### Observer Island 425 | 426 | If you want to play locally for yourself only, then setup is very straight 427 | forward. 428 | 429 | ```bash 430 | cd custom-maps/ 431 | unzip ~/Downloads/observer-island.zip 432 | 433 | # the map should be in the current directory 434 | ls 435 | # next move the harmony mods to the appropriate location 436 | mv Mods/* ../harmony-mods/ 437 | 438 | # clean up remaining files so that only the map remains 439 | rm -r changelog.txt Mods observer-island-* Prefabs/ 440 | 441 | # go back to the root of this repository and start the dedicated server 442 | cd .. 443 | docker compose up -d 444 | ``` 445 | 446 | After about 5 minutes you should be able to connect to `localhost:28015`. If 447 | you want to make this map available for multiplayer within your LAN or worldwide 448 | refer to the previous sections for custom map hosting for remote play. 449 | 450 | # Road Map 451 | 452 | - :heavy_check_mark: Initial working vanilla server 453 | - :heavy_check_mark: Basic admin actions like shell login and RCON access 454 | - :heavy_check_mark: Support for adding server mods and automatic mod updates 455 | - :heavy_check_mark: Limit server resources 456 | - :heavy_check_mark: Support for customizing initial Map generation on first 457 | time startup. 458 | - :heavy_check_mark: Support for custom server mods (Oxide plugins) 459 | - :heavy_check_mark: Support for custom Maps. 460 | - :heavy_check_mark: Improve documentation 461 | 462 | [compose]: https://docs.docker.com/compose/install/ 463 | [docker]: https://docs.docker.com/engine/install/ 464 | [fp-custom-maps]: https://wiki.facepunch.com/rust/Hosting_a_custom_map 465 | [git]: https://git-scm.com/ 466 | [lgsm]: https://linuxgsm.com/ 467 | [map-obs-isle]: https://lone.design/product/observer-island/ 468 | [rust]: https://rust.facepunch.com/ 469 | --------------------------------------------------------------------------------