openSUSE:Systemd packaging guidelines

Jump to: navigation, search
This article describes packaging guidelines for systemd services.

RPM macros

Starting with openSUSE 12.1, the systemd-rpm-macros package provides several RPM macros to package unit files, tmpfiles and sysusers configuration.

As of 2020-11-20, systemd-rpm-macros is required by rpm-build in both Leap and Tumbleweed, so you do not need to request it from a specfile; nonetheless, feel free to be explicit about this dependency, in case rpm-build drops it in a future version.

BuildRequires: systemd-rpm-macros

Build requirements

If you need to link some compiled sources against systemd libraries, you should use this pkgconfig virtual provider:

BuildRequires: pkgconfig(libsystemd)

This will allow the build system to pick systemd-mini-devel as needed.

Similarly, if you need to use systemd programs during build, do use

BuildRequires: pkgconfig(systemd)

to allow the use of systemd-mini as needed.

Runtime requirements

The systemd rpm macros such as %service_add_post that expand to shell code and which invoke systemctl and other related utilities as part of scriptlets are able to detect the absence of systemd and, in fact, work on installations that are completely free of systemd. As such, it is not necessary and even discouraged to use Requires(post): systemd or the related %systemd_requires.

If, and only if, the programs that ship as part of the package have systemd commands as a necessity and could otherwise not function may you add the appropriate Requires.

Unit files

Systemd unit files, including but not limited to service, socket, timer and path files should always be installed in %_unitdir, i.e. /usr/lib/systemd/system, and never in /etc/systemd/system, so they can be overridden by users without conflicting with packaging. An exception are user unit files which must be installed in %{_prefix}/lib/systemd/user, i.e. /usr/lib/systemd/user.

This command is an example of a legal install line where %{S:3} is an example reference to your service file:

install -D -m 644 %{SOURCE3} %{buildroot}%{_unitdir}/foo.service

Systemd unit files ought not to be edited by system administrators. As a result, the files are not to be marked as %config in the %files section of the specfile. Instead, modifications are to be placed by the admin in the /etc/systemd/system directory, either as a full replacement file such as /etc/systemd/system/foo.service, or by an extending fragment such as /etc/systemd/system/foo.service.d/my.conf.

Registering unit files in install scripts

To tell systemd about changed unit files, systemctl daemon-reload should be invoked. To do so from a specfile, one can use various %service_* macros provided by systemd-rpm-macros. These macros will handle sysv initscripts migration transparently (as long as initscripts and systemd services have similar names).

%pre
%service_add_pre foo.service bar.socket elsewhere.path

%post
%service_add_post foo.service bar.socket elsewhere.path

%preun
%service_del_preun foo.service bar.socket elsewhere.path

%postun
%service_del_postun foo.service bar.socket elsewhere.path

Compare with the manual pages systemd.service(5), systemd.socket(5), and systemd.path(5) as well as the overview systemd.exec(5) and systemd.unit(5).

If your package is supposed to build for openSUSE older than 12.1, you should use condition tests for those macros.

During package update, %service_del_postun restarts units. If units are not to be restarted, %service_del_postun_without_restart should be used instead.

Enabling units

By default, services are not enabled when package is installed. If you want your service to be enabled by default, you should do a submit request (with OBS) on systemd-presets-common-SUSE or systemd-presets-branding-openSUSE package, modifying default-openSUSE.preset file by adding e.g.:

enable your_service_name.service

Such an automatically enabled service shouldn't require any manual configuration before it can start properly.

Recent openSUSE and SUSE products contain two important preset files in two preset packages. systemd-presets-common-SUSE with 95-default-SUSE.preset (or 90-default-SUSE.preset up to Leap 15.0) and systemd-presets-branding-openSUSE (or generally systemd-presets-branding-brand) with 90-default-openSUSE.preset (or 90-default-brand.preset).

Both are technically equivalent. Depending on a purpose of your service, pick a proper one:

systemd-presets-common-SUSE is intended for services that are mandatory for a proper function of the system or the package. Placing preset into this file means: All systems should use this value as a default. This package is a common base for both open and enterprise products and it is part of all base installations.

systemd-presets-branding-openSUSE is intended for vendor customization. Placing a preset here means: It is a wise default for openSUSE, but not enabling this service in other product makes perfect sense. The package structure makes it possible to create a custom brand with a custom systemd-presets-branding-brand as a part of custom branding. Generic rules for branding packages are explained in openSUSE:Packaging_Branding.

Note: Third party packages can install a custom preset file, but it is not allowed for packages in the openSUSE distribution.

Preset change and upgrade

When you change preset of a service, upgrade scripts calls one-time customization reset. It means, that if you add enable, then it will force-enable the service on all systems during the upgrade. And vice versa, if you delete an enable or add disable, it will force-disable the service during the upgrade.

Installation order

Consider an installed system where systemd is running, and where you plan on installing a program package P2 whose unit files make use of a feature only available in a newer systemd version. An example of such is perhaps the RestrictSUIDSGID unit directive, which became available in systemd-242.

If P2 is installed on its own, it will have to make do with the available features (which usually means systemd ignores unrecognized lines in unit files). This is not necessarily a bad thing. If however, a newer systemd package is planned to be installed in the same transaction as P2, it can be beneficial to have systemd installed first, so that the features have become available by the time P2 is installed and restarted.

To that end, one can add %{?systemd_ordering} to the corresponding %package block (could as well be the implied main subpackage). For example,

   Name: foo
   Version: 0
   Release: 0
   Requires: bash
   %{?systemd_ordering}
   …
   %post
   %service_add_post foo.service
   …

Of course it is still possible to split the transaction and install just P2 on its own, then systemd on its own. It is not %systemd_ordering's intent to cover this, also because, if there was a hard dependency on anything, you would need to know the exact version number.

(For historians, this was the Github pull request.)

Instantiated services

If an instantiated service (compare with manual page systemd.unit(5)) is needed, the service file in the RPM should be called foo@.service. Therefore, this command is a legal install line:

install -D -m 644 %{SOURCE3} %{buildroot}%{_unitdir}/foo@.service

The instance template should at present not be passed to %service_*, as these macros do not deal with them. Likewise, instances should in general not be enabled (from .spec files), but only through presets (see above).

After installation of an RPM package, the administrator can instantiate a service via a command like:

sytemctl enable autossh@someinstance.service

The admin can override the service just like any other unit, that is, by creating a file /etc/systemd/system/foo@someinstance.service.d/my.conf, or using systemctl edit foo@someinstance.

Thus each instance must be individually instantiated and overridden by the system administrator or the appropriate systemd generator (compare with manual page systemd.generator(7)) as specified in http://www.freedesktop.org/wiki/Software/systemd/Generators .

Shipping unit file drop-ins

Users are given the possibility to alter configuration settings for a unit shipped by a package provided by SUSE, without having to modify the unit file itself. This is normally done by the mean of "drop-in" files. A complete description of drop-in files can be found in systemd.unit(5) man page.

To avoid any conflicts that may happen between the drop-in files shipped by SUSE and users' ones, it is recommended to prefix all drop-in filenames with a two-digit number and a dash, to simplify the ordering of the files. For example "29-override.conf".

Furthermore the following ranges of the two-digit number are reserved as follow:

  • [0-19] is reserved for systemd upstream
  • [20-29] is reserved for systemd shipped by SUSE
  • [30-39] is reserved for SUSE packages (other than systemd)
  • [40-49] is reserved for 3rd party packages
  • 50 is reserved for unit drop-in files created with `systemctl set-property`

Hence by using a two-digit number above 50, users will be assured that none of the drop-in files shipped by SUSE will override users own drop-in files in the future.

Sandboxing

Many services run with root privileges and as such open the system up to local and sometimes remote attacks. Some of this can be mitigated by removing some privileges from the service via setting in the service file. Consider applying the sandbox setting per Systemd Hardening Effort

Timers

Timer units are a systemd feature that helps define time activated services or events. It is an alternative to cron jobs.

Since 2018-11-09, a process of migration from cron to systemd timers (fate#323635) is in progress. See boo#1115430 (openSUSE) and bsc#1115399 (SLE, internal) trackerbugs that track all packages that should be migrated for openSUSE and SLE.

Timers are a unit type whose configuration filename ends in .timer, and this timer unit activates the matching service unit (foo.timer activates foo.service). Systemd automatically pairs timer and services with the same name, and a different relationship can be specified by an extra Unit= line in the timer.

Timers need to include a [Timer] section that defines when the timer is activated. For the whole description of the time format used for timer configuration, please see systemd.timer(5). For time and date specifications, see systemd.time(7).

Timer units can be either provided by upstream or by a packager who writes them for the distribution (e.g. during migration from a cron job to a systemd timer).

Like other units, timers should be passed as argument to the %service_* call, and only be enabled through presets.

Examples

Example logrotate.timer

[Unit]
Description=Daily rotation of log files
Documentation=man:logrotate(8) man:logrotate.conf(5)

[Timer]
OnCalendar=daily
AccuracySec=12h
Persistent=true

[Install]
WantedBy=timers.target

Example logrotate.service

[Unit]
Description=Rotate log files
Documentation=man:logrotate(8) man:logrotate.conf(5)
ConditionACPower=true

[Service]
Type=oneshot
ExecStart=/usr/sbin/logrotate /etc/logrotate.conf

MAILTO

Systemd currently does not support the equivalent of cron's MAILTO function for sending emails on job failure. See Arch Linux wiki for a workaround.

Creating files and subdirectories in /var/run and /run

Since openSUSE 12.2, /var/run (which is either bind mounted or symlinked to /run) are mounted as tmpfs, so packages should not contain any directories (or files) under /var/run (or /run), since they will disappear at the next reboot.

If new files / directories need to be created there, you should package a tmpfiles.d file (see man tmpfiles.d for the syntax), installed in %_tmpfilesdir(/usr/lib/tmpfiles.d/). One example of such a file:

# create a directory with permissions 0770 owned by user foo and group bar
d /var/run/my_new_directory 0770 foo bar

You should install them in %install section like this:

%install
...
install -D -m 0644 %{SOURCE4} %{buildroot}%{_tmpfilesdir}/%{name}.conf

If you expect this file or directory to be available after package installation (and before reboot), remember to add in your package %post section:

%post
%tmpfiles_create %_tmpfilesdir/<file_name>

And don't forget to add it to the %files section, prefixed by the %ghost macro:

%files
...
%ghost %{_localstatedir}/run/my_new_directory

This will prevent RPM Lint from warning about tmpfile-not-in-filelist. Users will be able to easily query who created the directory, it will get uninstalled on package removal and other RPM Lint checks will see it.

Backward compatibility

rc symlink

It's still possible to keep the /usr/sbin/rcname symlink. In order to make it work with systemd, link to /usr/sbin/service for each unit (service and target types are supported):

%install
...
mkdir -p %{buildroot}%{_sbindir}
ln -s /usr/sbin/service %{buildroot}%{_sbindir}/rcname

%files
...
%{_sbindir}/rcname

Extra actions

Init scripts sometimes implemented additional actions besides the usual start/stop/status etc. For systemd service files that extra feature doesn't exist. It's still convenient to have for some services though. Therefore /usr/sbin/service implements "legacy actions" like Fedora.

Suppose your previous init script "foo" had an action "frob". Now the service file is called "foo.service" and you want to still support the "frob" action. In Factory since 2014-03-11 (post 13.1) you can create a script

 /usr/lib/initscripts/legacy-actions/foo/frob

that implements the feature you need. Obviously the action is only available when calling the service via /usr/sbin/service, ie

 # service foo frob

or

 # rcfoo frob