Skip to content

Commit

Permalink
add Auto Pause feature
Browse files Browse the repository at this point in the history
  • Loading branch information
MusclePr committed May 16, 2024
1 parent d1e3bb1 commit e100a10
Show file tree
Hide file tree
Showing 16 changed files with 1,001 additions and 92 deletions.
23 changes: 22 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
jo=1.9-1 \
jq=1.6-2.1 \
netcat-traditional=1.10-47 \
iputils-ping libpcap0.8 \
mitmproxy \
iproute2 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

Expand All @@ -71,6 +74,13 @@ RUN case ${TARGETARCH} in \
&& chmod +x supercronic \
&& mv supercronic /usr/local/bin/supercronic

# install patched knockd (as same as https://github.com/itzg/docker-minecraft-server/blob/master/build/ubuntu/install-packages.sh)
RUN wget --progress=dot:giga https://github.com/Metalcape/knock/releases/download/0.8.1/knock-0.8.1-${TARGETARCH}.tar.gz -O /tmp/knock.tar.gz && \
tar -xf /tmp/knock.tar.gz -C /usr/local/ && rm /tmp/knock.tar.gz && \
ln -s /usr/local/sbin/knockd /usr/sbin/knockd && \
setcap cap_net_raw=ep /usr/local/sbin/knockd && \
find /usr/lib -name 'libpcap.so.0.8' -execdir cp '{}' libpcap.so.1 \;

# hadolint ignore=DL3044
ENV HOME=/home/steam \
PORT= \
Expand Down Expand Up @@ -102,6 +112,9 @@ ENV HOME=/home/steam \
AUTO_REBOOT_WARN_MINUTES=5 \
AUTO_REBOOT_EVEN_IF_PLAYERS_ONLINE=false \
AUTO_REBOOT_CRON_EXPRESSION="0 0 * * *" \
AUTO_PAUSE_ENABLED=false \
AUTO_PAUSE_TIMEOUT_EST=180 \
AUTO_PAUSE_KNOCK_INTERFACE=eth0 \
DISCORD_SUPPRESS_NOTIFICATIONS= \
DISCORD_WEBHOOK_URL= \
DISCORD_CONNECT_TIMEOUT=30 \
Expand Down Expand Up @@ -156,7 +169,15 @@ RUN chmod +x /home/steam/server/*.sh && \
mv /home/steam/server/backup.sh /usr/local/bin/backup && \
mv /home/steam/server/update.sh /usr/local/bin/update && \
mv /home/steam/server/restore.sh /usr/local/bin/restore && \
ln -sf /home/steam/server/rest_api.sh /usr/local/bin/rest-cli
ln -sf /home/steam/server/rest_api.sh /usr/local/bin/rest-cli && \
ln -sf /home/steam/server/rcon.sh /usr/local/bin/rcon-cli && \
ln -sf /home/steam/server/is_safe_timing.sh /usr/local/bin/is_safe_timing && \
ln -sf /home/steam/server/autopause.sh /usr/local/bin/autopause && \
ln -sf /home/steam/server/autopaused-ctl.sh /usr/local/sbin/autopaused-ctl

# install mitmproxy addons
RUN mkdir -p /home/steam/autopause/addons && \
mv /home/steam/server/PalIntercept.py ../autopause/addons/

WORKDIR /home/steam/server

Expand Down
78 changes: 73 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ It is highly recommended you set the following environment values before startin
| AUTO_REBOOT_ENABLED | Enables automatic reboots | false | true/false | 0.21.0 |
| AUTO_REBOOT_WARN_MINUTES | How long to wait to reboot the server, after the player were informed. | 5 | Integer | 0.21.0 |
| AUTO_REBOOT_EVEN_IF_PLAYERS_ONLINE | Restart the Server even if there are players online. | false | true/false | 0.21.0 |
| AUTO_PAUSE_ENABLED | Enables automatic pause (with ENABLE_PLAYER_LOGGING=true required.) | false | true/false | 0.36.0 |
| AUTO_PAUSE_TIMEOUT_EST | default 180 (seconds) describes the time between the last client disconnect and the pausing of the process (read as timeout established) | 180 | Integer | 0.36.0 |
| AUTO_PAUSE_KNOCK_INTERFACE | The docker default is eth0. Select another NIC if necessary. | eth0 | "string" | 0.36.0 |
| AUTO_PAUSE_LOG | Enable auto-pause logging | true | true/false | 0.36.0 |
| AUTO_PAUSE_DEBUG | Enable auto-pause debug logging | false | true/false | 0.36.0 |
| TARGET_MANIFEST_ID | Locks game version to corespond with Manifest ID from Steam Download Depot. | | See [Manifest ID Table](#locking-specific-game-version) | 0.27.0 |
| DISCORD_WEBHOOK_URL | Discord webhook url found after creating a webhook on a discord server. | | `https://discord.com/api/webhooks/<webhook_id>` | 0.22.0 |
| DISCORD_SUPPRESS_NOTIFICATIONS | Enables/Disables `@silent` messages for the server messages. | false | boolean | 0.34.0 |
Expand Down Expand Up @@ -292,11 +297,12 @@ It is highly recommended you set the following environment values before startin

### Game Ports

| Port | Info |
|-------|------------------|
| 8211 | Game Port (UDP) |
| 27015 | Query Port (UDP) |
| 25575 | RCON Port (TCP) |
| Port | Info |
|-------|---------------------|
| 8211 | Game Port (UDP) |
| 27015 | Query Port (UDP) |
| 25575 | RCON Port (TCP) |
| 8212 | REST API Port (TCP) |

## Using RCON

Expand Down Expand Up @@ -529,6 +535,68 @@ AUTO_REBOOT_CRON_EXPRESSION is a cron expression, in a Cron-Expression you defin
Set AUTO_REBOOT_CRON_EXPRESSION to change the set the schedule, default is everynight at midnight according to the
timezone set with TZ

## Configuring Automatic Pause

An auto-pause functionality is provided that monitors whether clients are connected to the server.

If a client is not connected for a specified time, the PalServer process will try to put into a sleep state to save power.

The auto-pause service will retry several times to avoid the timing of writing save data.

When a client attempts to connect while the process is paused, then process will be restored to a running state.

The experience for the client does not change.

In the paused state, world time is stopped.

This feature can be enabled by setting the environment variable `AUTO_PAUSE_ENABLED` to "true".

A starting, example compose file has been provided in `examples/docker-compose-autopause.yml`.

| Variable | Info | Default Values | Allowed Values |
|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|----------------|
| AUTO_PAUSE_ENABLED | Enables automatic pause (Puts the server to sleep to save power when there are no online players) | false | true/false |
| AUTO_PAUSE_TIMEOUT_EST | default 180 (seconds) describes the time between the last client disconnect and the pausing of the process (read as timeout established) | 180 | Integer |
| AUTO_PAUSE_KNOCK_INTERFACE | If the default interface doesn't work, check the interface using the "ip a" command inside the container. Using the loopback interface (lo) may not produce the desired results. | eth0 | "string" |
| AUTO_PAUSE_LOG | Enable auto-pause logging | true | true/false |
| AUTO_PAUSE_DEBUG | Enable auto-pause debug logging | false | true/false |

### Resume manually

A file called `.paused` is created in `/palworld` directory when the server is paused and removed when the server is resumed.

Other services may check for this file's existence before waking the server.

Alternatively, resume with the following command:

```shell
docker exec -it palworld-server autopause resume
```

### Service control manually

A `.skip-pause` file can be created in the `/palworld` directory to make the server skip autopausing,
for as long as the file is present.

Alternatively, you can control with the following command:

```shell
docker exec -it palworld-server autopause stop
docker exec -it palworld-server autopause continue
```

This `autopause stop` command is also used during automatic restarts, automatic updates, and container stops.
It is also used to shutdown command via REST API/RCON.

### With Community Server

If the environment variable `COMMUNITY` is true, A proxy server is started within the container to
maintain registration on the community server list.

The proxy server captures communication with `api.palworldgames.com`.

The auto-pause service will replay captured data in the paused state.

## Editing Server Settings

### With Environment Variables
Expand Down
18 changes: 18 additions & 0 deletions examples/docker-compose-autopause.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
services:
palworld:
image: thijsvanloef/palworld-server-docker:latest
restart: unless-stopped
container_name: palworld-server
stop_grace_period: 30s
ports:
- 8211:8211/udp
environment:
SERVER_NAME: "palworld-server-docker by Thijs van Loef"
SERVER_DESCRIPTION: "palworld-server-docker by Thijs van Loef"
SERVER_PASSWORD: "worldofpals"
ADMIN_PASSWORD: "adminPasswordHere"
AUTO_PAUSE_ENABLED: true
AUTO_PAUSE_TIMEOUT_EST: 180 # Time to pause when absent. (sec)
volumes:
- ./palworld:/palworld/
33 changes: 33 additions & 0 deletions scripts/PalIntercept.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
This script intercepts the communication content with "api.palworldgame.com".
"""
import os
import stat
import logging

from mitmproxy import http
from mitmproxy.http import Headers

BASEDIR = "/home/steam/autopause"
REGISTER_JSON_PATH = BASEDIR + "/register.json"
UPDATE_JSON_PATH = BASEDIR + "/update.json"

class PalIntercept:
def __init__(self):
st = os.stat(BASEDIR)
self.uid = st.st_uid
self.gid = st.st_gid

def response(self, flow: http.HTTPFlow):
if flow.request.host == "api.palworldgame.com" and flow.request.is_http11 and flow.response.status_code == 200:
if flow.request.path == "/server/register":
with open(REGISTER_JSON_PATH, "wb") as f:
f.write(flow.request.content)
os.chown(REGISTER_JSON_PATH, self.uid, self.gid)
if flow.request.path == "/server/update":
with open(UPDATE_JSON_PATH, "wb") as f:
f.write(flow.request.content)
os.chown(UPDATE_JSON_PATH, self.uid, self.gid)


addons = [PalIntercept()]
29 changes: 29 additions & 0 deletions scripts/autopause.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
SCRIPT_DIR=${SCRIPT_DIR:-$(dirname "$(readlink -fn "${0}")")}
#shellcheck source=scripts/helper_functions.sh
source "${SCRIPT_DIR}/helper_functions.sh"
#shellcheck source=scripts/helper_autopause.sh
source "${SCRIPT_DIR}/helper_autopause.sh"

if ! AutoPauseEx_isEnabled; then
echo "An autopause service has not started yet."
return 1;
fi

case "${1}" in
"resume")
AutoPauseEx_resume "${2}"
;;
"stop"|"skip")
AutoPauseEx_stopService on "${2}"
;;
"continue")
AutoPauseEx_stopService off "${2}"
;;
*)
echo "Usage: $(basename "${0}") <command> [reason]"
echo "command:"
echo " resume ... resume from paused state"
echo " stop ... stop service"
echo " continue ... continue service"
esac
51 changes: 51 additions & 0 deletions scripts/autopaused-ctl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
# Control the port knock daemon.
# Called only on the server side.

basedir="/home/steam/autopause"
config="${basedir}/knockd.cfg"

case "${1}" in
"start")
if [ ! -f "${config}" ]; then
cat - << EOF > "${config}"
[options]
logfile = ${basedir}/knockd.log
interface = ${AUTO_PAUSE_KNOCK_INTERFACE:-eth0}
[resume-by-player]
sequence = ${PORT:-8211}:udp
seq_cooldown = 5
command = autopause resume "LOGIN from %IP%"
[resume-by-rcon]
sequence = ${RCON_PORT:-25575}
seq_timeout = 1
command = autopause resume "RCON from %IP%"
tcpflags = syn
[resume-by-rest]
sequence = ${REST_API_PORT:-8212}
seq_timeout = 1
command = autopause resume "REST_API from %IP%"
tcpflags = syn
EOF
fi
pid=$(pidof knockd)
if [ -n "${pid}" ]; then
echo "Already started knockd ${pid}"
return
fi
knockd -d -c "${config}" -p "${basedir}/knockd.pid"
;;
"stop")
pid=$(pidof knockd)
if [ -z "${pid}" ]; then
echo "Already stopped knockd ${pid}"
return
fi
kill -KILL "${pid}"
;;
*)
echo "Usage: $(basename "${0}") <command>"
echo "command:"
echo " start ... launch knockd"
echo " stop ... kill knockd"
esac
Loading

0 comments on commit e100a10

Please sign in to comment.