Systemd – Stop Service Before Suspend, Start After Resume

linuxsystemdUbuntu

What I want

I have a systemd service that I would like to have stopped before suspend/shutdown, and start up again after resume.

System details

System details below.

$ lsb_release -dc
Description:    Ubuntu 20.04.1 LTS
Codename:   focal

$ systemd --version
systemd 245 (245.4-4ubuntu3.3)
+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=hybrid

What I have so far

I have two services, myservice-resume.service and myservice-suspend.service respectively starting and stopping a python process at suspend and resume. The python script issues commands to an SDK server that controls RGB lighting. When on is passed as an argument (as in ExecStart), the process must be left running in the background to keep issuing commands as part of a loop. When the process catches a SIGINT signal the lighting is switched off and gracefully exits. In this setup, myservice-suspend.service is triggered before suspend and causes stopping of myservice-resume.service due to conflict.

myservice-resume.service

[Unit]
Description=Start myservice-resume.service after suspend and shutdown

[Service]
Type=simple
ExecStart=/path/to/python3 /path/to/script.py on

myservice-suspend.service
[Unit]
Description=Stop myservice-resume.service before suspend and shutdown
Before=suspend.target shutdown.target
Conflicts=myservice-resume.service

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

[Install]
WantedBy=suspend.target shutdown.target

In this setup, I start the service (and lighting) using systemctl start myservice-resume.service and successfully turn off lighting using systemctl start myservice-suspend.service, systemctl stop myservice-resume.service, or by doing a system suspend using systemctl suspend. I'd like to have the first service, myservice-resume.service, automatically start again on system resume. I'd imagine that this would involve adding some clever After/Before/WantedBy targets in the [Unit] and [Install] sections, but I can't determine an appropriate way to set this up.

Research/What I've tried

A related post (Systemd: stop service before suspend, restart after resume) hinted that I could configure a service to run after resume from suspend by adding After=suspend.target to the Unit section of myservice-resume.service. I've tried this, but the systemctl log shows that the unit was not started again on resume.

This post (Writing systemd unit file for suspend/resume) points the OP to the systemd man pages to come up with a solution (and clarifies the purpose of After/WantedBy), but I couldn't find a solution here either.

Best Answer

The need for an After= or Before= can finally be seen in examples from archlinux (a remarkable source of help as usual). Based on that link, there are two solutions to running a command on suspend and resume.

One method is to use two units, say mysyssuspend and mysysresume. The following examples just run the date command to syslog so we can see when they get activated:

/etc/systemd/system/mysyssuspend.service

[Unit]
Before=suspend.target
[Service]
Type=simple
StandardOutput=syslog
ExecStart=/bin/date +'mysyssuspend start %%H:%%M:%%S'
[Install]
WantedBy=suspend.target

/etc/systemd/system/mysysresume.service

[Unit]
After=suspend.target
[Service]
Type=simple
StandardOutput=syslog
ExecStart=/bin/date +'mysysresume start %%H:%%M:%%S'
[Install]
WantedBy=suspend.target

As usual, do a systemctl daemon-reload and systemctl enable mysyssuspend mysysresume after creating the unit files.

The first unit has a Before dependency on the suspend target and gets run when the computer enters suspend. The second unit similarly has an After dependency, and gets run on resuming.

The other method puts all the commands in a single unit: /etc/systemd/system/mysuspendresume.service

[Unit]
Before=sleep.target
StopWhenUnneeded=yes
[Service]
Type=oneshot
StandardOutput=syslog
RemainAfterExit=yes
ExecStart=/bin/date +'mysuspendresume start %%H:%%M:%%S'
ExecStop=/bin/date +'mysuspendresume stop %%H:%%M:%%S'
[Install]
WantedBy=sleep.target

This works with StopWhenUnneeded=yes, so the service is stopped when no active service requires it. The sleep target also has StopWhenUnneeded, so when it is finished it will run ExecStop of our unit. The RemainAfterExit is needed so that our unit is still seen as active, even after ExecStart has finished.

I tested both of these methods on Ubuntu 18.04.5 with systemd version 237 and they both seem to work correctly.


Rather than trying to merge your requirement into the above working mechanisms, it is probably more pragmatic to use one of them to stop/start an independent unit. For example, use the second method and add a mylongrun service:

/etc/systemd/system/mysuspendresume.service

[Unit]
Before=sleep.target
StopWhenUnneeded=yes
[Service]
Type=oneshot
StandardOutput=syslog
RemainAfterExit=yes
ExecStart=-/bin/date +'my1 %%H:%%M:%%S' ; /bin/systemctl stop mylongrun ; /bin/date +'my2 %%H:%%M:%%S'
ExecStop=-/bin/date +'my3 %%H:%%M:%%S' ; /bin/systemctl start mylongrun ; /bin/date +'my4 %%H:%%M:%%S'
[Install]
WantedBy=sleep.target

/etc/systemd/system/mylongrun.service

[Unit]
Description=Long Run
[Service]
Type=simple
StandardOutput=syslog
ExecStart=/bin/bash -c 'date +"my11 %%H:%%M:%%S"; while sleep 2; do date +"my12 %%H:%%M:%%S"; done'
ExecStop=/bin/bash -c 'date +"my13 %%H:%%M:%%S"; sleep 10; date +"my14 %%H:%%M:%%S"'
[Install]
WantedBy=multi-user.target

Testing this by starting mylongrun then closing the lid gives the following journalctl entries:

09:29:19 bash[3626]: my12 09:29:19
09:29:21 bash[3626]: my12 09:29:21
09:29:22 systemd-logind[803]: Lid closed.
09:29:22 systemd-logind[803]: Suspending...
09:29:22 date[3709]: my1 09:29:22
09:29:22 systemd[1]: Stopping Long Run...
09:29:22 bash[3715]: my13 09:29:22
09:29:23 bash[3626]: my12 09:29:23
09:29:25 bash[3626]: my12 09:29:25
09:29:27 bash[3626]: my12 09:29:27
09:29:29 bash[3626]: my12 09:29:29
09:29:31 bash[3626]: my12 09:29:31
09:29:32 bash[3715]: my14 09:29:32
09:29:32 systemd[1]: Stopped Long Run.
09:29:32 date[3729]: my2 09:29:32
09:29:32 systemd[1]: Reached target Sleep.
09:29:33 systemd[1]: Starting Suspend...

We can see the long running stop command (sleep 10) completed correctly. On resume, the long run command is started again:

09:35:12 systemd[1]: Stopped target Sleep.
09:35:12 systemd[1]: mysuspendresume.service: Unit not needed anymore. Stopping.
09:35:12 systemd[1]: Reached target Suspend.
09:35:12 date[3813]: my3 09:35:12
09:35:12 systemd[1]: Started Long Run.
09:35:12 date[3817]: my4 09:35:12
09:35:12 bash[3816]: my11 09:35:12
09:35:14 bash[3816]: my12 09:35:14
09:35:16 bash[3816]: my12 09:35:16
09:35:18 bash[3816]: my12 09:35:18