On demand activation with systemd-sockets

Yannik Schmidt
3 min readSep 19, 2020

--

In this article I will showcase a template to activate an arbitrary application once it is accessed and deactivate it again automatically if it’s no longer used. While it is not difficult per se it unfortunately requires a lot of systemd units and can be confusion at first. If you only want the code go to my GitHub.

Overview

Actual service

Assuming we have a file /usr/bin/roflcopter.sh that runs continuously once started and provides a service on a Unix or network socket, we first need a systemd unit that starts this program.

[Unit]
Description=ScriptWapper
Requires=check-deactivate.service

[Service]
Type=simple
ExecStart=/usr/bin/roflcopter.sh
ExecStop=pkill roflcopter

[Install]
WantedBy=multi-user.target

Systemd socket proxy

We now need a systemd managed socket which can proxy connections, meaning forward thus connections from the systemd socket to the actual applications once they are activated. Some programs don’t need this intermediary and support systemd sockets directly, but many don’t.

[Unit]
Description=Socket Proxy for script-wrapper
After=script-wrapper.service
Requires=script-wrapper.service

[Service]
ExecStart=/lib/systemd/systemd-socket-proxyd 127.0.0.1:ANYPORT

[Install]
WantedBy=multi-user.target

Systemd socket

Then we need the actual systemd socket that kicks off every other unit once a it receives a connection attempt.

[Unit]
Description=Socket activator for roflcopter.sh
PartOf=script-wrapper-proxy.service

[Socket]
ListenStream=ANYPORT
BindIPv6Only=both
Accept=false

[Install]
WantedBy=multi-user.target

Checking if the service can be deactivated

A “type simple” systemd service expects the program to run continuously until stopped. It would be nice if our script would just “know” by itself if it can stop, however if you apply this solution to an existing application it might be preferable to have an external, second script that is regularly called to check if the service should continue to run. For this we need a systemd-timer and unfortunately since a timer cannot call a script but only another Unit we also need a wrapper for the script just like for our roflcopter.sh.

In our case we call this unit check-deactivate.service and put as a requirement of our ScriptWrapper.service so that it is started when the ScriptWrapper unit is started.

The script is expected to stop once it has finished checking the status and act accordingly (meaning stopped the service or let it run). It therefore should be of type oneshot.

/usr/bin/checker.sh
if [ users -gt o]
systemctl stop ScriptWrapper
-----------------------------------------[Unit]
Description=check if unit can be stopped

[Service]
Type=oneshot
ExecStart=/usr/local/bin/checker.sh

And the timer with correct dependencies:

[Unit]
Description=call checker-service
After=script-wrapper.service
PartOf=script-wrapper.service

[Timer]
OnUnitActiveSec=15min
OnActiveSec=14min
Persistent=false
Unit=script-wrapper-checker.service

Timeouts when waiting for the application to start

Depending on how long the application needs to start, it might be that the client side application perceives the connection attempt as a timeout. Which essentially means that the client has to reinitiate the connection which might be perceived as bad user experience.

Costs of start and stop

Starting and stopping some applications may not fare well against just letting it idle. Consider carefully if the saved idle time is worth the activation and deactivation costs.

Racecondition between shutdown and startup request

If a request is made during the servers services shutdown, the service will not restart. Could be circumvented by having a startup script that waits for the shutdown, though even if it does happen it only means a client has to do another connection attempt.

Originally published at https://blog.atlantishq.de.

--

--

Yannik Schmidt

Python programmer at heart, Linux Expert at work, GameDev on my good days, web developer on the bad days.