rcS.d migration

Sysvinit booted by executing all scripts in rcS.d , then rcN.d where N is the target runlevel (2 by default in debian). Debian systemd currently has a patch to preserve the rcS.d synchronization point, forcing all rcS.d init scripts to run before the scripts in rcN.d. The problem is solved by providing native units with correct dependencies for each service.

Main dependencies involved

The main issue is that Debian systemd makes all rcS.d ordered Before=sysinit.target, which in turn is ordered Before=basic.target. On the other hand, systemd by default orders all units After=basic.target (this can be removed with DefaultDependencies=no).

This causes problems if some service with DefaultDependency=yes tries to order itself before any of the services in rcS.d, as this causes a dependency loop, which systemd breaks at a random point.

Note that in systemd there are two dimensions: functional dependencies and ordering dependencies. The cycles we care about are the ordering ones, as these are the ones that systemd must break.

How to solve this

The sysv generator only applies to sysv init scripts if there is no corresponding service file. There are two solutions:

Of course you can combine these -- native service files are preferrable under systemd (more efficient, better logging, finer-grained and more powerful dependencies, socket activation, etc.), but correcting wrong rcS SysV init scripts will benefit non-systemd cases.

Do you really need a service?

Sometimes, the correct solution is not to use a service at all but use another facility provided by systemd. If your service runs in early boot to write files or create runtime directories, consider using systemd-tmpfiles. If your service loads kernel modules, consider installing a file in /usr/lib/modules-load.d instead.

If your service can be replaced by these alternative facilities, then you should instruct systemd to ignore the original SysV init script. You can do this by masking myscript service with the command:

# systemctl mask myscript.service

or by simply installing a symlink /lib/systemd/system/myscript.service pointing to /dev/null, where myscript is the name of your initscript as installed in /etc/init.d. Note that shipping this symlink in the package can reintroduce the dependency cycle if the package is uninstalled but not purged. This will be solved once the rcS debian-specific patch is removed, in the meantime we are investigating alternative solutions to the problem.

What dependencies should you use?

Depending on the services provided, your script needs to hook itself in the correct place in the boot sequence. As mentioned before, there are two types of dependencies. The first decision is should your service start in rescue or single user mode. If so, then make your unit WantedBy=sysinit.target. If not, use WantedBy=multi-user.target.

If you decide to make your service WantedBy=sysinit.target, please make sure to not impose overly strict ordering requirements. In particular, try to avoid making your service order itself Before=sysinit.target or Before=basic.target, as that increases the chances of dependency loops (this may be unavoidable if your service is needed to mount local filesystems). If you use DefaultDependencies=no, then you need to add Conflicts=shutdown.target and Before=shutdown.target, or your service will not be gracefully stopped on shutdown.

The next subsections show some guidelines for determining which ordering dependencies to use in your service.

Your service is not really about early boot

The first step is to define if the init script really belongs in early boot. If it doesn't, consider moving your script to runlevel 2 and just use DefaultDependencies=yes (the default). If your script needs to execute before another service because it provides some setup for it, use Before=other.service. In this case be careful that the other service is ordered After=basic.target (this is implied by DefaultDependencies=yes) or you will reintroduce an ordering cycle.

Your service is needed to mount local file systems

In this case, you should create a service with the following dependencies:

[Unit]
Description=An early boot service
DefaultDependencies=no
Wants=local-fs-pre.target
Before=local-fs-pre.target shutdown.target
Conflicts=shutdown.target

Your service is needed to mount remote file systems

In this case, you should create a service with the following dependencies:

[Unit]
Description=An early boot service
DefaultDependencies=no
Wants=remote-fs-pre.target
Before=remote-fs-pre.target shutdown.target
Conflicts=shutdown.target

If your service requires internet connectivity, add Requires=network-online.target and After=network-online.target. Please try to avoid network-online.target if possible: if your service can gracefully handle network failures then it probably doesn't need network-online.target.

Your service is needed to configure firewalls or network interfaces

If you need to configure firewalls, network interfaces, or anything else which needs to happen before bringing up the first network interface, then you should order the service as follows (eg, if you need to run before ifupdown/networkd):

[Unit]
Description=An early boot service
DefaultDependencies=no
Wants=network-pre.target
Before=network-pre.target shutdown.target
Conflicts=shutdown.target

Your service needs access to non-root filesystems

If you need access to anything more than a read-only root filesystem, then you need to instruct systemd to order your service after that path is available. The most common case of requiring a writable /var for logging or accessing stored data, would be accomplished as follows:

[Unit]
Description=An early boot service
DefaultDependencies=no
After=local-fs.target
Before=shutdown.target
Conflicts=shutdown.target
RequiresMountsFor=/var/log /var/lib

RequiresMountsFor accepts a space separated lists of the paths needed. This option ensures your service is started after those paths are available, and it is stopped before those paths are unmounted.

In particular, your service does not need to depend on remote-fs.target, even if your SysV script used the $remote_fs facility.

If your service is ordered Before=sysinit.target, then you must not access any possibly remote filesystems. These include /var and /usr, so you cannot rely on them being present.

Bugs

See the usertagged bugs.