4

In systemd (v237 on UbuntuLinux 18.04 bionic), I can create a service file (for A.service), and specify another service Requisite=B.service. Meaning if I try to start A.service and B.service isn't already running, then A.service will not be started. It's a weak version of Requires, which will start B.service when I try to start A.service.

Is there an opposite? Can I say “If B.service is running, then don't start this service” / “If B.service is running, then A.service cannot start”?

The docs say if I do Conflicts=B.service, then starting A will stop B and then start A. But I don't want B stopped, I just want A to fail to start. I want something that's to Conflicts what Requisite is to Requires.

I could probably change the ExecStart to be a shell command that'll fail is systemctl is-active B.service or some sort of hack. Is there a proper solution?

Amandasaurus
  • 30,211
  • 62
  • 184
  • 246
  • 1
    I don't think there is a way to do that, except writing a script. Did you see https://unix.stackexchange.com/questions/503719/how-to-set-a-conflict-in-systemd-in-one-direction-only ? – Lenniey May 11 '20 at 06:56

2 Answers2

6

Generally the way to do this is with an ExecStartPre that checks whether the other services is running. If an ExecStartPre command returns an error code then the rest of the startup process is aborted.

ExecStartPre=/bin/bash -xc '/usr/bin/systemctl is-active --quiet other-unit.service && exit 1 || exit 0'
Thiago Conrado
  • 245
  • 2
  • 7
Jason Kohles
  • 181
  • 4
  • just need to mention that is-active will check for active services that is not the same thing as running; if the tested unit is active, it will exit 1; that will lead to a unit starting fail – Thiago Conrado Dec 23 '20 at 18:52
4

Something I found later, the best option for this situation is ExecCondition= because it will allow you do do a proper error control; ExecStartPre= will always generate an error if exits with non-zero; while ExecCondition= will permits to halt the execution without throwing an error, or trowing an error if required. This will be very handy for notifications on ExecStartPost= processing (you can set a trigger to notify that the service did not start because the other was running).

ExecCondition= Optional commands that are executed before the command(s) in ExecStartPre=. Syntax is the same as for ExecStart=, except that multiple command lines are allowed and the commands are executed one after the other, serially.

The behavior is like an ExecStartPre= and condition check hybrid: when an ExecCondition= command exits with exit code 1 through 254 (inclusive), the remaining commands are skipped and the unit is not marked as failed. However, if an ExecCondition= command exits with 255 or abnormally (e.g. timeout, killed by a signal, etc.), the unit will be considered failed (and remaining commands will be skipped). Exit code of 0 or those matching SuccessExitStatus= will continue execution to the next command(s).

The same recommendations about not running long-running processes in ExecStartPre= also applies to ExecCondition=. ExecCondition= will also run the commands in ExecStopPost=, as part of stopping the service, in the case of any non-zero or abnormal exits, like the ones described above.

The code logic is the same:

ExecCondition=/bin/bash -xc '/usr/bin/systemctl is-active --quiet other-unit.service && { [[ %i == "main" ]] && exit 255 || exit 1; } || exit 0'

The advantage of ExecCondition= over ExecStartPre=, is that in this example, it will check if this service instance is the "main" and will exit with a failed status code, so you can easily notice it; also you can use as a trigger for ExecStartPost= and ExecStopPost=.

One recommendation is to use many entries of ExecCondition= instead running too many commands in a single entry despite permitted; it will enhance troubleshooting and prevent failed state due timeout.

Thiago Conrado
  • 245
  • 2
  • 7
  • What does the `%i` in that code do? – joshua.r.smith Jul 17 '21 at 22:37
  • 1
    if the service that you are testing have instances, %i, will be translated to the instance name so, the condition will operate only for a specific instance http://0pointer.de/public/systemd-man/systemd.unit.html – Thiago Conrado Jul 21 '21 at 12:20