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=yesWantedBy=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/