Debian packaging with systemd

Building a debian package that sets up a systemd unit typically uses the dh-systemd debhelper add-on. The standard dh_installinit(1) debhelper actually installs unit files found in your package into the right place, but for versions <10, it doesn’t properly start or stop the units upon installation or removal. dh-systemd adds the necessary adjustments to your package’s postrm and poststart maintainer scripts to trigger not only starting and stopping the units, but also enabling them to start at boot.

Adding Systemd Support

Your package control file must specify a build-depends relationship with dh-systemd. The internet suggests specifying version > 1.5, but release notes are hard to find.

In debian/control:

Build-Depends: debhelper, dh-systemd (>= 1.5)

Additionally, you’ll need to tell dh to use systemd in your debian/rules file:

  dh @ --with=systemd

This means you’ll need to build the package on a system that actually has dh-systemd available to it. If you have to build packages for a variety of init systems (as I unfortunately do), you can get creative with generating rules and control files for each target system.

Bare Bones Example

Check out a basic example at github.com/mypetyak/systemd-debian-skeleton.

Build the package with dpkg-buildpackage -us -uc. If you’re not on a machine with dh-systemd, you can build the package inside a VM as described below.

Testing the Package in a Systemd-Equipped VM

To do a test installation on a machine without systemd, you could use a docker image that’s hacked to allow proper systemd support as pid 1, but this is a case where a virtual machine is more natural and useful. Use vagrant to spin up a systemd-enabled VM:

❯ vagrant init ubuntu/xenial64
❯ vagrant up
❯ vagrant ssh

$ sudo apt-get update && sudo apt-get -y install dh-systemd
$ pushd /vagrant/systemd-demo
$ dpkg-buildpackage -us -uc
$ sudo dpkg -i ../systemd-demo_0.0.1_amd64.deb 

We can check the state of the unit file installed by the package (and see that it’s pinging google.com), and also confirm its deactivation upon package removal.

$ systemctl status systemd-demo.service | head -n 15
● systemd-demo.service - Simple demonstration service
   Loaded: loaded (/lib/systemd/system/systemd-demo.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2017-11-09 05:38:07 UTC; 4min 22s ago
 Main PID: 15752 (ping)
    Tasks: 1
   Memory: 208.0K
      CPU: 119ms
   CGroup: /system.slice/systemd-demo.service
           └─15752 /bin/ping google.com

Nov 09 05:42:20 ubuntu-xenial ping[15752]: 64 bytes from sea15s07-in-f78.1e100.net (216.58.193.78): icmp_seq=253 ttl=63 time=6.05 ms
Nov 09 05:42:21 ubuntu-xenial ping[15752]: 64 bytes from sea15s07-in-f78.1e100.net (216.58.193.78): icmp_seq=254 ttl=63 time=11.6 ms
Nov 09 05:42:22 ubuntu-xenial ping[15752]: 64 bytes from sea15s07-in-f78.1e100.net (216.58.193.78): icmp_seq=255 ttl=63 time=4.00 ms
Nov 09 05:42:23 ubuntu-xenial ping[15752]: 64 bytes from sea15s07-in-f78.1e100.net (216.58.193.78): icmp_seq=256 ttl=63 time=14.8 ms
Nov 09 05:42:24 ubuntu-xenial ping[15752]: 64 bytes from sea15s07-in-f78.1e100.net (216.58.193.78): icmp_seq=257 ttl=63 time=9.63 ms

$ sudo apt-get -y remove systemd-demo

$ systemctl status systemd-demo.service | head -n 4
● systemd-demo.service
   Loaded: masked (/dev/null; bad)
   Active: inactive (dead) since Thu 2017-11-09 05:43:46 UTC; 27s ago
 Main PID: 15752 (code=killed, signal=TERM)

The Systemd-Specific Package Additions

Extracting the resulting maintenance scripts from the package shows dh-systemd’s handiwork; steps have been added to the scripts run following installation, before removal, and after removal:

$ dpkg-deb -e systemd-demo_0.0.1_amd64.deb /tmp/controlfiles

$ cat /tmp/controlfiles/postinst
#!/bin/sh
set -e
# Automatically added by dh_systemd_enable
# This will only remove masks created by d-s-h on package removal.
deb-systemd-helper unmask systemd-demo.service >/dev/null || true

# was-enabled defaults to true, so new installations run enable.
if deb-systemd-helper --quiet was-enabled systemd-demo.service; then
        # Enables the unit on first installation, creates new
        # symlinks on upgrades if the unit file has changed.
        deb-systemd-helper enable systemd-demo.service >/dev/null || true
else
        # Update the statefile to add new symlinks (if any), which need to be
        # cleaned up on purge. Also remove old symlinks.
        deb-systemd-helper update-state systemd-demo.service >/dev/null || true
fi
# End automatically added section
# Automatically added by dh_installinit
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ]; then
        if [ -x "/etc/init.d/systemd-demo" ]; then
                update-rc.d systemd-demo defaults >/dev/null
        fi
        if [ -x "/etc/init.d/systemd-demo" ] || [ -e "/etc/init/systemd-demo.conf" ]; then
                invoke-rc.d systemd-demo start || exit $?
        fi
fi
# End automatically added section
# Automatically added by dh_systemd_start
if [ -d /run/systemd/system ]; then
        systemctl --system daemon-reload >/dev/null || true
        deb-systemd-invoke start systemd-demo.service >/dev/null || true
fi
# End automatically added section

$ cat /tmp/controlfiles/prerm
#!/bin/sh
set -e
# Automatically added by dh_systemd_start
if [ -d /run/systemd/system ]; then
        deb-systemd-invoke stop systemd-demo.service >/dev/null
fi
# End automatically added section
# Automatically added by dh_installinit
if [ -x "/etc/init.d/systemd-demo" ] || [ -e "/etc/init/systemd-demo.conf" ]; then
        invoke-rc.d systemd-demo stop || exit $?
fi
# End automatically added section

$ cat /tmp/controlfiles/postrm
#!/bin/sh
set -e
# Automatically added by dh_systemd_start
if [ -d /run/systemd/system ]; then
        systemctl --system daemon-reload >/dev/null || true
fi
# End automatically added section
# Automatically added by dh_installinit
if [ "$1" = "purge" ] ; then
        update-rc.d systemd-demo remove >/dev/null
fi


# In case this system is running systemd, we make systemd reload the unit files
# to pick up changes.
if [ -d /run/systemd/system ] ; then
        systemctl --system daemon-reload >/dev/null || true
fi
# End automatically added section
# Automatically added by dh_systemd_enable
if [ "$1" = "remove" ]; then
        if [ -x "/usr/bin/deb-systemd-helper" ]; then
                deb-systemd-helper mask systemd-demo.service >/dev/null
        fi
fi

if [ "$1" = "purge" ]; then
        if [ -x "/usr/bin/deb-systemd-helper" ]; then
                deb-systemd-helper purge systemd-demo.service >/dev/null
                deb-systemd-helper unmask systemd-demo.service >/dev/null
        fi
fi
# End automatically added section