├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md └── stale.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.full ├── LICENSE.md ├── README.md ├── app.yml ├── docker_build.sh ├── docker_build_force.sh ├── docker_push.sh ├── docker_run.sh ├── docker_run_modded.sh ├── docker_run_prerelease.sh ├── docker_run_staging.sh ├── fix_conn.sh ├── heartbeat_app ├── app.js ├── package-lock.json └── package.json ├── install.txt ├── nginx_rcon.conf ├── rcon_app ├── app.js ├── package-lock.json └── package.json ├── restart_app ├── app.js ├── package-lock.json └── package.json ├── scheduler_app ├── app.js ├── package-lock.json └── package.json ├── shutdown_app ├── app.js ├── package-lock.json └── package.json ├── start_rust.sh └── update_check.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | sample.txt 2 | node_modules 3 | scheduler_app/node_modules 4 | shutdown_app/node_modules 5 | restart_app/node_modules 6 | rcon_app/node_modules 7 | app.yml 8 | *.log 9 | rust_data* 10 | .DS_Store 11 | docker_*.sh 12 | .git/ 13 | .git* 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. See error 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Environment (please complete the following information):** 23 | - OS: [e.g. Ubuntu 14.04] 24 | - Docker: [e.g. 17.03.1-cei] 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Your checklist for this pull request 2 | 3 | Please review the [guidelines for contributing](../../CONTRIBUTING.md) to this repository. 4 | 5 | - [ ] Make sure to keep your code style as close to the original as possible 6 | - [ ] Make sure your code doesn't fail with the default or custom configuration 7 | 8 | ### Description 9 | Please describe your pull request. 10 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 30 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 14 9 | 10 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 11 | exemptLabels: 12 | - pinned 13 | - security 14 | - "[Status] Maybe Later" 15 | 16 | # Set to true to ignore issues in a project (defaults to false) 17 | exemptProjects: false 18 | 19 | # Set to true to ignore issues in a milestone (defaults to false) 20 | exemptMilestones: false 21 | 22 | # Label to use when marking as stale 23 | staleLabel: wontfix 24 | 25 | # Comment to post when marking as stale. Set to `false` to disable 26 | markComment: > 27 | This issue has been automatically marked as stale because it has not had 28 | recent activity. It will be closed if no further activity occurs. Thank you 29 | for your contributions. 30 | 31 | # Comment to post when removing the stale label. 32 | # unmarkComment: > 33 | # Your comment here. 34 | 35 | # Comment to post when closing a stale Issue or Pull Request. 36 | # closeComment: > 37 | # Your comment here. 38 | 39 | # Limit the number of actions per hour, from 1-30. Default is 30 40 | limitPerRun: 30 41 | 42 | # Limit to only `issues` or `pulls` 43 | # only: issues 44 | 45 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 46 | # pulls: 47 | # daysUntilStale: 30 48 | # markComment: > 49 | # This pull request has been automatically marked as stale because it has not had 50 | # recent activity. It will be closed if no further activity occurs. Thank you 51 | # for your contributions. 52 | 53 | # issues: 54 | # exemptLabels: 55 | # - confirmed 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sample.txt 2 | rust_data*/ 3 | scheduler_app/node_modules/ 4 | restart_app/node_modules/ 5 | shutdown_app/node_modules/ 6 | rcon_app/node_modules/ 7 | 8 | # Docker project generated files to ignore 9 | # if you want to ignore files created by your editor/tools, 10 | # please consider a global .gitignore https://help.github.com/articles/ignoring-files 11 | *.exe 12 | *.exe~ 13 | *.orig 14 | *.rej 15 | *.test 16 | .*.swp 17 | .DS_Store 18 | .bashrc 19 | .dotcloud 20 | .flymake* 21 | .git/ 22 | .gopath/ 23 | .hg/ 24 | .vagrant* 25 | Vagrantfile 26 | a.out 27 | autogen/ 28 | bin 29 | build_src 30 | bundles/ 31 | docker/docker 32 | dockerversion/version_autogen.go 33 | docs/AWS_S3_BUCKET 34 | docs/GITCOMMIT 35 | docs/GIT_BRANCH 36 | docs/VERSION 37 | docs/_build 38 | docs/_static 39 | docs/_templates 40 | docs/changed-files 41 | # generated by man/md2man-all.sh 42 | man/man1 43 | man/man5 44 | man/man8 45 | pyenv 46 | vendor/pkg/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | - Cracked or pirated versions are not supported in any way, shape or form and issues regarding them should not be posted 4 | - Provide as much details as possible, as this will help tremendously in assisting you 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM didstopia/base:nodejs-12-steamcmd-ubuntu-18.04 2 | 3 | LABEL maintainer="Didstopia " 4 | 5 | # Fix apt-get warnings 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install dependencies 9 | RUN apt-get update && \ 10 | apt-get install -y --no-install-recommends \ 11 | nginx \ 12 | expect \ 13 | tcl \ 14 | libsdl2-2.0-0:i386 \ 15 | libgdiplus && \ 16 | rm -rf /var/lib/apt/lists/* 17 | 18 | # Remove default nginx stuff 19 | RUN rm -fr /usr/share/nginx/html/* && \ 20 | rm -fr /etc/nginx/sites-available/* && \ 21 | rm -fr /etc/nginx/sites-enabled/* 22 | 23 | # Install webrcon (specific commit) 24 | COPY nginx_rcon.conf /etc/nginx/nginx.conf 25 | RUN curl -sL https://github.com/Facepunch/webrcon/archive/24b0898d86706723d52bb4db8559d90f7c9e069b.zip | bsdtar -xvf- -C /tmp && \ 26 | mv /tmp/webrcon-24b0898d86706723d52bb4db8559d90f7c9e069b/* /usr/share/nginx/html/ && \ 27 | rm -fr /tmp/webrcon-24b0898d86706723d52bb4db8559d90f7c9e069b 28 | 29 | # Customize the webrcon package to fit our needs 30 | ADD fix_conn.sh /tmp/fix_conn.sh 31 | 32 | # Create the volume directories 33 | RUN mkdir -p /steamcmd/rust /usr/share/nginx/html /var/log/nginx 34 | 35 | # Setup proper shutdown support 36 | ADD shutdown_app/ /app/shutdown_app/ 37 | WORKDIR /app/shutdown_app 38 | RUN npm install 39 | 40 | # Setup restart support (for update automation) 41 | ADD restart_app/ /app/restart_app/ 42 | WORKDIR /app/restart_app 43 | RUN npm install 44 | 45 | # Setup scheduling support 46 | ADD scheduler_app/ /app/scheduler_app/ 47 | WORKDIR /app/scheduler_app 48 | RUN npm install 49 | 50 | # Setup scheduling support 51 | ADD heartbeat_app/ /app/heartbeat_app/ 52 | WORKDIR /app/heartbeat_app 53 | RUN npm install 54 | 55 | # Setup rcon command relay app 56 | ADD rcon_app/ /app/rcon_app/ 57 | WORKDIR /app/rcon_app 58 | RUN npm install 59 | RUN ln -s /app/rcon_app/app.js /usr/bin/rcon 60 | 61 | # Add the steamcmd installation script 62 | ADD install.txt /app/install.txt 63 | 64 | # Copy the Rust startup script 65 | ADD start_rust.sh /app/start.sh 66 | 67 | # Copy the Rust update check script 68 | ADD update_check.sh /app/update_check.sh 69 | 70 | # Copy extra files 71 | COPY README.md LICENSE.md /app/ 72 | 73 | # Set the current working directory 74 | WORKDIR / 75 | 76 | # Fix permissions 77 | RUN chown -R 1000:1000 \ 78 | /steamcmd \ 79 | /app \ 80 | /usr/share/nginx/html \ 81 | /var/log/nginx 82 | 83 | # Run as a non-root user by default 84 | ENV PGID 1000 85 | ENV PUID 1000 86 | 87 | # Expose necessary ports 88 | EXPOSE 8080 89 | EXPOSE 28015 90 | EXPOSE 28016 91 | EXPOSE 28082 92 | 93 | # Setup default environment variables for the server 94 | ENV RUST_SERVER_STARTUP_ARGUMENTS "-batchmode -load -nographics +server.secure 1" 95 | ENV RUST_SERVER_IDENTITY "docker" 96 | ENV RUST_SERVER_PORT "" 97 | ENV RUST_SERVER_QUERYPORT "" 98 | ENV RUST_SERVER_SEED "12345" 99 | ENV RUST_SERVER_NAME "Rust Server [DOCKER]" 100 | ENV RUST_SERVER_DESCRIPTION "This is a Rust server running inside a Docker container!" 101 | ENV RUST_SERVER_URL "https://hub.docker.com/r/didstopia/rust-server/" 102 | ENV RUST_SERVER_BANNER_URL "" 103 | ENV RUST_RCON_WEB "1" 104 | ENV RUST_RCON_PORT "28016" 105 | ENV RUST_RCON_PASSWORD "docker" 106 | ENV RUST_APP_PORT "28082" 107 | ENV RUST_UPDATE_CHECKING "0" 108 | ENV RUST_HEARTBEAT "0" 109 | ENV RUST_UPDATE_BRANCH "public" 110 | ENV RUST_START_MODE "0" 111 | ENV RUST_OXIDE_ENABLED "0" 112 | ENV RUST_OXIDE_UPDATE_ON_BOOT "1" 113 | ENV RUST_RCON_SECURE_WEBSOCKET "0" 114 | ENV RUST_SERVER_WORLDSIZE "3500" 115 | ENV RUST_SERVER_MAXPLAYERS "500" 116 | ENV RUST_SERVER_SAVE_INTERVAL "600" 117 | 118 | # Define directories to take ownership of 119 | ENV CHOWN_DIRS "/app,/steamcmd,/usr/share/nginx/html,/var/log/nginx" 120 | 121 | # Expose the volumes 122 | # VOLUME [ "/steamcmd/rust" ] 123 | 124 | # Start the server 125 | CMD [ "bash", "/app/start.sh"] 126 | -------------------------------------------------------------------------------- /Dockerfile.full: -------------------------------------------------------------------------------- 1 | FROM didstopia/rust-server:latest 2 | 3 | RUN bash -eo pipefail -c "/steamcmd/steamcmd.sh +runscript /app/install.txt" 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Didstopia 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust server that runs inside a Docker container 2 | [![Docker Automated build](https://img.shields.io/docker/automated/didstopia/rust-server.svg)](https://hub.docker.com/r/didstopia/rust-server/) 3 | [![Docker build status](https://img.shields.io/docker/build/didstopia/rust-server.svg)](https://hub.docker.com/r/didstopia/rust-server/) 4 | [![Docker Pulls](https://img.shields.io/docker/pulls/didstopia/rust-server.svg)](https://hub.docker.com/r/didstopia/rust-server/) 5 | [![Docker stars](https://img.shields.io/docker/stars/didstopia/rust-server.svg)](https://hub.docker.com/r/didstopia/rust-server) 6 | 7 | **DISCLAIMER:** 8 | ``` 9 | Cracked or pirated versions of Rust are not supported in any way, shape or form. Please do not post issues regarding these. 10 | ``` 11 | 12 | --- 13 | 14 | **TUTORIAL**: We've written a guide on how to use this image [here](http://rust.didscraft.com/rust-server-on-linux-using-docker/). 15 | 16 | **NOTE**: This image will install/update on startup. The path ```/steamcmd/rust``` can be mounted on the host for data persistence. 17 | Also note that this image provides the new web-based RCON, so you should set ```RUST_RCON_PASSWORD``` to a more secure password. 18 | This image also supports having a modded server (using Oxide), check the ```RUST_OXIDE_ENABLED``` variable below. 19 | 20 | # How to run the server 21 | 1. Set the environment variables you wish to modify from below (note the RCON password!) 22 | 2. Optionally mount ```/steamcmd/rust``` somewhere on the host or inside another container to keep your data safe 23 | 3. Enjoy! 24 | 25 | The following environment variables are available: 26 | ``` 27 | RUST_SERVER_STARTUP_ARGUMENTS (DEFAULT: "-batchmode -load -nographics +server.secure 1") 28 | RUST_SERVER_IDENTITY (DEFAULT: "docker" - Mainly used for the name of the save directory) 29 | RUST_SERVER_PORT (DEFAULT: "" - Rust server port 28015 if left blank or numeric value) 30 | RUST_SERVER_QUERYPORT (DEFAULT: "" - Rust server query port 28016 if left blank or numeric value) 31 | RUST_SERVER_SEED (DEFAULT: "12345" - The server map seed, must be an integer) 32 | RUST_SERVER_WORLDSIZE (DEFAULT: "3500" - The map size, must be an integer) 33 | RUST_SERVER_LEVELURL (DEFAULT: "" - An URL pointing towards a custom map. When using this RUST_SERVER_SEED and RUST_SERVER_WORLDSIZE are ignored.) 34 | RUST_SERVER_NAME (DEFAULT: "Rust Server [DOCKER]" - The publicly visible server name) 35 | RUST_SERVER_MAXPLAYERS (DEFAULT: "500" - Maximum players on the server, must be an integer) 36 | RUST_SERVER_DESCRIPTION (DEFAULT: "This is a Rust server running inside a Docker container!" - The publicly visible server description) 37 | RUST_SERVER_URL (DEFAULT: "https://hub.docker.com/r/didstopia/rust-server/" - The publicly visible server website) 38 | RUST_SERVER_BANNER_URL (DEFAULT: "" - The publicly visible server banner image URL) 39 | RUST_SERVER_SAVE_INTERVAL (DEFAULT: "600" - Amount of seconds between automatic saves.) 40 | RUST_RCON_WEB (DEFAULT "1" - Set to 1 or 0 to enable or disable the web-based RCON server) 41 | RUST_RCON_PORT (DEFAULT: "28016" - RCON server port) 42 | RUST_RCON_PASSWORD (DEFAULT: "docker" - RCON server password, please change this!) 43 | RUST_APP_PORT (DEFAULT: "28082" - Rust+ companion app port) 44 | RUST_BRANCH (DEFAULT: Not set - Sets the branch argument to use, eg. set to "-beta prerelease" for the prerelease branch) 45 | RUST_UPDATE_CHECKING (DEFAULT: "0" - Set to 1 to enable fully automatic update checking, notifying players and restarting to install updates) 46 | RUST_UPDATE_BRANCH (DEFAULT: "public" - Set to match the branch that you want to use for updating, ie. "prerelease" or "public", but do not specify arguments like "-beta") 47 | RUST_START_MODE (DEFAULT: "0" - Determines if the server should update and then start (0), only update (1) or only start (2)) 48 | RUST_OXIDE_ENABLED (DEFAULT: "0" - Set to 1 to automatically install the latest version of Oxide) 49 | RUST_OXIDE_UPDATE_ON_BOOT (DEFAULT: "1" - Set to 0 to disable automatic update of Oxide on boot) 50 | RUST_RCON_SECURE_WEBSOCKET (DEFAULT: "0" - Set to 1 to enable secure websocket connections to the RCON web interface) 51 | RUST_HEARTBEAT (DEFAULT: "0" - Set to 1 to enable the heartbeat service which will forcibly quit the server if it becomes unresponsive to queries) 52 | ``` 53 | 54 | # Logging and rotating logs 55 | 56 | The image now supports log rotation, and all you need to do to enable it is to remove any `-logfile` arguments from your startup arguments. 57 | Log files will be created under `logs/` with the server identity and the current date and time. 58 | When the server starts up or restarts, it will move old logs to `logs/archive/`. 59 | 60 | # How to send or receive command to/from the server 61 | 62 | We recently added a small application, called *rcon*, that can both send and receive messages to the server, much like the console on the Windows version, but this happens to use RCON (webrcon). 63 | To use it, simply run the following on the host: `docker exec rust-server rcon say Hello World`, substituting *rust-server* for your own container name. 64 | 65 | # Rust+ companion app support 66 | 67 | The image sets up `app.port` to `28082` by default, but you can optionally override this with the `RUST_APP_PORT` environment variable. 68 | If you need to set additional options, such as `app.listenip` or `app.publicip`, you can supply these to `RUST_SERVER_STARTUP_ARGUMENTS` environment variable, but be careful to also include the default values. 69 | More information on the Rust+ companion app integration can be found [here](https://wiki.facepunch.com/rust/rust-companion-server). 70 | 71 | # Troubleshooting 72 | 73 | - If the server exits by itself after seemingly starting up fine, make sure the Docker VM has at least 4GB of RAM. 74 | - If you can connect to the RCON web UI, but not the game itself, make sure you've exposed port 28015 as UDP, not TCP. 75 | 76 | # Anything else 77 | 78 | If you need help, have questions or bug submissions, feel free to contact me **@Dids** on Twitter, and on the *Rust Server Owners* Slack community. 79 | -------------------------------------------------------------------------------- /app.yml: -------------------------------------------------------------------------------- 1 | # http://installer.71m.us/install?url=https://github.com/didstopia/rust-server 2 | name: DockerRustServer 3 | image: ubuntu-14-04-x64 4 | min_size: 4gb 5 | config: 6 | runcmd: 7 | - sudo apt-get clean && sudo apt-get update 8 | - sudo DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade && sudo apt-get autoremove -y 9 | - sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D 10 | - sudo echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" > /etc/apt/sources.list.d/docker.list && sudo apt-get update 11 | - sudo apt-get install docker-engine -y 12 | - sudo docker pull didstopia/rust-server 13 | -------------------------------------------------------------------------------- /docker_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -t didstopia/rust-server:latest . 4 | -------------------------------------------------------------------------------- /docker_build_force.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build --no-cache -t didstopia/rust-server:latest . 4 | -------------------------------------------------------------------------------- /docker_push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #docker tag didstopia/rust-server:latest didstopia/rust-server:latest 4 | docker push didstopia/rust-server:latest -------------------------------------------------------------------------------- /docker_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./docker_build.sh 4 | 5 | # Run a server without a volume mount (also doesn't remove it after termination) 6 | # docker run -p 0.0.0.0:28015:28015 -p 0.0.0.0:28015:28015/udp -p 0.0.0.0:28016:28016 -p 0.0.0.0:8080:8080 --name rust-server -it didstopia/rust-server:latest 7 | 8 | # Run a vanilla server 9 | docker run -p 0.0.0.0:28015:28015 -p 0.0.0.0:28015:28015/udp -p 0.0.0.0:28016:28016 -p 0.0.0.0:8080:8080 -v $(pwd)/rust_data:/steamcmd/rust $@ --name rust-server -it --rm didstopia/rust-server:latest 10 | #docker run -p 0.0.0.0:28015:28015 -p 0.0.0.0:28015:28015/udp -p 0.0.0.0:28016:28016 -p 0.0.0.0:8080:8080 -e RUST_SERVER_STARTUP_ARGUMENTS="-batchmode -load -nographics +server.secure 1 -logfile /dev/null" -e RUST_START_MODE="2" -v $(pwd)/rust_data:/steamcmd/rust --name rust-server -it --rm didstopia/rust-server:latest 11 | 12 | # Run a modded server 13 | #docker run -p 0.0.0.0:28015:28015 -p 0.0.0.0:28015:28015/udp -p 0.0.0.0:28016:28016 -p 0.0.0.0:8080:8080 -e RUST_OXIDE_ENABLED=1 -v $(pwd)/rust_data:/steamcmd/rust --name rust-server -it --rm didstopia/rust-server:latest 14 | -------------------------------------------------------------------------------- /docker_run_modded.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./docker_build.sh 4 | 5 | # Run a modded server 6 | docker run --network=host -p 0.0.0.0:28215:28015 -p 0.0.0.0:28215:28015/udp -p 28216:28216 -p 0.0.0.0:8082:8080 -m 2g -v $(pwd)/rust_data_modded:/steamcmd/rust -e RUST_SERVER_STARTUP_ARGUMENTS="-batchmode -load -nographics -logfile /dev/stdout +server.secure 1 +server.saveinterval 60" -e RUST_RCON_PORT=28216 -e RUST_UPDATE_CHECKING=0 -e RUST_BRANCH="public" -e RUST_UPDATE_BRANCH="public" -e RUST_OXIDE_ENABLED=1 --name rust-server-modded -d didstopia/rust-server:latest 7 | 8 | docker logs -f rust-server-modded 9 | -------------------------------------------------------------------------------- /docker_run_prerelease.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./docker_build.sh 4 | 5 | # Run a prerelease server 6 | docker run -p 0.0.0.0:28115:28015 -p 0.0.0.0:28115:28015/udp -p 28116:28116 -p 0.0.0.0:8081:8080 -m 2g -v $(pwd)/rust_data_prerelease:/steamcmd/rust -e RUST_UPDATE_CHECKING=1 -e RUST_RCON_PORT=28116 -e RUST_BRANCH="prerelease" -e RUST_UPDATE_BRANCH="prerelease" -e RUST_SERVER_STARTUP_ARGUMENTS="-batchmode -load -nographics +server.secure 1" --name rust-server-prerelease -d didstopia/rust-server:latest 7 | 8 | docker logs -f rust-server-prerelease 9 | -------------------------------------------------------------------------------- /docker_run_staging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./docker_build.sh 4 | 5 | # Run a staging server 6 | docker run -p 0.0.0.0:28115:28015 -p 0.0.0.0:28115:28015/udp -p 28116:28116 -p 0.0.0.0:8081:8080 -m 2g -v $(pwd)/rust_data_staging:/steamcmd/rust -e RUST_START_MODE=2 -e RUST_UPDATE_CHECKING=1 -e RUST_RCON_PORT=28116 -e RUST_BRANCH="staging" -e RUST_UPDATE_BRANCH="staging" -e RUST_SERVER_STARTUP_ARGUMENTS="-batchmode -load -nographics +server.secure 1" --name rust-server-staging -d didstopia/rust-server:latest 7 | 8 | docker logs -f rust-server-staging 9 | -------------------------------------------------------------------------------- /fix_conn.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Enable debugging 4 | # set -x 5 | 6 | # $scope.Address = $scope.Address.trim(); 7 | 8 | # Replace the trimmed address field with the current domain/hostname and RCON port 9 | OLD_TRIM_CODE='scope.Address = $scope.Address.trim();' 10 | NEW_ADDRESS_CODE='scope.Address = $location.host() + '"\":$RUST_RCON_PORT\";" 11 | if [ ! -z "$RUST_RCON_HOSTNAME" ]; then 12 | NEW_ADDRESS_CODE='scope.Address = '"\"$RUST_RCON_HOSTNAME:$RUST_RCON_PORT\";" 13 | fi 14 | sed -i -e 's/'"$OLD_TRIM_CODE"'/'"$NEW_ADDRESS_CODE"'/g' /usr/share/nginx/html/js/connection.js 15 | 16 | # Replace the help text with our own 17 | OLD_HELP_TEXT="Enter the address (including rcon port) and the rcon password below to connect." 18 | NEW_HELP_TEXT="Enter the rcon password below to connect." 19 | sed -i -e 's/'"$OLD_HELP_TEXT"'/'"$NEW_HELP_TEXT"'/g' /usr/share/nginx/html/html/connect.html 20 | 21 | # Remove the address lines (14-19) if they exist 22 | if grep -q "ng-model=\"Address\"" /usr/share/nginx/html/html/connect.html; then 23 | sed -i -e '14,19d' /usr/share/nginx/html/html/connect.html 24 | fi 25 | 26 | # Enable or disable secure websockets 27 | if [ "$RUST_RCON_SECURE_WEBSOCKET" = "1" ]; then 28 | # Change "ws://" to "wss://" in /usr/share/nginx/html/js/rconService.js 29 | echo "Enabling secure websockets!" 30 | sed -i -e 's/ws:\/\//wss:\/\//g' /usr/share/nginx/html/js/rconService.js 31 | else 32 | # Change "wss://" to "ws://" in /usr/share/nginx/html/js/rconService.js 33 | echo "Disabling secure websockets!" 34 | sed -i -e 's/wss:\/\//ws:\/\//g' /usr/share/nginx/html/js/rconService.js 35 | fi 36 | -------------------------------------------------------------------------------- /heartbeat_app/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const debug = false; 3 | 4 | const query = require('source-server-query'); 5 | const childProcess = require('child_process') 6 | 7 | var serverHostname = 'localhost'; 8 | var serverPort = process.env.RUST_SERVER_PORT; 9 | var rconPort = process.env.RUST_RCON_PORT; 10 | var rconPassword = process.env.RUST_RCON_PASSWORD; 11 | 12 | var started = false; 13 | var failcount = 0; 14 | const maxfails = 3; 15 | const querytimeout = 3000; 16 | const checkfreq = 60000; 17 | const processname = '/steamcmd/rust/RustDedicated'; 18 | 19 | async function check_server () { 20 | var pass = false; 21 | var qo = null; 22 | 23 | await query.info('127.0.0.1',serverPort, querytimeout).then((q) => { qo=q; pass= true; } ).catch((error) => { return }); 24 | if (started) { 25 | if (pass) { 26 | if (debug) console.log(`Heartbeat::Running... (Players: ${qo.players})`) 27 | failcount = 0; 28 | } else { 29 | if (debug) console.log("Heartbeat::Failed to respond..."); 30 | failcount++; 31 | if (failcount >= maxfails) { 32 | if (debug) console.log("Heartbeat::Max Fails hit! Restarting..."); 33 | clearInterval(timer); 34 | restart_server(); 35 | } 36 | } 37 | } else { 38 | if (pass) { 39 | if (debug) console.log("Heartbeat::Started..."); 40 | started = true; 41 | } else { 42 | if (debug) console.log("Heartbeat::Waiting for start..."); 43 | } 44 | } 45 | } 46 | 47 | var timer = setInterval(check_server, checkfreq); 48 | 49 | function restart_server () { 50 | console.log('Heartbeat::Restarting...') 51 | 52 | var WebSocket = require('ws'); 53 | var ws = new WebSocket('ws://' + serverHostname + ':' + rconPort + '/' + rconPassword); 54 | ws.on('open', function open() { 55 | if (debug) console.log('Heartbeat::Sending rcon quit...') 56 | rcon_send('global.kickall Restarting', ws); 57 | rcon_send('quit', ws); 58 | }) 59 | setTimeout(kill_rust, 60 * 1000, 2); 60 | setTimeout(kill_rust, 90 * 1000, 15); 61 | setTimeout(kill_rust, 120 * 1000, 9); 62 | } 63 | 64 | 65 | function rcon_send (command, ws) { 66 | var packet = { 67 | Identifier: -1, 68 | Message: command, 69 | Name: 'WebRcon' 70 | } 71 | ws.send(JSON.stringify(packet)); 72 | } 73 | 74 | function kill_rust (signal) { 75 | console.log(`Heatbeat::Terminating Process... signal: ${signal}`) 76 | childProcess.execSync(`kill -s ${signal} $(pidof ${processname})`); 77 | } 78 | -------------------------------------------------------------------------------- /heartbeat_app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heartbeat_app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "heartbeat_app", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "source-server-query": "^2.1.4", 12 | "ws": "^8.6.0" 13 | } 14 | }, 15 | "node_modules/source-server-query": { 16 | "version": "2.1.4", 17 | "resolved": "https://registry.npmjs.org/source-server-query/-/source-server-query-2.1.4.tgz", 18 | "integrity": "sha512-3DytX089tMt+oR1LPSssaLZAX93era1c1W95P0R3fNCHIlVcZteD+KVpjWJy5BPcpuFyRdM4f/iFzozW5WDwOQ==" 19 | }, 20 | "node_modules/ws": { 21 | "version": "8.6.0", 22 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", 23 | "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", 24 | "engines": { 25 | "node": ">=10.0.0" 26 | }, 27 | "peerDependencies": { 28 | "bufferutil": "^4.0.1", 29 | "utf-8-validate": "^5.0.2" 30 | }, 31 | "peerDependenciesMeta": { 32 | "bufferutil": { 33 | "optional": true 34 | }, 35 | "utf-8-validate": { 36 | "optional": true 37 | } 38 | } 39 | } 40 | }, 41 | "dependencies": { 42 | "source-server-query": { 43 | "version": "2.1.4", 44 | "resolved": "https://registry.npmjs.org/source-server-query/-/source-server-query-2.1.4.tgz", 45 | "integrity": "sha512-3DytX089tMt+oR1LPSssaLZAX93era1c1W95P0R3fNCHIlVcZteD+KVpjWJy5BPcpuFyRdM4f/iFzozW5WDwOQ==" 46 | }, 47 | "ws": { 48 | "version": "8.6.0", 49 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", 50 | "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", 51 | "requires": {} 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /heartbeat_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heartbeat_app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "nodejs app.js" 8 | }, 9 | "author": "", 10 | "private": "true", 11 | "dependencies": { 12 | "source-server-query": "^2.1.4", 13 | "ws": "^8.6.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /install.txt: -------------------------------------------------------------------------------- 1 | @sSteamCmdForcePlatformType linux 2 | login anonymous 3 | force_install_dir /steamcmd/rust 4 | app_info_update 1 5 | app_update 258550 validate 6 | quit -------------------------------------------------------------------------------- /nginx_rcon.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | error_log /tmp/nginx_error.log; 3 | pid /tmp/nginx.pid; 4 | 5 | events 6 | { 7 | worker_connections 128; 8 | } 9 | 10 | http 11 | { 12 | client_body_temp_path /tmp/client_body; 13 | fastcgi_temp_path /tmp/fastcgi_temp; 14 | proxy_temp_path /tmp/proxy_temp; 15 | scgi_temp_path /tmp/scgi_temp; 16 | uwsgi_temp_path /tmp/uwsgi_temp; 17 | include /etc/nginx/mime.types; 18 | default_type application/octet-stream; 19 | 20 | server 21 | { 22 | listen 8080; 23 | server_name localhost; 24 | location / 25 | { 26 | root /usr/share/nginx/html; 27 | index index.html index.htm; 28 | error_log /tmp/nginx_error.log; 29 | access_log /tmp/nginx_access.log; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rcon_app/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argumentString = '' 4 | var args = process.argv.splice(process.execArgv.length + 2) 5 | for (var i = 0; i < args.length; i++) { 6 | if (i === args.length - 1) argumentString += args[i] 7 | else argumentString += args[i] + ' ' 8 | } 9 | 10 | if (argumentString.length < 1) { 11 | console.log('RconApp::Error: Please specify an RCON command') 12 | process.exit() 13 | } 14 | 15 | console.log('RconApp::Relaying RCON command: ' + argumentString) 16 | 17 | var serverHostname = 'localhost' 18 | var serverPort = process.env.RUST_RCON_PORT 19 | var serverPassword = process.env.RUST_RCON_PASSWORD 20 | 21 | var messageSent = false 22 | var WebSocket = require('ws') 23 | var ws = new WebSocket('ws://' + serverHostname + ':' + serverPort + '/' + serverPassword) 24 | ws.on('open', function open () { 25 | setTimeout(function () { 26 | messageSent = true 27 | ws.send(createPacket(argumentString)) 28 | setTimeout(function () { 29 | ws.close(1000) 30 | setTimeout(function () { 31 | console.log('RconApp::Command relayed') 32 | process.exit() 33 | }) 34 | }, 1000) 35 | }, 250) 36 | }) 37 | ws.on('message', function (data, flags) { 38 | if (!messageSent) return 39 | try { 40 | var json = JSON.parse(data) 41 | if (json !== undefined) { 42 | if (json.Message !== undefined && json.Message.length > 0) { 43 | console.log('RconApp::Received message:', json.Message) 44 | } 45 | } else console.log('RconApp::Error: Invalid JSON received') 46 | } catch (e) { 47 | if (e) console.log('RconApp::Error:', e) 48 | } 49 | }) 50 | ws.on('error', function (e) { 51 | console.log('RconApp::Error:', e) 52 | process.exit() 53 | }) 54 | 55 | function createPacket (command) { 56 | var packet = 57 | { 58 | Identifier: -1, 59 | Message: command, 60 | Name: 'WebRcon' 61 | } 62 | return JSON.stringify(packet) 63 | } 64 | -------------------------------------------------------------------------------- /rcon_app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rcon_app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async-limiter": { 8 | "version": "1.0.0", 9 | "resolved": "https://npm.didstopia.com/async-limiter/-/async-limiter-1.0.0.tgz", 10 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 11 | }, 12 | "ws": { 13 | "version": "6.1.2", 14 | "resolved": "https://npm.didstopia.com/ws/-/ws-6.1.2.tgz", 15 | "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", 16 | "requires": { 17 | "async-limiter": "~1.0.0" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /rcon_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rcon_app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "nodejs app.js" 8 | }, 9 | "author": "", 10 | "private": "true", 11 | "dependencies": { 12 | "ws": "^6.1.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /restart_app/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var debug = false 4 | 5 | var request = require('request') 6 | var isRestarting = false 7 | var now = Math.floor(new Date() / 1000) 8 | var timeout = (1000 * 60) * 30 9 | var updateCheckInterval = (1000 * 60) * 5 10 | 11 | if (debug) { 12 | updateCheckInterval = 5000 // Check every 5 seconds 13 | timeout = 1000 * 60 // Timeout after a minute 14 | } 15 | 16 | // Timeout after 30 minutes and restart 17 | setTimeout(function () { 18 | console.log('RestartApp::Timeout exceeded, forcing a restart') 19 | restart() 20 | }, timeout) 21 | 22 | // Start checking for client updates 23 | checkForClientUpdate() 24 | 25 | function checkForClientUpdate () { 26 | if (isRestarting) { 27 | if (debug) console.log("RestartApp::We're restarting, skipping client update check..") 28 | return 29 | } 30 | 31 | console.log('RestartApp::Checking if a client update is available..') 32 | request({ url: 'https://whenisupdate.com/api.json', headers: { Referer: 'rust-docker-server' }, timeout: 10000 }, function (error, response, body) { 33 | if (!error && response.statusCode === 200) { 34 | var info = JSON.parse(body) 35 | var latest = info.latest 36 | if (latest !== undefined && latest.length > 0) { 37 | if (latest >= now) { 38 | console.log('RestartApp::Client update is out, forcing a restart') 39 | restart() 40 | return 41 | } 42 | } 43 | if (debug) console.log('RestartApp::Client update not out yet..') 44 | } else if (debug) { 45 | console.log('RestartApp::Error: ' + error) 46 | } 47 | 48 | // Keep checking for client updates every 5 minutes 49 | setTimeout(function () { 50 | checkForClientUpdate() 51 | }, updateCheckInterval) 52 | }) 53 | } 54 | 55 | function restart () { 56 | if (debug) console.log('RestartApp::Restarting..') 57 | if (isRestarting) { 58 | if (debug) console.log("RestartApp::We're already restarting..") 59 | return 60 | } 61 | isRestarting = true 62 | 63 | var serverHostname = 'localhost' 64 | var serverPort = process.env.RUST_RCON_PORT 65 | var serverPassword = process.env.RUST_RCON_PASSWORD 66 | 67 | var WebSocket = require('ws') 68 | var ws = new WebSocket('ws://' + serverHostname + ':' + serverPort + '/' + serverPassword) 69 | ws.on('open', function open () { 70 | setTimeout(function () { 71 | ws.send(createPacket("say NOTICE: We're updating the server in 5 minutes, so get to a safe spot!")) 72 | setTimeout(function () { 73 | ws.send(createPacket("say NOTICE: We're updating the server in 4 minutes, so get to a safe spot!")) 74 | setTimeout(function () { 75 | ws.send(createPacket("say NOTICE: We're updating the server in 3 minutes, so get to a safe spot!")) 76 | setTimeout(function () { 77 | ws.send(createPacket("say NOTICE: We're updating the server in 2 minutes, so get to a safe spot!")) 78 | setTimeout(function () { 79 | ws.send(createPacket("say NOTICE: We're updating the server in 1 minute, so get to a safe spot!")) 80 | setTimeout(function () { 81 | ws.send(createPacket('global.kickall Updating/Restarting')) 82 | setTimeout(function () { 83 | ws.send(createPacket('quit')) 84 | // ws.send(createPacket("restart 60")); // NOTE: Don't use restart, because that doesn't actually restart the container! 85 | setTimeout(function () { 86 | ws.close(1000) 87 | 88 | // After 2 minutes, if the server's still running, forcibly shut it down 89 | setTimeout(function () { 90 | var fs = require('fs') 91 | fs.unlinkSync('/tmp/restart_app.lock') 92 | 93 | var childProcess = require('child_process') 94 | childProcess.execSync('kill -s 2 $(pidof bash)') 95 | }, 1000 * 60 * 2) 96 | }, 1000) 97 | }, 1000) 98 | }, 1000 * 60) 99 | }, 1000 * 60) 100 | }, 1000 * 60) 101 | }, 1000 * 60) 102 | }, 1000 * 60) 103 | }, 1000) 104 | }) 105 | } 106 | 107 | function createPacket (command) { 108 | var packet = 109 | { 110 | Identifier: -1, 111 | Message: command, 112 | Name: 'WebRcon' 113 | } 114 | return JSON.stringify(packet) 115 | } 116 | -------------------------------------------------------------------------------- /restart_app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restart_app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "6.6.2", 9 | "resolved": "https://npm.didstopia.com/ajv/-/ajv-6.6.2.tgz", 10 | "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", 11 | "requires": { 12 | "fast-deep-equal": "^2.0.1", 13 | "fast-json-stable-stringify": "^2.0.0", 14 | "json-schema-traverse": "^0.4.1", 15 | "uri-js": "^4.2.2" 16 | } 17 | }, 18 | "asn1": { 19 | "version": "0.2.4", 20 | "resolved": "https://npm.didstopia.com/asn1/-/asn1-0.2.4.tgz", 21 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 22 | "requires": { 23 | "safer-buffer": "~2.1.0" 24 | } 25 | }, 26 | "assert-plus": { 27 | "version": "1.0.0", 28 | "resolved": "https://npm.didstopia.com/assert-plus/-/assert-plus-1.0.0.tgz", 29 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 30 | }, 31 | "async-limiter": { 32 | "version": "1.0.0", 33 | "resolved": "https://npm.didstopia.com/async-limiter/-/async-limiter-1.0.0.tgz", 34 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 35 | }, 36 | "asynckit": { 37 | "version": "0.4.0", 38 | "resolved": "https://npm.didstopia.com/asynckit/-/asynckit-0.4.0.tgz", 39 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 40 | }, 41 | "aws-sign2": { 42 | "version": "0.7.0", 43 | "resolved": "https://npm.didstopia.com/aws-sign2/-/aws-sign2-0.7.0.tgz", 44 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 45 | }, 46 | "aws4": { 47 | "version": "1.8.0", 48 | "resolved": "https://npm.didstopia.com/aws4/-/aws4-1.8.0.tgz", 49 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 50 | }, 51 | "bcrypt-pbkdf": { 52 | "version": "1.0.2", 53 | "resolved": "https://npm.didstopia.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 54 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 55 | "requires": { 56 | "tweetnacl": "^0.14.3" 57 | } 58 | }, 59 | "caseless": { 60 | "version": "0.12.0", 61 | "resolved": "https://npm.didstopia.com/caseless/-/caseless-0.12.0.tgz", 62 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 63 | }, 64 | "combined-stream": { 65 | "version": "1.0.7", 66 | "resolved": "https://npm.didstopia.com/combined-stream/-/combined-stream-1.0.7.tgz", 67 | "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", 68 | "requires": { 69 | "delayed-stream": "~1.0.0" 70 | } 71 | }, 72 | "core-util-is": { 73 | "version": "1.0.2", 74 | "resolved": "https://npm.didstopia.com/core-util-is/-/core-util-is-1.0.2.tgz", 75 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 76 | }, 77 | "dashdash": { 78 | "version": "1.14.1", 79 | "resolved": "https://npm.didstopia.com/dashdash/-/dashdash-1.14.1.tgz", 80 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 81 | "requires": { 82 | "assert-plus": "^1.0.0" 83 | } 84 | }, 85 | "delayed-stream": { 86 | "version": "1.0.0", 87 | "resolved": "https://npm.didstopia.com/delayed-stream/-/delayed-stream-1.0.0.tgz", 88 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 89 | }, 90 | "ecc-jsbn": { 91 | "version": "0.1.2", 92 | "resolved": "https://npm.didstopia.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 93 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 94 | "requires": { 95 | "jsbn": "~0.1.0", 96 | "safer-buffer": "^2.1.0" 97 | } 98 | }, 99 | "extend": { 100 | "version": "3.0.2", 101 | "resolved": "https://npm.didstopia.com/extend/-/extend-3.0.2.tgz", 102 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 103 | }, 104 | "extsprintf": { 105 | "version": "1.3.0", 106 | "resolved": "https://npm.didstopia.com/extsprintf/-/extsprintf-1.3.0.tgz", 107 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 108 | }, 109 | "fast-deep-equal": { 110 | "version": "2.0.1", 111 | "resolved": "https://npm.didstopia.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 112 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 113 | }, 114 | "fast-json-stable-stringify": { 115 | "version": "2.0.0", 116 | "resolved": "https://npm.didstopia.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 117 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 118 | }, 119 | "forever-agent": { 120 | "version": "0.6.1", 121 | "resolved": "https://npm.didstopia.com/forever-agent/-/forever-agent-0.6.1.tgz", 122 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 123 | }, 124 | "form-data": { 125 | "version": "2.3.3", 126 | "resolved": "https://npm.didstopia.com/form-data/-/form-data-2.3.3.tgz", 127 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 128 | "requires": { 129 | "asynckit": "^0.4.0", 130 | "combined-stream": "^1.0.6", 131 | "mime-types": "^2.1.12" 132 | } 133 | }, 134 | "getpass": { 135 | "version": "0.1.7", 136 | "resolved": "https://npm.didstopia.com/getpass/-/getpass-0.1.7.tgz", 137 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 138 | "requires": { 139 | "assert-plus": "^1.0.0" 140 | } 141 | }, 142 | "har-schema": { 143 | "version": "2.0.0", 144 | "resolved": "https://npm.didstopia.com/har-schema/-/har-schema-2.0.0.tgz", 145 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 146 | }, 147 | "har-validator": { 148 | "version": "5.1.3", 149 | "resolved": "https://npm.didstopia.com/har-validator/-/har-validator-5.1.3.tgz", 150 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 151 | "requires": { 152 | "ajv": "^6.5.5", 153 | "har-schema": "^2.0.0" 154 | } 155 | }, 156 | "http-signature": { 157 | "version": "1.2.0", 158 | "resolved": "https://npm.didstopia.com/http-signature/-/http-signature-1.2.0.tgz", 159 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 160 | "requires": { 161 | "assert-plus": "^1.0.0", 162 | "jsprim": "^1.2.2", 163 | "sshpk": "^1.7.0" 164 | } 165 | }, 166 | "is-typedarray": { 167 | "version": "1.0.0", 168 | "resolved": "https://npm.didstopia.com/is-typedarray/-/is-typedarray-1.0.0.tgz", 169 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 170 | }, 171 | "isstream": { 172 | "version": "0.1.2", 173 | "resolved": "https://npm.didstopia.com/isstream/-/isstream-0.1.2.tgz", 174 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 175 | }, 176 | "jsbn": { 177 | "version": "0.1.1", 178 | "resolved": "https://npm.didstopia.com/jsbn/-/jsbn-0.1.1.tgz", 179 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 180 | }, 181 | "json-schema": { 182 | "version": "0.2.3", 183 | "resolved": "https://npm.didstopia.com/json-schema/-/json-schema-0.2.3.tgz", 184 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 185 | }, 186 | "json-schema-traverse": { 187 | "version": "0.4.1", 188 | "resolved": "https://npm.didstopia.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 189 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 190 | }, 191 | "json-stringify-safe": { 192 | "version": "5.0.1", 193 | "resolved": "https://npm.didstopia.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 194 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 195 | }, 196 | "jsprim": { 197 | "version": "1.4.1", 198 | "resolved": "https://npm.didstopia.com/jsprim/-/jsprim-1.4.1.tgz", 199 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 200 | "requires": { 201 | "assert-plus": "1.0.0", 202 | "extsprintf": "1.3.0", 203 | "json-schema": "0.2.3", 204 | "verror": "1.10.0" 205 | } 206 | }, 207 | "mime-db": { 208 | "version": "1.37.0", 209 | "resolved": "https://npm.didstopia.com/mime-db/-/mime-db-1.37.0.tgz", 210 | "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" 211 | }, 212 | "mime-types": { 213 | "version": "2.1.21", 214 | "resolved": "https://npm.didstopia.com/mime-types/-/mime-types-2.1.21.tgz", 215 | "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", 216 | "requires": { 217 | "mime-db": "~1.37.0" 218 | } 219 | }, 220 | "oauth-sign": { 221 | "version": "0.9.0", 222 | "resolved": "https://npm.didstopia.com/oauth-sign/-/oauth-sign-0.9.0.tgz", 223 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 224 | }, 225 | "performance-now": { 226 | "version": "2.1.0", 227 | "resolved": "https://npm.didstopia.com/performance-now/-/performance-now-2.1.0.tgz", 228 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 229 | }, 230 | "psl": { 231 | "version": "1.1.31", 232 | "resolved": "https://npm.didstopia.com/psl/-/psl-1.1.31.tgz", 233 | "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" 234 | }, 235 | "punycode": { 236 | "version": "2.1.1", 237 | "resolved": "https://npm.didstopia.com/punycode/-/punycode-2.1.1.tgz", 238 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 239 | }, 240 | "qs": { 241 | "version": "6.5.2", 242 | "resolved": "https://npm.didstopia.com/qs/-/qs-6.5.2.tgz", 243 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 244 | }, 245 | "request": { 246 | "version": "2.88.0", 247 | "resolved": "https://npm.didstopia.com/request/-/request-2.88.0.tgz", 248 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 249 | "requires": { 250 | "aws-sign2": "~0.7.0", 251 | "aws4": "^1.8.0", 252 | "caseless": "~0.12.0", 253 | "combined-stream": "~1.0.6", 254 | "extend": "~3.0.2", 255 | "forever-agent": "~0.6.1", 256 | "form-data": "~2.3.2", 257 | "har-validator": "~5.1.0", 258 | "http-signature": "~1.2.0", 259 | "is-typedarray": "~1.0.0", 260 | "isstream": "~0.1.2", 261 | "json-stringify-safe": "~5.0.1", 262 | "mime-types": "~2.1.19", 263 | "oauth-sign": "~0.9.0", 264 | "performance-now": "^2.1.0", 265 | "qs": "~6.5.2", 266 | "safe-buffer": "^5.1.2", 267 | "tough-cookie": "~2.4.3", 268 | "tunnel-agent": "^0.6.0", 269 | "uuid": "^3.3.2" 270 | } 271 | }, 272 | "safe-buffer": { 273 | "version": "5.1.2", 274 | "resolved": "https://npm.didstopia.com/safe-buffer/-/safe-buffer-5.1.2.tgz", 275 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 276 | }, 277 | "safer-buffer": { 278 | "version": "2.1.2", 279 | "resolved": "https://npm.didstopia.com/safer-buffer/-/safer-buffer-2.1.2.tgz", 280 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 281 | }, 282 | "sshpk": { 283 | "version": "1.16.0", 284 | "resolved": "https://npm.didstopia.com/sshpk/-/sshpk-1.16.0.tgz", 285 | "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==", 286 | "requires": { 287 | "asn1": "~0.2.3", 288 | "assert-plus": "^1.0.0", 289 | "bcrypt-pbkdf": "^1.0.0", 290 | "dashdash": "^1.12.0", 291 | "ecc-jsbn": "~0.1.1", 292 | "getpass": "^0.1.1", 293 | "jsbn": "~0.1.0", 294 | "safer-buffer": "^2.0.2", 295 | "tweetnacl": "~0.14.0" 296 | } 297 | }, 298 | "tough-cookie": { 299 | "version": "2.4.3", 300 | "resolved": "https://npm.didstopia.com/tough-cookie/-/tough-cookie-2.4.3.tgz", 301 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 302 | "requires": { 303 | "psl": "^1.1.24", 304 | "punycode": "^1.4.1" 305 | }, 306 | "dependencies": { 307 | "punycode": { 308 | "version": "1.4.1", 309 | "resolved": "https://npm.didstopia.com/punycode/-/punycode-1.4.1.tgz", 310 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 311 | } 312 | } 313 | }, 314 | "tunnel-agent": { 315 | "version": "0.6.0", 316 | "resolved": "https://npm.didstopia.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 317 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 318 | "requires": { 319 | "safe-buffer": "^5.0.1" 320 | } 321 | }, 322 | "tweetnacl": { 323 | "version": "0.14.5", 324 | "resolved": "https://npm.didstopia.com/tweetnacl/-/tweetnacl-0.14.5.tgz", 325 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 326 | }, 327 | "uri-js": { 328 | "version": "4.2.2", 329 | "resolved": "https://npm.didstopia.com/uri-js/-/uri-js-4.2.2.tgz", 330 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 331 | "requires": { 332 | "punycode": "^2.1.0" 333 | } 334 | }, 335 | "uuid": { 336 | "version": "3.3.2", 337 | "resolved": "https://npm.didstopia.com/uuid/-/uuid-3.3.2.tgz", 338 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 339 | }, 340 | "verror": { 341 | "version": "1.10.0", 342 | "resolved": "https://npm.didstopia.com/verror/-/verror-1.10.0.tgz", 343 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 344 | "requires": { 345 | "assert-plus": "^1.0.0", 346 | "core-util-is": "1.0.2", 347 | "extsprintf": "^1.2.0" 348 | } 349 | }, 350 | "ws": { 351 | "version": "6.1.2", 352 | "resolved": "https://npm.didstopia.com/ws/-/ws-6.1.2.tgz", 353 | "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", 354 | "requires": { 355 | "async-limiter": "~1.0.0" 356 | } 357 | } 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /restart_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restart_app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "nodejs app.js" 8 | }, 9 | "author": "", 10 | "private": "true", 11 | "dependencies": { 12 | "request": "^2.88.0", 13 | "ws": "^6.1.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scheduler_app/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var debug = false 4 | 5 | var childProcess = require('child_process') 6 | 7 | var startupDelayInSeconds = 60 * 5 8 | var runIntervalInSeconds = 60 * 5 9 | 10 | if (debug) { 11 | startupDelayInSeconds = 1 12 | runIntervalInSeconds = 60 13 | } 14 | 15 | // Start the endless loop after a delay (allow the server to start) 16 | setTimeout(function () { 17 | checkForUpdates() 18 | }, 1000 * startupDelayInSeconds) 19 | 20 | function checkForUpdates () { 21 | setTimeout(function () { 22 | if (debug) console.log('SchedulerApp::Running bash /app/update_check.sh') 23 | childProcess.exec('bash /app/update_check.sh', { env: process.env }, function (err, stdout, stderr) { 24 | if (debug) console.log('SchedulerApp::bash /app/update_check.sh STDOUT: ' + stdout) 25 | if (debug && err) console.log('SchedulerApp::bash /app/update_check.sh ERR: ' + err) 26 | if (debug && stderr) console.log('SchedulerApp::bash /app/update_check.sh STDERR: ' + stderr) 27 | checkForUpdates() 28 | }) 29 | }, 1000 * runIntervalInSeconds) 30 | } 31 | -------------------------------------------------------------------------------- /scheduler_app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scheduler_app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /scheduler_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scheduler_app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "nodejs app.js" 8 | }, 9 | "author": "", 10 | "private": "true" 11 | } 12 | -------------------------------------------------------------------------------- /shutdown_app/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var serverHostname = 'localhost' 4 | var serverPort = process.env.RUST_RCON_PORT 5 | var serverPassword = process.env.RUST_RCON_PASSWORD 6 | 7 | console.log('ShutdownApp::RCON connecting to server') 8 | 9 | var WebSocket = require('ws') 10 | var ws = new WebSocket('ws://' + serverHostname + ':' + serverPort + '/' + serverPassword) 11 | ws.on('open', function open () { 12 | console.log('ShutdownApp::RCON connection opened') 13 | setTimeout(function () { 14 | console.log('ShutdownApp::RCON sending "save" command') 15 | setTimeout(function () { 16 | console.log('ShutdownApp::RCON sending "quit" command') 17 | ws.send(createPacket('quit')) 18 | setTimeout(function () { 19 | console.log('ShutdownApp::RCON terminating') 20 | ws.close(1000) 21 | }, 1000) 22 | }, 1000) 23 | }, 1000) 24 | }) 25 | ws.on('close', function close () { 26 | console.log('ShutdownApp::RCON connection closed') 27 | process.exit(0) 28 | }) 29 | ws.on('error', function (err) { 30 | console.log('ShutdownApp::RCON error:', err) 31 | process.exit(1) 32 | }) 33 | 34 | function createPacket (command) { 35 | var packet = 36 | { 37 | Identifier: -1, 38 | Message: command, 39 | Name: 'WebRcon' 40 | } 41 | return JSON.stringify(packet) 42 | } 43 | -------------------------------------------------------------------------------- /shutdown_app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shutdown_app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async-limiter": { 8 | "version": "1.0.0", 9 | "resolved": "https://npm.didstopia.com/async-limiter/-/async-limiter-1.0.0.tgz", 10 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 11 | }, 12 | "ws": { 13 | "version": "6.1.2", 14 | "resolved": "https://npm.didstopia.com/ws/-/ws-6.1.2.tgz", 15 | "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", 16 | "requires": { 17 | "async-limiter": "~1.0.0" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /shutdown_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shutdown_app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "nodejs app.js" 8 | }, 9 | "author": "", 10 | "private": "true", 11 | "dependencies": { 12 | "ws": "^6.1.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /start_rust.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Enable debugging 4 | # set -x 5 | 6 | # Print the user we're currently running as 7 | echo "Running as user: $(whoami)" 8 | 9 | # Define the exit handler 10 | exit_handler() 11 | { 12 | echo "Shutdown signal received" 13 | 14 | # Execute the RCON shutdown command 15 | node /app/shutdown_app/app.js 16 | killer=$! 17 | wait "$killer" 18 | 19 | # Stop the web server 20 | pkill -f nginx 21 | 22 | echo "Exiting.." 23 | exit 24 | } 25 | 26 | # Trap specific signals and forward to the exit handler 27 | trap 'exit_handler' SIGINT SIGTERM 28 | 29 | # Rust includes a 64-bit version of steamclient.so, so we need to tell the OS where it exists 30 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/steamcmd/rust/RustDedicated_Data/Plugins/x86_64 31 | 32 | # Define the install/update function 33 | install_or_update() 34 | { 35 | # Install Rust from install.txt 36 | echo "Installing or updating Rust.. (this might take a while, be patient)" 37 | bash /steamcmd/steamcmd.sh +runscript /app/install.txt 38 | 39 | # Terminate if exit code wasn't zero 40 | if [ $? -ne 0 ]; then 41 | echo "Exiting, steamcmd install or update failed!" 42 | exit 1 43 | fi 44 | } 45 | 46 | # Remove old lock files (used by restart_app/ and update_check.sh) 47 | rm -fr /tmp/*.lock 48 | 49 | # Create the necessary folder structure 50 | if [ ! -d "/steamcmd/rust" ]; then 51 | echo "Missing /steamcmd/rust, creating.." 52 | mkdir -p /steamcmd/rust 53 | fi 54 | if [ ! -d "/steamcmd/rust/server/${RUST_SERVER_IDENTITY}" ]; then 55 | echo "Missing /steamcmd/rust/server/${RUST_SERVER_IDENTITY}, creating.." 56 | mkdir -p "/steamcmd/rust/server/${RUST_SERVER_IDENTITY}" 57 | fi 58 | 59 | # Install/update steamcmd 60 | echo "Installing/updating steamcmd.." 61 | curl -s http://media.steampowered.com/installer/steamcmd_linux.tar.gz | bsdtar -xvf- -C /steamcmd 62 | 63 | # Check which branch to use 64 | if [ ! -z ${RUST_BRANCH+x} ]; then 65 | echo "Using branch arguments: $RUST_BRANCH" 66 | 67 | # Add "-beta" if necessary 68 | INSTALL_BRANCH="${RUST_BRANCH}" 69 | if [ ! "$RUST_BRANCH" == "public" ]; then 70 | INSTALL_BRANCH="-beta ${RUST_BRANCH}" 71 | fi 72 | sed -i "s/app_update 258550.*validate/app_update 258550 $INSTALL_BRANCH validate/g" /app/install.txt 73 | else 74 | sed -i "s/app_update 258550.*validate/app_update 258550 validate/g" /app/install.txt 75 | fi 76 | 77 | # Disable auto-update if start mode is 2 78 | if [ "$RUST_START_MODE" = "2" ]; then 79 | # Check that Rust exists in the first place 80 | if [ ! -f "/steamcmd/rust/RustDedicated" ]; then 81 | install_or_update 82 | else 83 | echo "Rust seems to be installed, skipping automatic update.." 84 | fi 85 | else 86 | install_or_update 87 | 88 | # Run the update check if it's not been run before 89 | if [ ! -f "/steamcmd/rust/build.id" ]; then 90 | ./app/update_check.sh 91 | else 92 | OLD_BUILDID="$(cat /steamcmd/rust/build.id)" 93 | STRING_SIZE=${#OLD_BUILDID} 94 | if [ "$STRING_SIZE" -lt "6" ]; then 95 | ./app/update_check.sh 96 | fi 97 | fi 98 | fi 99 | 100 | # Check if Oxide is enabled 101 | if [ "$RUST_OXIDE_ENABLED" = "1" ]; then 102 | # Next check if Oxide doesn't' exist, or if we want to always update it 103 | INSTALL_OXIDE="0" 104 | if [ ! -f "/steamcmd/rust/CSharpCompiler.x86_x64" ]; then 105 | INSTALL_OXIDE="1" 106 | fi 107 | if [ "$RUST_OXIDE_UPDATE_ON_BOOT" = "1" ]; then 108 | INSTALL_OXIDE="1" 109 | fi 110 | 111 | # If necessary, download and install latest Oxide 112 | if [ "$INSTALL_OXIDE" = "1" ]; then 113 | echo "Downloading and installing latest Oxide.." 114 | OXIDE_URL="https://umod.org/games/rust/download/develop" 115 | curl -sL $OXIDE_URL | bsdtar -xvf- -C /steamcmd/rust/ 116 | chmod 755 /steamcmd/rust/CSharpCompiler.x86_x64 > /dev/null 2>&1 & 117 | fi 118 | fi 119 | 120 | # Start mode 1 means we only want to update 121 | if [ "$RUST_START_MODE" = "1" ]; then 122 | echo "Exiting, start mode is 1.." 123 | exit 124 | fi 125 | 126 | # Remove extra whitespace from startup command 127 | RUST_STARTUP_COMMAND=$(echo "$RUST_SERVER_STARTUP_ARGUMENTS" | tr -s " ") 128 | 129 | # Add RCON support if necessary 130 | if [ ! -z ${RUST_RCON_PORT+x} ]; then 131 | RUST_STARTUP_COMMAND="$RUST_STARTUP_COMMAND +rcon.port $RUST_RCON_PORT" 132 | fi 133 | if [ ! -z ${RUST_RCON_PASSWORD+x} ]; then 134 | RUST_STARTUP_COMMAND="$RUST_STARTUP_COMMAND +rcon.password $RUST_RCON_PASSWORD" 135 | fi 136 | 137 | if [ ! -z ${RUST_RCON_WEB+x} ]; then 138 | RUST_STARTUP_COMMAND="$RUST_STARTUP_COMMAND +rcon.web $RUST_RCON_WEB" 139 | if [ "$RUST_RCON_WEB" = "1" ]; then 140 | # Fix the webrcon (customizes a few elements) 141 | bash -c "RUST_RCON_SECURE_WEBSOCKET=${RUST_RCON_SECURE_WEBSOCKET} /tmp/fix_conn.sh" 142 | 143 | # Start nginx (in the background) 144 | echo "Starting web server.." 145 | nginx 146 | NGINX=$! 147 | sleep 5 148 | fi 149 | fi 150 | 151 | # Disable logrotate if "-logfile" is set in $RUST_STARTUP_COMMAND 152 | LOGROTATE_ENABLED=1 153 | RUST_STARTUP_COMMAND_LOWERCASE=`echo "$RUST_STARTUP_COMMAND" | sed 's/./\L&/g'` 154 | if [[ $RUST_STARTUP_COMMAND_LOWERCASE == *" -logfile "* ]]; then 155 | LOGROTATE_ENABLED=0 156 | fi 157 | 158 | if [ "$LOGROTATE_ENABLED" = "1" ]; then 159 | echo "Log rotation enabled!" 160 | 161 | # Log to stdout by default 162 | RUST_STARTUP_COMMAND="$RUST_STARTUP_COMMAND -logfile /dev/stdout" 163 | echo "Using startup arguments: $RUST_SERVER_STARTUP_ARGUMENTS" 164 | 165 | # Create the logging directory structure 166 | if [ ! -d "/steamcmd/rust/logs/archive" ]; then 167 | mkdir -p /steamcmd/rust/logs/archive 168 | fi 169 | 170 | # Set the logfile filename/path 171 | DATE=`date '+%Y-%m-%d_%H-%M-%S'` 172 | RUST_SERVER_LOG_FILE="/steamcmd/rust/logs/$RUST_SERVER_IDENTITY"_"$DATE.txt" 173 | 174 | # Archive old logs 175 | echo "Cleaning up old logs.." 176 | mv /steamcmd/rust/logs/*.txt /steamcmd/rust/logs/archive | true 177 | else 178 | echo "Log rotation disabled!" 179 | fi 180 | 181 | # Disable logging to stdout/stderr if "-logfile /dev/" is used 182 | STDLOG_ENABLED=1 183 | if [[ $RUST_STARTUP_COMMAND_LOWERCASE == *" -logfile /dev/"* ]]; then 184 | echo "Disabling internal stdout/stderr logging!" 185 | STDLOG_ENABLED=0 186 | fi 187 | 188 | # Start the scheduler (only if update checking is enabled) 189 | if [ "$RUST_UPDATE_CHECKING" = "1" ]; then 190 | echo "Starting scheduled task manager.." 191 | node /app/scheduler_app/app.js & 192 | fi 193 | 194 | # Start the heartbeat(only if enabled) 195 | if [ "$RUST_HEARTBEAT" = "1" ]; then 196 | echo "Starting heartbeat.." 197 | node /app/heartbeat_app/app.js & 198 | fi 199 | 200 | # Set the working directory 201 | cd /steamcmd/rust 202 | 203 | # Run the server 204 | echo "Starting Rust.." 205 | 206 | # Preparing arguments 207 | function add_argument_pair { 208 | local -n arr=$1 209 | if [ ! -z "${!3}" ]; then 210 | arr+=("${2}"); 211 | arr+=("${!3}"); 212 | fi 213 | } 214 | 215 | ARGUMENTS=() 216 | 217 | add_argument_pair ARGUMENTS "+server.port" "RUST_SERVER_PORT" 218 | add_argument_pair ARGUMENTS "+server.queryport" "RUST_SERVER_QUERYPORT" 219 | add_argument_pair ARGUMENTS "+server.identity" "RUST_SERVER_IDENTITY" 220 | 221 | 222 | if [ -z "$RUST_SERVER_LEVELURL" ]; then 223 | add_argument_pair ARGUMENTS "+server.worldsize" "RUST_SERVER_WORLDSIZE" 224 | add_argument_pair ARGUMENTS "+server.seed" "RUST_SERVER_SEED" 225 | echo "Generating procedural map.." 226 | else 227 | add_argument_pair ARGUMENTS "+server.levelurl" "RUST_SERVER_LEVELURL" 228 | echo "Using custom map.." 229 | fi 230 | 231 | add_argument_pair ARGUMENTS "+server.hostname" "RUST_SERVER_NAME" 232 | add_argument_pair ARGUMENTS "+server.url" "RUST_SERVER_URL" 233 | add_argument_pair ARGUMENTS "+server.headerimage" "RUST_SERVER_BANNER_URL" 234 | add_argument_pair ARGUMENTS "+server.description" "RUST_SERVER_DESCRIPTION" 235 | add_argument_pair ARGUMENTS "+server.maxplayers" "RUST_SERVER_MAXPLAYERS" 236 | add_argument_pair ARGUMENTS "+server.saveinterval" "RUST_SERVER_SAVE_INTERVAL" 237 | add_argument_pair ARGUMENTS "+app.port" "RUST_APP_PORT" 238 | 239 | if [ "$LOGROTATE_ENABLED" = "1" ]; then 240 | unbuffer /steamcmd/rust/RustDedicated $RUST_STARTUP_COMMAND "${ARGUMENTS[@]}" 2>&1 | grep --line-buffered -Ev '^\s*$|Filename' | tee $RUST_SERVER_LOG_FILE & 241 | elif [ "$STDLOG_ENABLED" = "1" ]; then 242 | /steamcmd/rust/RustDedicated $RUST_STARTUP_COMMAND "${ARGUMENTS[@]}" 2>&1 & 243 | else 244 | /steamcmd/rust/RustDedicated $RUST_STARTUP_COMMAND "${ARGUMENTS[@]}" & 245 | fi 246 | 247 | child=$! 248 | wait "$child" 249 | 250 | pkill -f nginx 251 | 252 | echo "Exiting.." 253 | exit 254 | -------------------------------------------------------------------------------- /update_check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -m 4 | 5 | # Use a lock file to determine if we're already checking for updates 6 | if ! mkdir /tmp/update_check.lock; then 7 | echo "Failed to acquire lock" 8 | exit 1 9 | fi 10 | trap 'rm -rf /tmp/update_check.lock' EXIT # remove the lockdir on exit 11 | 12 | # Check if restart app is already running and bail out if so 13 | #IS_RUNNING=`pgrep -fl restart_app` 14 | #if [ $IS_RUNNING -ge 1 ]; then 15 | # echo "Update checker is already running.." 16 | # exit 17 | #fi 18 | 19 | # Check if we are auto-updating or not 20 | if [ "$RUST_UPDATE_CHECKING" = "1" ]; then 21 | echo "Checking Steam for updates.." 22 | else 23 | exit 0 24 | fi 25 | 26 | # Get the old build id (default to 0) 27 | OLD_BUILDID=0 28 | if [ -f "/steamcmd/rust/build.id" ]; then 29 | OLD_BUILDID="$(cat /steamcmd/rust/build.id)" 30 | fi 31 | 32 | # Minimal validation for the update branch 33 | STRING_SIZE=${#RUST_UPDATE_BRANCH} 34 | if [ "$STRING_SIZE" -lt "1" ]; then 35 | RUST_UPDATE_BRANCH=public 36 | fi 37 | 38 | # Remove the old cached app info if it exists 39 | # if [ -f "/root/Steam/appcache/appinfo.vdf" ]; then 40 | # rm -fr /root/Steam/appcache/appinfo.vdf 41 | # fi 42 | if [ -f "/app/Steam/appcache/appinfo.vdf" ]; then 43 | rm -fr /app/Steam/appcache/appinfo.vdf 44 | fi 45 | 46 | # Get the new build id directly from Steam 47 | NEW_BUILDID="$(./steamcmd/steamcmd.sh +login anonymous +app_info_update 1 +app_info_print "258550" +quit | grep -EA 1000 "^\s+\"branches\"$" | grep -EA 5 "^\s+\"$RUST_UPDATE_BRANCH\"$" | grep -m 1 -EB 10 "^\s+}$" | grep -E "^\s+\"buildid\"\s+" | tr '[:blank:]"' ' ' | tr -s ' ' | sed "s/ buildid //g" | xargs)" 48 | 49 | # Check that we actually got a new build id 50 | STRING_SIZE=${#NEW_BUILDID} 51 | if [ "$STRING_SIZE" -lt "6" ]; then 52 | echo "Error getting latest server build id from Steam.." 53 | exit 54 | fi 55 | 56 | # Skip update checking if this is the first time 57 | if [ ! -f "/steamcmd/rust/build.id" ]; then 58 | echo "First time running update check (server build id not found), skipping update.." 59 | echo $NEW_BUILDID > /steamcmd/rust/build.id 60 | exit 61 | else 62 | STRING_SIZE=${#OLD_BUILDID} 63 | if [ "$STRING_SIZE" -lt "6" ]; then 64 | echo "First time running update check (server build id empty), skipping update.." 65 | echo $NEW_BUILDID > /steamcmd/rust/build.id 66 | exit 67 | fi 68 | fi 69 | 70 | # Check if the builds match and quit if so 71 | if [ "$OLD_BUILDID" = "$NEW_BUILDID" ]; then 72 | echo "Build id $OLD_BUILDID is already the latest, skipping update.." 73 | exit 74 | else 75 | # Use a lock file to determine if we're already checking for updates 76 | if ! mkdir /tmp/restart_app.lock; then 77 | echo "Failed to acquire lock" 78 | exit 1 79 | fi 80 | 81 | echo "Latest server build id ($NEW_BUILDID) is newer than the current one ($OLD_BUILDID), waiting for client update.." 82 | echo $NEW_BUILDID > /steamcmd/rust/build.id 83 | exec node /app/restart_app/app.js 84 | child=$! 85 | wait "$child" 86 | fi 87 | --------------------------------------------------------------------------------