
Wprowadzenie
Podczas pracy pod linuxem pojawiają się zadania związane z tworzeniem interaktywnych skryptów, które są uruchamiane przy włączeniu lub wyłączeniu systemu. W systemie V było to łatwe, ale systemd wprowadza zmiany. Z drugiej strony posiada własne timery.
Do czego służą targety
Często pisze się, że targety są odpowiednikiem runlevel w systemie V-init. Z całym szacunkiem, się z tym nie zgadzam. Ich jest więcej i można dzielić pakiety na grupy, a na przykład uruchamiać grupę usług jednym poleceniem, wykonując dodatkowe czynności. Poza tym nie mają hierarchii, tylko zależności.
Przykład targetu przy włączeniu (przegląd możliwości) z uruchomieniem interaktywnego skryptu
Opis samego targetu:
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.serviceTen target uruchomi się, gdy uruchomi się multi-user.target i wywoła installer.service. Może być kilka takich serwisów.
cat installer.service
[Unit]
# opis
Description=installer interactive dialog
[Service]
# Uruchomić raz, gdy reszta będzie uruchomiona
Type=idle
# Polecenie uruchamiające - wywołanie skryptu
ExecStart=\/usr\/bin\/installer.sh
# Interaktywna interakcja z użytkownikiem przez tty3
StandardInput=tty
TTYPath=\/dev\/tty3
TTYReset=yes
TTYVHangup=yes
[Install]
WantedBy=installer.targetI na koniec przykład wykonywanego skryptu:
#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answerNajważniejsze jest wybranie final.target - targetu, do którego system powinien dojść podczas uruchamiania. W trakcie uruchamiania systemd przejdzie przez zależności i uruchomi wszystkie potrzebne.
Wybór final.target można zrealizować na różne sposoby, użyłem do tego opcji bootloadera.
Ostateczne uruchomienie wygląda tak:
- Uruchamia się bootloader
- Bootloader rozpoczyna uruchamianie firmware'u, przekazując parametr final.target
- Systemd rozpoczyna uruchamianie systemu. Kolejno idzie do installer.target lub work.target z basic.target przez ich zależności (na przykład, multi-user.target). Ostatnie prowadzą system do pracy w odpowiednim trybie.
Przygotowanie firmware'u do uruchomienia
Przy tworzeniu firmware'ów zawsze pojawia się zadanie przywracania stanu systemu przy starcie i jego zachowania przy wyłączaniu. Pod stanem rozumie się pliki konfiguracyjne, zrzuty baz danych, ustawienia interfejsów itd.
Systemd uruchamia procesy w jednym targetcie równolegle. Istnieją zależności, które pozwalają określić kolejność uruchamiania skryptów.
Jak to działa u mnie w projekcie ( )
- System się uruchamia
- Uruchamiana jest usługa settings_restore.service. Sprawdza obecność pliku settings.txt w sekcji danych. Jeśli go nie ma, na jego miejsce wstawiany jest plik wzorcowy. Następnie następuje przywracanie ustawień systemowych:
- hasło administratora
- hostname,
- strefa czasowa
- lokalizacja
- Określenie, czy całe medium jest wykorzystywane. Domyślnych rozmiar obrazu jest niewielki — dla wygody kopii i nagrywania na nośniku. Przy starcie sprawdzane jest, czy są jeszcze niewykorzystane miejsca. Jeśli tak — dysk jest przekształcany.
- Generacja machine-id z adresu MAC. Jest to istotne dla uzyskania tego samego adresu przez DHCP
- Ustawienia sieciowe
- Ogranicza rozmiar logów
- Przygotowuje się zewnętrzny dysk (jeśli włączona jest odpowiednia opcja i dysk jest nowy)
- Uruchamia się postgresq
- uruchamiana jest usługa restore. Jest potrzebna do przygotowania Zabbixa i jego bazy danych:
- Sprawdzane jest, czy baza danych Zabbix już istnieje. Jeśli nie — tworzona jest na podstawie inicjalizujących zrzutów (dostarczane z Zabbixem)
- tworzona jest lista stref czasowych (potrzebna do ich wyświetlania w interfejsie webowym)
- Znajduje się bieżący adres IP, który jest wyświetlany w issue (zaproszenie do logowania się w konsoli)
- Zmienia się zaproszenie — pojawia się fraza Gotowy do pracy
- Oprogramowanie jest gotowe do pracy
Ważne są pliki usług, to one ustalają kolejność ich uruchamiania
[Unit]
Description=przywróć ustawienia systemu
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.targetJak widać, ustawiłem zależności, aby najpierw wykonał mój skrypt, a dopiero później uruchamiała się sieć i startowała bazodanowa SU.
I druga usługa (przygotowanie Zabbixa)
#!/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.targetTutaj jest trochę trudniej. Uruchomienie również w multi-user.target, ale PO uruchomieniu bazy danych PostgreSQL i mojego setting_restore. Ale PRZED uruchomieniem usług Zabbixa.
Usługa z timerem dla logrotate
Systemd może zastąpić CRON. Serio. Co więcej, dokładność nie co minutę, a co sekundę (a nuż się przyda). Można również stworzyć monotoniczny timer, wywoływany na podstawie zdarzenia.
To właśnie monotoniczny timer, liczący czas od uruchomienia maszyny, stworzyłem.
Do tego potrzebne będą 2 pliki
logrotateTimer.service — opis usługi:
[Unit]
Description=uruchom logrotate
[Service]
ExecStart=logrotate \/etc\/logrotate.conf
TimeoutSec=300Wszystko jest proste — opis polecenia uruchomienia.
Drugi plik logrotateTimer.timer — to on definiuje działanie timerów:
[Unit]
Description=Uruchom logrotate
[Timer]
OnBootSec=15min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.targetCo tutaj jest:
- opisanie timera
- Czas pierwszego uruchomienia, licząc od załadunku systemu
- okres kolejnych uruchomień
- Zależność od usługi timerów. Właściwie to jest ten wiersz, który tworzy timer
Interaktywny skrypt przy wyłączeniu i własny cel wyłączenia
W innej implementacji musiałem stworzyć bardziej złożoną wersję wyłączania maszyny — przez własny cel, aby wykonać wiele działań. Zwykle zaleca się utworzenie usługi oneshot z opcją RemainAfterExit, ale to nie daje możliwości utworzenia interaktywnego skryptu.
Chodzi o to, że komendy uruchamiane opcją ExecOnStop są wykonywane poza TTY! Można to prosto sprawdzić — wstaw komendę tty i zapisz jej wynik.
Dlatego zrealizowałem wyłączenie przez własny cel. Nie roszczę sobie pretensji o 100% poprawność, ale to działa!
Jak to było zrobione (w dużym skrócie):
Utworzyłem cel my_shutdown.target, który nie zależał od innych:
my_shutdown.target
[Unit]
Description=moje wyłączenie
AllowIsolate=yes
Wants=my_shutdown.service Po przejściu do tego celu (przez systemctl isolate my_shutdown.target), uruchamiał usługę my_shutdown.service, której zadaniem jest proste — wykonanie skryptu my_shutdown.sh:
[Unit]
Description=MOJE wyłączenie
[Service]
Type=oneshot
ExecStart=\/usr\/bin\/my_shutdown.sh
StandardInput=tty
TTYPath=\/dev\/tty3
TTYReset=yes
TTYVHangup=yes
WantedBy=my_shutdown.target- Wewnątrz tego skryptu wykonuję potrzebne działania. Można dodać wiele skryptów do celu, dla elastyczności i wygody:
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/
$commandUwaga. Użycie plików \/tmp\reboot i \/tmp\shutdown. Nie można wywołać celu z parametrami. Można tylko usługę.
Ale używam celu, aby mieć elastyczność w działaniu i gwarantowaną kolejność wykonania działań.
Jednak najciekawsze było potem. Maszynę trzeba wyłączyć\/zrestartować. I tu są 2 opcje:
- Zastąpić komendy reboot, shutdown i inne (są one zresztą linkami symbolicznymi do systemctl) własnym skryptem. Wewnątrz skryptu — przejście do my_shutdown.target. A skrypty wewnątrz celu następnie wywołują bezpośrednio systemctl, na przykład, systemctl reboot
- Prostsza, ale mi się nie podobająca opcja. Wszystkie interfejsy wywołują nie shutdown\/reboot\/inne, a bezpośrednio wywołują cel systemctl isolate my_shutdown.target
Wybrałem pierwszą opcję. W systemd reboot (jak i poweroff) są linkami symbolicznymi do systemd.
ls -l \/sbin\/poweroff
lrwxrwxrwx 1 root root 14 wrz 30 18:23 \/sbin\/poweroff -> \/bin\/systemctlDlatego można je zastąpić swoimi skryptami:
reboot
#!/bin/sh
touch /tmp/reboot
sudo systemctl isolate my_shutdown.target
fiŹródło: habr.com
