Systemd: timers,targets,interactive services

boozlachu
4 min readFeb 5, 2020

--

About

In linux development, sometimes, we require to create interactive start/stops scripts. With System V init system it is quite simple, with Systemd the same task is a bit more complicated. But, anyway, Systemd has it’s own timers and they looks pretty good.

Systemd-targets are, definetly, not an alternative to system V runlevel. First targets dont’t have hierarchy, only dependencies. For example, you can get runlevel 5 without start runlevel. Second in systemd you can group services by tasks (i.e. you can create lamp.target for lamp server) and control this group by start/stop commands (not so simple as in system V, but it works).

There are many articles about this, but i don’t want to write anything that was mentioned before. Instead i would like to focus on others — interactive services of systemd.

Interactive service in systemd — why to use ?

In normal operation, interactive services are not needed. But, if you are a firmware developer, your life is not so simple as that.

First task — installer. It shoud be interactive by default.
Second task — some confirmation on shutdown. For example, i can get confirmation for system upgrade. How does it work:

  • System starts
  • User logs-in and work
  • OS get firmware from update-server
  • On shutdown, system will ask user : “Install upgrade, y/n?”

This schema is not my dream, it’s my own work experience 😄

Lesson 1. Systemd interactive script on start

As an example, i will show my installer-call variant. It’s a script called from installer.service, which in turn is called by installer.target. Installer.target is my final.target in my firmware.

cat installer.service
[Unit]
# simple installer
Description=installer interactive dialog
[Service]
# Run oneshot, byt only where all other starts.
Type=idle
# Command call installer
ExecStart=/usr/bin/installer.sh
# user interaction in tty3
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes
[Install]
WantedBy=installer.target

Beginning of installer-script:

#!/bin/bash
# Go to tty3
chvt 3
echo "Install, y/n ?"
read user_answer

Lesson 2. Systemd interactive script on stop

Interactive service on start is simple. But becomes much harder on shutdown, because systemd shutdown-commands are not running on tty! You can test it — add a ‘tty’ command to your script and save it’s output.

The reason for the question is why is this needed? For example, I use an interactive shutdown script to ask the user whether to update the system or not? This is not my idea, but my technical requirements dictate this option.

I have read a lot if internet pages to find out — how to do it. Most of them recommend to make a service with the RemainAfterExit option. It’s good for non-interactive services. Example:

[Unit]
Description=non-interactive shutdown-only service example
[Service]
Type=oneshot
ExecStart=/bin/true
ExecStop=/usr/bin/my_shutdown_script.sh
RemainAfterExit=yes

How does it work? On service-start /bin/true command will be invoked. After that, the service will have active (running) status. On service stop /usr/bin/my_shutdown_script.sh will be invoked as a command.

Of couse, execStart can be a normal call like /usr/bin/my_startup_script.sh

This solution pretty work pretty well, not taking in mind one particular problem — my_shutdown_script.sh will be run without a tty attached, so it can’t interact with user.

My own solution is not so simple, but it works.
In systemd, reboot/poweroff/shutdown is not a binary file but a soft link to systemd binary:

ls -l /sbin/poweroff 
lrwxrwxrwx 1 root root 14 sep 30 18:23 /sbin/poweroff -> /bin/systemctl

Ok, that’s mean, that i can easily replace it with custom scripts.

Next, reboot/poweroff/shutdown is targets of systemd:

systemctl list-unit-files |grep  "shutdown\|poweroff\|reboot"
akmods-shutdown.service disabled
canberra-system-shutdown-reboot.service disabled
canberra-system-shutdown.service disabled
dracut-shutdown.service static
iscsi-shutdown.service static
plymouth-poweroff.service static
plymouth-reboot.service static
systemd-poweroff.service static
systemd-reboot.service static
poweroff.target disabled
reboot.target enabled
shutdown.target static
virt-guest-shutdown.target static

So the solution is:

  • replace /sbin/poweoff /sbin/shutdown /sbin/reboot with my custom scripts, which isolate (call) my shutdown.target
  • in shutdown.target run shutdown.service as interactive script on tty3
  • after interactive part, run normal shutdown/reboot/poweroff from systemd

my_shutdown.target

[Unit]
Description=KTL shutdown
AllowIsolate=yes
Wants=ktl_shutdown.service

my_shutdown.service

[Unit]
Description=MY shutdown
[Service]
Type=oneshot
ExecStart=/usr/bin/my_shutdown.sh
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes
WantedBy=my_shutdown.target

my_shutdown.sh

#!/bin/bash --loginif [ -f /tmp/reboot ];then
command="systemctl reboot"
elif [ -f /tmp/shutdown ]; then
command="systemctl shutdown"
else
exit 1
fi
# Here you can write needed commands
# For example, save files to ftp-server on disk-less station
$command

/sbin/reboot

#!/bin/shtouch /tmp/shutdown
sudo systemctl isolate my_shutdown.target

/sbin/poweroff

#!/bin/shtouch /tmp/shutdown
sudo systemctl isolate my_shutdown.target

Lesson 3 timers. Good, but cron will be alive

Systemd timers can be used as an alternative to cron. Of couse, not always. Good information can be found here: https://wiki.archlinux.org/index.php/Systemd/Timers#As_a_cron_replacement

There are two types of systemd.timers:

  • realtime timers (works as cron jobs on calendar events)
  • monotonic timers (activate after a time span relative to a starting time).

In my experience, i use a monotonic timer for logrotate job. Yes, i am aware about journald, but some software (lighttpd) don’t make much use of it.

You need to create 2 files for systemd.timer: timer unit files and a normal service unit file.

Be careful! Timer name and service name must be identical.

logrotateTimer.service

[Unit]
Description=run logrotate
[Service]
ExecStart=logrotate /etc/logrotate.conf

logrotateTimer.timer

[Unit]
Description=Run logrotate
[Timer]
# First start - after 15min from system start
OnBootSec=15min
# Next runs - ewvery 15 minutes
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target

List of references

https://wiki.archlinux.org/index.php/Systemd/Timers
https://www.freedesktop.org/software/systemd/man/

--

--