diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 503b771..eda3f63 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,91 +1,39 @@ -name: Docker +name: Publish Docker Image to GHCR on: push: - branches: [ "master" ] - # Publish semver tags as releases. - tags: [ 'v*.*.*' ] + branches: + - main pull_request: - branches: [ "master" ] - -env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io - # github.repository as / - IMAGE_NAME: ${{ github.repository }} - jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write - + test: + name: Start script tests + runs-on: ubuntu-24.04 steps: - - name: Checkout repository + - uses: actions/checkout@v4 + - run: bash test/install_or_update_test.sh + + build-and-publish: + name: Build and Publish Docker Image + needs: test + # PRs run the tests only; the image is published from main. + if: github.event_name == 'push' + runs-on: ubuntu-24.04 + steps: + - name: Checkout repo uses: actions/checkout@v4 - - # Install the cosign tool except on PR - # https://github.com/sigstore/cosign-installer - - name: Install cosign - if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@v3.3.0 - with: - cosign-release: 'v2.2.2' # optional - - # Set up BuildKit Docker container builder to be able to build - # multi-platform images and export cache - # https://github.com/docker/setup-buildx-action - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@master # v3.0.0 - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@master # v3.0.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@master # v5.0.0 + - name: Login to GitHub Container Registry + # this step requires a GH_TOKEN with read:packages write:packages and delete:packages + # tested with a classic token with the above permissions + uses: docker/login-action@v3 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@master # v5.0.0 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # Sign the resulting Docker image digest except on PRs. - # This will only write to the public Rekor transparency log when the Docker - # repository is public to avoid leaking data. If you would like to publish - # transparency data even for private images, pass --force to cosign below. - # https://github.com/sigstore/cosign - - name: Sign the published Docker image - if: ${{ github.event_name != 'pull_request' }} - env: - # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable - TAGS: ${{ steps.meta.outputs.tags }} - DIGEST: ${{ steps.build-and-push.outputs.digest }} - # This step uses the identity token to provision an ephemeral certificate - # against the sigstore community Fulcio instance. - run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} + registry: ghcr.io + username: ${{github.actor}} + password: ${{secrets.GITHUB_TOKEN}} + - name: Build and Publish Base Image + run: | + docker build . --tag ghcr.io/compscidr/rust-server:$(date +%m-%d-%Y) + docker tag ghcr.io/compscidr/rust-server:$(date +%m-%d-%Y) ghcr.io/compscidr/rust-server:latest + docker push ghcr.io/compscidr/rust-server:$(date +%m-%d-%Y) + docker push ghcr.io/compscidr/rust-server:latest \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5729baa..39d487e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea sample.txt rust_data*/ scheduler_app/node_modules/ @@ -44,3 +45,5 @@ man/man5 man/man8 pyenv vendor/pkg/ + +rust-server diff --git a/Dockerfile b/Dockerfile index 0353694..fb8f468 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ -FROM didstopia/base:nodejs-12-steamcmd-ubuntu-18.04 +# nodejs-22-steamcmd-ubuntu-24.04 (Ubuntu 24.04, glibc 2.39, node 22) pinned by digest +FROM didstopia/base:nodejs-22-steamcmd-ubuntu-24.04@sha256:ae8f7bdab405f669eadcc7178627dd47def015e22227247547904b22156f5a47 -LABEL maintainer="Didstopia & Zaroxh " +LABEL org.opencontainers.image.source="https://github.com/compscidr/rust-server" # Fix apt-get warnings ARG DEBIAN_FRONTEND=noninteractive @@ -35,27 +36,27 @@ RUN mkdir -p /steamcmd/rust /usr/share/nginx/html /var/log/nginx # Setup proper shutdown support ADD shutdown_app/ /app/shutdown_app/ WORKDIR /app/shutdown_app -RUN npm install +RUN npm ci # Setup restart support (for update automation) ADD restart_app/ /app/restart_app/ WORKDIR /app/restart_app -RUN npm install +RUN npm ci # Setup scheduling support ADD scheduler_app/ /app/scheduler_app/ WORKDIR /app/scheduler_app -RUN npm install +RUN npm ci # Setup scheduling support ADD heartbeat_app/ /app/heartbeat_app/ WORKDIR /app/heartbeat_app -RUN npm install +RUN npm ci # Setup rcon command relay app ADD rcon_app/ /app/rcon_app/ WORKDIR /app/rcon_app -RUN npm install +RUN npm ci RUN ln -s /app/rcon_app/app.js /usr/bin/rcon # Add the steamcmd installation script @@ -124,7 +125,9 @@ ENV CHOWN_DIRS "/app,/steamcmd,/usr/share/nginx/html,/var/log/nginx" # VOLUME [ "/steamcmd/rust" ] # Container Healthcheck -HEALTHCHECK --start-period=15m --retries=1 CMD /bin/bash -c 'if rcon test | grep -q "RconApp::Error"; then exit 1; else exit 0; fi' +# rcon reads RUST_RCON_PORT/RUST_RCON_PASSWORD from the environment, but the real +# values may live in /etc/rust/rust.env (sourced by start.sh), so source it here too +HEALTHCHECK --start-period=15m --retries=1 CMD /bin/bash -c 'set -a; [ -f /etc/rust/rust.env ] && source /etc/rust/rust.env; set +a; if rcon test | grep -q "RconApp::Error"; then exit 1; else exit 0; fi' # Start the server CMD [ "bash", "/app/start.sh"] diff --git a/README.md b/README.md index 2ed0ba5..2a77cc4 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,28 @@ # Rust server that runs inside a Docker container -Fork of [Didstopia/rust-server](https://github.com/Didstopia/rust-server) +Fork of [zaroxh/rust-server](https://github.com/zaroxh/rust-server) and [Didstopia/rust-server](https://github.com/Didstopia/rust-server) This fork adds a healthcheck and more environment variables to set (see below for a full list). +It also lets you override the environment variables with an .env file on +the docker volume. This has made it easier for me to manage wipe seeds with +a cron job on the host that changes the .env value and restarts the container. +I found if you run containers via ansible and pass the .env file, restarting +the container will keep the same old values from when it was originally run. +This setup allows overriding anything passed in from docker. The config file +is located at `/etc/rust/rust.env` in the container. You can map a volume +locally to the file for peristence. + +I also split the seed into its own .env file at `/etc/rust/seed.env` to make +it easier to update configs without affecting the map. + +For an example see the `docker-compose.yml` file. + ## docker-compose.yml ``` services: rust-server: container_name: rust-server - image: ghcr.io/zaroxh/rust-server:master + image: ghcr.io/compscidr/rust-server restart: unless-stopped ports: - 28015:28015/tcp @@ -21,6 +35,7 @@ services: - 28082:28082/udp volumes: - ./rust-server/:/steamcmd/rust + - ./rust.env:/etc/rust/rust.env environment: - RUST_SERVER_IDENTITY=rust-server - RUST_SERVER_NAME=[EU] Rust Server running in Docker Container @@ -58,8 +73,27 @@ RUST_BRANCH (DEFAULT: Not set - Sets the branch argument to use, eg. set to "-be RUST_UPDATE_CHECKING (DEFAULT: "0" - Set to 1 to enable fully automatic update checking, notifying players and restarting to install updates) 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") RUST_START_MODE (DEFAULT: "0" - Determines if the server should update and then start (0), only update (1) or only start (2)) +STEAMCMD_RETRY_DELAY (DEFAULT: "60" - Seconds to wait before exiting when the boot-time game update fails; see below) RUST_OXIDE_ENABLED (DEFAULT: "0" - Set to 1 to automatically install the latest version of Oxide) RUST_OXIDE_UPDATE_ON_BOOT (DEFAULT: "1" - Set to 0 to disable automatic update of Oxide on boot) RUST_RCON_SECURE_WEBSOCKET (DEFAULT: "0" - Set to 1 to enable secure websocket connections to the RCON web interface) RUST_HEARTBEAT (DEFAULT: "0" - Set to 1 to enable the heartbeat service which will forcibly quit the server if it becomes unresponsive to queries) ``` + +## When the game update fails at boot + +The container updates Rust with steamcmd on every start. steamcmd exits 0 even when the +update failed — on 2026-09-03 it printed `Error! App '258550' state is 0x486 after update +job.` because Facepunch's monthly depot was mid-rollout at the moment the server restarted. +The old script carried on, installed an Oxide built for the new Rust over the old one, and +`RustDedicated` hung at boot. A hang is not an exit, so `restart: unless-stopped` never +fired, and the server stayed down until someone noticed. + +Now the start script accepts only steamcmd's own `Success! App '258550' fully installed` +line. Anything else waits `STEAMCMD_RETRY_DELAY` seconds and exits 1 before Oxide is +touched, so a container restart policy (`unless-stopped` or `always`) retries until Steam +serves the update. Restarting a wipe or maintenance window into the first minutes of a +Facepunch release now costs a few restart loops instead of an outage. + +If you run the game with your own wrapper instead of this image, it needs the same rule: +a failed update must exit non-zero, not launch the stale build. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..563ff30 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +services: + rust-server: + container_name: rust-server + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + ports: + - 26015:28015/tcp + - 26015:28015/udp + - 26016:28016/tcp + - 26016:28016/udp + - 26017:28017/tcp + - 26017:28017/udp + - 26082:28082/tcp + - 26082:28082/udp + volumes: + - ./rust-server/:/steamcmd/rust + - ./rust.env:/etc/rust/rust.env + - ./seed.env:/etc/rust/seed.env \ No newline at end of file diff --git a/heartbeat_app/package-lock.json b/heartbeat_app/package-lock.json index 8a4c139..9314769 100644 --- a/heartbeat_app/package-lock.json +++ b/heartbeat_app/package-lock.json @@ -1,7 +1,7 @@ { "name": "heartbeat_app", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -15,18 +15,20 @@ "node_modules/source-server-query": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/source-server-query/-/source-server-query-2.1.4.tgz", - "integrity": "sha512-3DytX089tMt+oR1LPSssaLZAX93era1c1W95P0R3fNCHIlVcZteD+KVpjWJy5BPcpuFyRdM4f/iFzozW5WDwOQ==" + "integrity": "sha512-3DytX089tMt+oR1LPSssaLZAX93era1c1W95P0R3fNCHIlVcZteD+KVpjWJy5BPcpuFyRdM4f/iFzozW5WDwOQ==", + "license": "MIT" }, "node_modules/ws": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", - "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -37,18 +39,5 @@ } } } - }, - "dependencies": { - "source-server-query": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/source-server-query/-/source-server-query-2.1.4.tgz", - "integrity": "sha512-3DytX089tMt+oR1LPSssaLZAX93era1c1W95P0R3fNCHIlVcZteD+KVpjWJy5BPcpuFyRdM4f/iFzozW5WDwOQ==" - }, - "ws": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", - "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", - "requires": {} - } } } diff --git a/rcon_app/package-lock.json b/rcon_app/package-lock.json index e53b033..721554b 100644 --- a/rcon_app/package-lock.json +++ b/rcon_app/package-lock.json @@ -1,19 +1,28 @@ { "name": "rcon_app", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "async-limiter": { + "packages": { + "": { + "name": "rcon_app", "version": "1.0.0", - "resolved": "https://npm.didstopia.com/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "dependencies": { + "ws": "^6.1.3" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "license": "MIT" }, - "ws": { - "version": "6.1.2", - "resolved": "https://npm.didstopia.com/ws/-/ws-6.1.2.tgz", - "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", - "requires": { + "node_modules/ws": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", + "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", + "license": "MIT", + "dependencies": { "async-limiter": "~1.0.0" } } diff --git a/restart_app/package-lock.json b/restart_app/package-lock.json index 7fecada..dd53a1c 100644 --- a/restart_app/package-lock.json +++ b/restart_app/package-lock.json @@ -1,252 +1,363 @@ { "name": "restart_app", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "ajv": { - "version": "6.6.2", - "resolved": "https://npm.didstopia.com/ajv/-/ajv-6.6.2.tgz", - "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", - "requires": { - "fast-deep-equal": "^2.0.1", + "packages": { + "": { + "name": "restart_app", + "version": "1.0.0", + "dependencies": { + "request": "^2.88.0", + "ws": "^6.1.3" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://npm.didstopia.com/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { "safer-buffer": "~2.1.0" } }, - "assert-plus": { + "node_modules/assert-plus": { "version": "1.0.0", - "resolved": "https://npm.didstopia.com/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://npm.didstopia.com/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "license": "MIT" }, - "asynckit": { + "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://npm.didstopia.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, - "aws-sign2": { + "node_modules/aws-sign2": { "version": "0.7.0", - "resolved": "https://npm.didstopia.com/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } }, - "aws4": { - "version": "1.8.0", - "resolved": "https://npm.didstopia.com/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" }, - "bcrypt-pbkdf": { + "node_modules/bcrypt-pbkdf": { "version": "1.0.2", - "resolved": "https://npm.didstopia.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { "tweetnacl": "^0.14.3" } }, - "caseless": { + "node_modules/caseless": { "version": "0.12.0", - "resolved": "https://npm.didstopia.com/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://npm.didstopia.com/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "requires": { + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "core-util-is": { + "node_modules/core-util-is": { "version": "1.0.2", - "resolved": "https://npm.didstopia.com/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" }, - "dashdash": { + "node_modules/dashdash": { "version": "1.14.1", - "resolved": "https://npm.didstopia.com/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" } }, - "delayed-stream": { + "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://npm.didstopia.com/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } }, - "ecc-jsbn": { + "node_modules/ecc-jsbn": { "version": "0.1.2", - "resolved": "https://npm.didstopia.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, - "extend": { + "node_modules/extend": { "version": "3.0.2", - "resolved": "https://npm.didstopia.com/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" }, - "extsprintf": { + "node_modules/extsprintf": { "version": "1.3.0", - "resolved": "https://npm.didstopia.com/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://npm.didstopia.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://npm.didstopia.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" }, - "forever-agent": { + "node_modules/forever-agent": { "version": "0.6.1", - "resolved": "https://npm.didstopia.com/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } }, - "form-data": { + "node_modules/form-data": { "version": "2.3.3", - "resolved": "https://npm.didstopia.com/form-data/-/form-data-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { + "license": "MIT", + "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" } }, - "getpass": { + "node_modules/getpass": { "version": "0.1.7", - "resolved": "https://npm.didstopia.com/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { "assert-plus": "^1.0.0" } }, - "har-schema": { + "node_modules/har-schema": { "version": "2.0.0", - "resolved": "https://npm.didstopia.com/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://npm.didstopia.com/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" } }, - "http-signature": { + "node_modules/http-signature": { "version": "1.2.0", - "resolved": "https://npm.didstopia.com/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" } }, - "is-typedarray": { + "node_modules/is-typedarray": { "version": "1.0.0", - "resolved": "https://npm.didstopia.com/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" }, - "isstream": { + "node_modules/isstream": { "version": "0.1.2", - "resolved": "https://npm.didstopia.com/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" }, - "jsbn": { + "node_modules/jsbn": { "version": "0.1.1", - "resolved": "https://npm.didstopia.com/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://npm.didstopia.com/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" }, - "json-schema-traverse": { + "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://npm.didstopia.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" }, - "json-stringify-safe": { + "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://npm.didstopia.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://npm.didstopia.com/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "mime-db": { - "version": "1.37.0", - "resolved": "https://npm.didstopia.com/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" - }, - "mime-types": { - "version": "2.1.21", - "resolved": "https://npm.didstopia.com/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", - "requires": { - "mime-db": "~1.37.0" + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" } }, - "oauth-sign": { + "node_modules/oauth-sign": { "version": "0.9.0", - "resolved": "https://npm.didstopia.com/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } }, - "performance-now": { + "node_modules/performance-now": { "version": "2.1.0", - "resolved": "https://npm.didstopia.com/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "psl": { - "version": "1.1.31", - "resolved": "https://npm.didstopia.com/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://npm.didstopia.com/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://npm.didstopia.com/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "request": { - "version": "2.88.0", - "resolved": "https://npm.didstopia.com/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", + "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", @@ -254,7 +365,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -264,26 +375,46 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://npm.didstopia.com/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "safer-buffer": { + "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://npm.didstopia.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sshpk": { - "version": "1.16.0", - "resolved": "https://npm.didstopia.com/sshpk/-/sshpk-1.16.0.tgz", - "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==", - "requires": { + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", @@ -293,65 +424,86 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" } }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://npm.didstopia.com/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://npm.didstopia.com/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" } }, - "tunnel-agent": { + "node_modules/tunnel-agent": { "version": "0.6.0", - "resolved": "https://npm.didstopia.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "tweetnacl": { + "node_modules/tweetnacl": { "version": "0.14.5", - "resolved": "https://npm.didstopia.com/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://npm.didstopia.com/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { "punycode": "^2.1.0" } }, - "uuid": { - "version": "3.3.2", - "resolved": "https://npm.didstopia.com/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } }, - "verror": { + "node_modules/verror": { "version": "1.10.0", - "resolved": "https://npm.didstopia.com/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, - "ws": { - "version": "6.1.2", - "resolved": "https://npm.didstopia.com/ws/-/ws-6.1.2.tgz", - "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", - "requires": { + "node_modules/ws": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", + "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", + "license": "MIT", + "dependencies": { "async-limiter": "~1.0.0" } } diff --git a/rust.env b/rust.env new file mode 100644 index 0000000..e931623 --- /dev/null +++ b/rust.env @@ -0,0 +1,3 @@ +RUST_SERVER_DESCRIPTION="this is some multiline +description to see if it works" +RUST_SERVER_NAME="Test Server" \ No newline at end of file diff --git a/scheduler_app/package-lock.json b/scheduler_app/package-lock.json index 5785d1a..0e7eadc 100644 --- a/scheduler_app/package-lock.json +++ b/scheduler_app/package-lock.json @@ -1,5 +1,12 @@ { "name": "scheduler_app", "version": "1.0.0", - "lockfileVersion": 1 + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scheduler_app", + "version": "1.0.0" + } + } } diff --git a/seed.env b/seed.env new file mode 100644 index 0000000..11316ff --- /dev/null +++ b/seed.env @@ -0,0 +1 @@ +RUST_SERVER_SEED=54321 \ No newline at end of file diff --git a/shutdown_app/package-lock.json b/shutdown_app/package-lock.json index d110a4a..b9fb52e 100644 --- a/shutdown_app/package-lock.json +++ b/shutdown_app/package-lock.json @@ -1,19 +1,28 @@ { "name": "shutdown_app", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "async-limiter": { + "packages": { + "": { + "name": "shutdown_app", "version": "1.0.0", - "resolved": "https://npm.didstopia.com/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "dependencies": { + "ws": "^6.1.3" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "license": "MIT" }, - "ws": { - "version": "6.1.2", - "resolved": "https://npm.didstopia.com/ws/-/ws-6.1.2.tgz", - "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", - "requires": { + "node_modules/ws": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", + "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", + "license": "MIT", + "dependencies": { "async-limiter": "~1.0.0" } } diff --git a/start_rust.sh b/start_rust.sh index 51aa3a6..2526846 100755 --- a/start_rust.sh +++ b/start_rust.sh @@ -3,13 +3,22 @@ # Enable debugging # set -x +# import env variables from /etc/rust/rust.env +# handles multi-line and variables with spaces if they use +# quotes +set -o allexport +source /etc/rust/rust.env +# lets you split the seed into a separate config file to make wipes easier +source /etc/rust/seed.env +set +o allexport + # Print the user we're currently running as echo "Running as user: $(whoami)" # Define the exit handler exit_handler() { - echo "Shutdown signal received" + echo "Shutdown signal received" # Execute the RCON shutdown command node /app/shutdown_app/app.js @@ -30,15 +39,30 @@ trap 'exit_handler' SIGINT SIGTERM export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/steamcmd/rust/RustDedicated_Data/Plugins/x86_64 # Define the install/update function +# +# steamcmd's exit status says nothing about whether app_update worked. On 2026-09-03 it +# printed "Error! App '258550' state is 0x486 after update job." (Facepunch's monthly +# depot was mid-rollout at the moment the server restarted) and exited 0; this script +# carried on, installed an Oxide built for the NEW Rust over the OLD one, and RustDedicated +# hung at boot with a MissingMethodException. A hang is not an exit, so the container's +# restart policy never fired, and both servers stayed down for five days. +# +# So the only proof accepted is steamcmd's own success line. Anything else exits non-zero +# BEFORE the Oxide step, the restart policy brings the container back, and the loop +# converges once Steam serves the update. The delay keeps that loop from hitting Steam +# every ~20s: docker resets its restart backoff for any container that ran over 10s. +# STEAMCMD_SH and STEAMCMD_RETRY_DELAY exist so test/install_or_update_test.sh can +# stub the binary and skip the wait. install_or_update() { # Install Rust from install.txt echo "Installing or updating Rust.. (this might take a while, be patient)" - bash /steamcmd/steamcmd.sh +runscript /app/install.txt + bash "${STEAMCMD_SH:-/steamcmd/steamcmd.sh}" +runscript /app/install.txt | tee /tmp/steamcmd_update.log - # Terminate if exit code wasn't zero - if [ $? -ne 0 ]; then - echo "Exiting, steamcmd install or update failed!" + # Terminate unless steamcmd reported a successful install (also printed when already up to date) + if ! grep -q "Success! App '258550' fully installed" /tmp/steamcmd_update.log; then + echo "Exiting, steamcmd install or update failed! Retrying in ${STEAMCMD_RETRY_DELAY:-60}s.." + sleep "${STEAMCMD_RETRY_DELAY:-60}" exit 1 fi } diff --git a/test/install_or_update_test.sh b/test/install_or_update_test.sh new file mode 100755 index 0000000..70acd8b --- /dev/null +++ b/test/install_or_update_test.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Pins the fix for the 2026-09-03 outage: steamcmd printed +# "Error! App '258550' state is 0x486 after update job." (Facepunch's monthly depot was +# mid-rollout), exited 0, and start_rust.sh carried on to install an Oxide built for the +# NEW Rust over the OLD one. RustDedicated then hung at boot — no exit, so the container's +# restart policy never fired — for five days. install_or_update must treat anything but +# steamcmd's own success line as a failure and exit non-zero, so the restart policy retries +# until Steam serves the update. +# +# Runs anywhere: the function is extracted from start_rust.sh and pointed at a stub steamcmd. +set -u +cd "$(dirname "$0")/.." + +# $1: what the stub steamcmd prints. Returns install_or_update's exit status. +run() { + local stub + stub=$(mktemp) + printf 'echo "%s"\nexit 0\n' "$1" > "$stub" + ( + export STEAMCMD_SH=$stub STEAMCMD_RETRY_DELAY=0 + eval "$(sed -n '/^install_or_update()/,/^}/p' start_rust.sh)" + install_or_update > /dev/null 2>&1 + ) +} + +if run "Error! App '258550' state is 0x486 after update job."; then + echo "FAIL: a failed update (steamcmd exit 0, no success line) must exit non-zero" + exit 1 +fi +if ! run "Success! App '258550' fully installed."; then + echo "FAIL: a successful update must carry on" + exit 1 +fi +echo "ok"