Systemd, interactive scripts and timers

Systemd, interactive scripts and timers

Introduction

When developing under linux, there are tasks of creating interactive scripts that are executed when the system is turned on or shut down. In system V this was easy to do, but with systemd it makes adjustments. But it knows its own timers.

Why are targets needed?

It is often written that targets are analogous to runlevel in system V -init. I fundamentally disagree. There are more of them and you can separate packages into groups and, for example, launch a group of services with one command, perform additional actions. Also, they don't have a hierarchy, only dependencies.

Target example on inclusion (feature overview) with interactive script execution

Description of the target itself:

cat installer.target
[Unit]
Description=My installer
Requires=multi-user.target 
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target 
AllowIsolate=yes
Wants=installer.service

This target will run when multi-user.target is run and will call installer.service. However, there may be several such services.

cat installer.service
[Unit]
# описаниС
Description=installer interactive dialog

[Service]
# Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·, ΠΊΠΎΠ³Π΄Π° ΠΎΡΡ‚Π°Π»ΡŒΠ½ΠΎΠ΅ Π±ΡƒΠ΄Π΅Ρ‚ Π·Π°ΠΏΡƒΡ‰Π΅Π½Π½ΠΎ
Type=idle
# Команда запуска - Π²Ρ‹Π·ΠΎΠ² скрипта
ExecStart=/usr/bin/installer.sh
# Π˜Π½Ρ‚Π΅Ρ€Π°ΠΊΡ‚ΠΈΠ²Π½ΠΎΠ΅ взаимодСйствиС с ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΌ Ρ‡Π΅Ρ€Π΅Π· tty3
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes

[Install]
WantedBy=installer.target

And finally, an example of a running script:

#!/bin/bash
# ΠŸΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΈΠΌ Π² tty3
chvt 3
echo "Install, y/n ?"
read user_answer

The most important thing is to choose final.target - target, to which the system should come at startup. During the startup process, systemd will go through the dependencies and run everything it needs.
You can select final.target in many ways, I used the loader option for this.

The final launch looks like this:

  1. Bootloader starts
  2. The bootloader starts the firmware by passing the final.target parameter
  3. systemd starts system startup. Goes sequentially to installer.target or work.target from basic.target via their dependencies (e.g. multi-user.target). The latter and lead the system to work in the desired mode.

Preparing the firmware for launch

When creating firmware, there is always the task of restoring the system state at startup and saving it at shutdown. State refers to configuration files, database dumps, interface settings, and so on.

Systemd runs a process on the same target in parallel. There are dependencies that allow you to determine the sequence in which scripts are run.

How does it work in my project ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)

  1. System starts
  2. The settings_restore.service service starts. It checks for the existence of the settings.txt file in the data section. If it is not there, then a reference file is put in its place. Next, the system settings are restored:
    • administrator password
    • hostname
    • time zone
    • locale
    • Determine if all of the media is in use. By default, the image size is small - for ease of copying and writing to media. At startup, it checks whether there is still unused space. If there is, the disk is repartitioned.
    • Generate machine-id from MAC address. This is important for obtaining the same address via DHCP.
    • Network settings
    • The size of logs is limited
    • An external disk is being prepared for work (if the corresponding option is enabled and the disk is new)
  3. start postgresq
  4. the restore service starts. It is needed to prepare zabbix itself and its database:
    • Checks if the zabbix database already exists. If not, it is created from initializing dumps (they are supplied with zabbix)
    • a list of time zones is created (needed to display them in the web interface)
    • The current IP is located, it is displayed in the issue (invitation to enter in the console)
  5. The invitation changes - the phrase Ready to work appears
  6. Firmware ready to go

Service files are important, they set the sequence of their launch

[Unit]
Description=restore system settings
Before=network.service prepare.service postgresql.service systemd-networkd.service systemd-resolved.service

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

[Install]
WantedBy=multi-user.target

As you can see, I installed dependencies so that my script would work first, and only then the network would rise and the DBMS would start.

And the second service (preparing zabbix)

#!/bin/sh
[Unit]
Description=monitor prepare system
After=postgresql.service settings_restore.service
Before=zabbix-server.service zabbix-agent.service

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

[Install]
WantedBy=multi-user.target

It's a bit more complicated here. The launch is also in multi-user.target, but AFTER starting the postgresql DBMS and my setting_restore. But BEFORE starting zabbix services.

Timer service for logrotate

Systemd can replace CRON. Seriously. Moreover, the accuracy is not up to a minute, but up to a second (and suddenly you need it). Or you can create a monotonous timer called by a timeout from an event.
It was the monotonous timer that counts the time from the start of the machine that I created.
This will require 2 files
logrotateTimer.service - the actual description of the service:

[Unit]
Description=run logrotate

[Service]
ExecStart=logrotate /etc/logrotate.conf
TimeoutSec=300

It's simple - a description of the launch command.
The second file is logrotateTimer.timer - this is what sets the timers to work:

[Unit]
Description=Run logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

What is here:

  • timer description
  • First run time, starting from system boot
  • period of further launches
  • Dependency on the timer service. In fact, this is a string and makes the timer

Interactive script on shutdown and custom shutdown target

In another development, I had to do a more complex option for turning off the machine - through my own target, in order to perform many actions. It is generally recommended to create a oneshot service with the RemainAfterExit option, but this does not create an interactive script.

But the fact is that the commands launched by the ExecOnStop option are executed outside the TTY! It's easy to check - paste the tty command and save its output.

Therefore, I implemented the shutdown through my target. I don't claim to be 100% correct, but it works!
How it was done (in general terms):
Created a target my_shutdown.target that did not depend on anyone:
my_shutdown.target

[Unit]
Description=my shutdown
AllowIsolate=yes
Wants=my_shutdown.service 

When switching to this target (via systemctl isolate my_shutdwn.target), it launched the my_shutdown.service service, the task of which is simple - to execute the my_shutdown.sh script:

[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

  • Inside this script, I perform the necessary actions. You can add many scripts to the target, for flexibility and convenience:

my_shutdown.sh

#!/bin/bash --login
if [ -f /tmp/reboot ];then
    command="systemctl reboot"
elif [ -f /tmp/shutdown ]; then
    command="systemctl poweroff"
fi
#Π’ΠΎΡ‚ здСсь Π½ΡƒΠΆΠ½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹
#НапримСр, cp /home/user/data.txt /storage/user/
    $command

Note. Using the files /tmp/reboot and /tmp/shutdown. You cannot call target with parameters. You can only service.

But I use target to have flexibility in work and guaranteed order of actions.

However, the most interesting thing happened later. The machine needs to be turned off/restarted. And there are 2 options:

  • Replace the reboot, shutdown and other commands (they are still symlinks to systemctl) with your own script. Inside the script is a transition to my_shutdown.target. And the scripts inside the target then call systemctl directly, for example, systemctl reboot
  • More simple, but I do not like the option. In all interfaces, call not shutdown / reboot / others, but directly call the systemctl target isolate my_shutdown.target

I chose the first option. In systemd, reboot (as well as poweroff) are symlinks to systemd.

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

Therefore, they can be replaced with your own scripts:
reboot

#!/bin/sh
    touch /tmp/reboot
    sudo systemctl isolate my_shutdown.target
fi

Source: habr.com

Add a comment