Cross-distribution packaging: Providing a systemd service
We use the upcoming opsi 4.1 to remove obsolete raiminigs that accumulated over time. This is also a good time to get the packaging up to date.
On systems running systemd we used some hacks with opsi 4.0 that allowed us to treat support for systemd as an optional feature. Now we want to make systemd first class, remove hacks and use the standards that the various distributions recommend.
I want to show how we end up packaging opsiconfd to work with systems using
Debians .deb
package format and those using .rpm
.
.deb files work the same across all major distributions and we serve
Debian, Ubuntu and Univention Corporate Server (UCS) with the same setup.
On the RPM-side we focus on the distributions provided by SUSE (SLES, openSUSE),
Red Hat (RHEL) and the open source variant CentOS.
.deb
Debian packaging really shines in this regard! They make things extremely easy for a package maintainer and I can hardly image how this could be any easier.
We store opsiconfd.service
in the debian
folder of our package.
This will lead to proper distribution of the file into the right places.
The control
file needs a dependency to systemd
if this it not yet there.
We add dh-systemd (>= 1.5)
to the Build-Depends
.
This allows us to call dh
with the parameter --with systemd
in our rules
file.
With all this in place we removed all the hacks we had because we do not need them anymore. Great!
.rpm
This is where things get ugly. If you have some booze around: this is a good time to pour yourself one!
It is a miracle to me why the various .rpm based distributions are unable to provide a way that works for all distributions. At least they all seem to have settled to the use of macros but why can't they use the same name for all of their macros? So armed with knowledge from Fedoras packaging guide and openSUSEs systemd packaging guidelines we can get to work!
The BuildRequires
in our .spec
must list systemd
there.
If you are running SUSE you also want to have systemd-rpm-macros:
%if 0%{?suse_version} >= 1210 BuildRequires: systemd-rpm-macros %endif
For runtime requirements we can make use of a macro: %{?systemd_requires}
.
For our %pre
section SUSE-based distributions have their own macro.
Red Hat-style distributions lack this and you will only notice that when you
are installing the package - a little trap left to the unobservant packager.
%if 0%{?suse_version} %service_add_pre opsiconfd.service %endif
We use the %install
to adjust various differences between distributions.
First we make sure to reference the correct service names in our unit -
they are different not only between distributions but also between versions.
It would be much nicer if the Debian and RPM world could use the same names
for their services!
We use the same unit for all distributions and therefore simply alter debian/opsiconfd.service
. We decided against having different unit files for different distributions because we do not want to have to maintain multiple files.
%if 0%{?suse_version} >= 1315 || 0%{?centos_version} >= 700 || 0%{?rhel_version} >= 700 # Adjusting to the correct service names sed --in-place "s/=smbd.service/=smb.service/" "debian/opsiconfd.service" || true sed --in-place "s/=isc-dhcp-server.service/=dhcpd.service/" "debian/opsiconfd.service" || true %endif
We use mkdir
in our unit the create directories required during runtime.
If you are doing the same you need to make sure to adjust the path to it!
MKDIR_PATH=$(which mkdir) CHOWN_PATH=$(which chown) sed --in-place "s!=-/bin/mkdir!=-$MKDIR_PATH!" "debian/opsiconfd.service" || true sed --in-place "s!=-/bin/chown!=-$CHOWN_PATH!" "debian/opsiconfd.service" || true
In the end we install our service file through
install -D -m 644 debian/opsiconfd.service %{buildroot}%{_unitdir}/opsiconfd.service
.
Noticed %{_unitdir}
? This is indeed a placeholder that works on every distribution I came across!
Let's continue to %post
. This one is easy once we are equipped with
more distribution specific switches. We add the following:
%if 0%{?rhel_version} || 0%{?centos_version} %systemd_post opsiconfd.service %else %service_add_post opsiconfd.service %endif
This now repeats with slightly different macros for %preun
and %postun
.
The good thing about this is that these sections are empty except for the systemd macros.
%preun %if 0%{?rhel_version} || 0%{?centos_version} %systemd_preun opsiconfd.service %else %service_del_preun opsiconfd.service %endif %postun %if 0%{?rhel_version} || 0%{?centos_version} %systemd_postun opsiconfd.service %else %service_del_postun opsiconfd.service %endif
The last thing we need to do is to list our unit under %files
.
Again we can leverage %{_unitdir}
for this:
%{_unitdir}/opsiconfd.service
Systemd units are not to be listed as config files.
In addition SUSE distribution builds of the package may fail because of
rpmlint complains about units in /var/...
listed as config files.
What a ride! But at least we now have proper support for systemd in our package!