Creating a system service with systemd
While I've got some grumblings with systemd over how it handles (or not) certain things, it's the most popular service manager on Linux systems today. By this, I mean it starts and stops the various services (like your SSH server, web server, cron) automatically, according to the rules laid out in special service files.
Since it's so popular and I keep having to write services (and look up how to do so every time), I thought I'd write a post here about it to save me the trouble :P
Bear in mind that systemd isn't the only service manager out there. Others include [OpenRC](), [runit](), [upstart](), and more! If you're using one of these (I'm looking to investigate using one of these on my next server rebuild), then this tutorial isn't for you. I will probably be releasing a tutorial down the road for OpenRC though, if I get around to having a server running an OS that uses it.
systemd stores it's service files in
/etc/systemd/system/, so to start we need to create a new file in there:
sudo sensible-editor /etc/systemd/system/service_name.service
With the new file open in your favourite editor, it's time to set out our service definition. This is done with an ini-like syntax. Here's an example:
[Unit] Description=Gitea After=syslog.target rsyslog.service network.target [Service] Type=simple User=git Group=git WorkingDirectory=/srv/git/gitea ExecStart=/srv/git/gitea/gitea web Restart=always Environment=USER=git HOME=/srv/git [Install] WantedBy=multi-user.target
[Unit] section defines the metadata about the service. It's fairly self explanatory actually - we set the description of the service here, and also the other services (space
` separated) that we want our service to be started after with theAfter` property.
Next comes the
[Service] section. This section specifies how it should start the service. We tell it that it's a simple service (in other words it doesn't do anything fancy - other types are available, but we won't use them here), the user and group it should run under, and working directory of the process, and the command (and it's arguments) to execute in order to start the process.
In addition, we also tell it to restart the service if it crashes, and set a few environment variables to refine the way Gitea behaves. Very cool!
The final section,
[Install], simply specifies the systemd-equivalent of which run-level this service should start on. It's very interesting from a how-does-my-system-work perspective - I recommend reading this Stack Exchange answer and this article for more information - it's a topic for another post here on this blog :-)
To start this new service, do the following:
sudo systemctl daemon-reload sudo systemctl enable service_name.service sudo systemctl start service_name.service
This starts our new service and configures it to automatically start when the system first boots.
With that taken care of, we've now got the basics down of our very own service file! We can take this further though. What if there's a secret key that we need to pass to a service on startup in an environment variable, but we don't want to specify it in the service because it's world-readable?
The answer here is a clever bit of shell scripting. Consider the following service file:
[Unit] Description=Awesome XMPP Bot After=network.target prosody.service [Service] Type=simple User=bot WorkingDirectory=/srv/bot ExecStart=/srv/bot/start_service.sh Restart=on-failure # Other Restart options: or always, on-abort, etc [Install] WantedBy=multi-user.target
In this case, we've defined a service file for an XMPP bot (public server directory). In order for it to connect to an XMPP server, it needs a JID (a username - formatted like an email address) and password. However, we don't want to specify these directly in the service file because they are secret!
Instead, we've specified that it should start a shell script that's located at
/srv/bot/start_service.sh instead of the bot itself. Here's the contents of that shell script:
#!/usr/bin/env bash source .xmpp_credentials export XMPP_JID; export XMPP_PASSWORD; exec /usr/bin/mono Bot.exe
This simple shell script loads the contents of the file
.xmpp_credentials, specifies that the
XMPP_PASSWORD environment variables should be passed to any further (sub) processes, and asks for the current process to be terminated and replaced with an instance of Mono executing our bot's code that stored in
Bot.exe (this way we don't have an extra
bash process sitting around doing nothing, since it's job is done as soon as we start the bot itself).
In this way, we can store our precious private details in a file that we can lock down so that only the bot's user account can read it. Here's what that
.xmpp_credentials file might look like:
#!/usr/bin/env bash XMPP_JID="firstname.lastname@example.org"; XMPP_PASSWORD="sekret";
....and if I run
ls .xmpp_credentials, I might see something like this:
-r-x------ 1 bot bot 104 Nov 10 21:27 .xmpp_credentials
Here the file permissions allow only the
bot user to read and execute the file, but not modify it (
sudo chown bot:bot .xmpp_credentials and
sudo chmod 0500 .xmpp_credentials set these permissions for the curious).
These completes the tutorial on setting up services with systemd. We've seen how to create service files and make them start on boot (much easier than alternatives like running a command manually or using screen!). We've also learnt a simple way to hide credentials (though more advanced alternatives do exist).
Found this useful? Found a better way to do it? Comment below!
Sources and Further Reading
- Creating Systemd Service Files
- Systemd: start service after specific service
- Systemd service - what is
- Linux Runlevels Explained