diff --git a/.github/actions/unittest-on-build/action.yml b/.github/actions/unittest-on-build/action.yml index 13127c9..d53a39a 100644 --- a/.github/actions/unittest-on-build/action.yml +++ b/.github/actions/unittest-on-build/action.yml @@ -23,7 +23,10 @@ runs: pwd git --version docker version - echo "TIMEOUT_SECONDS: ${{ inputs.timeout_seconds }}" + free -m + cat /proc/cpuinfo | grep "model name" + echo "Repository value of TIMEOUT_SECONDS is: ${{ inputs.timeout_seconds }}" + echo "This should be set on develop or main branch!" - name: Run server shell: bash @@ -33,6 +36,8 @@ runs: -p 8211:8211/udp \ -p 8212:8212/tcp \ -p 25575:25575/tcp \ + -e PUID=7351 \ + -e PGID=2431 \ -e ADMIN_PASSWORD=123 \ -e SERVER_PASSWORD=456 \ -e SERVER_SETTINGS_MODE=auto \ @@ -45,27 +50,47 @@ runs: shell: bash run: | START_TIME=$(date +%s) - echo "TIMEOUT_SECONDS: ${{ inputs.timeout_seconds }}" + TIMEOUT_SECONDS=${{ inputs.timeout_seconds || '300' }} + echo "TIMEOUT_SECONDS: $TIMEOUT_SECONDS" # Set the timezone to Germany (Central European Time) export TZ=Europe/Berlin + + # Source color functions + source ./includes/colors.sh + + # Track last log position to avoid duplicate output + LAST_LOG_LINES=0 while ! docker logs palworld-dedicated-server 2>&1 | grep -q "Setting breakpad minidump AppID"; do CURRENT_TIME=$(date +%s) ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) - if [ $ELAPSED_TIME -gt ${{ inputs.timeout_seconds }} ]; then - echo "Timeout reached. Server failed to start within ${{ inputs.timeout_seconds }} seconds." - printf "\e[0;32m%s\e[0m\n" "*****Container LOGS*****" + if [ $ELAPSED_TIME -gt $TIMEOUT_SECONDS ]; then + echo "Timeout reached. Server failed to start within $TIMEOUT_SECONDS seconds." + es "*****Final Container LOGS*****" docker logs palworld-dedicated-server exit 1 fi - echo "$(date '+%H:%M:%S') - Waiting for server to start..." + # Show current status with warning color + ew ">>> $(date '+%H:%M:%S') - Waiting for server to start... (${ELAPSED_TIME}s elapsed)" + # Get current log lines count and show new logs if any + CURRENT_LOG_LINES=$(docker logs palworld-dedicated-server 2>&1 | wc -l) + if [ $CURRENT_LOG_LINES -gt $LAST_LOG_LINES ]; then + es ">>> Recent container logs" + es "╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮" + docker logs palworld-dedicated-server 2>&1 | tail -n +$((LAST_LOG_LINES + 1)) | head -n $((CURRENT_LOG_LINES - LAST_LOG_LINES)) + es "╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯" + LAST_LOG_LINES=$CURRENT_LOG_LINES + fi + + echo "---" sleep 5 done - echo "Server successfully started" - printf "\e[0;32m%s\e[0m\n" "*****Container LOGS*****" + + echo "Server successfully started in $ELAPSED_TIME seconds" + es "*****Final Container LOGS*****" docker logs palworld-dedicated-server diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4dac781 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 + +updates: + - package-ecosystem: "docker" + directories: + - "/" + schedule: + interval: "weekly" + patterns: ["*"] + multi-ecosystem-group: "all-dependencies" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + patterns: ["*"] + multi-ecosystem-group: "all-dependencies" + +multi-ecosystem-groups: + all-dependencies: + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/auto-merge-master-to-develop.yml b/.github/workflows/auto-merge-master-to-develop.yml index 2da6487..42ea62a 100644 --- a/.github/workflows/auto-merge-master-to-develop.yml +++ b/.github/workflows/auto-merge-master-to-develop.yml @@ -9,7 +9,7 @@ jobs: timeout-minutes: 1 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set Git config run: | git config --local user.email "actions@github.com" diff --git a/.github/workflows/docker-build-and-push-develop.yml b/.github/workflows/docker-build-and-push-develop.yml index a7c7f1e..c7cb4c5 100644 --- a/.github/workflows/docker-build-and-push-develop.yml +++ b/.github/workflows/docker-build-and-push-develop.yml @@ -11,17 +11,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | jammsen/palworld-dedicated-server @@ -32,7 +32,7 @@ jobs: run: echo "GIT_SHORT_SHA7=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" - name: Build the images - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . push: false @@ -50,20 +50,20 @@ jobs: timeout_seconds: ${{ vars.TIMEOUT_SECONDS }} - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Push the images - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . push: true diff --git a/.github/workflows/docker-build-and-push-prod.yml b/.github/workflows/docker-build-and-push-prod.yml index 6b8f677..5983a98 100644 --- a/.github/workflows/docker-build-and-push-prod.yml +++ b/.github/workflows/docker-build-and-push-prod.yml @@ -11,17 +11,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | jammsen/palworld-dedicated-server @@ -32,7 +32,7 @@ jobs: run: echo "GIT_SHORT_SHA7=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" - name: Build the images - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . push: false @@ -52,20 +52,20 @@ jobs: timeout_seconds: ${{ vars.TIMEOUT_SECONDS }} - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Push the images - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . push: true @@ -78,7 +78,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v4 + uses: peter-evans/dockerhub-description@v5 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/unittest-on-pr.yml b/.github/workflows/unittest-on-pr.yml index cada784..e1d4472 100644 --- a/.github/workflows/unittest-on-pr.yml +++ b/.github/workflows/unittest-on-pr.yml @@ -1,7 +1,9 @@ --- name: unit-test-component-run-on-pr on: - pull_request: # Run tests on all pull requests + pull_request: # Run tests on all pull requests, except those only touching CI/config files + paths-ignore: + - '.github/**' concurrency: group: "${{ github.workflow }} @ ${{ github.ref }}" @@ -19,17 +21,19 @@ jobs: pwd git --version docker version - echo "Repository value of TIMEOUT_SECONDS: ${{ vars.TIMEOUT_SECONDS }}" + free -m + cat /proc/cpuinfo | grep "model name" + echo "Repository value of TIMEOUT_SECONDS is: ${{ vars.TIMEOUT_SECONDS }}" echo "This should not be set on a PR from a different source!" - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and export to Docker - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: file: ./Dockerfile load: true @@ -43,6 +47,8 @@ jobs: -p 8211:8211/udp \ -p 8212:8212/tcp \ -p 25575:25575/tcp \ + -e PUID=7351 \ + -e PGID=2431 \ -e ADMIN_PASSWORD=123 \ -e SERVER_PASSWORD=456 \ -e SERVER_SETTINGS_MODE=auto \ @@ -55,30 +61,48 @@ jobs: run: | START_TIME=$(date +%s) TIMEOUT_SECONDS=${{ vars.TIMEOUT_SECONDS || '300' }} - echo "TIMEOUT_SECONDS: ${{ vars.TIMEOUT_SECONDS }}" + echo "TIMEOUT_SECONDS: $TIMEOUT_SECONDS" # Set the timezone to Germany (Central European Time) export TZ=Europe/Berlin + # Source color functions + source ./includes/colors.sh + + # Track last log position to avoid duplicate output + LAST_LOG_LINES=0 + while ! docker logs palworld-dedicated-server 2>&1 | grep -q "Setting breakpad minidump AppID"; do CURRENT_TIME=$(date +%s) ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) - if [ $ELAPSED_TIME -gt ${{ vars.TIMEOUT_SECONDS }} ]; then - echo "Timeout reached. Server failed to start within ${{ vars.TIMEOUT_SECONDS }} seconds." - printf "\e[0;32m%s\e[0m\n" "*****Container LOGS*****" + if [ $ELAPSED_TIME -gt $TIMEOUT_SECONDS ]; then + echo "Timeout reached. Server failed to start within $TIMEOUT_SECONDS seconds." + es "*****Final Container LOGS*****" docker logs palworld-dedicated-server exit 1 fi - echo "$(date '+%H:%M:%S') - Waiting for server to start..." + # Show current status with warning color + ew ">>> $(date '+%H:%M:%S') - Waiting for server to start... (${ELAPSED_TIME}s elapsed)" + # Get current log lines count and show new logs if any + CURRENT_LOG_LINES=$(docker logs palworld-dedicated-server 2>&1 | wc -l) + if [ $CURRENT_LOG_LINES -gt $LAST_LOG_LINES ]; then + es ">>> Recent container logs" + es "╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮" + docker logs palworld-dedicated-server 2>&1 | tail -n +$((LAST_LOG_LINES + 1)) | head -n $((CURRENT_LOG_LINES - LAST_LOG_LINES)) + es "╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯" + LAST_LOG_LINES=$CURRENT_LOG_LINES + fi + + echo "---" sleep 5 done - echo "Server successfully started" - printf "\e[0;32m%s\e[0m\n" "*****Container LOGS*****" + + echo "Server successfully started in $ELAPSED_TIME seconds" + es "*****Final Container LOGS*****" docker logs palworld-dedicated-server - - name: Test if port 8766, 27015 and 27016 are listening run: | nc -z -u -v 127.0.0.1 8211 || exit 2 diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml new file mode 100644 index 0000000..4800a5d --- /dev/null +++ b/.github/workflows/update-readme.yml @@ -0,0 +1,53 @@ +--- +name: Update README +on: + push: + paths: + - 'compose.yml' + +jobs: + update_readme: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Update README + run: | + python3 - <<'EOF' + import re + + # Read compose.yml + with open('compose.yml', 'r') as f: + compose_content = f.read() + + # Wrap content in a yaml code block + markdown_content = '```yaml\n' + compose_content + '\n```' + + # Read README.md + with open('README.md', 'r') as f: + readme_content = f.read() + + # Replace the section between the markers + pattern = r'.*?' + replacement = '\n' + markdown_content + '\n' + new_readme = re.sub(pattern, replacement, readme_content, flags=re.DOTALL) + + with open('README.md', 'w') as f: + f.write(new_readme) + EOF + + - name: Commit changes + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add README.md + if git diff --quiet HEAD; then + echo "No changes to commit" + else + git commit -m "ci: update README with latest compose.yml content" + git push + fi diff --git a/.gitignore b/.gitignore index f0020e1..dcbb77c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .vscode/ game/ test/ +compose-*.yml docker-compose-*.yml custom.env configs/DefaultPalWorldSettings.ini diff --git a/CHANGELOG.md b/CHANGELOG.md index b52a17a..c5421e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ [Back to main](README.md#changelog) +## 2026-05-23 + +- gosu from 1.17 to 1.19 and supercronic from 0.2.34 to 0.2.45 - by @jammsen (#312) +- Updated docker-image pinning and github-actions versions by @jammsen (#312) +- Refactored from docker-compose.yml to new standard compose.yml including gitignore by @jammsen (#312) +- Implemented finally a version of auto-updating the compose.yml @jahusa02 (#188) +- Added the function to run a custom script, that the user can place at /palworld/custom-script - Off by default, completely opt-in and added a very explicit PSA in the README.md for it by @jammsen for @Jadiction (#273) + +## 2025-09-06 + +- Made envsubst integration complete and useful, added strtolower checks, removed obsolete moreutils package from PR, removed trailing spaces, added more error-fallbacks in config.sh around the envsubst integration - by @jammsen (idea by @codebam) (#311) + +## 2025-07-18 + +- Updated supercronic to release v0.2.34 @GlitchApotamus (#304) + ## 2025-07-17 - Added new config-settings for v0.6.0 @GlitchApotamus (#301) diff --git a/Dockerfile b/Dockerfile index ec6e6cf..c7a526a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.0-bookworm AS rconclibuilder +FROM golang:1.26.3-bookworm@sha256:386d475a660466863d9f8c766fec64d7fdad3edac2c6a05020c09534d71edb4b AS rconclibuilder WORKDIR /build @@ -16,12 +16,12 @@ RUN curl -fsSLO "$GORCON_RCONCLI_URL" \ && rm -Rf "$GORCON_RCONCLI_DIR" \ && go build -v ./cmd/gorcon -FROM debian:bookworm-slim AS supercronicverify +FROM debian:bookworm-slim@sha256:0104b334637a5f19aa9c983a91b54c89887c0984081f2068983107a6f6c21eeb AS supercronicverify # Latest releases available at https://github.com/aptible/supercronic/releases -ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 \ - SUPERCRONIC=supercronic-linux-amd64 \ - SUPERCRONIC_SHA1SUM=cd48d45c4b10f3f0bfdd3a57d054cd05ac96812b +ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.45/supercronic-linux-amd64 \ + SUPERCRONIC_SHA1SUM=e894b193bea75a5ee644e700c59e30eedc804cf7 \ + SUPERCRONIC=supercronic-linux-amd64 RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests ca-certificates curl \ @@ -35,7 +35,7 @@ RUN curl -fsSLO "$SUPERCRONIC_URL" \ && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \ && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic -FROM cm2network/steamcmd:root +FROM cm2network/steamcmd:root@sha256:e6b6b3503bf0e41feafe12dc709c90151afba193e1292cac55d28a7d470b1493 LABEL maintainer="Sebastian Schmidt - https://github.com/jammsen/docker-palworld-dedicated-server" LABEL org.opencontainers.image.authors="Sebastian Schmidt" @@ -77,6 +77,9 @@ ENV DEBIAN_FRONTEND=noninteractive \ RCON_PLAYER_DEBUG=false \ RCON_PLAYER_DETECTION_STARTUP_DELAY=60 \ RCON_PLAYER_DETECTION_CHECK_INTERVAL=15 \ + # Custom-script-settings + CUSTOM_SCRIPT_ENABLED=false \ + CUSTOM_SCRIPT_PATH="/palworld/custom-script.sh" \ # Webhook-settings WEBHOOK_ENABLED=false \ WEBHOOK_DEBUG_ENABLED=false \ @@ -111,7 +114,7 @@ ENV DEBIAN_FRONTEND=noninteractive \ # PalWorldSettings.ini settings DIFFICULTY=None \ RANDOMIZER_TYPE=None \ - RANDOMIZER_SEED="" \ + RANDOMIZER_SEED="" \ IS_RANDOMIZER_PAL_LEVEL_RANDOM=false \ DAYTIME_SPEEDRATE=1.000000 \ NIGHTTIME_SPEEDRATE=1.000000 \ @@ -206,9 +209,10 @@ EXPOSE 25575/tcp # Install minimum required packages for dedicated server COPY --from=rconclibuilder /build/gorcon /usr/local/bin/rcon COPY --from=supercronicverify /usr/local/bin/supercronic /usr/local/bin/supercronic +COPY --from=tianon/gosu /gosu /usr/local/bin/gosu RUN apt-get update \ - && apt-get install -y --no-install-recommends --no-install-suggests procps xdg-user-dirs \ + && apt-get install -y --no-install-recommends --no-install-suggests procps xdg-user-dirs gettext-base \ && apt-get autoremove -y --purge \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* @@ -218,18 +222,24 @@ COPY --chmod=755 scripts/ /scripts COPY --chmod=755 includes/ /includes COPY --chmod=644 configs/rcon.yaml /home/steam/steamcmd/rcon.yaml COPY --chmod=644 configs/PalWorldSettings.ini.template / -COPY --chmod=755 gosu-amd64 /usr/local/bin/gosu RUN mkdir -p "$BACKUP_PATH" \ && ln -s /scripts/backupmanager.sh /usr/local/bin/backup \ && ln -s /scripts/rconcli.sh /usr/local/bin/rconcli \ - && ln -s /scripts/restart.sh /usr/local/bin/restart \ - && gosu --version \ + && ln -s /scripts/restart.sh /usr/local/bin/restart + +RUN gosu --version \ && gosu nobody true +RUN rconcli --version \ + && rcon --version + +RUN supercronic --version + + VOLUME ["${GAME_ROOT}"] -HEALTHCHECK --interval=10s --timeout=10s --start-period=30s --retries=3 \ +HEALTHCHECK --interval=10s --timeout=10s --start-period=30s --retries=3 \ CMD pgrep -x "PalServer-Linux" >/dev/null 2>&1 || exit 1 ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index a30c156..3e5d28c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,18 @@ This Docker image includes a Palworld Dedicated Server based on Linux and Docker ___ +> [!CAUTION] +> **Public Service Announcement — Custom Script Feature** +> +> After many community requests, this image now supports running a custom script before the server starts. +> This feature is entirely **opt-in** and is controlled by the `CUSTOM_SCRIPT_ENABLED` environment variable, which defaults to `false`. +> +> **This image will never ship with a custom script of any kind.** +> +> If you come across a Docker image that appears to be this one but includes a bundled custom script, please be careful — it is not this image and I have no affiliation with it. +> +> This feature was added at the request of the community. While I am glad to offer the option, I will not be providing support for it, and I refuse to accept **any liability** for any harm, data loss, corruption, or security issues that may result from its use. Please use it at your own discretion. — Public Service Announcement. + ## Table of Contents - [Docker - Palworld Dedicated Server](#docker---palworld-dedicated-server) @@ -89,8 +101,8 @@ This 2 persons helped a lot along to way and made me and this project better! So - In older versions we asked you to setup permissions via CHMOD or CHOWN, this should not be needed anymore! 2. Set up Port-Forwarding or NAT for the ports in the Docker-Compose file 3. Pull the latest version of the image with `docker pull jammsen/palworld-dedicated-server:latest` -4. Download the [docker-compose.yml](docker-compose.yml) and [default.env](default.env) -5. Set up the `docker-compose.yml` and `default.env` to your liking +4. Download the [compose.yml](compose.yml) and [default.env](default.env) +5. Set up the `compose.yml` and `default.env` to your liking - Make sure you setup PUID and PGID according to the user you want to use - **PUID and PGID 0 will error out, thats on purpose!** - if you use Docker as root, then you can just use 1000 inside the container @@ -107,7 +119,38 @@ See [this file](/docs/ENV_VARS.md) for the documentation ### Gameserver with RCON-CLI-Tool -See [example docker-compose.yml](docker-compose.yml). + +```yaml +services: + palworld-dedicated-server: + container_name: palworld-dedicated-server + image: jammsen/palworld-dedicated-server:latest + restart: unless-stopped + logging: + driver: "local" + options: + max-size: "10m" + max-file: "3" + ports: + - target: 8211 # Gamerserver port inside of the container + published: 8211 # Gamerserver port on your host + protocol: udp + mode: host + - target: 8212 # Gameserver API port inside of the container + published: 8212 # Gameserver API port on your host + protocol: tcp + mode: host + - target: 25575 # RCON port inside of the container + published: 25575 # RCON port on your host + protocol: tcp + mode: host + env_file: + - ./default.env + volumes: + - ./game:/palworld + +``` + ## Run RCON commands diff --git a/docker-compose.yml b/compose.yml similarity index 98% rename from docker-compose.yml rename to compose.yml index 9e8713e..a756155 100644 --- a/docker-compose.yml +++ b/compose.yml @@ -1,4 +1,3 @@ -version: '3.9' services: palworld-dedicated-server: container_name: palworld-dedicated-server diff --git a/configs/PalWorldSettings.ini.template b/configs/PalWorldSettings.ini.template index 0dccaf9..f726aac 100644 --- a/configs/PalWorldSettings.ini.template +++ b/configs/PalWorldSettings.ini.template @@ -1,2 +1,2 @@ [/Script/Pal.PalGameWorldSettings] -OptionSettings=(Difficulty=None,RandomizerType=None,RandomizerSeed="",bIsRandomizerPalLevelRandom=False,DayTimeSpeedRate=1.000000,NightTimeSpeedRate=1.000000,ExpRate=1.000000,PalCaptureRate=1.000000,PalSpawnNumRate=1.000000,PalDamageRateAttack=1.000000,PalDamageRateDefense=1.000000,PlayerDamageRateAttack=1.000000,PlayerDamageRateDefense=1.000000,PlayerStomachDecreaceRate=1.000000,PlayerStaminaDecreaceRate=1.000000,PlayerAutoHPRegeneRate=1.000000,PlayerAutoHpRegeneRateInSleep=1.000000,PalStomachDecreaceRate=1.000000,PalStaminaDecreaceRate=1.000000,PalAutoHPRegeneRate=1.000000,PalAutoHpRegeneRateInSleep=1.000000,BuildObjectHpRate=1.000000,BuildObjectDamageRate=1.000000,BuildObjectDeteriorationDamageRate=1.000000,CollectionDropRate=1.000000,CollectionObjectHpRate=1.000000,CollectionObjectRespawnSpeedRate=1.000000,EnemyDropItemRate=1.000000,DeathPenalty=All,bEnablePlayerToPlayerDamage=False,bEnableFriendlyFire=False,bEnableInvaderEnemy=True,bActiveUNKO=False,bEnableAimAssistPad=True,bEnableAimAssistKeyboard=False,DropItemMaxNum=3000,DropItemMaxNum_UNKO=100,BaseCampMaxNum=128,BaseCampWorkerMaxNum=15,DropItemAliveMaxHours=1.000000,bAutoResetGuildNoOnlinePlayers=False,AutoResetGuildTimeNoOnlinePlayers=72.000000,GuildPlayerMaxNum=20,BaseCampMaxNumInGuild=4,PalEggDefaultHatchingTime=72.000000,WorkSpeedRate=1.000000,AutoSaveSpan=30.000000,bIsMultiplay=False,bIsPvP=False,bHardcore=False,bPalLost=False,bCharacterRecreateInHardcore=False,bCanPickupOtherGuildDeathPenaltyDrop=False,bEnableNonLoginPenalty=True,bEnableFastTravel=True,bIsStartLocationSelectByMap=True,bExistPlayerAfterLogout=False,bEnableDefenseOtherGuildPlayer=False,bInvisibleOtherGuildBaseCampAreaFX=False,bBuildAreaLimit=False,ItemWeightRate=1.000000,CoopPlayerMaxNum=4,ServerPlayerMaxNum=32,ServerName="Default Palworld Server",ServerDescription="",AdminPassword="",ServerPassword="",PublicPort=8211,PublicIP="",RCONEnabled=False,RCONPort=25575,Region="",bUseAuth=True,BanListURL="https://api.palworldgame.com/api/banlist.txt",RESTAPIEnabled=False,RESTAPIPort=8212,bShowPlayerList=False,ChatPostLimitPerMinute=10,CrossplayPlatforms=(Steam,Xbox,PS5,Mac),bIsUseBackupSaveData=True,LogFormatType=Text,SupplyDropSpan=180,EnablePredatorBossPal=True,MaxBuildingLimitNum=0,ServerReplicatePawnCullDistance=15000.000000,bAllowGlobalPalboxExport=True,bAllowGlobalPalboxImport=False,EquipmentDurabilityDamageRate=1.000000,ItemContainerForceMarkDirtyInterval=1.000000) \ No newline at end of file +OptionSettings=(Difficulty=${DIFFICULTY},RandomizerType=${RANDOMIZER_TYPE},RandomizerSeed="${RANDOMIZER_SEED}",bIsRandomizerPalLevelRandom=${IS_RANDOMIZER_PAL_LEVEL_RANDOM},DayTimeSpeedRate=${DAYTIME_SPEEDRATE},NightTimeSpeedRate=${NIGHTTIME_SPEEDRATE},ExpRate=${EXP_RATE},PalCaptureRate=${PAL_CAPTURE_RATE},PalSpawnNumRate=${PAL_SPAWN_NUM_RATE},PalDamageRateAttack=${PAL_DAMAGE_RATE_ATTACK},PalDamageRateDefense=${PAL_DAMAGE_RATE_DEFENSE},PlayerDamageRateAttack=${PLAYER_DAMAGE_RATE_ATTACK},PlayerDamageRateDefense=${PLAYER_DAMAGE_RATE_DEFENSE},PlayerStomachDecreaceRate=${PLAYER_STOMACH_DECREASE_RATE},PlayerStaminaDecreaceRate=${PLAYER_STAMINA_DECREACE_RATE},PlayerAutoHPRegeneRate=${PLAYER_AUTO_HP_REGENE_RATE},PlayerAutoHpRegeneRateInSleep=${PLAYER_AUTO_HP_REGENE_RATE_IN_SLEEP},PalStomachDecreaceRate=${PAL_STOMACH_DECREACE_RATE},PalStaminaDecreaceRate=${PAL_STAMINA_DECREACE_RATE},PalAutoHPRegeneRate=${PAL_AUTO_HP_REGENE_RATE},PalAutoHpRegeneRateInSleep=${PAL_AUTO_HP_REGENE_RATE_IN_SLEEP},BuildObjectHpRate=${BUILD_OBJECT_HP_RATE},BuildObjectDamageRate=${BUILD_OBJECT_DAMAGE_RATE},BuildObjectDeteriorationDamageRate=${BUILD_OBJECT_DETERIORATION_DAMAGE_RATE},CollectionDropRate=${COLLECTION_DROP_RATE},CollectionObjectHpRate=${COLLECTION_OBJECT_HP_RATE},CollectionObjectRespawnSpeedRate=${COLLECTION_OBJECT_RESPAWN_SPEED_RATE},EnemyDropItemRate=${ENEMY_DROP_ITEM_RATE},DeathPenalty=${DEATH_PENALTY},bEnablePlayerToPlayerDamage=${ENABLE_PLAYER_TO_PLAYER_DAMAGE},bEnableFriendlyFire=${ENABLE_FRIENDLY_FIRE},bEnableInvaderEnemy=${ENABLE_INVADER_ENEMY},bActiveUNKO=${ACTIVE_UNKO},bEnableAimAssistPad=${ENABLE_AIM_ASSIST_PAD},bEnableAimAssistKeyboard=${ENABLE_AIM_ASSIST_KEYBOARD},DropItemMaxNum=${DROP_ITEM_MAX_NUM},DropItemMaxNum_UNKO=${DROP_ITEM_MAX_NUM_UNKO},BaseCampMaxNum=${BASE_CAMP_MAX_NUM},BaseCampWorkerMaxNum=${BASE_CAMP_WORKER_MAXNUM},DropItemAliveMaxHours=${DROP_ITEM_ALIVE_MAX_HOURS},bAutoResetGuildNoOnlinePlayers=${AUTO_RESET_GUILD_NO_ONLINE_PLAYERS},AutoResetGuildTimeNoOnlinePlayers=${AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS},GuildPlayerMaxNum=${GUILD_PLAYER_MAX_NUM},BaseCampMaxNumInGuild=${BASE_CAMP_MAX_NUM_IN_GUILD},PalEggDefaultHatchingTime=${PAL_EGG_DEFAULT_HATCHING_TIME},WorkSpeedRate=${WORK_SPEED_RATE},AutoSaveSpan=${AUTO_SAVE_SPAN},bIsMultiplay=${IS_MULTIPLAY},bIsPvP=${IS_PVP},bHardcore=${HARDCORE},bPalLost=${PAL_LOST},bCharacterRecreateInHardcore=${CHARACTER_RECREATE_IN_HARDCORE},bCanPickupOtherGuildDeathPenaltyDrop=${CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP},bEnableNonLoginPenalty=${ENABLE_NON_LOGIN_PENALTY},bEnableFastTravel=${ENABLE_FAST_TRAVEL},bIsStartLocationSelectByMap=${IS_START_LOCATION_SELECT_BY_MAP},bExistPlayerAfterLogout=${EXIST_PLAYER_AFTER_LOGOUT},bEnableDefenseOtherGuildPlayer=${ENABLE_DEFENSE_OTHER_GUILD_PLAYER},bInvisibleOtherGuildBaseCampAreaFX=${INVISBIBLE_OTHER_GUILD_BASE_CAMP_AREA_FX},bBuildAreaLimit=${BUILD_AREA_LIMIT},ItemWeightRate=${ITEM_WEIGHT_RATE},CoopPlayerMaxNum=${COOP_PLAYER_MAX_NUM},ServerPlayerMaxNum=${MAX_PLAYERS},ServerName="${SERVER_NAME}",ServerDescription="${SERVER_DESCRIPTION}",AdminPassword="${ADMIN_PASSWORD}",ServerPassword="${SERVER_PASSWORD}",PublicPort=${PUBLIC_PORT},PublicIP="${PUBLIC_IP}",RCONEnabled=${RCON_ENABLED},RCONPort=${RCON_PORT},Region="${REGION}",bUseAuth=${USEAUTH},BanListURL="${BAN_LIST_URL}",RESTAPIEnabled=${RESTAPI_ENABLED},RESTAPIPort=${RESTAPI_PORT},bShowPlayerList=${SHOW_PLAYER_LIST},ChatPostLimitPerMinute=${CHAT_POST_LIMIT_PER_MINUTE},CrossplayPlatforms=${CROSSPLAY_PLATFORMS},bIsUseBackupSaveData=${ENABLE_WORLD_BACKUP},LogFormatType=${LOG_FORMAT_TYPE},SupplyDropSpan=${SUPPLY_DROP_SPAN},EnablePredatorBossPal=${ENABLE_PREDATOR_BOSS_PAL},MaxBuildingLimitNum=${MAX_BUILDING_LIMIT_NUM},ServerReplicatePawnCullDistance=${SERVER_REPLICATE_PAWN_CULL_DISTANCE},bAllowGlobalPalboxExport=${ALLOW_GLOBAL_PALBOX_EXPORT},bAllowGlobalPalboxImport=${ALLOW_GLOBAL_PALBOX_IMPORT},EquipmentDurabilityDamageRate=${EQUIPMENT_DURABILITY_DAMAGE_RATE},ItemContainerForceMarkDirtyInterval=${ITEM_CONTAINER_FORCE_MARK_DIRTY_INTERVAL}) diff --git a/configs/rcon.yaml b/configs/rcon.yaml index 5acff51..2086382 100644 --- a/configs/rcon.yaml +++ b/configs/rcon.yaml @@ -1,3 +1,3 @@ default: - address: "127.0.0.1:###RCON_PORT###" - password: "###ADMIN_PASSWORD###" + address: "127.0.0.1:${RCON_PORT}" + password: "${ADMIN_PASSWORD}" diff --git a/default.env b/default.env index 630e197..da27261 100644 --- a/default.env +++ b/default.env @@ -20,6 +20,9 @@ RCON_PLAYER_DETECTION=true RCON_PLAYER_DEBUG=false RCON_PLAYER_DETECTION_STARTUP_DELAY=60 RCON_PLAYER_DETECTION_CHECK_INTERVAL=15 +# Custom-script-settings +CUSTOM_SCRIPT_ENABLED=false +CUSTOM_SCRIPT_PATH=/palworld/custom-script.sh # Webhook-settings WEBHOOK_ENABLED=false WEBHOOK_DEBUG_ENABLED=false diff --git a/docker-build.sh b/docker-build.sh index 2ce6069..d188cdd 100755 --- a/docker-build.sh +++ b/docker-build.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -docker build "$@" --tag=jammsen/palworld-dedicated-server:latest . \ No newline at end of file +docker build --no-cache --progress plain "$@" --tag=jammsen/palworld-dedicated-server:latest . \ No newline at end of file diff --git a/docs/ENV_VARS.md b/docs/ENV_VARS.md index 8e7da0d..2eb2ab4 100644 --- a/docs/ENV_VARS.md +++ b/docs/ENV_VARS.md @@ -29,6 +29,8 @@ These settings control the behavior of the Docker container: | RCON_PLAYER_DEBUG | Set to enabled will post debug messages to the console output | false | Boolean | | RCON_PLAYER_DETECTION_STARTUP_DELAY | Initial delay for start checking for players, consider steam-updates and server start up in seconds | 60 | Integer | | RCON_PLAYER_DETECTION_CHECK_INTERVAL | Interval in seconds to wait for next check in the infinite loop | 15 | Integer | +| CUSTOM_SCRIPT_ENABLED | Set to enabled will execute a custom script before the gameserver starts, see `CUSTOM_SCRIPT_PATH` | false | Boolean | +| CUSTOM_SCRIPT_PATH | Absolute path to the custom script to execute; the file must exist at container runtime (e.g. mounted via a volume) | /palworld/custom-script.sh | String (absolute path) | | WEBHOOK_ENABLED | Set to enabled will send webhook notifications, NEEDS `WEBHOOK_URL` | false | Boolean | | WEBHOOK_DEBUG_ENABLED | Set to enabled will enable feedback of curl and not use --silent | false | Boolean | | WEBHOOK_URL | Defines the url the webhook to send data to | | Url | diff --git a/gosu-amd64 b/gosu-amd64 deleted file mode 100644 index 0e62305..0000000 Binary files a/gosu-amd64 and /dev/null differ diff --git a/includes/config.sh b/includes/config.sh index 20be659..1817868 100644 --- a/includes/config.sh +++ b/includes/config.sh @@ -47,389 +47,77 @@ function e_with_counter() { function setup_palworld_settings_ini() { ei ">>> Setting up PalWorldSettings.ini ..." if [ ! -d "${GAME_CONFIG_PATH}" ]; then - mkdir -p "${GAME_CONFIG_PATH}/" + mkdir -p "${GAME_CONFIG_PATH}/" || { + ee "Failed to create directory ${GAME_CONFIG_PATH}" + return 1 + } fi + + # if SERVER_NAME contains ###RANDOM###, replace it now + if [[ "${SERVER_NAME:-}" == *"###RANDOM###"* ]]; then + # generate a 6-char alphanumeric token + rand="$(LC_ALL=C tr -dc 'A-Za-z0-9' Copying PalWorldSettings.ini.template to ${GAME_SETTINGS_FILE}" - cp --no-preserve=ownership "${PALWORLD_TEMPLATE_FILE}" "${GAME_SETTINGS_FILE}" + ENVSUBST_SELECTORS='$DIFFICULTY $RANDOMIZER_TYPE $RANDOMIZER_SEED $IS_RANDOMIZER_PAL_LEVEL_RANDOM + $DAYTIME_SPEEDRATE $NIGHTTIME_SPEEDRATE $EXP_RATE $PAL_CAPTURE_RATE + $PAL_SPAWN_NUM_RATE $PAL_DAMAGE_RATE_ATTACK $PAL_DAMAGE_RATE_DEFENSE + $PLAYER_DAMAGE_RATE_ATTACK $PLAYER_DAMAGE_RATE_DEFENSE + $PLAYER_STOMACH_DECREASE_RATE $PLAYER_STAMINA_DECREACE_RATE + $PLAYER_AUTO_HP_REGENE_RATE $PLAYER_AUTO_HP_REGENE_RATE_IN_SLEEP + $PAL_STOMACH_DECREACE_RATE $PAL_STAMINA_DECREACE_RATE + $PAL_AUTO_HP_REGENE_RATE $PAL_AUTO_HP_REGENE_RATE_IN_SLEEP + $BUILD_OBJECT_HP_RATE $BUILD_OBJECT_DAMAGE_RATE + $BUILD_OBJECT_DETERIORATION_DAMAGE_RATE $COLLECTION_DROP_RATE + $COLLECTION_OBJECT_HP_RATE $COLLECTION_OBJECT_RESPAWN_SPEED_RATE + $ENEMY_DROP_ITEM_RATE $DEATH_PENALTY $ENABLE_PLAYER_TO_PLAYER_DAMAGE + $ENABLE_FRIENDLY_FIRE $ENABLE_INVADER_ENEMY $ACTIVE_UNKO + $ENABLE_AIM_ASSIST_PAD $ENABLE_AIM_ASSIST_KEYBOARD $DROP_ITEM_MAX_NUM + $DROP_ITEM_MAX_NUM_UNKO $BASE_CAMP_MAX_NUM $BASE_CAMP_WORKER_MAXNUM + $DROP_ITEM_ALIVE_MAX_HOURS $AUTO_RESET_GUILD_NO_ONLINE_PLAYERS + $AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS $GUILD_PLAYER_MAX_NUM + $BASE_CAMP_MAX_NUM_IN_GUILD $PAL_EGG_DEFAULT_HATCHING_TIME + $WORK_SPEED_RATE $AUTO_SAVE_SPAN $IS_MULTIPLAY $IS_PVP $HARDCORE + $PAL_LOST $CHARACTER_RECREATE_IN_HARDCORE + $CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP $ENABLE_NON_LOGIN_PENALTY + $ENABLE_FAST_TRAVEL $IS_START_LOCATION_SELECT_BY_MAP + $EXIST_PLAYER_AFTER_LOGOUT $ENABLE_DEFENSE_OTHER_GUILD_PLAYER + $INVISBIBLE_OTHER_GUILD_BASE_CAMP_AREA_FX $BUILD_AREA_LIMIT + $ITEM_WEIGHT_RATE $COOP_PLAYER_MAX_NUM $MAX_PLAYERS $SERVER_NAME + $SERVER_DESCRIPTION $ADMIN_PASSWORD $SERVER_PASSWORD $PUBLIC_PORT + $PUBLIC_IP $RCON_ENABLED $RCON_PORT $REGION $USEAUTH $BAN_LIST_URL + $RESTAPI_ENABLED $RESTAPI_PORT $SHOW_PLAYER_LIST + $CHAT_POST_LIMIT_PER_MINUTE $CROSSPLAY_PLATFORMS $ENABLE_WORLD_BACKUP + $LOG_FORMAT_TYPE $SUPPLY_DROP_SPAN $ENABLE_PREDATOR_BOSS_PAL + $MAX_BUILDING_LIMIT_NUM $SERVER_REPLICATE_PAWN_CULL_DISTANCE + $ALLOW_GLOBAL_PALBOX_EXPORT $ALLOW_GLOBAL_PALBOX_IMPORT + $EQUIPMENT_DURABILITY_DAMAGE_RATE $ITEM_CONTAINER_FORCE_MARK_DIRTY_INTERVAL' + - if [[ -n ${DIFFICULTY+x} ]]; then - e_with_counter "Difficulty to '$DIFFICULTY'" - sed -E -i "s/Difficulty=[a-zA-Z]*/Difficulty=$DIFFICULTY/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${RANDOMIZER_TYPE+x} ]]; then - e_with_counter "RandomizerType to '$RANDOMIZER_TYPE'" - sed -E -i "s/RandomizerType=[a-zA-Z]*/RandomizerType=$RANDOMIZER_TYPE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${RANDOMIZER_SEED+x} ]]; then - e_with_counter "RandomizerSeed to '$RANDOMIZER_SEED'" - sed -E -i "s/RandomizerSeed=\"[^\"]*\"/RandomizerSeed=\"$RANDOMIZER_SEED\"/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${IS_RANDOMIZER_PAL_LEVEL_RANDOM+x} ]]; then - e_with_counter "bIsRandomizerPalLevelRandom to '$IS_RANDOMIZER_PAL_LEVEL_RANDOM'" - sed -E -i "s/bIsRandomizerPalLevelRandom=[a-zA-Z]*/bIsRandomizerPalLevelRandom=$IS_RANDOMIZER_PAL_LEVEL_RANDOM/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${DAYTIME_SPEEDRATE+x} ]]; then - e_with_counter "DayTimeSpeedRate to '$DAYTIME_SPEEDRATE'" - sed -E -i "s/DayTimeSpeedRate=[+-]?([0-9]*[.])?[0-9]+/DayTimeSpeedRate=$DAYTIME_SPEEDRATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${NIGHTTIME_SPEEDRATE+x} ]]; then - e_with_counter "NightTimeSpeedRate to '$NIGHTTIME_SPEEDRATE'" - sed -E -i "s/NightTimeSpeedRate=[+-]?([0-9]*[.])?[0-9]+/NightTimeSpeedRate=$NIGHTTIME_SPEEDRATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${EXP_RATE+x} ]]; then - e_with_counter "ExpRate to '$EXP_RATE'" - sed -E -i "s/ExpRate=[+-]?([0-9]*[.])?[0-9]+/ExpRate=$EXP_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_CAPTURE_RATE+x} ]]; then - e_with_counter "PalCaptureRate to '$PAL_CAPTURE_RATE'" - sed -E -i "s/PalCaptureRate=[+-]?([0-9]*[.])?[0-9]+/PalCaptureRate=$PAL_CAPTURE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_SPAWN_NUM_RATE+x} ]]; then - e_with_counter "PalSpawnNumRate to '$PAL_SPAWN_NUM_RATE'" - sed -E -i "s/PalSpawnNumRate=[+-]?([0-9]*[.])?[0-9]+/PalSpawnNumRate=$PAL_SPAWN_NUM_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_DAMAGE_RATE_ATTACK+x} ]]; then - e_with_counter "PalDamageRateAttack to '$PAL_DAMAGE_RATE_ATTACK'" - sed -E -i "s/PalDamageRateAttack=[+-]?([0-9]*[.])?[0-9]+/PalDamageRateAttack=$PAL_DAMAGE_RATE_ATTACK/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_DAMAGE_RATE_DEFENSE+x} ]]; then - e_with_counter "PalDamageRateDefense to '$PAL_DAMAGE_RATE_DEFENSE'" - sed -E -i "s/PalDamageRateDefense=[+-]?([0-9]*[.])?[0-9]+/PalDamageRateDefense=$PAL_DAMAGE_RATE_DEFENSE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PLAYER_DAMAGE_RATE_ATTACK+x} ]]; then - e_with_counter "PlayerDamageRateAttack to '$PLAYER_DAMAGE_RATE_ATTACK'" - sed -E -i "s/PlayerDamageRateAttack=[+-]?([0-9]*[.])?[0-9]+/PlayerDamageRateAttack=$PLAYER_DAMAGE_RATE_ATTACK/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PLAYER_DAMAGE_RATE_DEFENSE+x} ]]; then - e_with_counter "PlayerDamageRateDefense to '$PLAYER_DAMAGE_RATE_DEFENSE'" - sed -E -i "s/PlayerDamageRateDefense=[+-]?([0-9]*[.])?[0-9]+/PlayerDamageRateDefense=$PLAYER_DAMAGE_RATE_DEFENSE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PLAYER_STOMACH_DECREASE_RATE+x} ]]; then - e_with_counter "PlayerStomachDecreaceRate to '$PLAYER_STOMACH_DECREASE_RATE'" - sed -E -i "s/PlayerStomachDecreaceRate=[+-]?([0-9]*[.])?[0-9]+/PlayerStomachDecreaceRate=$PLAYER_STOMACH_DECREASE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PLAYER_STAMINA_DECREACE_RATE+x} ]]; then - e_with_counter "PlayerStaminaDecreaceRate to '$PLAYER_STAMINA_DECREACE_RATE'" - sed -E -i "s/PlayerStaminaDecreaceRate=[+-]?([0-9]*[.])?[0-9]+/PlayerStaminaDecreaceRate=$PLAYER_STAMINA_DECREACE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PLAYER_AUTO_HP_REGENE_RATE+x} ]]; then - e_with_counter "PlayerAutoHPRegeneRate to '$PLAYER_AUTO_HP_REGENE_RATE'" - sed -E -i "s/PlayerAutoHPRegeneRate=[+-]?([0-9]*[.])?[0-9]+/PlayerAutoHPRegeneRate=$PLAYER_AUTO_HP_REGENE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PLAYER_AUTO_HP_REGENE_RATE_IN_SLEEP+x} ]]; then - e_with_counter "PlayerAutoHpRegeneRateInSleep to '$PLAYER_AUTO_HP_REGENE_RATE_IN_SLEEP'" - sed -E -i "s/PlayerAutoHpRegeneRateInSleep=[+-]?([0-9]*[.])?[0-9]+/PlayerAutoHpRegeneRateInSleep=$PLAYER_AUTO_HP_REGENE_RATE_IN_SLEEP/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_STOMACH_DECREACE_RATE+x} ]]; then - e_with_counter "PalStomachDecreaceRate to '$PAL_STOMACH_DECREACE_RATE'" - sed -E -i "s/PalStomachDecreaceRate=[+-]?([0-9]*[.])?[0-9]+/PalStomachDecreaceRate=$PAL_STOMACH_DECREACE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_STAMINA_DECREACE_RATE+x} ]]; then - e_with_counter "PalStaminaDecreaceRate to '$PAL_STAMINA_DECREACE_RATE'" - sed -E -i "s/PalStaminaDecreaceRate=[+-]?([0-9]*[.])?[0-9]+/PalStaminaDecreaceRate=$PAL_STAMINA_DECREACE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_AUTO_HP_REGENE_RATE+x} ]]; then - e_with_counter "PalAutoHPRegeneRate to '$PAL_AUTO_HP_REGENE_RATE'" - sed -E -i "s/PalAutoHPRegeneRate=[+-]?([0-9]*[.])?[0-9]+/PalAutoHPRegeneRate=$PAL_AUTO_HP_REGENE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_AUTO_HP_REGENE_RATE_IN_SLEEP+x} ]]; then - e_with_counter "PalAutoHpRegeneRateInSleep to '$PAL_AUTO_HP_REGENE_RATE_IN_SLEEP'" - sed -E -i "s/PalAutoHpRegeneRateInSleep=[+-]?([0-9]*[.])?[0-9]+/PalAutoHpRegeneRateInSleep=$PAL_AUTO_HP_REGENE_RATE_IN_SLEEP/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${BUILD_OBJECT_HP_RATE+x} ]]; then - e_with_counter "BuildObjectHpRate to '$BUILD_OBJECT_HP_RATE'" - sed -E -i "s/BuildObjectHpRate=[+-]?([0-9]*[.])?[0-9]+/BuildObjectHpRate=$BUILD_OBJECT_HP_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${BUILD_OBJECT_DAMAGE_RATE+x} ]]; then - e_with_counter "BuildObjectDamageRate to '$BUILD_OBJECT_DAMAGE_RATE'" - sed -E -i "s/BuildObjectDamageRate=[+-]?([0-9]*[.])?[0-9]+/BuildObjectDamageRate=$BUILD_OBJECT_DAMAGE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${BUILD_OBJECT_DETERIORATION_DAMAGE_RATE+x} ]]; then - e_with_counter "BuildObjectDeteriorationDamageRate to '$BUILD_OBJECT_DETERIORATION_DAMAGE_RATE'" - sed -E -i "s/BuildObjectDeteriorationDamageRate=[+-]?([0-9]*[.])?[0-9]+/BuildObjectDeteriorationDamageRate=$BUILD_OBJECT_DETERIORATION_DAMAGE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${COLLECTION_DROP_RATE+x} ]]; then - e_with_counter "CollectionDropRate to '$COLLECTION_DROP_RATE'" - sed -E -i "s/CollectionDropRate=[+-]?([0-9]*[.])?[0-9]+/CollectionDropRate=$COLLECTION_DROP_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${COLLECTION_OBJECT_HP_RATE+x} ]]; then - e_with_counter "CollectionObjectHpRate to '$COLLECTION_OBJECT_HP_RATE'" - sed -E -i "s/CollectionObjectHpRate=[+-]?([0-9]*[.])?[0-9]+/CollectionObjectHpRate=$COLLECTION_OBJECT_HP_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${COLLECTION_OBJECT_RESPAWN_SPEED_RATE+x} ]]; then - e_with_counter "CollectionObjectRespawnSpeedRate to '$COLLECTION_OBJECT_RESPAWN_SPEED_RATE'" - sed -E -i "s/CollectionObjectRespawnSpeedRate=[+-]?([0-9]*[.])?[0-9]+/CollectionObjectRespawnSpeedRate=$COLLECTION_OBJECT_RESPAWN_SPEED_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENEMY_DROP_ITEM_RATE+x} ]]; then - e_with_counter "EnemyDropItemRate to '$ENEMY_DROP_ITEM_RATE'" - sed -E -i "s/EnemyDropItemRate=[+-]?([0-9]*[.])?[0-9]+/EnemyDropItemRate=$ENEMY_DROP_ITEM_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${DEATH_PENALTY+x} ]]; then - e_with_counter "DeathPenalty to '$DEATH_PENALTY'" - sed -E -i "s/DeathPenalty=[a-zA-Z]*/DeathPenalty=$DEATH_PENALTY/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_PLAYER_TO_PLAYER_DAMAGE+x} ]]; then - e_with_counter "bEnablePlayerToPlayerDamage to '$ENABLE_PLAYER_TO_PLAYER_DAMAGE'" - sed -E -i "s/bEnablePlayerToPlayerDamage=[a-zA-Z]*/bEnablePlayerToPlayerDamage=$ENABLE_PLAYER_TO_PLAYER_DAMAGE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_FRIENDLY_FIRE+x} ]]; then - e_with_counter "bEnableFriendlyFire to '$ENABLE_FRIENDLY_FIRE'" - sed -E -i "s/bEnableFriendlyFire=[a-zA-Z]*/bEnableFriendlyFire=$ENABLE_FRIENDLY_FIRE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_INVADER_ENEMY+x} ]]; then - e_with_counter "bEnableInvaderEnemy to '$ENABLE_INVADER_ENEMY'" - sed -E -i "s/bEnableInvaderEnemy=[a-zA-Z]*/bEnableInvaderEnemy=$ENABLE_INVADER_ENEMY/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ACTIVE_UNKO+x} ]]; then - e_with_counter "bActiveUNKO to '$ACTIVE_UNKO'" - sed -E -i "s/bActiveUNKO=[a-zA-Z]*/bActiveUNKO=$ACTIVE_UNKO/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_AIM_ASSIST_PAD+x} ]]; then - e_with_counter "bEnableAimAssistPad to '$ENABLE_AIM_ASSIST_PAD'" - sed -E -i "s/bEnableAimAssistPad=[a-zA-Z]*/bEnableAimAssistPad=$ENABLE_AIM_ASSIST_PAD/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_AIM_ASSIST_KEYBOARD+x} ]]; then - e_with_counter "bEnableAimAssistKeyboard to '$ENABLE_AIM_ASSIST_KEYBOARD'" - sed -E -i "s/bEnableAimAssistKeyboard=[a-zA-Z]*/bEnableAimAssistKeyboard=$ENABLE_AIM_ASSIST_KEYBOARD/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${DROP_ITEM_MAX_NUM+x} ]]; then - e_with_counter "DropItemMaxNum to '$DROP_ITEM_MAX_NUM'" - sed -E -i "s/DropItemMaxNum=[0-9]*/DropItemMaxNum=$DROP_ITEM_MAX_NUM/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${DROP_ITEM_MAX_NUM_UNKO+x} ]]; then - e_with_counter "DropItemMaxNum_UNKO to '$DROP_ITEM_MAX_NUM_UNKO'" - sed -E -i "s/DropItemMaxNum_UNKO=[0-9]*/DropItemMaxNum_UNKO=$DROP_ITEM_MAX_NUM_UNKO/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${BASE_CAMP_MAX_NUM+x} ]]; then - e_with_counter "BaseCampMaxNum to '$BASE_CAMP_MAX_NUM'" - sed -E -i "s/BaseCampMaxNum=[0-9]*/BaseCampMaxNum=$BASE_CAMP_MAX_NUM/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${BASE_CAMP_WORKER_MAXNUM+x} ]]; then - e_with_counter "BaseCampWorkerMaxNum to '$BASE_CAMP_WORKER_MAXNUM'" - sed -E -i "s/BaseCampWorkerMaxNum=[0-9]*/BaseCampWorkerMaxNum=$BASE_CAMP_WORKER_MAXNUM/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${DROP_ITEM_ALIVE_MAX_HOURS+x} ]]; then - e_with_counter "DropItemAliveMaxHours to '$DROP_ITEM_ALIVE_MAX_HOURS'" - sed -E -i "s/DropItemAliveMaxHours=[+-]?([0-9]*[.])?[0-9]+/DropItemAliveMaxHours=$DROP_ITEM_ALIVE_MAX_HOURS/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${AUTO_RESET_GUILD_NO_ONLINE_PLAYERS+x} ]]; then - e_with_counter "bAutoResetGuildNoOnlinePlayers to '$AUTO_RESET_GUILD_NO_ONLINE_PLAYERS'" - sed -E -i "s/bAutoResetGuildNoOnlinePlayers=[a-zA-Z]*/bAutoResetGuildNoOnlinePlayers=$AUTO_RESET_GUILD_NO_ONLINE_PLAYERS/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS+x} ]]; then - e_with_counter "AutoResetGuildTimeNoOnlinePlayers to '$AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS'" - sed -E -i "s/AutoResetGuildTimeNoOnlinePlayers=[+-]?([0-9]*[.])?[0-9]+/AutoResetGuildTimeNoOnlinePlayers=$AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${GUILD_PLAYER_MAX_NUM+x} ]]; then - e_with_counter "GuildPlayerMaxNum to '$GUILD_PLAYER_MAX_NUM'" - sed -E -i "s/GuildPlayerMaxNum=[0-9]*/GuildPlayerMaxNum=$GUILD_PLAYER_MAX_NUM/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${BASE_CAMP_MAX_NUM_IN_GUILD+x} ]]; then - e_with_counter "BaseCampMaxNumInGuild to '$BASE_CAMP_MAX_NUM_IN_GUILD'" - sed -E -i "s/BaseCampMaxNumInGuild=[0-9]*/BaseCampMaxNumInGuild=$BASE_CAMP_MAX_NUM_IN_GUILD/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_EGG_DEFAULT_HATCHING_TIME+x} ]]; then - e_with_counter "PalEggDefaultHatchingTime to '$PAL_EGG_DEFAULT_HATCHING_TIME'" - sed -E -i "s/PalEggDefaultHatchingTime=[+-]?([0-9]*[.])?[0-9]+/PalEggDefaultHatchingTime=$PAL_EGG_DEFAULT_HATCHING_TIME/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${WORK_SPEED_RATE+x} ]]; then - e_with_counter "WorkSpeedRate to '$WORK_SPEED_RATE'" - sed -E -i "s/WorkSpeedRate=[+-]?([0-9]*[.])?[0-9]+/WorkSpeedRate=$WORK_SPEED_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${AUTO_SAVE_SPAN+x} ]]; then - e_with_counter "AutoSaveSpan to '$AUTO_SAVE_SPAN'" - sed -E -i "s/AutoSaveSpan=[+-]?([0-9]*[.])?[0-9]+/AutoSaveSpan=$AUTO_SAVE_SPAN/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${IS_MULTIPLAY+x} ]]; then - e_with_counter "bIsMultiplay to '$IS_MULTIPLAY'" - sed -E -i "s/bIsMultiplay=[a-zA-Z]*/bIsMultiplay=$IS_MULTIPLAY/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${IS_PVP+x} ]]; then - e_with_counter "bIsPvP to $IS_PVP" - sed -E -i "s/bIsPvP=[a-zA-Z]*/bIsPvP=$IS_PVP/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${HARDCORE+x} ]]; then - e_with_counter "bHardcore to $HARDCORE" - sed -E -i "s/bHardcore=[a-zA-Z]*/bHardcore=$HARDCORE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PAL_LOST+x} ]]; then - e_with_counter "bPalLost to $PAL_LOST" - sed -E -i "s/bPalLost=[a-zA-Z]*/bPalLost=$PAL_LOST/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${CHARACTER_RECREATE_IN_HARDCORE+x} ]]; then - e_with_counter "bCharacterRecreateInHardcore to $CHARACTER_RECREATE_IN_HARDCORE" - sed -E -i "s/bCharacterRecreateInHardcore=[a-zA-Z]*/bCharacterRecreateInHardcore=$CHARACTER_RECREATE_IN_HARDCORE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP+x} ]]; then - e_with_counter "bCanPickupOtherGuildDeathPenaltyDrop to '$CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP'" - sed -E -i "s/bCanPickupOtherGuildDeathPenaltyDrop=[a-zA-Z]*/bCanPickupOtherGuildDeathPenaltyDrop=$CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_NON_LOGIN_PENALTY+x} ]]; then - e_with_counter "bEnableNonLoginPenalty to '$ENABLE_NON_LOGIN_PENALTY'" - sed -E -i "s/bEnableNonLoginPenalty=[a-zA-Z]*/bEnableNonLoginPenalty=$ENABLE_NON_LOGIN_PENALTY/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_FAST_TRAVEL+x} ]]; then - e_with_counter "bEnableFastTravel to '$ENABLE_FAST_TRAVEL'" - sed -E -i "s/bEnableFastTravel=[a-zA-Z]*/bEnableFastTravel=$ENABLE_FAST_TRAVEL/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${IS_START_LOCATION_SELECT_BY_MAP+x} ]]; then - e_with_counter "bIsStartLocationSelectByMap to '$IS_START_LOCATION_SELECT_BY_MAP'" - sed -E -i "s/bIsStartLocationSelectByMap=[a-zA-Z]*/bIsStartLocationSelectByMap=$IS_START_LOCATION_SELECT_BY_MAP/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${EXIST_PLAYER_AFTER_LOGOUT+x} ]]; then - e_with_counter "bExistPlayerAfterLogout to '$EXIST_PLAYER_AFTER_LOGOUT'" - sed -E -i "s/bExistPlayerAfterLogout=[a-zA-Z]*/bExistPlayerAfterLogout=$EXIST_PLAYER_AFTER_LOGOUT/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_DEFENSE_OTHER_GUILD_PLAYER+x} ]]; then - e_with_counter "bEnableDefenseOtherGuildPlayer to '$ENABLE_DEFENSE_OTHER_GUILD_PLAYER'" - sed -E -i "s/bEnableDefenseOtherGuildPlayer=[a-zA-Z]*/bEnableDefenseOtherGuildPlayer=$ENABLE_DEFENSE_OTHER_GUILD_PLAYER/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${INVISBIBLE_OTHER_GUILD_BASE_CAMP_AREA_FX+x} ]]; then - e_with_counter "bInvisibleOtherGuildBaseCampAreaFX to '$INVISBIBLE_OTHER_GUILD_BASE_CAMP_AREA_FX'" - sed -E -i "s/bInvisibleOtherGuildBaseCampAreaFX=[a-zA-Z]*/bInvisibleOtherGuildBaseCampAreaFX=$INVISBIBLE_OTHER_GUILD_BASE_CAMP_AREA_FX/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${BUILD_AREA_LIMIT+x} ]]; then - e_with_counter "bBuildAreaLimit to '$BUILD_AREA_LIMIT'" - sed -E -i "s/bBuildAreaLimit=[a-zA-Z]*/bBuildAreaLimit=$BUILD_AREA_LIMIT/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ITEM_WEIGHT_RATE+x} ]]; then - e_with_counter "ItemWeightRate to '$ITEM_WEIGHT_RATE'" - sed -E -i "s/ItemWeightRate=[+-]?([0-9]*[.])?[0-9]+/ItemWeightRate=$ITEM_WEIGHT_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${COOP_PLAYER_MAX_NUM+x} ]]; then - e_with_counter "CoopPlayerMaxNum to '$COOP_PLAYER_MAX_NUM'" - sed -E -i "s/CoopPlayerMaxNum=[0-9]*/CoopPlayerMaxNum=$COOP_PLAYER_MAX_NUM/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${MAX_PLAYERS+x} ]]; then - e_with_counter "max-players to '$MAX_PLAYERS'" - sed -E -i "s/ServerPlayerMaxNum=[0-9]*/ServerPlayerMaxNum=$MAX_PLAYERS/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${SERVER_NAME+x} ]]; then - e_with_counter "server name to '$SERVER_NAME'" - sed -E -i "s/ServerName=\"[^\"]*\"/ServerName=\"$SERVER_NAME\"/" "$GAME_SETTINGS_FILE" - if [[ "$SERVER_NAME" == *"###RANDOM###"* ]]; then - RAND_VALUE=$RANDOM - e "> Found standard template, using random numbers in server name" - sed -E -i -e "s/###RANDOM###/$RAND_VALUE/g" "$GAME_SETTINGS_FILE" - e "> Server name is now 'jammsen-docker-generated-$RAND_VALUE'" - fi - fi - if [[ -n ${SERVER_DESCRIPTION+x} ]]; then - e_with_counter "server description to '$SERVER_DESCRIPTION'" - sed -E -i "s/ServerDescription=\"[^\"]*\"/ServerDescription=\"$SERVER_DESCRIPTION\"/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ADMIN_PASSWORD+x} ]]; then - e_with_counter "server admin password to [REDACTED]" - sed -E -i "s/AdminPassword=\"[^\"]*\"/AdminPassword=\"$ADMIN_PASSWORD\"/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${SERVER_PASSWORD+x} ]]; then - e_with_counter "server password to [REDACTED]" - sed -E -i "s/ServerPassword=\"[^\"]*\"/ServerPassword=\"$SERVER_PASSWORD\"/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PUBLIC_PORT+x} ]]; then - e_with_counter "public port to '$PUBLIC_PORT'" - sed -E -i "s/PublicPort=[0-9]*/PublicPort=$PUBLIC_PORT/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${PUBLIC_IP+x} ]]; then - e_with_counter "public ip to '$PUBLIC_IP'" - sed -E -i "s/PublicIP=\"[^\"]*\"/PublicIP=\"$PUBLIC_IP\"/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${RCON_ENABLED+x} ]]; then - e_with_counter "rcon-enabled to '$RCON_ENABLED'" - sed -E -i "s/RCONEnabled=[a-zA-Z]*/RCONEnabled=$RCON_ENABLED/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${RCON_PORT+x} ]]; then - e_with_counter "RCONPort to '$RCON_PORT'" - sed -E -i "s/RCONPort=[0-9]*/RCONPort=$RCON_PORT/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${REGION+x} ]]; then - e_with_counter "Region to '$REGION'" - sed -E -i "s/Region=\"[^\"]*\"/Region=\"$REGION\"/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${USEAUTH+x} ]]; then - e_with_counter "bUseAuth to '$USEAUTH'" - sed -E -i "s/bUseAuth=[a-zA-Z]*/bUseAuth=$USEAUTH/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${BAN_LIST_URL+x} ]]; then - e_with_counter "BanListURL to '$BAN_LIST_URL'" - sed -E -i "s~BanListURL=\"[^\"]*\"~BanListURL=\"$BAN_LIST_URL\"~" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${RESTAPI_ENABLED+x} ]]; then - e_with_counter "RESTAPIEnabled to '$RESTAPI_ENABLED'" - sed -E -i "s/RESTAPIEnabled=[a-zA-Z]*/RESTAPIEnabled=$RESTAPI_ENABLED/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${RESTAPI_PORT+x} ]]; then - e_with_counter "RESTAPIPort to '$RESTAPI_PORT'" - sed -E -i "s/RESTAPIPort=[0-9]*/RESTAPIPort=$RESTAPI_PORT/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${SHOW_PLAYER_LIST+x} ]]; then - e_with_counter "bShowPlayerList to '$SHOW_PLAYER_LIST'" - sed -E -i "s/bShowPlayerList=[a-zA-Z]*/bShowPlayerList=$SHOW_PLAYER_LIST/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${CHAT_POST_LIMIT_PER_MINUTE+x} ]]; then - e_with_counter "ChatPostLimitPerMinute to '$CHAT_POST_LIMIT_PER_MINUTE'" - sed -E -i "s/ChatPostLimitPerMinute=[0-9]*/ChatPostLimitPerMinute=$CHAT_POST_LIMIT_PER_MINUTE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${CROSSPLAY_PLATFORMS+x} ]]; then - e_with_counter "CrossplayPlatforms to '$CROSSPLAY_PLATFORMS'" - sed -E -i "s/CrossplayPlatforms=\([^)]*\)/CrossplayPlatforms=$CROSSPLAY_PLATFORMS/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_WORLD_BACKUP+x} ]]; then - e_with_counter "bIsUseBackupSaveData to '$ENABLE_WORLD_BACKUP'" - sed -E -i "s/bIsUseBackupSaveData=[a-zA-Z]*/bIsUseBackupSaveData=$ENABLE_WORLD_BACKUP/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${LOG_FORMAT_TYPE+x} ]]; then - e_with_counter "LogFormatType to '$LOG_FORMAT_TYPE'" - sed -E -i "s/LogFormatType=[a-zA-Z]*/LogFormatType=$LOG_FORMAT_TYPE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${SUPPLY_DROP_SPAN+x} ]]; then - e_with_counter "SupplyDropSpan to '$SUPPLY_DROP_SPAN'" - sed -E -i "s/SupplyDropSpan=[0-9]*/SupplyDropSpan=$SUPPLY_DROP_SPAN/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ENABLE_PREDATOR_BOSS_PAL+x} ]]; then - e_with_counter "EnablePredatorBossPal to '$ENABLE_PREDATOR_BOSS_PAL'" - sed -E -i "s/EnablePredatorBossPal=[a-zA-Z]*/EnablePredatorBossPal=$ENABLE_PREDATOR_BOSS_PAL/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${MAX_BUILDING_LIMIT_NUM+x} ]]; then - e_with_counter "MaxBuildingLimitNum to '$MAX_BUILDING_LIMIT_NUM'" - sed -E -i "s/MaxBuildingLimitNum=[0-9]*/MaxBuildingLimitNum=$MAX_BUILDING_LIMIT_NUM/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${SERVER_REPLICATE_PAWN_CULL_DISTANCE+x} ]]; then - e_with_counter "ServerReplicatePawnCullDistance to '$SERVER_REPLICATE_PAWN_CULL_DISTANCE'" - sed -E -i "s/ServerReplicatePawnCullDistance=[+-]?([0-9]*[.])?[0-9]+/ServerReplicatePawnCullDistance=$SERVER_REPLICATE_PAWN_CULL_DISTANCE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ALLOW_GLOBAL_PALBOX_EXPORT+x} ]]; then - e_with_counter "bAllowGlobalPalboxExport to '$ALLOW_GLOBAL_PALBOX_EXPORT'" - sed -E -i "s/bAllowGlobalPalboxExport=[a-zA-Z]*/bAllowGlobalPalboxExport=$ALLOW_GLOBAL_PALBOX_EXPORT/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ALLOW_GLOBAL_PALBOX_IMPORT+x} ]]; then - e_with_counter "bAllowGlobalPalboxImport to '$ALLOW_GLOBAL_PALBOX_IMPORT'" - sed -E -i "s/bAllowGlobalPalboxImport=[a-zA-Z]*/bAllowGlobalPalboxImport=$ALLOW_GLOBAL_PALBOX_IMPORT/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${EQUIPMENT_DURABILITY_DAMAGE_RATE+x} ]]; then - e_with_counter "EquipmentDurabilityDamageRate to '$EQUIPMENT_DURABILITY_DAMAGE_RATE'" - sed -E -i "s/EquipmentDurabilityDamageRate=[+-]?([0-9]*[.])?[0-9]+/EquipmentDurabilityDamageRate=$EQUIPMENT_DURABILITY_DAMAGE_RATE/" "$GAME_SETTINGS_FILE" - fi - if [[ -n ${ITEM_CONTAINER_FORCE_MARK_DIRTY_INTERVAL+x} ]]; then - e_with_counter "ItemContainerForceMarkDirtyInterval to '$ITEM_CONTAINER_FORCE_MARK_DIRTY_INTERVAL'" - sed -E -i "s/ItemContainerForceMarkDirtyInterval=[+-]?([0-9]*[.])?[0-9]+/ItemContainerForceMarkDirtyInterval=$ITEM_CONTAINER_FORCE_MARK_DIRTY_INTERVAL/" "$GAME_SETTINGS_FILE" + if ! envsubst "$ENVSUBST_SELECTORS" < "${PALWORLD_TEMPLATE_FILE}" > "${GAME_SETTINGS_FILE}"; then + ee "Failed to generate ${GAME_SETTINGS_FILE}" + return 1 fi es ">>> Finished setting up PalWorldSettings.ini" } function setup_rcon_yaml () { - if [[ -n ${RCON_ENABLED+x} ]] && [ "$RCON_ENABLED" == "true" ] ; then + if [[ -n ${RCON_ENABLED+x} ]] && [[ "${RCON_ENABLED,,}" == "true" ]] ; then ei ">>> RCON is enabled - Setting up rcon.yaml ..." - if [[ -n ${RCON_PORT+x} ]]; then - sed -i "s/###RCON_PORT###/$RCON_PORT/" "$RCON_CONFIG_FILE" - else - ee "> RCON_PORT is not set, please set it for RCON functionality to work!" - fi - if [[ -n ${ADMIN_PASSWORD+x} ]]; then - sed -i "s/###ADMIN_PASSWORD###/$ADMIN_PASSWORD/" "$RCON_CONFIG_FILE" + if [[ -n ${RCON_PORT+x} ]] && [[ -n ${ADMIN_PASSWORD+x} ]]; then + TEMP_FILE=$(mktemp) + if envsubst '$RCON_PORT $ADMIN_PASSWORD' < "$RCON_CONFIG_FILE" > "$TEMP_FILE"; then + mv "$TEMP_FILE" "$RCON_CONFIG_FILE" + else + ee "Failed to process rcon.yaml" + rm -f "$TEMP_FILE" + return 1 + fi else - ee "> RCON_PORT is not set, please set it for RCON functionality to work!" + ee "> RCON_PORT and/or ADMIN_PASSWORD are not set; please set both for RCON to work!" fi es ">>> Finished setting up 'rcon.yaml' config file" else diff --git a/includes/playerdetection.sh b/includes/playerdetection.sh index b82a4a2..090c17b 100644 --- a/includes/playerdetection.sh +++ b/includes/playerdetection.sh @@ -21,7 +21,7 @@ rcon_showplayers_with_retry() { for ((i=0; i /dev/null) - if [[ -n $RCON_PLAYER_DEBUG ]] && [[ $RCON_PLAYER_DEBUG == "true" ]]; then + if [[ -n $RCON_PLAYER_DEBUG ]] && [[ "${RCON_PLAYER_DEBUG,,}" == "true" ]]; then ew "Debug: command_output = '$command_output'" ew "Exitcode was: $?" fi @@ -34,14 +34,14 @@ rcon_showplayers_with_retry() { # root@contaierid:/home/steam/steamcmd# rcon showplayers # name,playeruid,steamid readarray -t current_players <<< "$(echo "$command_output" | tail -n +2)" - if [[ -n $RCON_PLAYER_DEBUG ]] && [[ $RCON_PLAYER_DEBUG == "true" ]]; then + if [[ -n $RCON_PLAYER_DEBUG ]] && [[ "${RCON_PLAYER_DEBUG,,}" == "true" ]]; then ew "Debug: current_players = ${current_players[*]}" fi else # If there is no error exit code but data is missing at least 1 line, something is off # therefore we shouldnt set current_players to empty? # current_players=() - if [[ -n $RCON_PLAYER_DEBUG ]] && [[ $RCON_PLAYER_DEBUG == "true" ]]; then + if [[ -n $RCON_PLAYER_DEBUG ]] && [[ "${RCON_PLAYER_DEBUG,,}" == "true" ]]; then ew "Debug: No player data available." fi fi @@ -63,7 +63,7 @@ compare_players() { return fi - if [[ -n $RCON_PLAYER_DEBUG ]] && [[ $RCON_PLAYER_DEBUG == "true" ]]; then + if [[ -n $RCON_PLAYER_DEBUG ]] && [[ "${RCON_PLAYER_DEBUG,,}" == "true" ]]; then ew "Debug: current_players = ${current_players[*]}" fi if [[ ${#current_players[@]} -eq 0 ]]; then @@ -78,7 +78,7 @@ compare_players() { # fi for player_info in "${current_players[@]}"; do - if [[ -n $RCON_PLAYER_DEBUG ]] && [[ $RCON_PLAYER_DEBUG == "true" ]]; then + if [[ -n $RCON_PLAYER_DEBUG ]] && [[ "${RCON_PLAYER_DEBUG,,}" == "true" ]]; then ew "For-Loop-Debug: player_info = '$player_info'" fi # Extract player name, UID, and Steam ID from player info @@ -155,10 +155,10 @@ announce_join() { time=$(date '+[%H:%M:%S]') message="Player $1 has joined the server." echo "${time}: $message" - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_info_notification "$message" fi - if [[ -n $RCON_ENABLED ]] && [[ $RCON_ENABLED == "true" ]]; then + if [[ -n $RCON_ENABLED ]] && [[ "${RCON_ENABLED,,}" == "true" ]]; then broadcast_player_join "${1}" fi } @@ -168,10 +168,10 @@ announce_name_change() { time=$(date '+[%H:%M:%S]') message="Player $1 has changed their name to $2." echo "${time}: $message" - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_info_notification "$message" fi - if [[ -n $RCON_ENABLED ]] && [[ $RCON_ENABLED == "true" ]]; then + if [[ -n $RCON_ENABLED ]] && [[ "${RCON_ENABLED,,}" == "true" ]]; then broadcast_player_name_change "${1}" "${2}" fi } @@ -181,10 +181,10 @@ announce_leave() { time=$(date '+[%H:%M:%S]') message="Player $1 has left the server." echo "${time}: $message" - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_info_notification "$message" fi - if [[ -n $RCON_ENABLED ]] && [[ $RCON_ENABLED == "true" ]]; then + if [[ -n $RCON_ENABLED ]] && [[ "${RCON_ENABLED,,}" == "true" ]]; then broadcast_player_leave "${1}" fi } diff --git a/includes/server.sh b/includes/server.sh index 2ab5da3..3bbee4a 100644 --- a/includes/server.sh +++ b/includes/server.sh @@ -4,22 +4,70 @@ source /includes/colors.sh source /includes/rcon.sh source /includes/webhook.sh +function check_and_run_custom_script() { + if [[ -z $CUSTOM_SCRIPT_ENABLED ]] || [[ "${CUSTOM_SCRIPT_ENABLED,,}" != "true" ]]; then + return 0 + fi + + ei ">>> Custom script is enabled" + + # Validate CUSTOM_SCRIPT_PATH is set + if [[ -z $CUSTOM_SCRIPT_PATH ]]; then + ee ">>> CUSTOM_SCRIPT_PATH is not set, skipping custom script execution" + return 0 + fi + + # Security: require an absolute path to prevent relative-path or injection tricks + if [[ "$CUSTOM_SCRIPT_PATH" != /* ]]; then + ee ">>> CUSTOM_SCRIPT_PATH must be an absolute path (got: '$CUSTOM_SCRIPT_PATH'), skipping" + return 0 + fi + + # Security: reject any path containing '..' to prevent traversal + if [[ "$CUSTOM_SCRIPT_PATH" == *".."* ]]; then + ee ">>> CUSTOM_SCRIPT_PATH must not contain '..', skipping" + return 0 + fi + + # Check that the target is a regular file + if [[ ! -f "$CUSTOM_SCRIPT_PATH" ]]; then + ew "> Custom script not found at '$CUSTOM_SCRIPT_PATH', skipping" + return 0 + fi + + ei "> Found custom script at '$CUSTOM_SCRIPT_PATH', preparing to execute" + chmod u+x "$CUSTOM_SCRIPT_PATH" + + ei "> Executing custom script..." + local custom_script_exit_code=0 + # The '||' prevents set -e from aborting on a non-zero exit so we can log it + "$CUSTOM_SCRIPT_PATH" || custom_script_exit_code=$? + + if [[ $custom_script_exit_code -ne 0 ]]; then + ee ">>> Custom script exited with error code $custom_script_exit_code, continuing server startup" + else + es ">>> Custom script completed successfully" + fi +} + function start_server() { cd "$GAME_ROOT" || exit setup_configs ei ">>> Preparing to start the gameserver" START_OPTIONS=() - if [[ -n $COMMUNITY_SERVER ]] && [[ $COMMUNITY_SERVER == "true" ]]; then + if [[ -n $COMMUNITY_SERVER ]] && [[ "${COMMUNITY_SERVER,,}" == "true" ]]; then e "> Setting Community-Mode to enabled" START_OPTIONS+=("-publiclobby") fi - if [[ -n $MULTITHREAD_ENABLED ]] && [[ $MULTITHREAD_ENABLED == "true" ]]; then + if [[ -n $MULTITHREAD_ENABLED ]] && [[ "${MULTITHREAD_ENABLED,,}" == "true" ]]; then e "> Setting Multi-Core-Enhancements to enabled" START_OPTIONS+=("-useperfthreads" "-NoAsyncLoadingThread" "-UseMultithreadForDS") fi - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_start_notification fi + check_and_run_custom_script + es ">>> Starting the gameserver" ./PalServer.sh "${START_OPTIONS[@]}" } @@ -27,12 +75,12 @@ function start_server() { function stop_server() { ew ">>> Stopping server..." kill -SIGTERM "${PLAYER_DETECTION_PID}" - if [[ -n $RCON_ENABLED ]] && [[ $RCON_ENABLED == "true" ]]; then + if [[ -n $RCON_ENABLED ]] && [[ "${RCON_ENABLED,,}" == "true" ]]; then save_and_shutdown_server fi kill -SIGTERM "$(pidof PalServer-Linux-Shipping)" tail --pid="$(pidof PalServer-Linux-Shipping)" -f 2>/dev/null - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_stop_notification fi ew ">>> Server stopped gracefully" @@ -41,7 +89,7 @@ function stop_server() { function fresh_install_server() { ei ">>> Doing a fresh install of the gameserver..." - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_install_notification fi "${STEAMCMD_PATH}"/steamcmd.sh +force_install_dir "$GAME_ROOT" +login anonymous +app_update 2394010 validate +quit @@ -50,18 +98,21 @@ function fresh_install_server() { function update_server() { # Workaround fix for 0x6 error - ei ">>> Applying workaround fix for 'Error! App '2394010' state is 0x6 after update job.' message, since update 0.3.X..." - rm -f /palworld/steamapps/appmanifest_2394010.acf - if [[ -n $STEAMCMD_VALIDATE_FILES ]] && [[ $STEAMCMD_VALIDATE_FILES == "true" ]]; then + ei ">>> Checking if appmainfest_2394010.acf exists, which can cause the 'Error! App '2394010' state is 0x6 after update job" + if [[ -f /palworld/steamapps/appmanifest_2394010.acf ]]; then + ei ">>> Applying workaround fix for 'Error! App '2394010' state is 0x6 after update job.' message, since update 0.3.X..." + rm -f /palworld/steamapps/appmanifest_2394010.acf + fi + if [[ -n $STEAMCMD_VALIDATE_FILES ]] && [[ "${STEAMCMD_VALIDATE_FILES,,}" == "true" ]]; then ei ">>> Doing an update with validation of the gameserver files..." - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_update_notification fi "${STEAMCMD_PATH}"/steamcmd.sh +force_install_dir "$GAME_ROOT" +login anonymous +app_update 2394010 validate +quit es ">>> Done updating and validating the gameserver files" else ei ">>> Doing an update of the gameserver files..." - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_update_notification fi "${STEAMCMD_PATH}"/steamcmd.sh +force_install_dir "$GAME_ROOT" +login anonymous +app_update 2394010 +quit diff --git a/scripts/backupmanager.sh b/scripts/backupmanager.sh index d14118a..8b9f222 100644 --- a/scripts/backupmanager.sh +++ b/scripts/backupmanager.sh @@ -137,7 +137,7 @@ function create_backup() { mkdir -p "${LOCAL_BACKUP_PATH}" - if [[ -n $LOCAL_BACKUP_ANNOUNCE_MESSAGES_ENABLED ]] && [[ $LOCAL_BACKUP_ANNOUNCE_MESSAGES_ENABLED == "true" ]]; then + if [[ -n $LOCAL_BACKUP_ANNOUNCE_MESSAGES_ENABLED ]] && [[ "${LOCAL_BACKUP_ANNOUNCE_MESSAGES_ENABLED,,}" == "true" ]]; then rconcli broadcast "$(get_time) Saving in 5 seconds..." sleep 5 rconcli broadcast "$(get_time) Saving world..." @@ -154,13 +154,13 @@ function create_backup() { broadcast_backup_failed ee ">>> Backup failed" else - if [[ -n $LOCAL_BACKUP_ANNOUNCE_MESSAGES_ENABLED ]] && [[ $LOCAL_BACKUP_ANNOUNCE_MESSAGES_ENABLED == "true" ]]; then + if [[ -n $LOCAL_BACKUP_ANNOUNCE_MESSAGES_ENABLED ]] && [[ "${LOCAL_BACKUP_ANNOUNCE_MESSAGES_ENABLED,,}" == "true" ]]; then broadcast_backup_success fi es ">>> Backup '${backup_file_name}' created successfully" fi - if [[ -n ${LOCAL_BACKUP_RETENTION_POLICY} ]] && [[ ${LOCAL_BACKUP_RETENTION_POLICY} == "true" ]] && [[ ${LOCAL_BACKUP_RETENTION_AMOUNT_TO_KEEP} =~ ^[0-9]+$ ]]; then + if [[ -n ${LOCAL_BACKUP_RETENTION_POLICY} ]] && [[ "${LOCAL_BACKUP_RETENTION_POLICY,,}" == "true" ]] && [[ ${LOCAL_BACKUP_RETENTION_AMOUNT_TO_KEEP} =~ ^[0-9]+$ ]]; then ls -1t "${LOCAL_BACKUP_PATH}"/saved-*.tar.gz | tail -n +"$(($LOCAL_BACKUP_RETENTION_AMOUNT_TO_KEEP + 1))" | xargs -d '\n' rm -f -- fi } diff --git a/scripts/rconcli.sh b/scripts/rconcli.sh index f494343..9449606 100644 --- a/scripts/rconcli.sh +++ b/scripts/rconcli.sh @@ -8,7 +8,7 @@ source /includes/colors.sh # Arguments: # Example: run_rcon_cli "showplayers" run_rcon_cli() { - if [[ -z ${RCON_ENABLED+x} ]] || [[ "$RCON_ENABLED" != "true" ]]; then + if [[ -z ${RCON_ENABLED+x} ]] || [[ "${RCON_ENABLED,,}" != "true" ]]; then ew ">>> RCON is not enabled. Aborting RCON command ..." exit fi diff --git a/scripts/restart.sh b/scripts/restart.sh index 4a67f05..455e748 100644 --- a/scripts/restart.sh +++ b/scripts/restart.sh @@ -14,15 +14,15 @@ function get_time() { function schedule_restart() { ew ">>> Automatic restart was triggered..." PLAYER_DETECTION_PID=$(=1; counter--)); do - if [[ -n $RCON_ENABLED ]] && [[ $RCON_ENABLED == "true" ]]; then + if [[ -n $RCON_ENABLED ]] && [[ "${RCON_ENABLED,,}" == "true" ]]; then if check_is_server_empty; then ew ">>> Server is empty, restarting now" - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_restart_now_notification fi break @@ -31,26 +31,26 @@ function schedule_restart() { fi rconcli broadcast "$(get_time) AUTOMATIC RESTART IN $counter MINUTES" fi - if [[ -n $RESTART_DEBUG_OVERRIDE ]] && [[ $RESTART_DEBUG_OVERRIDE == "true" ]]; then + if [[ -n $RESTART_DEBUG_OVERRIDE ]] && [[ "${RESTART_DEBUG_OVERRIDE,,}" == "true" ]]; then sleep 1 else sleep 60 fi done - if [[ -n $RCON_ENABLED ]] && [[ $RCON_ENABLED == "true" ]]; then + if [[ -n $RCON_ENABLED ]] && [[ "${RCON_ENABLED,,}" == "true" ]]; then rconcli broadcast "$(get_time) Saving world before restart..." rconcli save rconcli broadcast "$(get_time) Saving done" sleep 15 kill -SIGTERM "${PLAYER_DETECTION_PID}" rcon "Shutdown 10" - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_stop_notification fi else ew ">>> Stopping server..." - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_stop_notification fi kill -SIGTERM "$(pidof PalServer-Linux-Shipping)" diff --git a/scripts/servermanager.sh b/scripts/servermanager.sh index 1890514..bd070c9 100755 --- a/scripts/servermanager.sh +++ b/scripts/servermanager.sh @@ -30,7 +30,7 @@ function start_main() { if [ ! -f "${GAME_ROOT}/PalServer.sh" ]; then fresh_install_server fi - if [ "${ALWAYS_UPDATE_ON_START}" == "true" ]; then + if [ "${ALWAYS_UPDATE_ON_START,,}" == "true" ]; then update_server fi setup_crons @@ -50,7 +50,7 @@ do start_main & START_MAIN_PID="$!" - if [[ -n $RCON_PLAYER_DETECTION ]] && [[ $RCON_PLAYER_DETECTION == "true" ]] && [[ -n $RCON_ENABLED ]] && [[ $RCON_ENABLED == "true" ]]; then + if [[ -n $RCON_PLAYER_DETECTION ]] && [[ "${RCON_PLAYER_DETECTION,,}" == "true" ]] && [[ -n $RCON_ENABLED ]] && [[ "${RCON_ENABLED,,}" == "true" ]]; then player_detection_loop & PLAYER_DETECTION_PID="$!" echo $PLAYER_DETECTION_PID > PLAYER_DETECTION.PID @@ -60,7 +60,7 @@ do ew "> Server main thread started with pid ${START_MAIN_PID}" wait ${START_MAIN_PID} - if [[ -n $WEBHOOK_ENABLED ]] && [[ $WEBHOOK_ENABLED == "true" ]]; then + if [[ -n $WEBHOOK_ENABLED ]] && [[ "${WEBHOOK_ENABLED,,}" == "true" ]]; then send_stop_notification fi exit 0;