2

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.

3

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
6
  • Your demo services mysysresume.service and mysyssuspend.service work beautifully for your example, but doesn't seem to hold up for processes that need to run continuously in the background (as in my case). The suspend service should be able to (a) stop the resume service prior to suspend and (b) allow the resume service to stop fully before suspending. I have tried to implement this by adding Conflicts= in the [Unit] section of the suspend service, which does work, but the resume service does not start again after resume. – CatThatKilledCuriosity Nov 19 '20 at 6:22
  • Likewise for /etc/systemd/system/mysuspendresume.service, it falls over for long-running processes. In this case, I changed ExecStart= to point to a shell script that runs a while loop (while true; do echo "Running..."; sleep 1; done). Suspend is stuck waiting for the service to complete before suspending, which never happens. – CatThatKilledCuriosity Nov 19 '20 at 6:46
  • Instead of Conflicts= would it be possible to add to mysyssuspend.service an explicit ExecStart=-/usr/bin/systemctl stop mysysresume to stop that unit. – meuh Nov 19 '20 at 7:03
  • Sure can. Adding this and removing Conflicts= does allow mysyssuspend.service to stop mysysresume.service (as shown by systemctl status mysysresume.service), but mysysresume.service isn't started again after resume. – CatThatKilledCuriosity Nov 19 '20 at 7:25
  • I've added a final section with an example that separates the working suspend/resume hook unit from a second independent unit to control. – meuh Nov 19 '20 at 8:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.