Debian – Minecraft server startup/shutdown with systemd

debianminecraftsystemd

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?

Best Answer

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.