Systemd, интерактивни скриптове и таймери

Systemd, интерактивни скриптове и таймери

Въведение

При разработка под linux възникват задачи за създаване на интерактивни скриптове, изпълнявани при включване или изключване на системата. В system V това беше лесно, но с systemd внася корекции. Затова то може да използва собствените си таймери.

Защо са нужни target

Често пишат, че target служат като аналог на runlevel в system V -init. Не съм съгласен. Те са повече и могат да разделят пакетите на групи и, например, да стартират група от услуги с една команда, да извършват допълнителни действия. Освен това, те нямат йерархия, само зависимости.

Пример за target при включване (преглед на възможности) с стартиране на интерактивен скрипт

Описание на самия target:

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

Този target ще стартира, когато се стартира multi-user.target и ще извика installer.service. В този случай може да има няколко такива услуги.

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

И накрая, пример за изпълняван скрипт:

#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answer

Най-важното е да изберете final.target - target, към който системата трябва да достигне при стартиране. В процеса на стартиране systemd ще премине през зависимостите и ще стартира всичко необходимо.
Можете да изберете final.target по различен начин, аз използвах за това опцията на зареждача.

Крайната стартиране изглежда така:

  1. Стартира зареждача
  2. Зареждача започва стартиране на фърмуера, предавайки параметъра final.target
  3. Systemd започва стартиране на системата. Последователно преминава към installer.target или work.target от basic.target през техните зависимости (например, multi-user.target). Последните водят системата до работа в нужния режим.

Подготовка на фърмуера за стартиране

При създаването на фърмуери винаги възниква задача за възстановяване на състоянието на системата при стартиране и неговото запазване при изключване. Под състояние се разбират конфигурационни файлове, дампове на бази данни, настройки на интерфейсите и т.н.

Systemd стартира процеси в един target паралелно. Има зависимости, които определят последователността на стартиране на скриптовете.

Как работи това в моя проект ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)

  1. Системата стартира
  2. Стартира услугата settings_restore.service. Тя проверява наличието на файла settings.txt в данните. Ако го няма, на негово място се поставя еталонен файл. След това се извършва възстановяване на системните настройки:
    • администраторска парола
    • hostname,
    • часова зона
    • локал
    • Определяне на наличието на излишно пространство на носителя. По подразбиране размерът на образа е малък — за удобство при копиране и запис на носителя. При стартиране се проверява дали има свободно пространство. Ако има, дискът се преизмерва.
    • Генериране на machine-id от MAC адреса. Това е важно за получаване на един и същ адрес по DHCP
    • Настройки на мрежата
    • Ограничаване на размера на логовете
    • Подготвя се външен диск (ако е активирана съответната опция и дискът е нов)
  3. стартира се postgresq
  4. стартира се услугата restore. Тя е необходима за подготовка на Zabbix и неговата база данни:
    • Проверява се дали базата данни zabbix вече съществува. Ако не, тя ще бъде създадена от инициализиращите дампове (предоставят се с Zabbix)
    • създава се списък с часови зони (необходимо за тяхното отображение в уеб интерфейса)
    • Намира се текущият IP, той е изведен в issue (покана за влизане в консолата)
  5. Променя се поканата — появява се фразата Ready to work
  6. Прошивката е готова за работа

Важни са файловете на услугите, именно те определят поредността на стартиране

[Unit]
Description=възстановяване на системните настройки
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

Както се вижда, зададох зависимости, за да стартира първо моя скрипт и след това да се свърже мрежата и стартира СУБД.

И втората услуга (подготовка на 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

Тук е малко по-сложно. Стартирането също е в multi-user.target, но СЛЕД стартиране на СУБД postgresql и моя setting_restore. Но ПРЕДИ стартиране на услугите Zabbix.

Услуга с таймер за logrotate

Systemd може да замени CRON. Наистина. И точността не е до минута, а до секунда (в случай, че е необходимо). Може да се създаде монотонен таймер, който да се извиква по таймаут от събитие.
Точно такъв монотонен таймер, който брои времето от стартиране на машината, създадох.
За това ще са необходими 2 файла
logrotateTimer.service — описание на услугата:

[Unit]
Description=изпълняване на logrotate

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

Всичко е просто — описание на командата за стартиране.
Вторият файл logrotateTimer.timer — той задава работата на таймерите:

[Unit]
Description=Изпълняване на logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

Какво тук е налично:

  • описание на таймера
  • Време на първоначалното стартиране, започвайки от зареждането на системата
  • период на последващи стартирания
  • Зависимост от услугата на таймерите. Всъщност, това е редица, която прави таймера

Интерактивен скрипт при изключване и вашият таргет за изключване

В друга разработка ми се наложи да направя по-сложен вариант за изключване на машината — чрез собствен таргет, за да изпълня множество действия. Обикновено се препоръчва да се създаде услуга oneshot с опция RemainAfterExit, но това не дава възможност за създаване на интерактивен скрипт.

Работата е там, че командите, стартирани с опция ExecOnStop, се изпълняват извън TTY! Просто проверете — вкарайте командата tty и запазете нейния изход.

Затова реализирах изключването чрез собствен таргет. За 100% точност не претендирам, но работи!
Как беше направено (в общи линии):
Създаден таргет my_shutdown.target, който не зависеше от никого:
my_shutdown.target

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

При преминаване в този таргет (чрез systemctl isolate my_shutdown.target), той стартира услугата my_shutdown.service, задачата на която е проста — да изпълни скрипта my_shutdown.sh:

[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 --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

Бележка. Използване на файловете \/tmp\/reboot и \/tmp\/shutdown. Не можете да извикате таргета с параметри. Можете да извикате само услугата.

Но използвам таргет, за да имам гъвкавост в работата и гарантиран ред на изпълнение на действията.

Но най-интересното всъщност беше след това. Машината трябва да се изключи\/рестартира. И тук има 2 варианта:

  • Да замените командите reboot, shutdown и др. (всички те са символни линкове на systemctl) с вашия скрипт. Вътре в скрипта — преминаване в my_shutdown.target. А скриптите вътре в таргета след това директно извикват systemctl, например, systemctl reboot
  • По-опростен, но неосвежаващ вариант. Във всички интерфейси да извикате не shutdown\/reboot\/други, а директно да извикате таргета systemctl isolate my_shutdown.target

Избрах първия вариант. В systemd reboot (както и poweroff) са символни линкове на systemd.

ls -l \/sbin\/poweroff 
lrwxrwxrwx 1 root root 14 сеп 30 18:23 \/sbin\/poweroff -> \/bin\/systemctl

Затова те могат да бъдат заменени с вашите скриптове:
reboot

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

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster