2

I have a service that runs batch jobs on a number of lab machines. The lab machines are desktop machines, and I want to run the service only if no user is currently logged in. That means:

  • When a user logs in, the service should be stopped.
  • When the last user logs out, the service should be started.

My current idea is to use Conflicts=, but this has a number of problems:

  • The service is not started again when the last user logs out.
  • Starting the service manually would lead to a forced logout of all users.
  • I don't know how to glob instantiated services (i.e. I would need Conflicts=user@*.service)

How do I define such a systemd service without the problems above?

1 Answer 1

1

Starting the service manually would lead to a forced logout of all users.

Instead of using Conflicts= in your service, create a new systemd target, vacant.target:

[Unit]
Description=units that should run when users aren't logged in

[Install]
WantedBy=multi-user.target

You might want your batch jobs or whatever to run in the time after systemd starts, but before users log in. If so, enable the new target: systemctl enable vacant.target. Perhaps you're wondering about the lack of Conflicts=, and having attempted this before, you're thinking:

I don't know how to glob instantiated services (i.e. I would need Conflicts=user@*.service)

Conflicts= affects the conflicted and conflicting units in essentially the same way. Globbing is not possible, so create a drop-in with systemctl edit '[email protected]':

[Unit]
Conflicts=vacant.target

[Service]
ExecStopPost=+/usr/local/bin/vacancy

Note the additional section that causes a script to run after each logout, addressing another problem:

The service is not started again when the last user logs out.

Due to the leading +, the script /usr/local/bin/vacancy runs with the privilege to operate on systemd units:

#!/bin/bash
# Start vacant.target if the list of logged-in users is empty.
[[ -z $(users) ]] && systemctl start vacant.target

Whatever service, timer, or other unit you want to run when no users are logged in, integrate this into the unit file:

[Unit]
StopWhenUnneeded=yes

[Install]
WantedBy=vacant.target manual.target

Finally, enabling your unit will trigger it to run with the rest of vacant.target. StopWhenUnneeded=yes will prevent the manual starting of the unit. To work around this, create manual.target:

[Unit]
Description=units that normally run when users aren't logged in

Run your units manually with systemctl start manual.target.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .