Debian packaging with systemd
Wed, Nov 8, 2017Building 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