In this post, I’ll describe how to use systemd to manage a Minecraft server. A named pipe will allow us to send commands to that server, which we’ll use in order to set up a job that periodically makes backups and uploads them to Google drive. This may sound fancy, but luckily systemd makes this a breeze to set up, once you’ve done a little Googling.

This tutorial does not assume you’ve ever set up a Minecraft server before. You don’t need to be familiar with systemd, but you should know how to use Ubuntu at a basic level. I won’t cover how to set up an Ubuntu server.

Let’s get started!

Setting up a Minecraft-Server-as-a-Service

First, we must create a systemd service that runs a Minecraft server. You don’t want to run that server as root, so create a minecraft user and group:

$ useradd minecraft

Next, install the Java Runtime Environment:

$ sudo apt-get install openjdk-9-jre-headless

Next, let’s create a directory to run the server from. The Minecraft server JAR will go in this directory.

$ mkdir /srv/minecraft/
$ cd /srv/minecraft/
$ curl -o minecraft_server.jar https://s3.amazonaws.com/Minecraft.Download/versions/1.10.2/minecraft_server.1.10.2.jar

At this point, you could start a server by running:

$ java -jar minecraft_server.jar

Instead, we’ll create a service that does almost the same thing, but runs as the minecraft user we just created.

Create /etc/systemd/system/minecraft-server.service:

[Unit]
Description=Minecraft server

[Service]
WorkingDirectory=/srv/minecraft
User=minecraft
Group=minecraft
ExecStart=/srv/minecraft/run-server.sh

[Install]
WantedBy=multi-user.target

As you can probably guess, this will invoke a file named run-server.sh. Let’s create this file in /srv/minecraft/run-server.sh:

#!/bin/bash

tail -f /tmp/minecraft_server_in | java -jar minecraft_server.jar

You’ll need to chmod +x run-server.sh.

Before this script can work, we must create a named pipe at /tmp/minecraft_server_in:

$ mkfifo /tmp/minecraft_server_in

And that’s it, you’ve service-ized Minecraft! You can start the server by running:

$ service minecraft-server start

You can test that the input is working by connecting to your new server, and running:

$ echo "time set day" >> /tmp/minecraft_server_in

Hopefully, dawn’s rosy-red fingers should caress your realm.

Adding a backup job

To take a backup of a Minecraft server, you need to copy the world directory in /srv/minecraft. However, you can’t simply copy that directory at any time; you need to disable “auto-saving” first. Otherwise, you may get a corrupted copy of the data, since you might be reading a file while the server is still writing to it.

Luckily, the Minecraft server has some commands to deal with this:

  • save-off disables auto-saving
  • save-on enables auto-saving
  • save-all saves the server

Our approach will be to tell the server to save-off, then save-all. We’ll then monitor the server’s logs, waiting for the server to output “Saved the world”. Once that happens, we’ll copy the world directory. When we’re finished, we run save-on.

Create a backup script called /srv/minecraft/backup-job.sh:

#!/bin/bash

echo "save-off" >> /tmp/minecraft_server_in
echo "save-all" >> /tmp/minecraft_server_in
# See comment below
echo "seed" >> /tmp/minecraft_server_in

# `journalctl -f` produces an infinite stream of output from the Minecraft server.
# `sed` will quit once one of those lines matches "Saved the world". Once `sed`
# dies, `journalctl` will die the next time it logs anything -- it will receive a
# SIGPIPE. That's why we send "seed" to the server -- it will generate additional
# logs that will cause `journalctl` to die. The `stdbuf -oL` is to prevent
# buffering from delaying the SIGPIPE.
stdbuf -oL journalctl --since "3 seconds ago" -f -u minecraft-server.service | sed '/\[Server thread\/INFO\]: Saved the world/q'

# If there's a particular directory you want to put this in, look into the
# --parent option.
gdrive upload backup.zip --name "backup-$(date).zip"
rm backup.zip

echo "save-on" >> /tmp/minecraft_server_in

Run chmod +x on this file. Before this script can work, you need to set up gdrive. Do so by running:

# This assumes you're using Ubuntu 64-bit. If not, you can use another download
# link from https://github.com/prasmussen/gdrive
$ curl -o /usr/local/bin/gdrive -L https://docs.google.com/uc?id=0B3X9GlR6EmbnQ0FtZmJJUXEyRTA\&export=download

As the minecraft user (run su minecraft), run gdrive list and it’ll help you with the Google API key shenanigans.

You’ll also need to add minecraft to two groups in order to silence a warning:

$ usermod -G adm -a minecraft
$ usermod -G systemd-journal -a minecraft
$ id minecraft
uid=1000(minecraft) gid=1000(minecraft) groups=1000(minecraft),4(adm),101(systemd-journal)

At this point, you should be able to run backup-job.sh successfully. Once it finishes executing, you should be able to find a file called “backup-Some Date Here.zip”.

Next, we’ll need to create a systemd service and a systemd timer. The timer will invoke the service periodically, and our new service will run backup-job.sh.

Create the service in /etc/systemd/system/minecraft-backup.service:

[Unit]
Description=Minecraft Backup

[Service]
Type=oneshot
User=minecraft
Group=minecraft
WorkingDirectory=/srv/minecraft
ExecStart=/srv/minecraft/backup-job.sh

Enable this service with:

$ systemctl enable minecraft-backup.service

You can verify the service works by running:

$ service minecraft-backup start

A backup should appear in your Google Drive.

Next, create the timer in /etc/systemd/system/minecraft-backup.timer:

[Unit]
Description=Run minecraft-backup.service daily

[Timer]
OnCalendar=*-*-* 12:00:00

[Install]
WantedBy=multi-user.target

This timer will execute minecraft-backup.service every day at 12PM. My server’s clock is on UTC; 12PM UTC is early in the morning in California, where I live.

Kick off your timer by running:

$ systemctl enable minecraft-backup.timer
$ systemctl start minecraft-backup.timer

You can check on your timer with systemctl list-timers.

And that’s it, you now have automatic backups! Now go out and have fun.

If you notice any errors or shortcomings in this post, please let me know! You can reach me by emailing: ulysse@ulysse.io .