Skip to content

Commit 204db1e

Browse files
authored
Merge pull request #6 from dnwjn/feature/scheduled-shutdown
Add support for scheduled shutdown
2 parents e4ba08e + e095cf7 commit 204db1e

File tree

4 files changed

+60
-30
lines changed

4 files changed

+60
-30
lines changed

Diff for: Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ ENV CONTAINER_NAME=palworld-server \
2525
QUERY_PORT=27015 \
2626
LOOP_SLEEP_SECONDS=30 \
2727
CONNECT_GRACE_SECONDS=60 \
28+
SHUTDOWN_DELAY_SECONDS=0 \
2829
DEBUG=false \
2930
DISCORD_TOKEN="" \
3031
DISCORD_CLIENT_ID="" \

Diff for: README.md

+12-11
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@ See the example [docker-compose.yml file][docker-compose] for an example configu
1414

1515
### Variables
1616

17-
| Name | Description | Default |
18-
|-------------------------|-------------------------------------------------------------------------|-------------------|
19-
| `CONTAINER_NAME` | The name of the server container. | `palworld-server` |
20-
| `GAME_PORT` | The same value as `PORT` in the server container. | `8211` |
21-
| `QUERY_PORT` | The same value as `QUERY_PORT` in the server container. | `27015` |
22-
| `LOOP_SLEEP_SECONDS` | How often to check the server status. | `30` |
23-
| `CONNECT_GRACE_SECONDS` | After starting the server, how long to wait before continuing the loop. | `60` |
24-
| `DISCORD_TOKEN` | The token of your Discord bot. | |
25-
| `DISCORD_CLIENT_ID` | The client ID of your Discord bot. | |
26-
| `DISCORD_GUILD_ID` | The ID of your Discord server. | |
27-
| `DEBUG` | If debug mode should be enabled. | `false` |
17+
| Name | Description | Default |
18+
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------|-------------------|
19+
| `CONTAINER_NAME` | The name of the server container. | `palworld-server` |
20+
| `GAME_PORT` | The same value as `PORT` in the server container. | `8211` |
21+
| `QUERY_PORT` | The same value as `QUERY_PORT` in the server container. | `27015` |
22+
| `LOOP_SLEEP_SECONDS` | How often to check the server status. | `30` |
23+
| `CONNECT_GRACE_SECONDS` | After starting the server, how long to wait before continuing the loop. | `60` |
24+
| `SHUTDOWN_DELAY_SECONDS` | How long to wait before shutting down the server after the last player leaves. The shutdown will be cancelled when a player joins. | `0` (immediately) |
25+
| `DISCORD_TOKEN` | The token of your Discord bot. | |
26+
| `DISCORD_CLIENT_ID` | The client ID of your Discord bot. | |
27+
| `DISCORD_GUILD_ID` | The ID of your Discord server. | |
28+
| `DEBUG` | If debug mode should be enabled. | `false` |
2829

2930
### Setup notes
3031

Diff for: docker-compose.yml.example

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ services:
1616
- QUERY_PORT=27015 # The same value as `QUERY_PORT` in the server container
1717
- LOOP_SLEEP_SECONDS=30 # How often to check the server status
1818
- CONNECT_GRACE_SECONDS=60 # After starting the server, how long to wait before continuing the loop
19+
- SHUTDOWN_DELAY_SECONDS=300 # How long to wait after the last player leaves to shutdown the server
1920
ports:
2021
- 8211:8211/udp # The same value as `GAME_PORT`
2122
# - 27015:27015/udp # Enable to show up in the server list

Diff for: scripts/entrypoint.sh

+46-19
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ TCPDUMP_TIMEOUT=30
99

1010
running=false
1111
skip_sleep=false
12+
shutdown_scheduled_at=false
1213

1314
echo_line() {
1415
echo -e "$1"
@@ -41,7 +42,7 @@ echo_debug() {
4142
}
4243

4344
is_running() {
44-
if [ "$( docker container inspect -f '{{.State.Status}}' ${CONTAINER_NAME} )" = "running" ]; then
45+
if [ "$( docker container inspect -f '{{.State.Status}}' $CONTAINER_NAME )" = "running" ]; then
4546
return 0
4647
else
4748
return 1
@@ -57,11 +58,11 @@ start_discord_handler() {
5758
}
5859

5960
start_tunnel() {
60-
echo_line "Started tunnel on game port ${GAME_PORT} to ${CONTAINER_NAME}."
61+
echo_line "Started tunnel on game port $GAME_PORT to $CONTAINER_NAME."
6162
socat TCP4-LISTEN:$GAME_PORT,fork,reuseaddr TCP4:$CONTAINER_NAME:$GAME_PORT &
6263

6364
if [ -n "$QUERY_PORT" ]; then
64-
echo_line "Started tunnel on query port ${QUERY_PORT} to ${CONTAINER_NAME}."
65+
echo_line "Started tunnel on query port $QUERY_PORT to $CONTAINER_NAME."
6566
socat TCP4-LISTEN:$QUERY_PORT,fork,reuseaddr TCP4:$CONTAINER_NAME:$QUERY_PORT &
6667
fi
6768
}
@@ -74,37 +75,37 @@ check_for_start() {
7475
return
7576
fi
7677

77-
echo_debug "Listening for connection attempts on port ${GAME_PORT} for ${TCPDUMP_TIMEOUT} seconds..."
78+
echo_debug "Listening for connection attempts on port $GAME_PORT for $TCPDUMP_TIMEOUT seconds..."
7879

7980
# Command to trigger locally: nc -vu localhost 8211
8081
tcpdump_output=$(timeout $TCPDUMP_TIMEOUT tcpdump -n -c 1 -i any port $GAME_PORT 2>&1)
8182

8283
if echo "$tcpdump_output" | grep -q "0 packets captured"; then
83-
echo_debug "No traffic logged for ${TCPDUMP_TIMEOUT} seconds."
84+
echo_debug "No traffic logged for $TCPDUMP_TIMEOUT seconds."
8485
skip_sleep=true
8586
return
8687
fi
8788

88-
echo_debug "Connection attempt detected on game port ${GAME_PORT}."
89+
echo_debug "Connection attempt detected on game port $GAME_PORT."
8990

9091
echo_info "***STARTING SERVER***"
91-
docker start "${CONTAINER_NAME}" > /dev/null
92+
docker start "$CONTAINER_NAME" > /dev/null
9293

9394
max_attempts=10
9495
attempt=0
95-
until [ $attempt -ge $max_attempts ] || docker inspect --format='{{.State.Health.Status}}' "${CONTAINER_NAME}" 2> /dev/null | grep -q "healthy"; do
96+
until [ $attempt -ge $max_attempts ] || docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2> /dev/null | grep -q "healthy"; do
9697
echo_line "Waiting for the server to be healthy..."
9798
sleep 5
9899
attempt=$(( attempt + 1 ))
99100
done
100101

101102
if [ $attempt -ge $max_attempts ]; then
102-
echo_warning "Server did not become healthy after ${max_attempts} attempts. Please check the server logs."
103+
echo_warning "Server did not become healthy after $max_attempts attempts. Please check the server logs."
103104
else
104105
echo_success "Server is healthy."
105106

106-
echo_line "Allowing users ${CONNECT_GRACE_SECONDS} seconds to connect..."
107-
sleep "${CONNECT_GRACE_SECONDS}"
107+
echo_line "Allowing users $CONNECT_GRACE_SECONDS seconds to connect..."
108+
sleep "$CONNECT_GRACE_SECONDS"
108109

109110
running=true
110111
skip_sleep=true
@@ -115,21 +116,47 @@ check_for_stop() {
115116
echo_debug "Checking for players..."
116117

117118
if is_running; then
118-
players_output=$(docker exec -i "${CONTAINER_NAME}" rcon-cli ShowPlayers)
119+
shutdown_now=false
120+
players_output=$(docker exec -i "$CONTAINER_NAME" rcon-cli ShowPlayers)
119121

120122
if [ "$players_output" = "name,playeruid,steamid" ]; then
121-
echo_line "No players found. Server will be shut down."
122-
echo_info "***STOPPING SERVER***"
123+
now=$(date +%s)
124+
125+
if [ "$shutdown_scheduled_at" != false ]; then
126+
echo_debug "Scheduled: $(date -d "@$shutdown_scheduled_at" +"%Y-%m-%d %H:%M:%S") | Now: $(date -d "@$now" +"%Y-%m-%d %H:%M:%S")"
127+
128+
if [[ $shutdown_scheduled_at -le $now ]]; then
129+
echo_debug "Scheduled shutdown is in the past."
130+
shutdown_now=true
131+
fi
132+
else
133+
if [ "$SHUTDOWN_DELAY_SECONDS" != "0" ]; then
134+
echo_line "No players found. Server will be shut down in $SHUTDOWN_DELAY_SECONDS seconds."
135+
shutdown_scheduled_at=$(($now + $SHUTDOWN_DELAY_SECONDS))
136+
echo_debug "Shutdown scheduled for $(date -d "@$shutdown_scheduled_at" +"%Y-%m-%d %H:%M:%S") (current time: $(date -d "@$now" +"%Y-%m-%d %H:%M:%S"))"
137+
else
138+
echo_line "No players found. Server will be shut down."
139+
shutdown_now=true
140+
fi
141+
fi
142+
elif [ "$shutdown_scheduled_at" != false ]; then
143+
echo_debug "Scheduled shutdown interrupted because server is not empty."
144+
shutdown_scheduled_at=false
145+
fi
123146

124-
docker stop "${CONTAINER_NAME}" > /dev/null
147+
if [ "$shutdown_now" = true ]; then
148+
echo_info "***STOPPING SERVER***"
149+
docker stop "$CONTAINER_NAME" > /dev/null
125150

126151
running=false
127152
skip_sleep=true
153+
shutdown_scheduled_at=false
128154
fi
129155
else
130156
echo_debug "Server has already been shut down."
131157
running=false
132158
skip_sleep=true
159+
shutdown_scheduled_at=false
133160
fi
134161
}
135162

@@ -145,8 +172,8 @@ run() {
145172

146173
if is_running; then
147174
echo_line "Server is already running."
148-
echo_line "Allowing users ${CONNECT_GRACE_SECONDS} seconds to connect..."
149-
sleep "${CONNECT_GRACE_SECONDS}"
175+
echo_line "Allowing users $CONNECT_GRACE_SECONDS seconds to connect..."
176+
sleep "$CONNECT_GRACE_SECONDS"
150177
running=true
151178
else
152179
echo_line "Server is not running."
@@ -163,8 +190,8 @@ run() {
163190
if [ "$skip_sleep" = true ]; then
164191
skip_sleep=false
165192
else
166-
echo_debug "Sleeping for ${LOOP_SLEEP_SECONDS} seconds..."
167-
sleep "${LOOP_SLEEP_SECONDS}"
193+
echo_debug "Sleeping for $LOOP_SLEEP_SECONDS seconds..."
194+
sleep "$LOOP_SLEEP_SECONDS"
168195
fi
169196
done
170197
}

0 commit comments

Comments
 (0)