enabling and disabling a systemd service in Python using DBus

The upcoming picockpit-client reacts to deletion events from the frontend by disabling it’s service (the application will stay installed, however).

Similarly, when you decide to run picockpit-client connect again, the service will be re-enabled and started automatically for you.

This is possible using the Python DBus interface.

the DBus is a bus system for interacting with other applications. The applications & system services publish a certain interface over the DBus, which you can use.

Here’s how to do it:

disable a service:

import dbus

system_bus = dbus.SystemBus()
systemd1 = system_bus.get_object(‘org.freedesktop.systemd1’, ‘/org/freedesktop/systemd1’)
manager = dbus.Interface(systemd1, ‘org.freedesktop.systemd1.Manager’)
manager.DisableUnitFiles([‘picockpit-client.service’], False)

manager.Reload()

image

We open the SystemBus – there is a SessionBus as well ( dbus.SessionBus() ) which is used to interact with a user session. In this case we want to access System services, however.

up to manager these are “standard” calls – in order to access systemd you need to go through them.

the next line is where we instruct the manager to DisableUnitFiles. Here’s some more notes about that:

  • the first variable is an array of strings – even if you want to disable just one service, you pass it as an array.
  • you pass in the name of your service including the ‘.service’ part – e.g. picockpit-client.service
  • the second variable is required and would be False to disable for every boot (and True to disable just for this runtime)

Here’s the signature for DisableUnitFiles from the official DBus / systemd / Freedesktop.org documentation:

image

Note, it is important to provide all variables when interacting with the DBus interface! They are not optional and “set to sane defaults”, as you might be used from many other interfaces / APIs.

Finally, I’ve read online that the changes need to be committed by using manager.Reload() (the documentation says: Reload() may be invoked to reload all unit files)

enable and start a service:

import dbus

system_bus = dbus.SystemBus()
systemd1 = system_bus.get_object(‘org.freedesktop.systemd1’, ‘/org/freedesktop/systemd1’)
manager = dbus.Interface(systemd1, ‘org.freedesktop.systemd1.Manager’)
manager.EnableUnitFiles([‘picockpit-client.service’], False, True)
manager.Reload()
job = manager.RestartUnit(‘picockpit-client.service’, ‘fail’)

image

Similar to the above, with the following difference for EnableUnitFiles:

  • there are two boolean variables after the array of strings which NEED to be passed in
  • the first boolean variable, as above, determines whether the service is to be enabled for just this runtime (True) or generally (False);
  • the second boolean variable is, somewhat cryptically, described as: “The second one controls whether symlinks pointing to other units shall be replaced if necessary.”. I have decided to set it to True.

Here’s the EnableUnitFiles signature from the documentation:

image

Looking at it, my choice of “True” might possibly not be sensible for other applications (“force” implies a kind of override of security).

Finally, RestartUnit to actually start the service:

  • note that this takes a string value, not an array of strings
  • the second value is a string specifying the mode. Please refer to the official documentation for the different modes which are possible.

Here is the RestartUnit signature:

image

Note: I do not handle the return values, ideally and especially for critical applications, you would want to look into that – refer to the official documentation.

Errors

Signature mismatch

ERROR:dbus.connection:Unable to set arguments ([‘picockpit-client.service’],) according to signature ‘asb’: <class ‘TypeError’>: More items found in D-Bus signature than in Python arguments

If you get an error like above, check whether you are passing all the parameters as required by the signature in the documentation.

In this case I had (wrong):

manager.DisableUnitFiles([‘picockpit-client.service’])

instead of the correct:

manager.DisableUnitFiles([‘picockpit-client.service’], False)

Also be sure to check if you are passing in a string (s), where an array of strings (as) is required.


Ref