From c232e218f3444527c590421700ea23b8d48a4b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palma?= Date: Mon, 12 Feb 2024 15:04:17 +0000 Subject: [PATCH] first commit --- .dockerignore | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 79 +++++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 40 +++ .github/workflows/development.yml | 76 +++++ .github/workflows/release.yml | 67 ++++ .github/workflows/tags.yml | 30 ++ .gitignore | 8 + CHANGELOG.md | 0 CODE_OF_CONDUCT.md | 134 ++++++++ Dockerfile | 197 +++++++++++ LICENSE | 21 ++ README.md | 241 +++++++++++++ SECURITY.md | 6 + configs/rcon.yaml | 3 + default.env | 115 +++++++ docker-compose.yml | 21 ++ docs/ENV_VARS.md | 143 ++++++++ docs/INSTALL.md | 3 + docs/dev/DEV_GUIDE.md | 54 +++ entrypoint.sh | 63 ++++ includes/backup.sh | 91 +++++ includes/colors.sh | 91 +++++ includes/config.sh | 377 +++++++++++++++++++++ includes/cron.sh | 22 ++ includes/rcon.sh | 91 +++++ includes/security.sh | 20 ++ includes/server.sh | 103 ++++++ includes/webhook.sh | 83 +++++ scripts/backupmanager.sh | 145 ++++++++ scripts/servermanager.sh | 24 ++ scripts/update.sh | 61 ++++ scripts/wrappers/backupmanager_cli.sh | 6 + scripts/wrappers/rconcli.sh | 23 ++ scripts/wrappers/restart.sh | 28 ++ 35 files changed, 2468 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/workflows/development.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tags.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 configs/rcon.yaml create mode 100644 default.env create mode 100644 docker-compose.yml create mode 100644 docs/ENV_VARS.md create mode 100644 docs/INSTALL.md create mode 100644 docs/dev/DEV_GUIDE.md create mode 100644 entrypoint.sh create mode 100644 includes/backup.sh create mode 100644 includes/colors.sh create mode 100644 includes/config.sh create mode 100644 includes/cron.sh create mode 100644 includes/rcon.sh create mode 100644 includes/security.sh create mode 100644 includes/server.sh create mode 100644 includes/webhook.sh create mode 100644 scripts/backupmanager.sh create mode 100644 scripts/servermanager.sh create mode 100644 scripts/update.sh create mode 100644 scripts/wrappers/backupmanager_cli.sh create mode 100644 scripts/wrappers/rconcli.sh create mode 100644 scripts/wrappers/restart.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6c7b69a --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.gitignore diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..a57a889 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,79 @@ +name: "🐞 Bug report" +description: Create a bug report to help us improve things +labels: ["bug"] +title: "[Bug Report] Your descriptive title here!" +body: + - type: markdown + attributes: + value: | + ## Important information + Thank you for taking the time to fill out this bug report! Please understand you are asking for support for the Docker-Image and Docker-Container. I'm NOT the creator of the Dedicated-Game-Server itself, i'm not involved in programming that. If you need help for that, you might want to ask here for topics related to that: https://tech.palworldgame.com/dedicated-server-guide + - type: checkboxes + id: understand-important-info + attributes: + label: Have you read the Important information text above + options: + - label: Yes i did + required: true + - type: textarea + id: current-behavior + attributes: + label: Current behavior + description: A clear and concise description of what the problem is. + placeholder: Currently... + validations: + required: true + - type: textarea + id: desired-behavior + attributes: + label: Desired behavior + description: Remember, im not familiar with your setup, your permission, your Docker settings and all that kind of stuff, please provide a clear description of what your desired outcome is. + placeholder: Desired behavior ... + validations: + required: true + - type: textarea + id: screenshot-links + attributes: + label: Links to screenshots + placeholder: ... + validations: + required: false + - type: textarea + id: reproduction + attributes: + label: To Reproduce + description: What steps can i do to reproduce your problem? + value: | + Steps to reproduce the behavior: + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + - type: textarea + id: software + attributes: + label: Software setup + value: | + - OS: + - Docker: + validations: + required: true + - type: textarea + id: hardware + attributes: + label: Hardware setup + value: | + - vCPU: + - RAM: + - Disk: + validations: + required: true + - type: textarea + id: more + attributes: + label: Additional context + placeholder: Add any other context about the problem here, in as much detail as possible. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..3f80e81 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,40 @@ +name: "🚀 Feature request" +description: Suggest a feature that will improve things +labels: ["enhancement"] +title: "[Feature Request] Your descriptive title here!" +body: + - type: markdown + attributes: + value: | + ## Important information + Thank you for taking the time to fill out this feature request! Please understand you are asking for support for the Docker-Image and Docker-Container. I'm NOT the creator of the Dedicated-Game-Server itself, i'm not involved in programming that. If you need help for that, you might want to ask here for topics related to that: https://tech.palworldgame.com/dedicated-server-guide + - type: checkboxes + id: understand-important-info + attributes: + label: Have you read the Important information text above + options: + - label: Yes i did + required: true + - type: textarea + id: feature-description + attributes: + label: Describe the feature + description: A clear and concise description of what you think would be a helpful addition, including the possible use cases and alternatives you have considered. If you have a working prototype or something similar, please include a link. + placeholder: Feature description + validations: + required: true + - type: checkboxes + id: additional-info + attributes: + label: Additional information + description: Additional information that helps us decide how to proceed. + options: + - label: Would you be willing to help implement this feature? + - type: checkboxes + id: required-info + attributes: + label: Final checks + description: Before submitting, please understand and agree to this + options: + - label: I understand that this is a feature request function. I also understand that if i use this to report a bug report, that my issue might be un-answered closed. + required: true \ No newline at end of file diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml new file mode 100644 index 0000000..95173da --- /dev/null +++ b/.github/workflows/development.yml @@ -0,0 +1,76 @@ +name: Development CI/CD + +on: + push: + branches: + - 'main' + +jobs: + development-image: + runs-on: ubuntu-latest + + permissions: + contents: write + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ github.repository_owner }}/palworld-dedicated-server + ghcr.io/${{ github.repository }} + tags: type=raw,value=dev + flavor: latest=false + + - name: Get short SHA + id: slug + run: echo "GIT_SHORT_SHA7=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" + + # Defines the readme version to the dev tag + - name: Update README.md + run: | + sed "s/readme-\(.*\)-blue/readme-dev-blue/g" README.md > temp.md + mv temp.md README.md + AUTHOR=$(git log -1 --pretty=format:'%an <%ae>') + git config --local user.email "actions@github.com" + git config --local user.name "GitHub Actions" + git add README.md + git commit --amend --no-edit --author "$AUTHOR" + git push origin HEAD:main --force + + - name: Build and push + id: build_push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64 + push: true + tags: | + ${{ steps.meta.outputs.tags }} + ${{ secrets.DOCKER_USERNAME }}/palworld-dedicated-server:${{ steps.slug.outputs.GIT_SHORT_SHA7 }} + ghcr.io/${{ github.repository }}:${{ steps.slug.outputs.GIT_SHORT_SHA7 }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6460ee1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: Release CI/CD + +on: + release: + types: + - 'published' + +jobs: + release-image: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ github.repository_owner }}/palworld-dedicated-server + ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern=v{{version}} + type=semver,pattern=v{{major}}.{{minor}} + type=semver,pattern=v{{major}} + flavor: latest=true + + - name: Get short SHA + id: slug + run: echo "GIT_SHORT_SHA7=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" + + - name: Build and push + id: build_push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64 + push: true + tags: | + ${{ steps.meta.outputs.tags }} + ${{ secrets.DOCKER_USERNAME }}/palworld-dedicated-server:${{ steps.slug.outputs.GIT_SHORT_SHA7 }} + ghcr.io/${{ github.repository }}:${{ steps.slug.outputs.GIT_SHORT_SHA7 }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/tags.yml b/.github/workflows/tags.yml new file mode 100644 index 0000000..959110d --- /dev/null +++ b/.github/workflows/tags.yml @@ -0,0 +1,30 @@ +name: Update README.md on tag creation + +on: + push: + tags: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Update README.md + run: | + echo "Tag ${{ github.ref }} was created or pushed." + VERSION=${GITHUB_REF#refs/tags/} + sed "s/readme-\(.*\)-blue/readme-$VERSION-blue/g" README.md > temp.md + mv temp.md README.md + AUTHOR=$(git log -1 --pretty=format:'%an <%ae>') + git config --local user.email "actions@github.com" + git config --local user.name "GitHub Actions" + git add README.md + git commit --amend --no-edit --author "$AUTHOR" + git push origin HEAD:main --force diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c19ef03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea/ +.vscode/ +game/ +palworld/ +test/ +docker-compose-*.yml +custom.env +test.env \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1a1292e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[INSERT CONTACT METHOD]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c9c6b82 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,197 @@ +FROM --platform=linux/amd64 cm2network/steamcmd:root + +LABEL maintainer="João Palma" +LABEL name="thejcpalma/palworld-dedicated-server-docker" +LABEL github="https://github.com/thejcpalma/palworld-dedicated-server-docker" +LABEL dockerhub="https://hub.docker.com/r/thejcpalma/palworld-dedicated-server" +LABEL org.opencontainers.image.authors="João Palma" +LABEL org.opencontainers.image.source="https://github.com/thejcpalma/palworld-dedicated-server-docker" + + +# Install minimum required packages for dedicated server +RUN apt-get update \ + && apt-get install -y --no-install-recommends --no-install-suggests procps xdg-user-dirs \ + && apt-get clean \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# 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 + +RUN curl -fsSLO "$SUPERCRONIC_URL" \ + && echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \ + && chmod +x "$SUPERCRONIC" \ + && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \ + && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic + +# Latest releases available at https://github.com/gorcon/rcon-cli/releases +ENV RCON_URL=https://github.com/gorcon/rcon-cli/releases/download/v0.10.3/rcon-0.10.3-amd64_linux.tar.gz \ + RCON_TGZ=rcon-0.10.3-amd64_linux.tar.gz \ + RCON_TGZ_MD5SUM=8601c70dcab2f90cd842c127f700e398 \ + RCON_BINARY=rcon + +RUN curl -fsSLO "$RCON_URL" \ + && echo "${RCON_TGZ_MD5SUM} ${RCON_TGZ}" | md5sum -c - \ + && tar xfz rcon-0.10.3-amd64_linux.tar.gz \ + && chmod +x "rcon-0.10.3-amd64_linux/$RCON_BINARY" \ + && mv "rcon-0.10.3-amd64_linux/$RCON_BINARY" "/usr/local/bin/${RCON_BINARY}" \ + && rm -Rf rcon-0.10.3-amd64_linux rcon-0.10.3-amd64_linux.tar.gz + + +# Define an environment variable for the server directory +ENV SERVER_DIR=/home/steam/server + +# Use the environment variable in the rest of the Dockerfile +COPY --chown=steam:steam --chmod=755 scripts/ ${SERVER_DIR}/scripts +COPY --chown=steam:steam --chmod=755 includes/ ${SERVER_DIR}/includes +COPY --chown=steam:steam --chmod=755 configs/ ${SERVER_DIR}/configs +COPY --chown=steam:steam --chmod=755 entrypoint.sh ${SERVER_DIR}/ + +RUN ln -s ${SERVER_DIR}/scripts/backupmanager.sh /usr/local/bin/backupmanager \ + && ln -s ${SERVER_DIR}/scripts/update.sh /usr/local/bin/update \ + && ln -s ${SERVER_DIR}/scripts/wrappers/backupmanager_cli.sh /usr/local/bin/backup \ + && ln -s ${SERVER_DIR}/scripts/wrappers/rconcli.sh /usr/local/bin/rconcli \ + && ln -s ${SERVER_DIR}/scripts/wrappers/restart.sh /usr/local/bin/restart + + +# Set the default shell to Bash with the 'pipefail' option enabled. +# This changes the pipeline's return status to be the value of the last command to exit with a non-zero status, or zero if all commands exit successfully. +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +ENV DEBIAN_FRONTEND=noninteractive \ + # Container-setttings + PUID=1000 \ + PGID=1000 \ + TZ="Europe/Berlin" \ + # Path-vars + GAME_ROOT="/palworld" \ + GAME_PATH="/palworld/Pal" \ + GAME_SAVE_PATH="/palworld/Pal/Saved" \ + GAME_CONFIG_PATH="/palworld/Pal/Saved/Config/LinuxServer" \ + GAME_SETTINGS_FILE="/palworld/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini" \ + GAME_ENGINE_FILE="/palworld/Pal/Saved/Config/LinuxServer/Engine.ini" \ + BACKUP_PATH="/palworld/backups" \ + STEAMCMD_PATH="/home/steam/steamcmd" \ + RCON_CONFIG_FILE="/home/steam/server/configs/rcon.yaml" \ + # SteamCMD-settings + ALWAYS_UPDATE_ON_START=true \ + STEAMCMD_VALIDATE_FILES=true \ + # Auto-update-settings + AUTO_UPDATE_ENABLED=true \ + AUTO_UPDATE_CRON_EXPRESSION="0 1 * * *" \ + AUTO_UPDATE_WARN_MINUTES=10 \ + # Auto-restart-settings + AUTO_RESTART_ENABLED=true \ + AUTO_RESTART_WARN_MINUTES=10 \ + AUTO_RESTART_CRON_EXPRESSION="0 0 * * *" \ + # Backup-settings + BACKUP_ENABLED=true \ + BACKUP_CRON_EXPRESSION="0 * * * *" \ + BACKUP_AUTO_CLEAN=true \ + BACKUP_AUTO_CLEAN_AMOUNT_TO_KEEP=72 \ + # Webhook-settings + WEBHOOK_ENABLED=false \ + WEBHOOK_URL= \ + WEBHOOK_START_TITLE="Server is starting" \ + WEBHOOK_START_DESCRIPTION="The gameserver is starting" \ + WEBHOOK_START_COLOR="65280" \ + WEBHOOK_STOP_TITLE="Server has been stopped" \ + WEBHOOK_STOP_DESCRIPTION="The gameserver has been stopped" \ + WEBHOOK_STOP_COLOR="16711680" \ + WEBHOOK_RESTART_TITLE="Server is restarting" \ + WEBHOOK_RESTART_DESCRIPTION="The gameserver is restarting" \ + WEBHOOK_RESTART_COLOR="16750848" \ + WEBHOOK_INSTALL_TITLE="Server is being installed" \ + WEBHOOK_INSTALL_DESCRIPTION="Server is being installed" \ + WEBHOOK_INSTALL_COLOR="1644912" \ + WEBHOOK_UPDATE_TITLE="Updating server" \ + WEBHOOK_UPDATE_DESCRIPTION="Server is being updated" \ + WEBHOOK_UPDATE_COLOR="16776960" \ + WEBHOOK_UPDATE_VALIDATE_TITLE="Updating and validating server" \ + WEBHOOK_UPDATE_VALIDATE_DESCRIPTION="Server is being updated and validated" \ + WEBHOOK_UPDATE_VALIDATE_COLOR="16776960" \ + # Gameserver-start-settings + MULTITHREAD_ENABLED=true \ + COMMUNITY_SERVER=true \ + # Config-setting - Warning: using 'auto' will overwrite the settings in the config file with the environment variables + SERVER_SETTINGS_MODE=auto \ + # Engine.ini + NETSERVERMAXTICKRATE=120 \ + # PalWorldSettings.ini + DIFFICULTY=None \ + DAYTIME_SPEEDRATE=1.000000 \ + NIGHTTIME_SPEEDRATE=1.000000 \ + EXP_RATE=1.000000 \ + PAL_CAPTURE_RATE=1.000000 \ + PAL_SPAWN_NUM_RATE=1.000000 \ + PAL_DAMAGE_RATE_ATTACK=1.000000 \ + PAL_DAMAGE_RATE_DEFENSE=1.000000 \ + PLAYER_DAMAGE_RATE_ATTACK=1.000000 \ + PLAYER_DAMAGE_RATE_DEFENSE=1.000000 \ + PLAYER_STOMACH_DECREASE_RATE=1.000000 \ + PLAYER_STAMINA_DECREACE_RATE=1.000000 \ + PLAYER_AUTO_HP_REGENE_RATE=1.000000 \ + PLAYER_AUTO_HP_REGENE_RATE_IN_SLEEP=1.000000 \ + PAL_STOMACH_DECREACE_RATE=1.000000 \ + PAL_STAMINA_DECREACE_RATE=1.000000 \ + PAL_AUTO_HP_REGENE_RATE=1.000000 \ + PAL_AUTO_HP_REGENE_RATE_IN_SLEEP=1.000000 \ + BUILD_OBJECT_DAMAGE_RATE=1.000000 \ + BUILD_OBJECT_DETERIORATION_DAMAGE_RATE=1.000000 \ + COLLECTION_DROP_RATE=1.000000 \ + COLLECTION_OBJECT_HP_RATE=1.000000 \ + COLLECTION_OBJECT_RESPAWN_SPEED_RATE=1.000000 \ + ENEMY_DROP_ITEM_RATE=1.000000 \ + DEATH_PENALTY=All \ + ENABLE_PLAYER_TO_PLAYER_DAMAGE=false \ + ENABLE_FRIENDLY_FIRE=false \ + ENABLE_INVADER_ENEMY=true \ + ACTIVE_UNKO=false \ + ENABLE_AIM_ASSIST_PAD=true \ + ENABLE_AIM_ASSIST_KEYBOARD=false \ + DROP_ITEM_MAX_NUM=3000 \ + DROP_ITEM_MAX_NUM_UNKO=100 \ + BASE_CAMP_MAX_NUM=128 \ + BASE_CAMP_WORKER_MAXNUM=15 \ + DROP_ITEM_ALIVE_MAX_HOURS=1.000000 \ + AUTO_RESET_GUILD_NO_ONLINE_PLAYERS=false \ + AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS=72.000000 \ + GUILD_PLAYER_MAX_NUM=20 \ + PAL_EGG_DEFAULT_HATCHING_TIME=72.000000 \ + WORK_SPEED_RATE=1.000000 \ + IS_MULTIPLAY=false \ + IS_PVP=false \ + CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP=false \ + ENABLE_NON_LOGIN_PENALTY=true \ + ENABLE_FAST_TRAVEL=true \ + IS_START_LOCATION_SELECT_BY_MAP=true \ + EXIST_PLAYER_AFTER_LOGOUT=false \ + ENABLE_DEFENSE_OTHER_GUILD_PLAYER=false \ + COOP_PLAYER_MAX_NUM=4 \ + MAX_PLAYERS=32 \ + SERVER_NAME="thejcpalma-docker-generated-###RANDOM###" \ + SERVER_DESCRIPTION="Palworld Dedicated Server running in Docker by thejcpalma" \ + ADMIN_PASSWORD=adminPasswordHere \ + SERVER_PASSWORD=serverPasswordHere \ + PUBLIC_PORT=8211 \ + PUBLIC_IP= \ + RCON_ENABLED=false \ + RCON_PORT=25575 \ + REGION= \ + USEAUTH=true \ + BAN_LIST_URL=https://api.palworldgame.com/api/banlist.txt + + +EXPOSE 8211/udp +EXPOSE 25575/tcp + +VOLUME ["${GAME_ROOT}"] + +WORKDIR ${SERVER_DIR} + +HEALTHCHECK --start-period=5m \ + CMD pgrep "PalServer-Linux" > /dev/null || exit 1 + +ENTRYPOINT ["/home/steam/server/entrypoint.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c02f01d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 João Carlos Fraqueiro da Palma + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..00a55c1 --- /dev/null +++ b/README.md @@ -0,0 +1,241 @@ +# Palworld Dedicated Server on Docker + +[![Release](https://github.com/thejcpalma/palworld-dedicated-server-docker/actions/workflows/release.yml/badge.svg)](https://github.com/thejcpalma/palworld-dedicated-server-docker/actions/workflows/release.yml) +[![Development](https://github.com/thejcpalma/palworld-dedicated-server-docker/actions/workflows/development.yml/badge.svg)](https://github.com/thejcpalma/palworld-dedicated-server-docker/actions/workflows/development.yml) +![Docker Pulls](https://img.shields.io/docker/pulls/thejcpalma/palworld-dedicated-server) +![Docker Stars](https://img.shields.io/docker/stars/thejcpalma/palworld-dedicated-server) +![Image Size](https://img.shields.io/docker/image-size/thejcpalma/palworld-dedicated-server/latest) +[![Static Badge](https://img.shields.io/badge/readme-0.1.0-blue?link=https%3A%2F%2Fgithub.com%2Fthejcpalma%2Fpalworld-dedicated-server-docker%2Fblob%2Fmain%2FREADME.md)](https://github.com/thejcpalma/palworld-dedicated-server-docker?tab=readme-ov-file#palworld-dedicated-server-docker) +![Discord](https://img.shields.io/discord/1206023011011141702?logo=discord&label=Discord&link=https%3A%2F%2Fdiscord.gg%2FycanfK9R5B) + + +[![Docker Hub](https://img.shields.io/badge/Docker_Hub-palworld-blue?logo=docker)](https://hub.docker.com/r/thejcpalma/palworld-dedicated-server) +[![ghrc](https://img.shields.io/badge/ghrc.io-palworld-blue?logo=github)](https://github.com/thejcpalma/palworld-dedicated-server-docker/pkgs/container/palworld-dedicated-server-docker) + +> [!TIP] +> Do you want to chat with the community? 🧑‍💻 +> +> **[Join us on Discord 🚀](https://discord.gg/ycanfK9R5B)** + +This Docker image includes your own [Palworld](https://store.steampowered.com/app/1623730/Palworld/) Dedicated Server. + +The container as been tested and will work on the following OS: + +* Linux (Ubuntu/Debian, Arch and Fedora) +* Windows 10,11 + +> [!IMPORTANT] +> At the moment, Xbox GamePass/Xbox Console players will not be able to join a dedicated server. +> +> They will need to join players using the invite code and are limited to sessions of 4 players max. + +___ + +## Table of Contents + +- [Palworld Dedicated Server on Docker](#palworld-dedicated-server-on-docker) + - [Table of Contents](#table-of-contents) + - [What You Need to Get Started](#what-you-need-to-get-started) + - [Server requirements](#server-requirements) + - [Need Support? Here's How!](#need-support-heres-how) + - [Let's Get Started!](#lets-get-started) + - [Environment variables](#environment-variables) + - [Docker-Compose examples](#docker-compose-examples) + - [Run RCON commands](#run-rcon-commands) + - [Backup Manager](#backup-manager) + - [Webhook integration](#webhook-integration) + - [Supported events](#supported-events) + - [FAQ](#faq) + - [How can I use the interactive console in Portainer with this image?](#how-can-i-use-the-interactive-console-in-portainer-with-this-image) + - [How can I look into the config of my Palworld container?](#how-can-i-look-into-the-config-of-my-palworld-container) + - [I'm seeing S\_API errors in my logs when I start the container?](#im-seeing-s_api-errors-in-my-logs-when-i-start-the-container) + - [I'm using Apple silicon type of hardware, can I run this?](#im-using-apple-silicon-type-of-hardware-can-i-run-this) + - [I changed the `BaseCampWorkerMaxNum` setting, why didn't this update the server?](#i-changed-the-basecampworkermaxnum-setting-why-didnt-this-update-the-server) + - [Software used](#software-used) + - [Major Shoutout](#major-shoutout) + - [License](#license) + +## What You Need to Get Started + +To use this Docker image, you need: + +* A computer running [Linux (Ubuntu, for example)](https://ubuntu.com/download/desktop), [Windows 10](https://www.microsoft.com/software-download/windows10), or [Windows 11](https://www.microsoft.com/software-download/windows11). +* [Docker](https://docs.docker.com/get-docker/) installed on your computer. +* [Docker-Compose](https://docs.docker.com/compose/install/) also installed on your computer. +* Some basic Port-Forwarding/NAT setup. + +Don't worry if you're not familiar with these concepts. The links provided will guide you through the installation and setup process. +If you're still having trouble, feel free to ask for help in Discord 😉 + +## Server requirements + +| Resource | 1-8 players | 8-12+ players | +| -------- | ----------------------------- | ------------------------------ | +| CPU | 4 CPU-Cores @ High GHz | 6-8 CPU Cores @ High GHz | +| RAM | 8GB RAM Base + 2GB per player | 12GB RAM Base + 2GB per player | +| Storage | 30GB (SSD) | 30GB+ (SSD) | + +Always follow the official [Palworld Dedicated Server Requirements](https://tech.palworldgame.com/getting-started/requirements) for the most accurate information. + +## Need Support? Here's How! + +Using this Docker image and have something to share? Here's how: + +- **Need Help?** Open a [new issue](https://github.com/username/repo/issues/new). Link to similar issues with #issue-number. Close the issue once resolved. + +- **Got a Suggestion?** We're all ears! Share your ideas in a [new issue](https://github.com/username/repo/issues/new). + +- **Found a Bug?** Help us improve by reporting it in a [new issue](https://github.com/username/repo/issues/new). + +**Community Guidelines:** + +- Avoid reviving old issues or off-topic comments. +- Issues inactive for a week will be closed, but feel free to open a new one. + +Enjoying the project? Give this repo and the [Docker-Hub repository](https://hub.docker.com/repository/docker/thejcpalma/palworld-dedicated-server) a star ⭐! + +💫 Thanks for being part of our journey! 🚀 + +## Let's Get Started! + +1. Make a new folder named `game` in your game-server-directory (For example: `/srv/palworld`). + - This is where the game server files, like configs and savegames, will be stored. +2. Set up Port-Forwarding or NAT for the ports mentioned in the Docker-Compose file. +3. Get the latest version of the image by typing `docker pull thejcpalma/palworld-dedicated-server:latest` in your terminal. +4. Download/Copy the [docker-compose.yml](docker-compose.yml) and [default.env](default.env) files. +5. Adjust the `docker-compose.yml` and `default.env` files as you like. + - Check out the [Environment-Variables](#environment-variables) section for more details. +6. Start the container by typing `docker-compose up -d && docker-compose logs -f` in your terminal. + - Keep an eye on the log. If you don't see any errors, you can close the logs with `ctrl+c`. +7. That's it! Now you can enjoy your game! 🎮😉 + +> [!TIP] +> +> For an in-depth guide, check out our detailed [installation guide](/docs/INSTALL.md). + + +## Environment variables + + +## Docker-Compose examples + + + +## Run RCON commands + +> [!NOTE] +> Please research the RCON-Commands on the official source: https://tech.palworldgame.com/server-commands + + + +## Backup Manager + +> [!WARNING] +> If RCON is disabled, the backup manager won't do saves via RCON before creating a backup. +> This means that the backup will be created from the last auto-save of the server. +> This can lead to data-loss and/or savegame corruption. +> +> **Recommendation:** Please make sure that RCON is enabled before using the backup manager. + +Usage: `docker exec palworld-dedicated-server backup [command] [arguments]` + +| Command | Argument | Required/Optional | Default Value | Values | Description | +| ------- | ------------------ | ----------------- | --------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| create | N/A | N/A | N/A | N/A | Creates a backup. | +| list | `` | Optional | N/A | Positive numbers | Lists all backups.
If `` is specified, only the most
recent `` backups are listed. | +| clean | `` | Optional | `BACKUP_RETENTION_AMOUNT_TO_KEEP` | Positive numbers | Cleans up backups.
If `` is specified, cleans and keeps
the most recent`` backups.
If not, default to `BACKUP_RETENTION_AMOUNT_TO_KEEP` var | + +Examples: + +```shell +$ docker exec palworld-dedicated-server backup +> Backup 'saved-20240203_032855.tar.gz' created successfully. +``` + +```shell +$ docker exec palworld-dedicated-server backup list +> Listing 2 backup file(s)! +2024-02-03 03:28:55 | saved-20240203_032855.tar.gz +2024-02-03 03:28:00 | saved-20240203_032800.tar.gz +``` + +```shell +$ docker exec palworld-dedicated-server backup_clean 3 +> 1 backup(s) cleaned, keeping 2 backups(s). +``` + +```shell +$ docker exec palworld-dedicated-server backup_list +> Listing 1 out of backup 2 file(s). +2024-02-03 03:30:00 | saved-20240203_033000.tar.gz +``` + +## Webhook integration + +To enable webhook integrations, you need to set the following environment variables in the `default.env`: + +```shell +WEBHOOK_ENABLED=true +WEBHOOK_URL="https://your.webhook.url" +``` + +After enabling the server should send messages in a Discord-Compatible way to your webhook url. + +> You can find more details about these variables [here](/docs/ENV_VARS.md#webhook-settings). + +### Supported events + +- Server starting +- Server stopped +- Server updating +- Server updating and validating + +## FAQ + +### How can I use the interactive console in Portainer with this image? + +> You can run this `docker exec -ti palworld-dedicated-server bash' or you could navigate to the **"Stacks"** tab in Portainer, select your stack, and click on the container name. Then click on the **"Exec console"** button. + +### How can I look into the config of my Palworld container? + +> You can run this `docker exec -ti palworld-dedicated-server cat /palworld/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini` and it will show you the config inside the container. + +### I'm seeing S_API errors in my logs when I start the container? + +> Errors like `[S_API FAIL] Tried to access Steam interface SteamUser021 before SteamAPI_Init succeeded.` are safe to ignore. + +### I'm using Apple silicon type of hardware, can I run this? + +> You can try to insert in your docker-compose file this parameter `platform: linux/amd64` at the palworld service. This isn't a special fix for Apple silicon, but to run on other than x86 hosts. The support for arm exists only by enforcing x86 emulation, if that isn't to host already. Rosetta is doing the translation/emulation. + +### I changed the `BaseCampWorkerMaxNum` setting, why didn't this update the server? + +> This is a confirmed bug. Changing `BaseCampWorkerMaxNum` in the `PalWorldSettings.ini` has no effect on the server. There are tools out there to help with this, like this one: + +> [!WARNING] +> Adding `WorldOption.sav` will break `PalWorldSetting.ini`. So any new changes to the settings (either on the file or via ENV VARS), you will have to create a new `WorldOption.sav` and update it every time for those changes to have an effect. + + + +## Software used + +- CM2Network SteamCMD - Debian-based (Officially recommended by Valve - https://developer.valvesoftware.com/wiki/SteamCMD#Docker) +- Supercronic - https://github.com/aptible/supercronic +- rcon-cli - https://github.com/gorcon/rcon-cli +- Palworld Dedicated Server (APP-ID: 2394010 - https://steamdb.info/app/2394010/config/) + + +## Major Shoutout + +This project was inspired by the following repositories: + +1. [Docker - Palworld Dedicated Server](https://github.com/jammsen/docker-palworld-dedicated-server) by [@jammsen](https://github.com/jammsen) ❤️ +2. [Palworld Dedicated Server Docker](https://github.com/thijsvanloef/palworld-server-docker) by [@thijsvanloef](https://github.com/thijsvanloef) + +A big thank you to the authors for their contributions to the open source community! + +_**A much bigger thank you to [@jammsen](https://github.com/jammsen) for being an amazing, experienced person and for all the knowledge shared. Your contributions and guidance have been invaluable to this project.**_ + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7fc6e02 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,6 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report (suspected) vulnerabilities at [the security page](https://github.com/thejcpalma/palworld-dedicated-server-docker/security). +I try to respond to vulnerability reports within 72 hours. diff --git a/configs/rcon.yaml b/configs/rcon.yaml new file mode 100644 index 0000000..5acff51 --- /dev/null +++ b/configs/rcon.yaml @@ -0,0 +1,3 @@ +default: + address: "127.0.0.1:###RCON_PORT###" + password: "###ADMIN_PASSWORD###" diff --git a/default.env b/default.env new file mode 100644 index 0000000..cd7b37c --- /dev/null +++ b/default.env @@ -0,0 +1,115 @@ +# Container-setttings +TZ=UTC + +# SteamCMD-settings +ALWAYS_UPDATE_ON_START=true +STEAMCMD_VALIDATE_FILES=true + +# Auto-update-settings +AUTO_UPDATE_ENABLED=true +AUTO_UPDATE_CRON_EXPRESSION="* * * * *" +AUTO_UPDATE_WARN_MINUTES=10 + +# Auto-restart-settings +AUTO_RESTART_ENABLED=true +AUTO_RESTART_WARN_MINUTES=10 +AUTO_RESTART_CRON_EXPRESSION="*/5 * * * *" + +# Backup-settings +BACKUP_ENABLED=true +BACKUP_CRON_EXPRESSION= 0 * * * * +BACKUP_RETENTION_POLICY=true +BACKUP_RETENTION_AMOUNT_TO_KEEP=72 + +# Webhook-settings +WEBHOOK_ENABLED=false +WEBHOOK_URL="YOUR-URL-IN-HERE" +WEBHOOK_START_TITLE="Server is starting" +WEBHOOK_START_DESCRIPTION="The gameserver is starting" +WEBHOOK_START_COLOR="2328576" +WEBHOOK_STOP_TITLE="Server has been stopped" +WEBHOOK_STOP_DESCRIPTION="The gameserver has been stopped" +WEBHOOK_STOP_COLOR="7413016" +WEBHOOK_INFO_TITLE="Info" +WEBHOOK_INFO_DESCRIPTION="This is an info from the server" +WEBHOOK_INFO_COLOR="2849520" +WEBHOOK_UPDATE_TITLE="Updating server" +WEBHOOK_UPDATE_DESCRIPTION="Server is being updated" +WEBHOOK_UPDATE_COLOR="2849520" +WEBHOOK_UPDATE_VALIDATION_TITLE="Updating and validating server" +WEBHOOK_UPDATE_VALIDATION_DESCRIPTION="Server is being updated and validated" +WEBHOOK_UPDATE_VALIDATION_COLOR="2849520" + +# Gameserver-start-settings +MULTITHREAD_ENABLED=true +COMMUNITY_SERVER=false # Enable this if you want your server to show up in the community servers tab, USE WITH SERVER_PASSWORD! + +# Config-setting - Warning: Every setting below here will be affected! +SERVER_SETTINGS_MODE=auto + +# Engine.ini settings +NETSERVERMAXTICKRATE=120 + +# PalWorldSettings.ini settings +DIFFICULTY=None +DAYTIME_SPEEDRATE=1.000000 +NIGHTTIME_SPEEDRATE=1.000000 +EXP_RATE=1.000000 +PAL_CAPTURE_RATE=1.000000 +PAL_SPAWN_NUM_RATE=1.000000 +PAL_DAMAGE_RATE_ATTACK=1.000000 +PAL_DAMAGE_RATE_DEFENSE=1.000000 +PLAYER_DAMAGE_RATE_ATTACK=1.000000 +PLAYER_DAMAGE_RATE_DEFENSE=1.000000 +PLAYER_STOMACH_DECREASE_RATE=1.000000 +PLAYER_STAMINA_DECREACE_RATE=1.000000 +PLAYER_AUTO_HP_REGENE_RATE=1.000000 +PLAYER_AUTO_HP_REGENE_RATE_IN_SLEEP=1.000000 +PAL_STOMACH_DECREACE_RATE=1.000000 +PAL_STAMINA_DECREACE_RATE=1.000000 +PAL_AUTO_HP_REGENE_RATE=1.000000 +PAL_AUTO_HP_REGENE_RATE_IN_SLEEP=1.000000 +BUILD_OBJECT_DAMAGE_RATE=1.000000 +BUILD_OBJECT_DETERIORATION_DAMAGE_RATE=1.000000 +COLLECTION_DROP_RATE=1.000000 +COLLECTION_OBJECT_HP_RATE=1.000000 +COLLECTION_OBJECT_RESPAWN_SPEED_RATE=1.000000 +ENEMY_DROP_ITEM_RATE=1.000000 +DEATH_PENALTY=All +ENABLE_PLAYER_TO_PLAYER_DAMAGE=false +ENABLE_FRIENDLY_FIRE=false +ENABLE_INVADER_ENEMY=true +ACTIVE_UNKO=false +ENABLE_AIM_ASSIST_PAD=true +ENABLE_AIM_ASSIST_KEYBOARD=false +DROP_ITEM_MAX_NUM=3000 +DROP_ITEM_MAX_NUM_UNKO=100 +BASE_CAMP_MAX_NUM=128 +BASE_CAMP_WORKER_MAXNUM=15 +DROP_ITEM_ALIVE_MAX_HOURS=1.000000 +AUTO_RESET_GUILD_NO_ONLINE_PLAYERS=false +AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS=72.000000 +GUILD_PLAYER_MAX_NUM=20 +PAL_EGG_DEFAULT_HATCHING_TIME=72.000000 +WORK_SPEED_RATE=1.000000 +IS_MULTIPLAY=false +IS_PVP=false +CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP=false +ENABLE_NON_LOGIN_PENALTY=true +ENABLE_FAST_TRAVEL=true +IS_START_LOCATION_SELECT_BY_MAP=true +EXIST_PLAYER_AFTER_LOGOUT=false +ENABLE_DEFENSE_OTHER_GUILD_PLAYER=false +COOP_PLAYER_MAX_NUM=4 +MAX_PLAYERS=32 +SERVER_NAME=thejcpalma-docker-generated-###RANDOM### +SERVER_DESCRIPTION=Palworld Dedicated Server running in Docker by thejcpalma +ADMIN_PASSWORD=adminPasswordHere +SERVER_PASSWORD=serverPasswordHere +PUBLIC_PORT=8211 +PUBLIC_IP= +RCON_ENABLED=true +RCON_PORT=25575 +REGION= +USEAUTH=true +BAN_LIST_URL=https://api.palworldgame.com/api/banlist.txt diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e42b62f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.9' +services: + palworld-dedicated-server: + #build: . + container_name: palworld-dedicated-server + image: thejcpalma/palworld-dedicated-server:latest + restart: unless-stopped + stop_grace_period: 30s # Set to however long you are willing to wait for the container to gracefully stop + ports: + - target: 8211 # Gamerserver port inside of the container + published: 8211 # Gamerserver port on your host + protocol: udp + 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: + - ./palworld:/palworld diff --git a/docs/ENV_VARS.md b/docs/ENV_VARS.md new file mode 100644 index 0000000..0a46124 --- /dev/null +++ b/docs/ENV_VARS.md @@ -0,0 +1,143 @@ +# Environment Variables + +[Back to main](../README.md#environment-variables) + +In this section you will find a lot of environment variables to control your container-behavior and gameserver-settings. Due to the extensive control options, the settings are split into two parts for documentation: **Container-Settings** and **Gameserver-Settings**. + +## Container-Settings + +These settings control the behavior of the Docker container: + +> **Important:** If you want to change the server settings via environment variables use the default value (`auto`) for the environment variable `SERVER_SETTINGS_MODE`, otherwise change it to `manual` and edit the config file directly. + +| Variable | Description | Default value | Allowed values | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| TZ | Timezone used for time stamping server backups | Europe/Berlin | See [TZ identifiers](#tz-identifiers) | +| ALWAYS_UPDATE_ON_START | Updates the server on startup | true | false/true | +| MULTITHREAD_ENABLED | Sets options for "Improved multi-threaded CPU performance" | true | false/true | +| COMMUNITY_SERVER | Set to enabled, the server will appear in the Community-Serverlist | true | false/true | +| BACKUP_ENABLED | Backup function, creates backups in your `game` directory | true | false/true | +| BACKUP_CRON_EXPRESSION | Needs a Cron-Expression - See [Cron expression](#cron-expression) | 0 * * * * (meaning every hour) | Cron-Expression | +| BACKUP_RETENTION_POLICY | Set to enabled, will cleanup old backups | false | false/true | +| BACKUP_RETENTION_AMOUNT_TO_KEEP | Defines how many backups in numbers to keep | 30 | Integer | +| SERVER_SETTINGS_MODE | Determines whether settings can be modified via environment variables or via file, except `COMMUNITY_SERVER` and `MULTITHREAD_ENABLED`! | `auto` | `auto`: Settings are modified only by environment variables, manual edits will be ignored
`manual`: Settings are modified only by editing the file directly, environment variables are ignored | +| STEAMCMD_VALIDATE_FILES | Set to enabled SteamCMD will also validate the gameserver files, making sure nothing is corrupted and also overwrite any file changes of the source
See https://developer.valvesoftware.com/wiki/SteamCMD#Downloading_an_App | true | false/true | + +### TZ identifiers + +The `TZ` setting affects logging output and the backup function. [TZ identifiers](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#Time_Zone_abbreviations) are a format for defining a timezone near you. + +### Cron expression + +The `BACKUP_CRON_EXPRESSION` setting affects the backup function. In a Cron-Expression, you define an interval for when to run jobs. This image uses Supercronic for crons, see https://github.com/aptible/supercronic#crontab-format or https://crontab-generator.org + +## Gameserver-Settings + +This section lists all the settings currently adjustable via Docker environment variables, based on the **order** and **contents of the DefaultPalWorldSettings.ini**. + +Information sources and credits to the following websites: + +- [Palworld Tech Guide](https://tech.palworldgame.com/optimize-game-balance) for the game server documentation +- [PalworldSettingGenerator](https://dysoncheng.github.io/PalWorldSettingGenerator/setting.html) for variable descriptions + +> **Important:** Please note that all of this is subject to change. **The game is still in early access.** +> +> To change a setting, you can set the environment variable to the value you want. If the environment variable is not set or is blank, the default value will be used. + +| Variable | Game setting | Description | Default value | Allowed value | +| ----------------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------- | +| NETSERVERMAXTICKRATE | NetServerMaxTickRate | Changes the TickRate of the server, be very careful with this setting! | 120 | 30-120 | +| DIFFICULTY | Difficulty | Choose one of the following:
`None`
`Normal`
`Difficult` | None | Enum | +| DAYTIME_SPEEDRATE | DayTimeSpeedRate | Day time speed - Smaller number means shorter days | 1.000000 | Float | +| NIGHTTIME_SPEEDRATE | NightTimeSpeedRate | Night time speed - Smaller number means shorter nights | 1.000000 | Float | +| EXP_RATE | ExpRate | EXP rate | 1.000000 | Float | +| PAL_CAPTURE_RATE | PalCaptureRate | Pal capture rate | 1.000000 | Float | +| PAL_SPAWN_NUM_RATE | PalSpawnNumRate | Pal appearance rate | 1.000000 | Float | +| PAL_DAMAGE_RATE_ATTACK | PalDamageRateAttack | Damage from pals multiplier | 1.000000 | Float | +| PAL_DAMAGE_RATE_DEFENSE | PalDamageRateDefense | Damage to pals multiplier | 1.000000 | Float | +| PLAYER_DAMAGE_RATE_ATTACK | PlayerDamageRateAttack | Damage from player multiplier | 1.000000 | Float | +| PLAYER_DAMAGE_RATE_DEFENSE | PlayerDamageRateDefense | Damage to player multiplier | 1.000000 | Float | +| PLAYER_STOMACH_DECREASE_RATE | PlayerStomachDecreaceRate | Player hunger depletion rate | 1.000000 | Float | +| PLAYER_STAMINA_DECREACE_RATE | PlayerStaminaDecreaceRate | Player stamina reduction rate | 1.000000 | Float | +| PLAYER_AUTO_HP_REGENE_RATE | PlayerAutoHPRegeneRate | Player auto HP regeneration rate | 1.000000 | Float | +| PLAYER_AUTO_HP_REGENE_RATE_IN_SLEEP | PlayerAutoHpRegeneRateInSleep | Player sleep HP regeneration rate | 1.000000 | Float | +| PAL_STOMACH_DECREACE_RATE | PalStomachDecreaceRate | Pal hunger depletion rate | 1.000000 | Float | +| PAL_STAMINA_DECREACE_RATE | PalStaminaDecreaceRate | Pal stamina reduction rate | 1.000000 | Float | +| PAL_AUTO_HP_REGENE_RATE | PalAutoHPRegeneRate | Pal auto HP regeneration rate | 1.000000 | Float | +| PAL_AUTO_HP_REGENE_RATE_IN_SLEEP | PalAutoHpRegeneRateInSleep | Pal sleep health regeneration rate (in Palbox) | 1.000000 | Float | +| BUILD_OBJECT_DAMAGE_RATE | BuildObjectDamageRate | Damage to structure multiplier | 1.000000 | Float | +| BUILD_OBJECT_DETERIORATION_DAMAGE_RATE | BuildObjectDeteriorationDamageRate | Structure deterioration rate | 1.000000 | Float | +| COLLECTION_DROP_RATE | CollectionDropRate | Gatherable items multiplier | 1.000000 | Float | +| COLLECTION_OBJECT_HP_RATE | CollectionObjectHpRate | Gatherable objects HP multiplier | 1.000000 | Float | +| COLLECTION_OBJECT_RESPAWN_SPEED_RATE | CollectionObjectRespawnSpeedRate | Gatherable objects respawn interval | 1.000000 | Float | +| ENEMY_DROP_ITEM_RATE | EnemyDropItemRate | Dropped Items Multiplier | 1.000000 | Float | +| DEATH_PENALTY | DeathPenalty | `None` : No lost
`Item` : Lost item without equipment
`ItemAndEquipment` : Lost item and equipment
`All`: Lost All item, equipment, pal(in inventory) | All | Enum | +| ENABLE_PLAYER_TO_PLAYER_DAMAGE | bEnablePlayerToPlayerDamage | Allows players to cause damage to players | false | Boolean | +| ENABLE_FRIENDLY_FIRE | bEnableFriendlyFire | Allow friendly fire | false | Boolean | +| ENABLE_INVADER_ENEMY | bEnableInvaderEnemy | Enable invaders | true | Boolean | +| ACTIVE_UNKO | bActiveUNKO | Enable UNKO | false | Boolean | +| ENABLE_AIM_ASSIST_PAD | bEnableAimAssistPad | Enable controller aim assist | true | Boolean | +| ENABLE_AIM_ASSIST_KEYBOARD | bEnableAimAssistKeyboard | Enable Keyboard aim assist | false | Boolean | +| DROP_ITEM_MAX_NUM | DropItemMaxNum | Maximum number of drops in the world | 3000 | Integer | +| DROP_ITEM_MAX_NUM_UNKO | DropItemMaxNum | Maximum number of UNKO drops in the world | 100 | Integer | +| BASE_CAMP_MAX_NUM | BaseCampMaxNum | Maximum number of base camps | 128 | Integer | +| BASE_CAMP_WORKER_MAXNUM | BaseCampWorkerMaxNum | Maximum number of workers | 15 | Integer | +| DROP_ITEM_ALIVE_MAX_HOURS | DropItemAliveMaxHours | Time it takes for items to despawn in hours | 1.000000 | Float | +| AUTO_RESET_GUILD_NO_ONLINE_PLAYERS | bAutoResetGuildNoOnlinePlayers | Automatically reset guild when no players are online | false | Bool | +| AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS | AutoResetGuildTimeNoOnlinePlayers | Time to automatically reset guild when no players are online | 72.000000 | Float | +| GUILD_PLAYER_MAX_NUM | GuildPlayerMaxNum | Max player of Guild | 20 | Integer | +| PAL_EGG_DEFAULT_HATCHING_TIME | PalEggDefaultHatchingTime | Time(h) to incubate massive egg | 72.000000 | Float | +| WORK_SPEED_RATE | WorkSpeedRate | Work speed multiplier | 1.000000 | Float | +| IS_MULTIPLAY | bIsMultiplay | Enable multiplayer | false | Boolean | +| IS_PVP | bIsPvP | Enable PVP | false | Boolean | +| CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP | bCanPickupOtherGuildDeathPenaltyDrop | Allow players from other guilds to pick up death penalty items | false | Boolean | +| ENABLE_NON_LOGIN_PENALTY | bEnableNonLoginPenalty | Enable non-login penalty | true | Boolean | +| ENABLE_FAST_TRAVEL | bEnableFastTravel | Enable fast travel | true | Boolean | +| IS_START_LOCATION_SELECT_BY_MAP | bIsStartLocationSelectByMap | Enable selecting of start location | true | Boolean | +| EXIST_PLAYER_AFTER_LOGOUT | bExistPlayerAfterLogout | Toggle for deleting players when they log off | false | Boolean | +| ENABLE_DEFENSE_OTHER_GUILD_PLAYER | bEnableDefenseOtherGuildPlayer | Allows defense against other guild players | false | Boolean | +| COOP_PLAYER_MAX_NUM | CoopPlayerMaxNum | Maximum number of players in a guild | 4 | Integer | +| MAX_PLAYERS | ServerPlayerMaxNum | Maximum number of people who can join the server | 32 | Integer | +| SERVER_NAME | ServerName | Server name | thejcpalma-docker-generated-###RANDOM### | Integer | +| SERVER_DESCRIPTION | ServerDescription | Server description | Palworld Dedicated Server running in Docker by thejcpalma | String | +| ADMIN_PASSWORD | server admin password | AdminPassword | adminPasswordHere | String | +| SERVER_PASSWORD | AdminPassword | Set the server password. | serverPasswordHere | String | +| PUBLIC_PORT | public port | Public port number | 8211 | Integer | +| PUBLIC_IP | public ip or FQDN | Public IP or FQDN | | String | +| RCON_ENABLED | RCONEnabled | Enable RCON - Use ADMIN_PASSWORD to login | false | Boolean | +| RCON_PORT | RCONPort | Port number for RCON | 25575 | Integer | +| REGION | Region | Area | | String | +| USEAUTH | bUseAuth | Use authentication | true | Boolean | +| BAN_LIST_URL | BanListURL | Which ban list to use | https://api.palworldgame.com/api/banlist.txt | String | + +## Webhook-Settings + +This section lists all the settings for the webhooks. + +> [!WARNING] +> +> Please note that Hex-Colors (Example #eeeeee) are not supported. Instead, use the Decimal representation of the color. +> To convert a Hex-Color to its Decimal representation, you can use online tools such as [SpyColor](https://www.spycolor.com/). +> Search for the Hex-Color and use the Decimal representation of that color. Using Hex-Colors will cause errors! + +| Variable | Description | Default Value | Allowed Values | +| --------------------------------------- | ------------------------------------------------- | --------------------------------------- | ----------------- | +| `WEBHOOK_ENABLED` | Determines if the webhook is enabled | `false` | false/true | +| `WEBHOOK_URL` | The URL for the webhook | `YOUR-URL-IN-HERE` | Valid webhook URL | +| `WEBHOOK_START_TITLE` | The title for the start webhook | `Server is starting` | Message | +| `WEBHOOK_START_DESCRIPTION` | The description for the start webhook | `The gameserver is starting` | Message | +| `WEBHOOK_START_COLOR` | The color for the start webhook | `2328576` | Color (see below) | +| `WEBHOOK_STOP_TITLE` | The title for the stop webhook | `Server has been stopped` | Message | +| `WEBHOOK_STOP_DESCRIPTION` | The description for the stop webhook | `The gameserver has been stopped` | Message | +| `WEBHOOK_STOP_COLOR` | The color for the stop webhook | `7413016"` | Color (see below) | +| `WEBHOOK_INFO_TITLE` | The title for the info webhook | `Info"` | Message | +| `WEBHOOK_INFO_DESCRIPTION` | The description for the info webhook | `This is an info from the server` | Message | +| `WEBHOOK_INFO_COLOR` | The color for the info webhook | `2849520` | Color (see below) | +| `WEBHOOK_UPDATE_TITLE` | The title for the update webhook | `Updating server` | Message | +| `WEBHOOK_UPDATE_DESCRIPTION` | The description for the update webhook | `Server is being updated` | Message | +| `WEBHOOK_UPDATE_COLOR` | The color for the update webhook | `2849520` | Color (see below) | +| `WEBHOOK_UPDATE_VALIDATION_TITLE` | The title for the update and validation webhook | `Updating and validating server` | Message | +| `WEBHOOK_UPDATE_VALIDATION_DESCRIPTION` | The description for the update and validation webhook | `Server is being updated and validated` | Message | +| `WEBHOOK_UPDATE_VALIDATION_COLOR` | The color for the update and validation webhook | `2849520` | Color (see below) | + +[Back to main](../README.md#environment-variables) diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 0000000..8838446 --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,3 @@ +# Install Guide + +## Coming soon... diff --git a/docs/dev/DEV_GUIDE.md b/docs/dev/DEV_GUIDE.md new file mode 100644 index 0000000..ddfc87d --- /dev/null +++ b/docs/dev/DEV_GUIDE.md @@ -0,0 +1,54 @@ +# Development Guide + +## Introduction + +This guide is intended for developers who want to contribute to the project. It provides information on how to set up your development environment, how to build and test the project, and how to contribute your changes back to the project. + +## Prerequisites + +Before you start, you need to have the following tools installed on your system: + +- [Git](https://git-scm.com/) +- [Docker](https://www.docker.com/) +- [Docker Compose](https://docs.docker.com/compose/) + +## Testing the changes + +Use the following `docker-compose-test.yml` file to test the changes: + +```yaml +version: '3.9' +services: + palworld-dedicated-server: + build: . + container_name: palworld-dedicated-server + image: thejcpalma/palworld-dedicated-server:latest + restart: unless-stopped + stop_grace_period: 30s # Set to however long you are willing to wait for the container to gracefully stop + ports: + - target: 8211 # Gamerserver port inside of the container + published: 8211 # Gamerserver port on your host + protocol: udp + mode: host + - target: 25575 # RCON port inside of the container + published: 25575 # RCON port on your host + protocol: tcp + mode: host + env_file: + - ./test.env + volumes: + - ./game:/palworld +``` + +To test the changes, you can run the following commands in the root directory of the project: + +```bash +cp default.env test.env +# Edit the test.env file to match your desired configuration + +# Create the file docker-compose-test.yml with the content above + +# Build and run the container in the background and then follow the logs +docker-compose -f docker-compose-test.yml up --build -d && \ +docker-compose -f docker-compose-test.yml logs -f +``` diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..96ad4dc --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# shellcheck disable=SC1091 + +set -e + +source "${PWD}"/includes/colors.sh +source "${PWD}"/includes/webhook.sh +source "${PWD}"/includes/server.sh + +if [[ "${PUID}" -eq 0 ]] || [[ "${PGID}" -eq 0 ]]; then + echo_warning ">>> Running as root is not supported, please fix your PUID and PGID!" + exit 1 +elif [[ "$(id -u steam)" -ne "${PUID}" ]] || [[ "$(id -g steam)" -ne "${PGID}" ]]; then + echo_info "> Current steam user UID is '$(id -u steam)' and GID is '$(id -g steam)'" + echo_info "> Setting new steam user UID to '${PUID}' and GID to '${PGID}'" + groupmod -g "${PGID}" steam && usermod -u "${PUID}" -g "${PGID}" steam +fi + +mkdir -p /palworld/backups +chown -R steam:steam /palworld /home/steam/ + +### Signal Handling (Handlers & Traps) + +# Handler for SIGTERM from docker-based stop events +function term_handler() { + echo_error ">>> SIGTERM received" + stop_server +} + +function start_handlers() { + + # If SIGTERM is sent to the process, call term_handler function + trap 'kill ${!}; term_handler' SIGTERM + + echo_info "> Handlers started" +} + +# Process loop to keep the server manager running +while true +do + echo_success ">>>> Starting server manager <<<<" + start_handlers + + # Start the server manager + su steam -c /home/steam/server/scripts/servermanager.sh & + + killpid="${!}" + echo_info "> Server main thread started with pid ${killpid}" + wait ${killpid} + + send_stop_notification + + # Wait for backup jobs to finish + mapfile -t backup_pids < <(pgrep backup) + if [ "${#backup_pids[@]}" -ne 0 ]; then + echo "Waiting for backup to finish" + for pid in "${backup_pids[@]}"; do + tail --pid="$pid" -f 2>/dev/null + done + fi + + exit 0; +done diff --git a/includes/backup.sh b/includes/backup.sh new file mode 100644 index 0000000..9c27afb --- /dev/null +++ b/includes/backup.sh @@ -0,0 +1,91 @@ +# shellcheck disable=SC2148,SC2012,SC1091 + +source "${PWD}"/includes/colors.sh +source "${PWD}"/includes/rcon.sh + +### Backup functions + +function create_backup() { + date=$(date +%Y%m%d_%H%M%S) + + backup_file_name="saved-${date}.tar.gz" + + # RCON broadcast backup start + rcon_broadcast_backup_start + + # Create backup + if ! tar cfz "${BACKUP_PATH}/${backup_file_name}" -C "${GAME_PATH}/" "Saved" ; then + rcon_broadcast_backup_failed + echo_error ">>> Backup failed." + else + rcon_broadcast_backup_success + echo_success ">>> Backup '${backup_file_name}' created successfully." + fi + + # If retention policy is enabled, clean old backups + if [[ -n ${BACKUP_AUTO_CLEAN} ]] && [[ ${BACKUP_AUTO_CLEAN,,} == "true" ]] && [[ ${BACKUP_AUTO_CLEAN_AMOUNT_TO_KEEP} =~ ^[0-9]+$ ]]; then + ls -1t "${BACKUP_PATH}"/saved-*.tar.gz | tail -n +$((BACKUP_AUTO_CLEAN_AMOUNT_TO_KEEP + 1)) | xargs -d '\n' rm -f -- + fi +} + +function list_backups() { + local num_backup_entries=${1} + + if [ -z "$(ls -A "${BACKUP_PATH}")" ]; then + echo_warning ">> No backups in the backup directory '${BACKUP_PATH}'." + exit 0 + fi + + files=$(ls -1t "${BACKUP_PATH}"/saved-*.tar.gz) + total_file_count=$(echo "${files}" | wc -l) + + if [ -z "${num_backup_entries}" ]; then + file_list=${files} + echo_success ">>> Listing ${total_file_count} backup file(s)!" + else + file_list=$(echo "${files}" | head -n "${num_backup_entries}") + echo_success ">>> Listing ${num_backup_entries} out of backup ${total_file_count} file(s)." + fi + + for file in $file_list; do + filename=$(basename "${file}") + + # get date from filename + date_str=${filename#saved-} # Remove 'saved-' prefix + date_str=${date_str%.tar.gz} # Remove '.tar.gz' suffix + + # Reformat the date string + date=$(date -d "${date_str:0:8} ${date_str:9:2}:${date_str:11:2}:${date_str:13:2}" +'%Y-%m-%d %H:%M:%S') + + echo_info -n "${date} | " && echo_base "${filename}" + done +} + +function clean_backups() { + local num_files_to_keep=${1} + + if [ -z "$(ls -A "${BACKUP_PATH}")" ]; then + echo_info "> No files in the backup directory '${BACKUP_PATH}'. Exiting..." + exit 0 + fi + + files=$(ls -1t "${BACKUP_PATH}"/saved-*.tar.gz) + files_to_delete=$(echo "${files}" | tail -n +$((num_files_to_keep + 1))) + + num_files=$(echo -n "${files}" | grep -c '^') + num_files_to_delete=$(echo -ne "${files_to_delete}" | grep -c '^') + + if [[ ${num_files_to_delete} -gt 0 ]]; then + echo "$files_to_delete" | xargs -d '\n' rm -f -- + if [[ ${num_files} -lt ${num_files_to_keep} ]]; then + num_files_to_keep="${num_files}" + fi + echo_success ">>> ${num_files_to_delete} backup(s) cleaned, keeping ${num_files_to_keep} backups(s)." + else + echo_info "> No backups to clean." + fi +} + +function restore_backup() { + echo "Not implemented yet." +} diff --git a/includes/colors.sh b/includes/colors.sh new file mode 100644 index 0000000..4f0e36d --- /dev/null +++ b/includes/colors.sh @@ -0,0 +1,91 @@ +# shellcheck disable=SC2148 +# Idea from https://colors.sh/ +# ANSI Color Codes - https://www.shellhacks.com/bash-colors/ +# Ansi Default Color Codes https://hyperskill.org/learn/step/18193#terminal-support +# Use ANSI whenever possible. Makes logs compatible with almost all systems. + +# Aliases for colorful echos with newlines +function echo_base() { + colorful_echos --base "${@}" # to remove +} + +function echo_error() { + colorful_echos --error "${@}" +} + +function echo_info() { + colorful_echos --info "${@}" +} + +function echo_success() { + colorful_echos --success "${@}" +} + +function echo_warning() { + colorful_echos --warning "${@}" +} + + +# This creates a wrapper for echo to add colors +function colorful_echos() { + # Set color constants + BASE="\e[97m" # Clean color + CLEAN="\e[0m" # Clean color + ERROR="\e[91m" # Red color for error + INFO="\e[94m" # Blue color for info + SUCCESS="\e[92m" # Green color for success + WARNING="\e[93m" # Yellow color for warning + + if [ $# -gt 3 ]; then + echo "Usage: $0 [--success|--error|--info|--warning|--base] [-n] " + echo " --success: Print a success message" + echo " --error: Print an error message" + echo " --info: Print an info message" + echo " --warning: Print a warning message" + echo " --base: Print a base message" + echo " -n: Do not print a newline at the end of the message" + exit 1 + fi + + + # Parse arguments + level=$1 + + shift + + nl_flag="\n" + if [ "$1" == "-n" ]; then + nl_flag="" + shift + fi + + message="${*}" + + # Set color based on argument + if [ "$level" == "--success" ]; then + color="$SUCCESS" + elif [ "$level" == "--error" ]; then + color="$ERROR" + elif [ "$level" == "--info" ]; then + color="$INFO" + elif [ "$level" == "--warning" ]; then + color="$WARNING" + elif [ "$level" == "--base" ]; then + color="$BASE" + else + echo -ne "$message" + return 0 + fi + + # print newlines in the beginning of the message + while [ "${message:0:2}" = "\\n" ]; do + # Print a newline + echo "" + # Remove the first two characters from the message + message="${message:2}" + done + + # Print message with the specified color + echo -ne "${color}${message}${CLEAN}${nl_flag}" + +} diff --git a/includes/config.sh b/includes/config.sh new file mode 100644 index 0000000..4f69e4f --- /dev/null +++ b/includes/config.sh @@ -0,0 +1,377 @@ +# shellcheck disable=SC2148,SC1091 + +source "${PWD}"/includes/colors.sh + +function setup_engine_ini() { + # Setup the engine config file + echo_warning ">> Setting up Engine.ini ..." + + pattern1="OnlineSubsystemUtils.IpNetDriver" + pattern2="^NetServerMaxTickRate=[0-9]*" + + echo_info "> Checking if config already exists..." + + if [ ! -f "${GAME_ENGINE_FILE}" ]; then + ew "> No config found, generating one!" + if [ ! -d "${GAME_CONFIG_PATH}" ]; then + mkdir -p "${GAME_CONFIG_PATH}/" + fi + # Create empty Engine.ini file + echo "" > "${GAME_ENGINE_FILE}" + else + echo_info "> Found existing config!" + fi + if grep -qE "${pattern1}" "${GAME_ENGINE_FILE}" 2>/dev/null; then + echo_info "> Found [/Script/OnlineSubsystemUtils.IpNetDriver] section." + else + echo_warning ">> Found no [/Script/OnlineSubsystemUtils.IpNetDriver], adding it." + echo -e "\n[/Script/OnlineSubsystemUtils.IpNetDriver]" >> "${GAME_ENGINE_FILE}" + fi + if grep -qE "${pattern2}" "${GAME_ENGINE_FILE}" 2>/dev/null; then + echo_info "> Found NetServerMaxTickRate parameter, changing it to '${NETSERVERMAXTICKRATE}'" + sed -E -i "s/${pattern2}/NetServerMaxTickRate=${NETSERVERMAXTICKRATE}/" "${GAME_ENGINE_FILE}" + else + echo_warning ">> Found no NetServerMaxTickRate parameter, adding it with value '${NETSERVERMAXTICKRATE}'" + echo "NetServerMaxTickRate=${NETSERVERMAXTICKRATE}" >> "${GAME_ENGINE_FILE}" + fi + + echo_success ">>> Finished setting up Engine.ini!" +} + +function setup_palworld_settings_ini() { + # Setup the server config file + echo_warning ">> Setting up PalWorldSettings.ini ..." + echo_info "> Checking if config already exists..." + if [ ! -f "${GAME_SETTINGS_FILE}" ]; then + echo_warning ">> No config found, generating one" + if [ ! -d "${GAME_CONFIG_PATH}" ]; then + mkdir -p "${GAME_CONFIG_PATH}/" + fi + # Copy default-config, which comes with SteamCMD to gameserver save location + cp "${GAME_ROOT}/DefaultPalWorldSettings.ini" "${GAME_SETTINGS_FILE}" + else + echo_info "> Found existing config!" + fi + + if [[ -n ${DIFFICULTY+x} ]]; then + echo "> Setting Difficulty to '$DIFFICULTY'" + sed -E -i "s/Difficulty=[a-zA-Z]*/Difficulty=$DIFFICULTY/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${DAYTIME_SPEEDRATE+x} ]]; then + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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_DAMAGE_RATE+x} ]]; then + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting GuildPlayerMaxNum to '$GUILD_PLAYER_MAX_NUM'" + sed -E -i "s/GuildPlayerMaxNum=[0-9]*/GuildPlayerMaxNum=$GUILD_PLAYER_MAX_NUM/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${PAL_EGG_DEFAULT_HATCHING_TIME+x} ]]; then + echo "> Setting 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 + echo "> Setting WorkSpeedRate to '$WORK_SPEED_RATE'" + sed -E -i "s/WorkSpeedRate=[+-]?([0-9]*[.])?[0-9]+/WorkSpeedRate=$WORK_SPEED_RATE/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${IS_MULTIPLAY+x} ]]; then + echo "> Setting 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 + echo "> Setting bIsPvP to $IS_PVP" + sed -E -i "s/bIsPvP=[a-zA-Z]*/bIsPvP=$IS_PVP/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP+x} ]]; then + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 + echo "> Setting 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 ${COOP_PLAYER_MAX_NUM+x} ]]; then + echo "> Setting 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 + echo "> Setting 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 + if [[ "$SERVER_NAME" == *"###RANDOM###"* ]]; then + RAND_VALUE=$RANDOM + echo "> Found standard template, using random numbers in server name" + sed -E -i -e "s/###RANDOM###/$RAND_VALUE/g" "$GAME_SETTINGS_FILE" + echo "> Server name is now 'thejcpalma-docker-generated-$RAND_VALUE'" + else + echo "> Setting server name to '$SERVER_NAME'" + sed -E -i "s/ServerName=\"[^\"]*\"/ServerName=\"$SERVER_NAME\"/" "$GAME_SETTINGS_FILE" + fi + fi + if [[ -n ${SERVER_DESCRIPTION+x} ]]; then + echo "> Setting server description to '$SERVER_DESCRIPTION'" + sed -E -i "s/ServerDescription=\"[^\"]*\"/ServerDescription=\"$SERVER_DESCRIPTION\"/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${ADMIN_PASSWORD+x} ]]; then + echo "> Setting server admin password to '$ADMIN_PASSWORD'" + sed -E -i "s/AdminPassword=\"[^\"]*\"/AdminPassword=\"$ADMIN_PASSWORD\"/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${SERVER_PASSWORD+x} ]]; then + echo "> Setting server password to '$SERVER_PASSWORD'" + sed -E -i "s/ServerPassword=\"[^\"]*\"/ServerPassword=\"$SERVER_PASSWORD\"/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${PUBLIC_PORT+x} ]]; then + echo "> Setting 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 + echo "> Setting public ip to '$PUBLIC_IP'" + sed -E -i "s/PublicIP=\"[^\"]*\"/PublicIP=\"$PUBLIC_IP\"/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${RCON_ENABLED+x} ]]; then + echo "> Setting 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 + echo "> Setting RCONPort to '$RCON_PORT'" + sed -E -i "s/RCONPort=[0-9]*/RCONPort=$RCON_PORT/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${REGION+x} ]]; then + echo "> Setting Region to '$REGION'" + sed -E -i "s/Region=\"[^\"]*\"/Region=\"$REGION\"/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${USEAUTH+x} ]]; then + echo "> Setting bUseAuth to '$USEAUTH'" + sed -E -i "s/bUseAuth=[a-zA-Z]*/bUseAuth=$USEAUTH/" "$GAME_SETTINGS_FILE" + fi + if [[ -n ${BAN_LIST_URL+x} ]]; then + echo "> Setting BanListURL to '$BAN_LIST_URL'" + sed -E -i "s~BanListURL=\"[^\"]*\"~BanListURL=\"$BAN_LIST_URL\"~" "$GAME_SETTINGS_FILE" + fi + + echo_success ">>> Finished setting up PalWorldSettings.ini" +} + + + +function setup_rcon_yaml () { + if [[ -n ${RCON_ENABLED+x} ]] && [ "${RCON_ENABLED,,}" == "true" ] ; then + echo_info "> RCON is enabled, setting 'rcon.yaml' config file..." + + if [[ "${SERVER_SETTINGS_MODE,,}" == "auto" ]]; then + # Use environment variables + echo_warning ">> Using environment variables to configure 'rcon.yaml' config file." + echo_info -n "> Admin Password: " && echo_base "$RCON_PORT" + echo_info -n "> RCON Port: " && echo_base "$rcon_port" + + if [[ -n ${RCON_PORT+x} ]]; then + sed -i "s/###RCON_PORT###/$RCON_PORT/" "$RCON_CONFIG_FILE" + else + echo_error ">>> 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" + else + echo_error ">>> ADMIN_PASSWORD is not set, please set it for RCON functionality to work!" + fi + else + echo_warning ">> Using file '$GAME_SETTINGS_FILE' to configure..." + + # Use file information + admin_password=$(awk -F'AdminPassword="' '{print $2}' "$GAME_SETTINGS_FILE" | awk -F'"' '{print $1}' | tr -d '\n') + rcon_port=$(awk -F'RCONPort=' '{print $2}' "$GAME_SETTINGS_FILE" | awk -F',' '{print $1}' | tr -d '\n') + + echo_info -n "> Admin Password: " && echo_base "$admin_password" + echo_info -n "> RCON Port: " && echo_base "$rcon_port" + + if [[ -n ${rcon_port+x} ]]; then + sed -i "s/###RCON_PORT###/$rcon_port/" "$RCON_CONFIG_FILE" + else + echo_error ">>> RCON_PORT is not set in the file, 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" + else + echo_error ">>> ADMIN_PASSWORD is not set in the file, please set it for RCON functionality to work!" + fi + fi + + echo_success ">>> Finished setting up 'rcon.yaml' config file." + else + echo_warning ">> RCON is disabled, skipping 'rcon.yaml' config file!" + fi +} + + +# Function to setup the server configs +function setup_configs() { + if [[ -n ${SERVER_SETTINGS_MODE} ]] && [[ ${SERVER_SETTINGS_MODE,,} == "auto" ]]; then + echo_warning ">> SERVER_SETTINGS_MODE is set to '${SERVER_SETTINGS_MODE}', using environment variables to configure the server!" + setup_engine_ini + setup_palworld_settings_ini + else + echo_warning ">> SERVER_SETTINGS_MODE is set to '${SERVER_SETTINGS_MODE}', NOT using environment variables to configure the server!" + echo_warning ">> File ${GAME_SETTINGS_FILE} has to be manually set by user." + fi + setup_rcon_yaml +} diff --git a/includes/cron.sh b/includes/cron.sh new file mode 100644 index 0000000..bd5cac5 --- /dev/null +++ b/includes/cron.sh @@ -0,0 +1,22 @@ +# shellcheck disable=SC2148 + +# Start supercronic and load crons +function setup_crons() { + echo "" > cronlist + # Auto backup cron job + if [[ -n ${BACKUP_ENABLED} ]] && [[ ${BACKUP_ENABLED,,} == "true" ]]; then + echo "${BACKUP_CRON_EXPRESSION} backupmanager create" >> cronlist + fi + # Auto update cron job + if [[ -n ${AUTO_UPDATE_ENABLED} ]] && [[ ${AUTO_UPDATE_ENABLED,,} == "true" ]]; then + echo "${AUTO_UPDATE_CRON_EXPRESSION} update" >> cronlist + fi + # Auto restart cron job + if [[ -n ${AUTO_RESTART_ENABLED} ]] && [[ ${AUTO_RESTART_ENABLED,,} == "true" ]]; then + echo "${AUTO_RESTART_CRON_EXPRESSION} restart ${AUTO_RESTART_WARN_TIME}" >> cronlist + fi + + /usr/local/bin/supercronic -passthrough-logs cronlist & + + echo_success ">>> Supercronic started" +} diff --git a/includes/rcon.sh b/includes/rcon.sh new file mode 100644 index 0000000..568e337 --- /dev/null +++ b/includes/rcon.sh @@ -0,0 +1,91 @@ +# shellcheck disable=SC2148 + +# Aliases to run RCON commands + + +function rcon_broadcast_save_and_shutdown() { + rconcli 'broadcast Server-shutdown-was-requested' + rconcli 'broadcast Saving-before-shutdown...' + rconcli 'save' + rconcli 'broadcast Saving-done' + rconcli 'broadcast Server-shutting-down...' + sleep 5 +} +# AUTO_RESTART_WARN_MINUTES or AUTO_UPDATE_WARN_MINUTES +function rcon_broadcast_restart() { + local restart_warn_minutes=${1} + + # Check if restart_warn_minutes is not set or is not a valid integer + if [[ -z "${restart_warn_minutes}" ]] || ! [[ "${restart_warn_minutes}" =~ ^[0-9]+$ ]]; then + echo "Error: restart_warn_minutes is not set or is not a valid integer" + return 1 + fi + + rconcli "broadcast Server-will-restart-in-${restart_warn_minutes}-minute(s)" + + i="${restart_warn_minutes}" + while ((i > 1)); do + sleep "1m" + if ((i <= 5)) || ((i % 5 == 0)); then + rconcli "broadcast Server-will-reboot-in-${i}-minute(s)" + fi + ((i--)) + done + + i=59 + while ((i > 0)); do + sleep "1s" + if ((i <= 5)) || ((i % 10 == 0)); then + rconcli "broadcast Server-will-restart-in-${i}-second(s)" + fi + ((i--)) + done + + rconcli "broadcast Server-is-shutting-down-now!" + rconcli save + sleep 1 + backupmanager create + sleep 1 +} + +function rcon_restart() { + local restart_warn_minutes=${1} + + if [ "$(rcon_get_player_count)" = "0" ]; then + echo_info "> No players are online. Restarting the server now..." + rcon_broadcast_restart 0 + else + rcon_broadcast_restart "${restart_warn_minutes}" + fi + sleep 1 + rconcli "shutdown 1" +} + +function rcon_broadcast_backup_start() { + time=$(date '+%H:%M:%S') + + rconcli "broadcast ${time}-Saving-in-5-seconds" + sleep 5 + rconcli 'broadcast Saving-world...' + rconcli 'save' + rconcli 'broadcast Saving-done' + rconcli 'broadcast Creating-backup' +} + +function rcon_broadcast_backup_success() { + rconcli 'broadcast Backup-done' +} + +function rcon_broadcast_backup_failed() { + rconcli 'broadcast Backup-failed' +} + +function rcon_get_player_count() { + local player_list + if [ "${RCON_ENABLED,,}" != true ]; then + echo 0 + return 0 + fi + player_list=$(rconcli "ShowPlayers") + echo -n "${player_list}" | wc -l +} diff --git a/includes/security.sh b/includes/security.sh new file mode 100644 index 0000000..ea4b606 --- /dev/null +++ b/includes/security.sh @@ -0,0 +1,20 @@ +# shellcheck disable=SC2148,SC1091 +source "${PWD}"/includes/colors.sh + +function check_for_default_credentials() { + echo_info "> Checking for existence of default credentials" + if [[ -n ${ADMIN_PASSWORD} ]] && [[ ${ADMIN_PASSWORD} == "adminPasswordHere" ]]; then + echo_error ">>> Security thread detected: Please change the default admin password. Aborting server start ..." + exit 1 + fi + if [[ -n ${SERVER_PASSWORD} ]] && [[ ${SERVER_PASSWORD} == "serverPasswordHere" ]]; then + echo_error ">>> Security thread detected: Please change the default server password. Aborting server start ..." + exit 1 + fi + if [[ "${SERVER_SETTINGS_MODE,,}" == "manual" ]] && [[ -s "${GAME_SETTINGS_FILE}" ]]; then + admin_password=$(awk -F'AdminPassword="' '{print $2}' "${GAME_SETTINGS_FILE}" | awk -F'"' '{print $1}' | tr -d '\n') + if [[ "${admin_password}" == "adminPasswordHere" ]]; then + echo_error ">>> Security thread detected: Please change the default server password. Aborting server start ..." + fi + fi +} diff --git a/includes/server.sh b/includes/server.sh new file mode 100644 index 0000000..2bbffd6 --- /dev/null +++ b/includes/server.sh @@ -0,0 +1,103 @@ +# shellcheck disable=SC2148,SC1091 +# IF Bash extension used: +# https://stackoverflow.com/a/13864829 +# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 + +source "${PWD}"/includes/config.sh +source "${PWD}"/includes/cron.sh +source "${PWD}"/includes/security.sh +source "${PWD}"/includes/webhook.sh +source "${PWD}"/includes/colors.sh +source "${PWD}"/includes/rcon.sh + +steamcmd_dir="/home/steam/steamcmd" + +# Function to install the gameserver +function install_server() { + + if [ -f "${GAME_ROOT}/PalServer.sh" ]; then + return 0 + fi + + # Force a fresh install of all + echo_warning ">> Doing a fresh install of the gameserver..." + + send_install_notification + + "${steamcmd_dir}"/steamcmd.sh +force_install_dir "${GAME_ROOT}" +login anonymous +app_update 2394010 validate +quit + echo_success ">>> Done installing the gameserver" +} + +# Function to update the gameserver +function update_server() { + + if [ "${ALWAYS_UPDATE_ON_START,,}" == "false" ]; then + return 1 + fi + + if [[ -n ${STEAMCMD_VALIDATE_FILES} ]] && [[ ${STEAMCMD_VALIDATE_FILES,,} == "true" ]]; then + echo_warning ">> Doing an update and validate of the gameserver files..." + + send_update_and_validate_notification + + "${steamcmd_dir}"/steamcmd.sh +force_install_dir "${GAME_ROOT}" +login anonymous +app_update 2394010 validate +quit + echo_success ">>> Done updating and validating the gameserver files" + + else + echo_warning ">> Doing an update of the gameserver files..." + + send_update_notification + + "${steamcmd_dir}"/steamcmd.sh +force_install_dir "${GAME_ROOT}" +login anonymous +app_update 2394010 +quit + echo_success ">>> Done updating the gameserver files" + fi +} + +# Function to start the gameserver +function start_server() { + echo_success ">>> Starting the gameserver" + pushd "${GAME_ROOT}" > /dev/null || exit + setup_configs + START_OPTIONS=() + if [[ -n ${COMMUNITY_SERVER} ]] && [[ ${COMMUNITY_SERVER,,} == "true" ]]; then + echo_info "> Setting Community-Mode to enabled" + START_OPTIONS+=("EpicApp=PalServer") + fi + if [[ -n ${MULTITHREAD_ENABLED} ]] && [[ ${MULTITHREAD_ENABLED,,} == "true" ]]; then + echo_info "> Setting Multi-Core-Enhancements to enabled" + START_OPTIONS+=("-useperfthreads" "-NoAsyncLoadingThread" "-UseMultithreadForDS") + fi + + send_start_notification + + ./PalServer.sh "${START_OPTIONS[@]}" + + popd > /dev/null || exit +} + +# Function to stop the gameserver +function stop_server() { + echo_warning ">>> Stopping server..." + + rcon_broadcast_save_and_shutdown + + send_stop_notification + + kill -SIGTERM "$(pidof PalServer-Linux-Test)" + tail --pid="$(pidof PalServer-Linux-Test)" -f 2>/dev/null +} + +# Function to restart the gameserver +function restart_server() { + local warn_minutes=${1} + + if [ "${RCON_ENABLED,,}" = true ]; then + if [[ -n "${warn_minutes}" ]] && [[ "${warn_minutes}" =~ ^[0-9]+$ ]]; then + rcon_restart "${warn_minutes}" + else + echo_error ">>> Unable to restart, warn_minutes is not a positive number: ${warn_minutes}" + fi + else + echo_warning ">> Rebooting server without RCON might cause data loss! Feature is disabled." + fi +} diff --git a/includes/webhook.sh b/includes/webhook.sh new file mode 100644 index 0000000..d1dcf4d --- /dev/null +++ b/includes/webhook.sh @@ -0,0 +1,83 @@ +# shellcheck disable=SC2148 +# Function to generate JSON data for the Discord message +# Webpage for COLOR-Calculation - https://www.spycolor.com/ +# IMPORTANT: Don't use Hex-Colors! Go to the page search for the Hex-Color. +# After that add the DECIMAL-Representation to the color field or it will break! + + +### Aliases to use in scripts +function send_start_notification() { + send_webhook_notification "$WEBHOOK_START_TITLE" "$WEBHOOK_START_DESCRIPTION" "$WEBHOOK_START_COLOR" +} + +function send_stop_notification() { + send_webhook_notification "$WEBHOOK_STOP_TITLE" "$WEBHOOK_STOP_DESCRIPTION" "$WEBHOOK_STOP_COLOR" +} + +function send_install_notification() { + send_webhook_notification "$WEBHOOK_INSTALL_TITLE" "$WEBHOOK_INSTALL_DESCRIPTION" "$WEBHOOK_INSTALL_COLOR" +} + +function send_update_notification() { + send_webhook_notification "$WEBHOOK_UPDATE_TITLE" "$WEBHOOK_UPDATE_DESCRIPTION" "$WEBHOOK_UPDATE_COLOR" +} + +function send_update_and_validate_notification() { + send_webhook_notification "$WEBHOOK_UPDATE_VALIDATE_TITLE" "$WEBHOOK_UPDATE_VALIDATE_DESCRIPTION" "$WEBHOOK_UPDATE_VALIDATE_COLOR" +} + +function send_restart_notification() { + send_webhook_notification "$WEBHOOK_RESTART_TITLE" "$WEBHOOK_RESTART_DESCRIPTION" "$WEBHOOK_RESTART_COLOR" +} + +function send_auto_update_start_notification() { + local message=${1} + send_webhook_notification "Server Auto Update Started" "${message}" "$WEBHOOK_UPDATE_COLOR" +} + +function send_auto_update_fail_notification() { + local fail_message="$1" + send_webhook_notification "Server Auto Update Failed" "Reason: $fail_message" "$WEBHOOK_UPDATE_COLOR" +} + + +### Webhook Functions + +# Function to send a notification to a webhook +function send_webhook_notification() { + + if ! check_webhook_enabled; then + return + fi + + local notification_title="$1" + local notification_description="$2" + local notification_color_code="$3" + + # Generate the JSON data + data=$(generate_post_data "$notification_title" "$notification_description" "$notification_color_code") + + # Debug Curl + #curl --ssl-no-revoke -H "Content-Type: application/json" -X POST -d "$data" "$WEBHOOK_URL" + # Prod Curl + curl --silent --ssl-no-revoke -H "Content-Type: application/json" -X POST -d "$data" "$WEBHOOK_URL" +} + +# Function to generate JSON data for the Discord message +generate_post_data() { + cat <> Not enough arguments" + print_usage + exit 1 + fi + + # Evaluate the command + case "$1" in + create) + if [ ${#} -ne 1 ]; then + ee ">>> Invalid number of arguments for 'create'" + print_usage + exit 1 + fi + create_backup + ;; + list) + if [ ${#} -gt 2 ]; then + ee ">>> Invalid number of arguments for 'list'" + print_usage + exit 1 + fi + + local number_to_list=${2:-""} + + if [[ -n "${number_to_list}" ]] && [[ ! "${number_to_list}" =~ ^[0-9]+$ ]]; then + ew ">> Invalid argument '${number_to_list}'. Please provide a positive integer." + exit 1 + fi + + list_backups "${number_to_list}" + ;; + clean) + if [ ${#} -gt 2 ]; then + ee ">>> Invalid number of arguments for 'clean'" + print_usage + exit 1 + fi + + local num_backup_entries=${2:-${BACKUP_AUTO_CLEAN_AMOUNT_TO_KEEP}} + + if ! [[ "${num_backup_entries}" =~ ^[0-9]+$ ]]; then + ew ">> Invalid argument '${num_backup_entries}'. Please provide a positive integer." + exit 1 + fi + + clean_backups "${num_backup_entries}" + ;; + restore) + if [ ${#} -ne 2 ]; then + ee ">>> Invalid number of arguments for 'restore'" + print_usage + exit 1 + fi + + local restore_file="${2}" + restore_backup "${restore_file}" + ;; + help) + if [ ${#} -ne 1 ]; then + ee ">>> Invalid number of arguments for 'help'" + print_usage + exit 1 + fi + print_usage + ;; + *) + ee ">>> Illegal option '${1}'" + print_usage + exit 1 + ;; + esac +} + +function check_required_directories() { + + if [ -z "${GAME_PATH}" ]; then + ee ">>> GAME_PATH environment variable not set.\n Exiting..." + exit 1 + elif [ ! -d "${GAME_PATH}" ]; then + ee ">>> Game directory '${GAME_PATH}' doesn't exist yet." + exit 1 + fi + + if [ -z "${GAME_SAVE_PATH}" ]; then + ee ">>> GAME_SAVE_PATH environment variable not set.\n Exiting..." + exit 1 + elif [ ! -d "${GAME_SAVE_PATH}" ]; then + ee ">>> Game save directory '${GAME_SAVE_PATH}' doesn't exist yet." + exit 1 + fi + + if [ ! -d "${BACKUP_PATH}" ]; then + ew ">> Backup directory ${BACKUP_PATH} doesn't exist. Creating it..." + mkdir -p "${BACKUP_PATH}" + fi +} + +function start_backup_manager() { + check_required_directories + parse_arguments "${@}" +} + +start_backup_manager "${@}" diff --git a/scripts/servermanager.sh b/scripts/servermanager.sh new file mode 100644 index 0000000..c1ec89a --- /dev/null +++ b/scripts/servermanager.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# shellcheck disable=SC1091 +# IF Bash extension used: +# https://stackoverflow.com/a/13864829 +# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 + +source "${PWD}"/includes/server.sh +source "${PWD}"/includes/cron.sh +source "${PWD}"/includes/security.sh + +### Server Manager Initialization +function start_server_manager() { + check_for_default_credentials + + install_server + + update_server + + setup_crons + + start_server +} + +start_server_manager diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100644 index 0000000..6bab440 --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +source "${PWD}/includes/colors.sh" +source "${PWD}/includes/webhook.sh" +source "${PWD}/includes/rcon.sh" + +# Check if the always auto update on boot feature is enabled +if [ "${ALWAYS_UPDATE_ON_BOOT}" = false ]; then + error_message="ALWAYS_UPDATE_ON_BOOT is disabled. Auto updating feature is disabled" + echo_warning ">> ${error_message}" + send_auto_update_fail_notification "${error_message}" + exit 0 +fi + +temp_file=$(mktemp) +http_code=$(curl https://api.steamcmd.net/v1/info/2394010 --output "$temp_file" --silent --location --write-out "%{http_code}") + +CURRENT_MANIFEST=$(awk '/manifest/{count++} count==2 {print $2; exit}' /palworld/steamapps/appmanifest_2394010.acf) +TARGET_MANIFEST=$(grep -Po '"2394012".*"gid": "\d+"' <"$temp_file" | sed -r 's/.*("[0-9]+")$/\1/') + +rm "$temp_file" + +# Check if the server is reachable +if [ "$http_code" -ne 200 ]; then + error_message="There was a problem reaching the Steam api. Unable to check for updates!" + echo_warning ">> ${error_message}" + send_auto_update_fail_notification "${error_message}" + exit 1 +fi + +# Check if the server response contains the expected ManifestID +if [ -z "$TARGET_MANIFEST" ]; then + error_message="The server response does not contain the expected ManifestID. Unable to check for updates!" + echo_warning ">> ${error_message}" + send_auto_update_fail_notification "${error_message}" + exit 1 +fi + +# Check if the server is up to date +if [ "$CURRENT_MANIFEST" != "$TARGET_MANIFEST" ]; then + # Only update if RCON is enabled + if [ "${RCON_ENABLED,,}" = true ]; then + message="New build was found. Updating the server from $CURRENT_MANIFEST to $TARGET_MANIFEST." + echo_warning ">> ${message}" + send_auto_update_start_notification "${message}" + + message="Server will update in ${AUTO_UPDATE_WARN_MINUTES} minutes!" + echo_warning ">> ${message}" + send_auto_update_start_notification "${message}" + + rm /palworld/steamapps/appmanifest_2394010.acf + + rcon_restart "${AUTO_UPDATE_WARN_MINUTES}" + else + error_message="An update is available, however auto updating without RCON is not supported!" + echo_warning ">> ${error_message}" + send_auto_update_fail_notification + fi +else + echo_info "> The server is up-to-date!" +fi \ No newline at end of file diff --git a/scripts/wrappers/backupmanager_cli.sh b/scripts/wrappers/backupmanager_cli.sh new file mode 100644 index 0000000..4a09baf --- /dev/null +++ b/scripts/wrappers/backupmanager_cli.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Wrapper for backup manager for cli usage +# Makes the usage easier when running the backup manager from the command line +# Usage is supposes to be 'backup [option] [arguments]' + +backupmanager "$@" diff --git a/scripts/wrappers/rconcli.sh b/scripts/wrappers/rconcli.sh new file mode 100644 index 0000000..17199d4 --- /dev/null +++ b/scripts/wrappers/rconcli.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# shellcheck disable=SC2148,SC1091 + +source "${PWD}"/includes/colors.sh +source "${PWD}"/includes/rcon.sh + +# Function to run RCON commands +# Arguments: +# Example: rconcli "showplayers" +rconcli() { + local cmd="$*" + if [[ -z ${RCON_ENABLED+x} ]] || [[ "${RCON_ENABLED,,}" != "true" ]]; then + echo_error ">>> RCON is not enabled. Aborting RCON command ..." + exit + fi + + echo_info -n "> RCON: " + output=$(rcon -c "${PWD}"/configs/rcon.yaml "${cmd}" | tr -d '\0') + echo_base "$output" + +} + +rconcli "$*" diff --git a/scripts/wrappers/restart.sh b/scripts/wrappers/restart.sh new file mode 100644 index 0000000..8711a34 --- /dev/null +++ b/scripts/wrappers/restart.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# In /wrappers folder because it uses functions from includes/server.sh +# and it's not a standalone script + + +# shellcheck disable=SC1091 +source "${PWD}"/includes/server.sh + +if [ $# -gt 1 ]; then + exit +elif [ $# -eq 0 ]; then + echo_warning ">>> Restarting server in 1 minute..." + restart_server 1 &> /dev/null & +else + if ! [[ $1 =~ ^[0-9]+$ ]]; then + echo_warning ">>> Invalid argument: ${1}" + exit + fi + + if [ "${1}" -eq 0 ]; then + echo_warning ">>> Restarting server now..." + restart_server 0 &> /dev/null & + exit + else + echo_warning ">>> Restarting server in ${1} minute(s)..." + restart_server "${1}" &> /dev/null & + fi +fi