Converting from sysvinit to systemd

Background

Like most people, I have used sysvinit to control which services are run on boot for years. In recent years, a replacement for sysvinit called systemd has been developed and is now appearing in most Linux distributions, such Debian and Raspbian.

Systemd is designed to start system processes in parallel to achieve faster boot times. The older SysVinit started processes sequentially, meaning everything has to wait for a slow service e.g. getting an IP address through DHCP to complete, not just those services that depend on it.

If you have a Python script that you want to run every time on boot, you will have read about using rc.local. This is a simpler way of doing things than in my case where I use a full daemon mechanism. SysVinit is an old-fashioned and outdated way of doing things. This guide is aimed at users looking to migrate away from rc.local and SysVinit daemons to the new systemd way of working.

My sysvinit Python script

I have a Python script that runs as a daemon and starts up using the traditional double-forking method. In other words, you can control it using the traditional commands:
$ sudo ./myscript.py start
$ sudo ./myscript.py stop
$ sudo ./myscript.py restart
$ sudo ./myscript.py status

With systemd, your script doesn’t need to contain any daemon boilerplate code at all to handle those actions. You don’t need to:

  • design your code to run as a daemon by double-forking
  • read the start/stop/restart/status command line arguments
  • keep a PID file in /var/run
  • redirect stdout/stderr to a log file in /var/log

Systemd does all of the above for you, so all you need is your Python script as you would run it at the terminal and a simple .service file.

myscript.service file
To define your service, you need to create a .service file. This file should be stored in the directory /lib/systemd/system

Here is an example that assumes that your Python script needs network access:
[Unit]
Description=My Script
After=network-online.target syslog.target

[Service]
Type=simple
WorkingDirectory=_CURDIR_
ExecStart=_CURDIR_/myscript.py
StandardOutput=syslog
StandardError=syslog
User=myuser
Group=mygroup
Restart=on-failure

[Install]
WantedBy=multi-user.target

You need to substitute _CURDIR_ with the full path of your script.

You can set the user/group that systemd runs your script as by changing myuser and mygroup to a suitable value. Leave these lines out completely if you want to run as root.

Any output from your program that would normally appear at the console, for example error messages or informational messages from a print() statement are set up to be stored by syslog so that you can still look at them if necessary.

If your script should fail for any reason, the Restart line tells systemd to restart your service.

Whenever you change or add a service file, it is necessary to get systemd to re-read it using the command:
$ sudo systemctl daemon-reload

Shebang line
Normally in a Python script, you would have the first line stating which version of Python to use (the ‘shebang’ line). In our case, to ensure smooth logging, we add the ‘-u’ switch to disable buffering on stdout/stderr which would delay any log messages from appearing.
#!env/bin/python -u

Some useful commands

Task Old SysVinit New Systemd
Start service $ sudo service myscript.py start $ sudo systemctl start myscript
Stop service $ sudo service myscript.py stop $ sudo systemctl stop myscript
Restart service $ sudo service myscript.py restart $ sudo systemctl restart myscript
View service status $ sudo service myscript.py status $ sudo systemctl status myscript
Check log $ cat /var/log/myscript.log $ sudo journalctl -u myscript
$ cat /var/log/syslog | grep myscript
Enable on boot $ sudo chkconfig myscript.py on $ sudo systemctl enable myscript
Disable on boot $ sudo chkconfig myscript.py off $ sudo systemctl disable myscript

Conclusion
I hope there is enough here to help you migrate your Python script from sysvinit to systemd. I realise that systemd is a lot more powerful and complicated than sysvinit and that there is an awful lot more to know than what I have included here.

Remember the reasons for moving your code from Python 2 to Python3? I would say that a lot of the same reasons apply for the move from sysvinit to systemd.

If you stay with the old way of doing things, eventually both you and your code will become old and obsolete.