This setup allows for attaching inhibitors of remote systems as dependencies of systemd units.
This is especially useful when a task requires a remote system to be available to complete successfully.
This configuration can be applied both to system and user service units.
The inhibitor service template (stub file):
# ~/.config/systemd/user/remote-inhibit-@.service
[Unit]
Description=Inhibit remote machine %j during active operations (unit: %i)
StopWhenUnneeded=true
[Service]
Type=exec
Restart=no
ExecStart=/usr/bin/ssh -aqtto IdentityAgent=none %u@%j \
systemd-run --user --scope \
systemd-inhibit --who %u@%l --why %i sleep infinity
ExecStop=/bin/kill -INT $MAINPIDTo use the template stub, hardlink it to include the hostname or FQDN of the remote system:
ln ~/.config/systemd/user/remote-inhibit-@.service \
~/.config/systemd/user/remote-inhibit-hostname@.serviceAnd add a requires drop-in on the dependent service (example):
systemctl --user edit --drop-in requires my-service.servicePopulate it with the following contents:
# ~/.config/systemd/user/my-service.service.d/requires.conf
[Unit]
Requires=remote-inhibit-hostname@%n.serviceNow anytime my-service.service starts, it will start remote-inhibit-hostname@my-service.service.service.
For this configuration to work, you'll need to setup ssh keys on the remote system for passwordless login:
ssh-keygen -t ed25519 # if no key exists already
ssh-copy-id user@hostname # follow the promptsYou will need to create a PolicyKit rule on the remote system to allow normal users to inhibit shutdown:
// /etc/polkit-1/rules.d/50-inhibit-shutdown-sudo.rules
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.login1.inhibit" ||
action.id == "org.freedesktop.login1.inhibit-block-shutdown")
&& subject.isInGroup("sudo")) {
return polkit.Result.YES;
}
});Replace sudo with the admin group you want to grant access to, then restart polkit.service.
- The stub link is done via hardlink instead of symlink, since systemd refuses to follow nested symlinks.
- The remote hostname is taken from the local part of the inhibit unit name (between
inhibit-and@).- Encoding a long name or IP can be avoided by adding an alias in
~/.ssh/configand using that.
- Encoding a long name or IP can be avoided by adding an alias in
- The inhibit instance name is derived from the full unit name of the invoking service unit.
- This also applies to other instance units. Ex:
inhibit-hostname@myservice@instance1.service.service
- This also applies to other instance units. Ex:
- The inhibit unit assumes the remote user will be the same as the one running the service.
- If needed the unit file can be adjusted by changing
%uwith another username.
- If needed the unit file can be adjusted by changing
- The setup works best with passwordless SSH access, so a local keyfile is preferable to an agent.
- If necessary, the key can be restricted in the remote
~/.ssh/authorized_hostsfile.
- If necessary, the key can be restricted in the remote
- For more details on the unit specifiers applied here, see
systemd.unit(5). - For more details on the inhibit interface, see
systemd-inhibit(1). - For more detauls on the PolicyKit rules, see
polkit(8).