8

I've been running a Minecraft server with a sysV init script. It is a very good script; it runs Minecraft in a "screen"; it can ensure Minecraft is never started twice; and it waits for Minecraft to shut down when stopped. It can even pass a command to Minecraft with /etc/init.d/minecraft command <command> -- this is useful for scheduled backups.

Now I've upgraded to Debian Jessie, which has systemd. But for now, I keep my old-style script because it's awesome. Still, I'm actually very supportive of systemd - it really looks like a lot of improvement, simplification and centralization. I remember systemd developers promising that "old sysV scripts will just work as they did before", but turns out it's not so easy!

I remember having problems with some startup scripts before; apparently, just putting a script into /etc/init.d and marking it executable is no longer enough - I had to "enable" them in order to make them work. "Well," I thought, "now it's recognized by systemd, and now I can control it via systemctl - and it should probably just use my old script to process the commands!" And turns out I was very much wrong.

It doesn't start properly, it doesn't stop properly, it does not display status properly, not to mention the absence of the "command" command. I've started to look for information about how systemd is better than sysV, and what can I do to simplify and empower everything. Apparently, systemctl just makes the simplest unit file possible on its own, and hopes it will suffice! Now I'm wondering if systemd is actually incapable of handling such complex situations at all!

I see that an average systemd service basically consists of some requirements and ExecStart. Like it's all systemd needs to monitor the daemon. Type in the conditions and the executable name, and systemd will handle its starting, stopping and who knows what else. But it's not that easy!! You can't just kill Minecraft's PID (not to mention it's different from the screen's PID)! I want to write more complex scripts for every action, maybe even add new actions like "command" (okay, I've already accepted that it's probably not possible at all). For "status", it has to monitor the Java process, and for stop, it has to send a command to the Minecraft console and then wait for both Java and screen to die! I also want to be sure that systemd will not just try to SIGHUP or SIGINT or SIGTERM it!

So, what is the slick, modern, "intended systemd way" to do it that really allows us to utilize all the "improvements" and "simplification" systemd gives us? Surely it should be able to handle something more complex than a simple one-process daemon started in a single line and killed with a SIGINT? Should I maybe create a systemd unit and manually specify calling my old script there in every command, like this:

ExecStart=/etc/init.d/minecraft start
ExecReload=/etc/init.d/minecraft reload
(and how do I make the "stop" command and explain how to find the processes to watch for the "status" command?..)

I am very pro-innovation, pro-Poettering and pro-systemd in this regard, and I believe there should be a way to do it better than it was before - maybe in an entirely different way, as it usually is with Poettering (which I like about him!). But this doesn't look like much of an improvement - more like a huge regression in need of a huge heap of kludges to even continue as it was before. "sysV scripts will continue working", my ponytail! I can't even be sure if it calls my script to stop Minecraft properly on system shutdown, or just looks at "systemctl status" and sees that it's already "inactive (dead)".

Any better ideas?

1

4 Answers 4

12

After browsing through the manpages few more times (yeah, the answer is never there the first time...), I've come up with a solution... which did not work. After browsing even more, I've finally come up with the most elegant solution possible.

[Unit]
Description=Minecraft server
After=local-fs.target network.target

[Service]
WorkingDirectory=/home/minecraft/minecraft_server
User=minecraft
Group=minecraft
Type=forking
# Run it as a non-root user in a specific directory

ExecStart=/usr/bin/screen -h 1024 -dmS minecraft ./minecraft_server.sh
# I like to keep my commandline to launch it in a separate file
# because sometimes I want to change it or launch it manually
# If it's in the WorkingDirectory, then we can use a relative path

# Send "stop" to the Minecraft server console
ExecStop=/usr/bin/screen -p 0 -S minecraft -X eval 'stuff \"stop\"\015'
# Wait for the PID to die - otherwise it's killed after this command finishes!
ExecStop=/bin/bash -c "while ps -p $MAINPID > /dev/null; do /bin/sleep 1; done"
# Note that absolute paths for all executables are required!

[Install]
WantedBy=multi-user.target

This really does look better than my original script! However, there are a few regressions.

  • If I want to pass commands to the server console, then I'll have to make a separate script to do it.
  • After running systemctl start minecraft or systemctl stop minecraft, be sure to check systemctl status minecraft, because those commands give no output at all, even if they actually fail. This is the only major regression compared to scripts - "always examine your output" is #1 rule in IT, but systemd doesn't seem to care about it...
  • Also, I expected systemd to be able to manage the service shutdown without the "wait for the PID to die" workaround. In the old init script, I had to do this manually, because it was a script, and systemd is trying to eliminate the need for complex scripts that to the same things; it eliminates the need to manually script in all the timeouts and killing whoever did not die, but "waiting for the pid to die" is the next most common thing, and we still need to script it.
2
  • thank you! I found this invaluable in writing my own. one addition I would love to see is a server message informing users the server is going down.
    – Mr Griever
    Feb 24, 2020 at 15:05
  • If your server is already going down, then sending a message will do no good - players will not be able to see it. :) You could add a delay after sending a message before shutdown, but that would increase the shutdown time, and probably not do much good either. They will understand that the server is shut down afterwards anyway. Still, if you want to send any kind of message, use the same line that sends the "stop" message. Feb 25, 2020 at 18:58
10

tl;dr: Don't use screen at all. Use RCON to control the server.

I realize that screen has been the de facto standard for managing Minecraft servers since forever, but after trying it both ways I've concluded that RCON solves the control problem in a much cleaner way.

The reason to run the server in a screen session is so that you can send commands to the server by stuffing them into its standard input using the screen session. You can also attach directly to the screen session to use the server's interactive console. These are important features, but screen isn't the only way to implement them.

Minecraft supports the RCON protocol for remote administration. You can use it locally to interact with the server. I use the mcrcon command line tool, which works either to send single commands or as an interactive terminal. This is both more powerful than screen and arguably less hacky (sending commands via eval and stuff always made me wince).

My minecraft.service file looks like this:

Description=Minecraft Server
After=network.target

[Service]
Type=simple
User=minecraft
Group=minecraft
WorkingDirectory=/srv/minecraft
ExecStart=/usr/local/bin/minecraft/start
ExecStop=/usr/local/bin/minecraft/stop
Restart=always

[Install]
WantedBy=default.target

The start script simply runs the server executable:

#!/bin/sh

cd /srv/minecraft
java -Xmx12G -Xms12G \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+UseG1GC \
  -XX:G1NewSizePercent=50 \
  -XX:MaxGCPauseMillis=50 \
  -XX:+AlwaysPreTouch \
  -jar server.jar nogui

The stop script sends a command to the server and waits for the process to stop:

#!/bin/sh

/usr/local/bin/minecraft/rcon stop

while kill -0 $MAINPID 2>/dev/null
do
  sleep 0.5
done

The rcon script is simply a shorthand:

#!/bin/sh

mcrcon -H localhost -P 25575 -p "password omitted" $@

My script hardcodes the RCON port and password, but you could easily get them from the server.properties file. Also, mcrcon will accept them via environment variables rather than command line parameters if you prefer.

Other notes:

  • You do have to enable RCON in your server.properties file, but you can leave the port closed in your firewall to prevent it from being used remotely.
  • To start an interactive terminal session, simply run rcon with no arguments (or call mcrcon directly).
  • To see the server logs, use journalctl. I use this to send all of my logs to a Discord webhook.

EDIT: A comment in another answer claims that a SIGINT will cause the Minecraft server to gracefully stop, just like sending a stop command. If so, then it's simpler and thus arguably better to just send a SIGINT rather than using RCON to stop the server. RCON remains useful for other administration tasks.

2
  • This is a fantastic solution. I had the same cringe moment when I saw all the eval stuff. I stole most of this for my paper MC server. Thanks!
    – brownmike
    Sep 25, 2021 at 9:03
  • Its been 3 years, but may be still very valid. im thinking about switching from using screen to either tmux or rcon as your answer describes, is rcon the way most hosting parties implement webdashboard control over minecraft servers? or does this vary alott between the screen, tmux and rcon methods?
    – CodeAsm
    Nov 5 at 19:57
3

I use this this service file:

[Unit]
Description=Minecraft server
Wants=network.target
After=network.target

[Service]
User=minecraft
Group=minecraft
Nice=5

WorkingDirectory=/home/minecraft/.minecraft/
KillMode=process
KillSignal=SIGINT
SuccessExitStatus=130

ExecStart=/usr/bin/java -Xms1G -Xmx1G -jar /home/minecraft/.minecraft/minecraft_server.jar nogui

[Install]
WantedBy=multi-user.target

I use SIGINT (ctrl+c) for stopping it:

KillSignal=SIGINT

and SuccessExitStatus for successful termination:

SuccessExitStatus=130
3
  • Stopping Minecraft server with Ctrl+C or SIGINT will NOT shut it down gracefully. The only way to do it properly is to send it a "stop" command. You can try it - see the difference between "stop" and Ctrl+C when running it manually. This script intercepts SIGTERM and SIGINT and sends "stop" instead for systemd-based systems (they like to simply terminate stuff): gist.github.com/sbstp/9b4e3bfa36547da782cb428d61a062eb<br> Jan 27, 2018 at 6:52
  • As much as I hate to be contrary @darkpenguin, from watching the logs, doing a Ctrl+C, or pkill java, seem to have identical effect as the "stop" command. All the "Stopping server", "Saving players", "Saving worlds", the three "Saving chunks" and six "All chunks are saved" messages appear normally. Can you give a link to something that shows that there's inadequate cleanup in those SIGTERM scenarios? I mean, in a full linux OS environment; not WSL Apr 8, 2021 at 3:54
  • 1
    I've tried again (on 1.12-Forge). SIGINT and SIGTERM seem to work the same. But "Ctrl+C" terminated my wrapper script instantly, while failing to stop Minecraft. I think this might have been what had me confused - and also lack of mention of signal handling in the documentation other than "To stop the server properly, use the 'stop' command". However, it seems like this was not always the case: hence github.com/itzg/docker-minecraft-server/issues/159 and curseforge.com/minecraft/mc-mods/signal-catcher and later also bugs.mojang.com/browse/BDS-437 for Bedrock. Apr 9, 2021 at 16:55
0

Here's one for tmux based on the answer of @darkpenguin:

[Unit]
After=network.target

[Service]
WorkingDirectory=/home/minecraft/server/
Type=forking
User=minecraft
ExecStart=/usr/bin/tmux new-session -s mine1 -n m -d "/home/minecraft/start_minecraft.sh"

# Send "stop" to the Minecraft server console
ExecStop=/usr/bin/tmux end-keys -t mine1 Enter 'Stop' Enter
# Wait for the PID to die - otherwise it's killed after this command finishes!
ExecStop=/bin/bash -c "while ps -p $MAINPID > /dev/null; do /bin/sleep 1; done"
# Note that absolute paths for all executables are required!

[Install]
WantedBy=default.target

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .