Automagic Minecraft Server Backups With Map Renderings Using GitLab CI and GitLab Pages
I recently set up a Minecraft server again, I usually do this by simply running the downloaded .jar
file in a screen session.
This time though, I did it a little bit better. In this post I’ll describe what I consider to be the perfect setup.
Systemd
I started by creating a systemd configuration for the server. Since I was using screen, I also found a way to extract the screen PID from inside the launch script and write it to a file so systemd can read it. The snippet for this is as follows:
screen -ls | grep .minecraft | grep -oP "([0-9]*(?=.minecraft))" > minecraft.pid
This will fetch all screens called minecraft
(which should be only one) and writes its PID to the file minecraft.pid
in the current directory.
The configuration file for systemd is pretty small and IMHO self explaining. The only things that need to be changed are the path where the server is installed and the user it shall run as (never run your services as root if possible).
[Unit]
Description=Minecraft Server
After=syslog.target network.target
Conflicts=minecraft-server.service
[Service]
Type=forking
User=mc-server
Group=mc-server
PIDFile=/home/mc-server/minecraft/minecraft.pid
ExecStart=/home/mc-server/minecraft/start-server.sh
ExecReload=/home/mc-server/minecraft/backup-server.sh
ExecStop=/home/mc-server/minecraft/stop-server.sh
[Install]
WantedBy=default.target
Start script
To start the server, I wrote a small bash script that resides inside the main minecraft folder. It basically kills leftover screen sessions, starts a new one and then starts the server inside it. After that it fetches the PID of the screen using the snippet above and prints out the PID file’s content (for debugging reasons). You also need to update the paths here.
#!/bin/bash
cd /home/mc-server/minecraft
echo "cleaning leftover screens..."
screen -wipe
echo "starting minecraft screen..."
screen -dmS minecraft bash
screen -S minecraft -X stuff 'java -Xmx8G -Xms2G -jar server.jar nogui'$'\n'
echo "fetching pid..."
screen -ls | grep .minecraft | grep -oP "([0-9]*(?=.minecraft))" > minecraft.pid
cat minecraft.pid
Stop script
The stop script will notify anyone currently playing on the server using the say
command and stops the server afterwards.
Please note that it might be unsafe just to kill the screen, therefore the script sends the actual stop
command to the server to shut it down.
You also need to update the paths here.
#!/bin/bash
set -euo pipefail
cd /home/mc-server/minecraft
screen -S minecraft -rX stuff 'say Server stopped by script! Stopping in 5...'$'\n'
sleep 5
screen -S minecraft -rX stuff 'stop'$'\n'
exit 0
Backups
Since I am a git junkie, I also backup the minecraft server using git (that way I could easily roll back changes made by griefers or similar).
I decided to link this script to the reload
command so I can call systemctl reload minecraft.service
from a cronjob to do automatic backups.
The backup script will stop the server, commit all changes made, sync with the server using git pull --rebase
(this allows for “updates” to the minecraft server itself by using the GitLab WebIDE), push the new commit and start the server again.
Note that you also need to update the paths and the commit author here.
#!/bin/bash
set -euo pipefail
cd /home/mc-server/minecraft
screen -S minecraft -X stuff 'say Backup starting...'$'\n'
screen -S minecraft -rX stuff 'stop'$'\n'
sleep 2
git add --all
git commit -m "scripted backup" --author="Minecraft Backup <minecraft@example.com>"
git pull --rebase
git push -u origin master
echo "restarting service..."
screen -S minecraft -X stuff 'java -Xmx8G -Xms2G -jar server.jar nogui'$'\n'
Maps
What is a cool minecraft server without a Google Maps-like website that shows the available worlds?
You may already have heard of overviewer. It’s an application that renders the map described above. Unfortunately the setup described in their documentation was a bit too annoying for me and also didn’t fit well into my automagic backup procedure (I’d have to run the renderer myself or start it using a cronjob) so I decided to build a Docker container for it and run it using GitLab CI.
The biggest problem with this (and the reason why I’m unable to just opensource this) is that overviewer needs an original minecraft.jar
whose distribution is probably not allowed according to Microsoft’s EULA (please don’t try to tell me that “Minecraft is still made by Mojang”, it’s all M$ now.)
On macOS you can find the file (for version 1.13.1
) here: ~/Library/Application\ Support/minecraft/versions/1.13.1/1.13.1.jar
On Linux it should be somewhere inside ~/.minecraft
.
On Windows, searching %APPDATA%
might be the way to go but I don’t know (and don’t really want to).
The last thing you need is the Dockerfile
(called Dockerfile
) which should contain the following:
FROM ubuntu:latest
RUN apt-get -qqq update && \
apt-get -yq upgrade && \
apt-get -yq install build-essential git python-willow python-dev python-numpy && \
apt clean
RUN git clone git://github.com/overviewer/Minecraft-Overviewer.git /overviewer && \
cd overviewer && \
git checkout minecraft113 && \
python2 setup.py build && \
python2 setup.py install && \
mkdir -p /root/.minecraft/versions/1.13.1/
ADD 1.13.1.jar /root/.minecraft/versions/1.13.1/
CMD bin/bash
If you put those two files inside a new folder and run docker build -t lerk/overviewer
, you’ll have the docker image available.
You can then run it using: docker run -it lerk/overviewer
Additionally, you will need a configuration file for overviewer, which is called overviewer.conf
.
An example configuration might look like this:
worlds["My World"] = "world"
worlds["My Nether"] = "world_nether"
renders["day"] = {
"world": "My World",
"title": "Daytime Render of My World",
"rendermode": "smooth_lighting",
"dimension": "overworld",
}
renders["night"] = {
"world": "My World",
"title": "Nighttime Render of My World",
"rendermode": "smooth_night",
"dimension": "overworld",
}
renders["nether"] = {
"world": "My Nether",
"title": "Render of My Nether",
"rendermode": "nether_smooth_lighting",
"dimension": "nether",
}
outputdir = "mcmap"
Please note that this config file will probably not work with your setup. You should consult the official documentation on this topic to get it working the way you want it to.
You can then start the docker container, pull the minecraft server repo (if you’ve put it in git) and render your first map by running the following command from inside the repo folder:
overviewer.py --config=overviewer.conf
If you used either the configuration above (and it works) or used the outputdir
parameter in your own config, you need to mkdir
the directory before starting the render or it will fail.
GitLab
Now to wrap this whole thing up, I want the renderings to start automatically after every backup (push into git). To do this I use my GitLab instance (to host the repo itself), GitLab CI (to run the renderer) and GitLab Pages (to host the resulting map).
All of this complicated sounding stuff is as easy as writing yaml (which means it’s not. At all!) and you simply need to create a .gitlab-ci.yml
inside the root folder of your repo:
image: lerk/overviewer
stages:
- render
- deploy
map:
stage: render
script:
- mkdir mcmap
- overviewer.py --config=overviewer.conf
artifacts:
paths:
- mcmap/
pages:
stage: deploy
dependencies:
- map
before_script: []
script:
- mv mcmap/ public/
artifacts:
paths:
- public
expire_in: 30 days
only:
- master
The image
parameter tells GitLab CI to run the build inside the overviewer container we’ve just built.
Obviously you need GitLab CI Runners that are capable of running Docker.
The map
job does the rendering itself.
It simply runs overviewer and sets the artifacts
property to tell GitLab to keep the render result.
The pages
job uses the artifacts from the map
job (by using the dependencies
property), copies it into a path (afaik the only one) GitLab Pages uses to search for deployable stuff.
After a pipeline completed successfully you should be able to see the updated map on the Pages-URL of your repo.