How to define systemd “ConsistsOf” relationship

systemd

I am creating a package foo which launches/closes upstream package bar. The relationship should be:

  • When you start foo, also start bar.
  • When to you stop/reload foo, also stop/reload bar.
  • bar may be started, stopped or reloaded without affecting foo

If /lib/systemd/system/bar.service looks like this:

[Unit]
Description=Bar

[Service]
ExecStart=/bin/sleep infinity
Restart=on-failure

Then then "normal" solution would be to add WantedBy and PartOf relationships to bar:

[Unit]
Description=Bar
PartOf=foo.service

[Service]
ExecStart=/bin/sleep infinity
Restart=on-failure

[Install]
WantedBy=foo.service

However, bar is an upstream package and I think it's not quite right to force bar to be aware of foo, or to patch bar.

I thought a perfect solution would be to create foo.service like this:

[Unit]
Description=Foo
Wants=bar.service
ConsistsOf=bar.service

[Service]
Type=oneshot
ExecStart=/bin/true
RemainAfterExit=yes

However my journalctl says:

Unknown lvalue 'ConsistsOf' in section 'Unit'

and man pages say:

When PartOf=b.service is used on a.service, this dependency will show as ConsistsOf=a.service in property listing of b.service. ConsistsOf= dependency cannot be specified directly.

I'm not sure why ConsistsOf= cannot be specified directly, but is there an alternative that I didn't consider?

Best Answer

The appropriate way to modify upstream/external bar.service to include a PartOf= relationship to another unit is to use a "drop-in" override config, which is a systemd feature allowing you to modify existing units without having to touch the original files.

From systemd.unit man page:

Along with a unit file foo.service, a "drop-in" directory foo.service.d/ may exist. All files with the suffix ".conf" from this directory will be parsed after the unit file itself is parsed. This is useful to alter or add configuration settings for a unit, without having to modify unit files.

In your particular case, you should create a drop-in such as /etc/systemd/system/bar.service.d/partof-foo.conf, with contents:

[Unit]
PartOf=foo.service

(The name partof-foo.conf is just a suggestion, anything with a .conf suffix should work.)

Then reload the daemon with systemctl daemon-reload.

After that is done, you can inspect the unit with systemctl cat bar, which will neatly show the override is being taken into account. Also, now, systemctl show foo will show a ConsistsOf= relationship to bar.service and the effects of that relationship will follow (stopping foo will cause bar to stop.)

For the second part (foo having Wants=bar.service), I suggest adding that directive directly to foo.service instead of using an [Install] stanza in an "drop-in" for bar.service.

For one, the [Install] section would have to be activated with a systemctl enable bar command, so in a way it's harder to maintain. (Also, I've seen bugs where [Install] from a "drop-in" is not respected. I believe these have been fixed, but might still exist in your distro's version of systemd.)

Furthermore, you might want to use a stronger Requires= relationship, rather than Wants=, since that will make foo.service fail if bar.service can't be started. (Consider also adding an ordering dependency, such as After=bar.service from foo.service, so foo will actually wait until bar is up before doing its own startup.)

Since foo.service is a file you control, simply include it there directly:

[Unit]
Description=Foo
Requires=bar.service
After=bar.service

That's assuming you'll want the ordering dependency as well, it's safe to omit it if you don't.

That should take care of everything.