Skip to content

Instantly share code, notes, and snippets.

@carchrae
Last active July 30, 2023 15:25
Show Gist options
  • Save carchrae/3dc0da023c8e9bb36441dcd439950870 to your computer and use it in GitHub Desktop.
Save carchrae/3dc0da023c8e9bb36441dcd439950870 to your computer and use it in GitHub Desktop.
stop (pause) inactive minecraft server process
#!/bin/bash
# /home/minecraft/bin/minecraft-sleep.sh
#set -v
cd "$(dirname "$0")"
# service name (i'm using systemd)
SERVICE_NAME="$1"
# set this to the port your minecraft server is on
#PORT=25565
PORT=`grep server-port ../$SERVICE_NAME/server.properties | cut -d '=' -f2`
# delay before starting
START_DELAY=600
# how frequently do we check for connections (seconds)
CHECK_DELAY=600
# script will start the server briefly duing each cycle
POLL_CYCLE=20
# how long to resume server during poll cycle
POLL_RUN=1
# delay in checking for connections during poll
POLL_DELAY=1
# maybe you want to customise this, eg if not using systemd
function find_pid(){
echo $(ps -o 'pid,ppid,args,unit' `pgrep java` | grep $SERVICE_NAME | xargs | cut -d ' ' -f1)
}
function pause(){
# this stops the process (like ctrl-z)
PID=$(find_pid)
if [ -n "$PID" ]; then
kill -STOP $PID
else
echo "could not find PID"
fi
}
function resume(){
PID=$(find_pid)
if [ -n "$PID" ]; then
kill -CONT $PID
else
echo "could not find PID"
fi
}
resume
sleep $START_DELAY
while true; do
# wait a bit before looping again
sleep 120
# some random so processes don't wake up all at the same time
sleep $[ ( $RANDOM % 10 ) + 1 ]s
echo "checking for activity on $PORT"
# while ( netstat -tn | grep $PORT | grep ESTABLISHED ); do
while ( netstat -tn | grep $PORT ); do
#echo "still active..."
sleep $CHECK_DELAY
done
echo "no active connections on $PORT, so pausing process $(find_pid)"
pause
echo "waiting for activity on $PORT, will run server for $POLL_RUN seconds every $POLL_CYCLE seconds."
ACTIVE=""
while [ -z "$ACTIVE" ]; do
#echo "Resume process $(find_pid) for $POLL_RUN seconds to prevent crashes/timeouts"
resume
sleep $POLL_RUN
pause
#echo "waiting for activity on $PORT"
POLL_UNTIL=$((SECONDS+POLL_CYCLE))
while [ $SECONDS -lt $POLL_UNTIL ]; do
ACTIVE=`netstat -tn | grep $PORT`
#echo "ACTIVE=$ACTIVE"
if [ -n "$ACTIVE" ]; then
break
else
sleep $POLL_DELAY
fi
done
done;
echo "Resuming process $(find_pid) due to activty $ACTIVE"
resume
done
#!/bin/bash
# /home/minecraft/bin/minecraft-wake.sh
## this just pokes the port of a minecraft instance, making it wake up. equivalent to `resume` in minecraft-sleep.sh
## you might want to use this if you have some scripts that run, eg backups, that need the server responding
## as if a user was logged in
cd "$(dirname "$0")"
#set -v
# service name (i'm using systemd)
SERVICE_NAME="$1"
# set this to the port your minecraft server is on
#PORT=25565
PORT=`grep server-port ../$SERVICE_NAME/server.properties | cut -d '=' -f2`
echo "boop" | nc -q 3 localhost $PORT
# /etc/systemd/system/[email protected]
# this is a systemd service, use start and/or enable (over reboots)
# presumes that minecraft runs as a restricted user 'minecraft'
[Unit]
Description=Minecraft Server: %i
After=network.target
[Service]
WorkingDirectory=/home/minecraft/%i
User=minecraft
Group=minecraft
Restart=always
ExecStartPre=/home/minecraft/bin/start-minecraft-sleep.sh %i
ExecStart=/usr/bin/screen -DmS mc-%i /home/minecraft/%i/start.sh
ExecStop=/home/minecraft/bin/stop-minecraft-sleep.sh %i || true
ExecStop=/bin/sleep 3
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say SERVER SHUTTING DOWN IN 15 SECONDS..."\015' || true
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say SERVER SHUTTING DOWN IN 10 SECONDS..."\015' || true
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say SERVER SHUTTING DOWN IN 5 SECONDS..."\015' || true
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "save-all"\015' || true
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "stop"\015' || true
[Install]
WantedBy=multi-user.target
#!/bin/bash
# /home/minecraft/bin/start-minecraft-sleep.sh
cd "$(dirname "$0")"
# the gawk is to add timestamps
./minecraft-sleep.sh $1 | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }' >> ../$1/logs/sleep.log &
#!/bin/bash
# /home/minecraft/bin/stop-minecraft-sleep.sh
cd "$(dirname "$0")"
#set -v
SERVICE_NAME=$1
# maybe you want to customise this, eg if not using systemd
function find_pid(){
echo $(ps -o 'pid,ppid,args,unit' `pgrep java` | grep $SERVICE_NAME | cut -d ' ' -f1)
}
function resume(){
PID=$(find_pid)
if [ -n "$PID" ]; then
kill -CONT $PID
else
echo "could not find PID"
fi
}
echo "stopping minecraft-sleep.sh"
pkill -f "bin/minecraft-sleep.sh $1" || true
sleep 1
echo "resuming process $(find_pid)"
resume
@KevinKurtz
Copy link

I like this approach better than installing knockd, I've modified it for my needs (I changed the times and whatnot so it doesn't run so long and checks less often, etc).

Might I suggest you incorporate one of my changes? There is a flaw that if the server isn't running (crashes? is shut down by terminal?) that this script just keeps running and checking for activity (and trying to resume the process every POLL_CYCLE) forever. We should exit the script if the process isn't running anymore.

in your resume function Instead of:
kill -CONT $PID

Use:
kill -CONT $PID || exit 1

If the process doesn't exist on the next resume cycle we shutdown.

@KevinKurtz
Copy link

KevinKurtz commented Jun 19, 2020

Also, unless I'm doing something wrong, I'm failing to get the PID when it is only 3 or 4 digits instead of 5. In my console there's a space in front of it in those cases so the f1 field grabs a space after the cut. I've temporary worked around this by changing the getPID function to echo $(pidof java) since I don't have any other java running. Otherwise, great script!

I've forked it so people can see an example of my comments.

@carchrae
Copy link
Author

glad it was useful @KevinKurtz!

on the PID thing, i just updated it with a fix. you can certainly rewrite the getPID function though - which is kind of why i wrapped it. i'm using systemd so i search for the service name (aka unit) rather than the process. but absolutely, pgrep or pidof, or even ps without so many options.

@carchrae
Copy link
Author

Might I suggest you incorporate one of my changes? There is a flaw that if the server isn't running (crashes? is shut down by terminal?) that this script just keeps running and checking for activity (and trying to resume the process every POLL_CYCLE) forever. We should exit the script if the process isn't running anymore.

it is intentional that the script will keep running as i'm managing this with systemd. systemd will restart minecraft if it exits, and if i stop the service, then the service will kill the sleep script. for the same reason, i don't presume the PID stays the same either.

i've added the full set of scripts (at this point, i guess this should be a repo) to better describe my context, but there is no reason you need to use systemd, init.d, monit, etc, to ensure the server is running.

@KevinKurtz
Copy link

it is intentional that the script will keep running as i'm managing this with systemd. systemd will restart minecraft if it exits, and if i stop the service, then the service will kill the sleep script. for the same reason, i don't presume the PID stays the same either.

That makes sense. I just incorporated your code as part of my run.sh script when my VPS boots and while looking through the logic I noticed there was no handling of the situation that there was no server actually available. If Minecraft dies on mine for some reason I wanted to script to stop polling. Thanks again! I've got it doing what I need for now. This was a huge help! I was going to write something much less elegant.

@carchrae
Copy link
Author

glad it was of help. and i'm not sure this is my most elegant code - but is bash ever elegant!? :D

and indeed, adapt to fit. that is what i did based on michiel's code. i just had to generalise it a bit, since i am running three servers on the same box, and knockd seemed like overkill.

as an aside, i looked at this briefly too; https://github.com/jkutner/heroku-buildpack-minecraft - which is pretty clever (and free, but low RAM), but convoluted in how it exposes the port using ngrok. in the end, i hosted the minecraft servers on a pc at home, since 512mb isn't enough for a minecraft server unless you run an old version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment