Einführung
Bei der Entwicklung für Linux stellt sich die Aufgabe, interaktive Skripte zu erstellen, die beim Ein- oder Herunterfahren des Systems ausgeführt werden. In System V war das einfach, aber mit systemd werden Anpassungen vorgenommen. Es kann aber eigene Timer haben.
Warum brauchen wir Ziele?
Es wird oft geschrieben, dass das Ziel als Analogon zum Runlevel im System V-init dient. Ich bin grundsätzlich anderer Meinung. Es gibt mehr davon und Sie können Pakete in Gruppen einteilen und beispielsweise eine Gruppe von Diensten mit einem Befehl starten und zusätzliche Aktionen ausführen. Darüber hinaus gibt es keine Hierarchie, sondern nur Abhängigkeiten.
Beispiel eines Ziels bei Aktivierung (Funktionsübersicht) mit laufendem interaktivem Skript
Beschreibung des Ziels selbst:
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
Dieses Ziel wird gestartet, wenn multi-user.target gestartet wird und installer.service aufruft. Es kann jedoch mehrere solcher Dienste geben.
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
Und zum Schluss noch ein Beispiel für das ausgeführte Skript:
#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answer
Das Wichtigste ist, final.target auszuwählen – das Ziel, zu dem das System beim Start gelangen soll. Während des Startvorgangs geht systemd die Abhängigkeiten durch und startet alles, was es benötigt.
Es gibt verschiedene Möglichkeiten, final.target auszuwählen. Ich habe hierfür die Loader-Option verwendet.
Der endgültige Start sieht so aus:
- Der Bootloader startet
- Der Bootloader beginnt mit dem Starten der Firmware, indem er den Parameter final.target übergibt
- Systemd beginnt, das System zu starten. Geht nacheinander von basic.target über ihre Abhängigkeiten zu installer.target oder work.target (z. B. multi-user.target). Letztere bringen das System in den gewünschten Modus zum Laufen
Vorbereiten der Firmware für den Start
Bei der Firmware-Erstellung stellt sich immer die Aufgabe, den Systemzustand beim Start wiederherzustellen und beim Herunterfahren zu speichern. Status bedeutet Konfigurationsdateien, Datenbank-Dumps, Schnittstelleneinstellungen usw.
Systemd führt Prozesse im selben Ziel parallel aus. Es gibt Abhängigkeiten, mit denen Sie die Startreihenfolge von Skripten bestimmen können.
Wie funktioniert es in meinem Projekt (
- Das System startet
- Der Dienst „settings_restore.service“ wird gestartet. Er prüft, ob die Datei „settings.txt“ im Datenabschnitt vorhanden ist. Ist sie nicht vorhanden, wird an ihrer Stelle eine Referenzdatei abgelegt. Als nächstes werden die Systemeinstellungen wiederhergestellt:
- Administrator-Passwort
- Hostname,
- Zeitzone
- Gebietsschema
- Bestimmt, ob alle Medien verwendet werden. Standardmäßig ist die Bildgröße klein, um das Kopieren und Aufzeichnen auf Medien zu erleichtern. Beim Start prüft es, ob noch ungenutzter Speicherplatz vorhanden ist. Wenn dies der Fall ist, wird die Festplatte neu partitioniert.
- Generieren der Maschinen-ID aus der MAC-Adresse. Dies ist wichtig, um über DHCP die gleiche Adresse zu erhalten
- Netzwerkeinstellungen
- Begrenzt die Größe der Protokolle
- Das externe Laufwerk wird für den Betrieb vorbereitet (sofern die entsprechende Option aktiviert ist und das Laufwerk neu ist)
- Postgresq starten
- Der Wiederherstellungsdienst wird gestartet. Es wird benötigt, um Zabbix selbst und seine Datenbank vorzubereiten:
- Prüft, ob bereits eine Zabbix-Datenbank vorhanden ist. Wenn nicht, wird es aus Initialisierungs-Dumps erstellt (im Lieferumfang von Zabbix enthalten).
- Es wird eine Liste der Zeitzonen erstellt (wird zur Anzeige im Webinterface benötigt)
- Die aktuelle IP wird gefunden, sie wird im Issue angezeigt (Einladung zur Anmeldung an der Konsole)
- Die Einladung ändert sich – der Satz „Bereit zur Arbeit“ erscheint
- Die Firmware ist einsatzbereit
Die Servicedateien sind wichtig, sie legen die Reihenfolge ihres Starts fest
[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
Wie Sie sehen, habe ich Abhängigkeiten installiert, damit mein Skript zuerst funktioniert und erst dann das Netzwerk hochfährt und das DBMS startet.
Und der zweite Gottesdienst (Zabbix-Vorbereitung)
#!/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
Hier ist es etwas komplizierter. Der Start erfolgt ebenfalls in multi-user.target, aber NACH dem Start des Postgresql-DBMS und meiner Einstellung_restore. Aber BEVOR Sie mit den Zabbix-Diensten beginnen.
Timer-Dienst für Logrotate
Systemd kann CRON ersetzen. Ernsthaft. Darüber hinaus ist die Genauigkeit nicht auf die Minute, sondern auf die Sekunde genau (was ist, wenn es nötig ist). Oder Sie können einen monotonen Timer erstellen, der durch eine Zeitüberschreitung eines Ereignisses aufgerufen wird.
Es war der monotone Timer, der die Zeit vom Start der von mir erstellten Maschine zählt.
Dazu sind 2 Dateien erforderlich
logrotateTimer.service – die tatsächliche Beschreibung des Dienstes:
[Unit]
Description=run logrotate
[Service]
ExecStart=logrotate /etc/logrotate.conf
TimeoutSec=300
Es ist ganz einfach: Beschreibung des Startbefehls.
In der zweiten Datei logrotateTimer.timer funktionieren die Timer:
[Unit]
Description=Run logrotate
[Timer]
OnBootSec=15min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target
Was gibt es hier:
- Timer-Beschreibung
- Erster Startzeitpunkt, beginnend mit dem Systemstart
- Zeitraum weiterer Markteinführungen
- Abhängigkeit vom Timer-Dienst. Tatsächlich ist dies die Zeichenfolge, die den Timer erstellt
Interaktives Skript beim Herunterfahren und Ihr Abschaltziel
In einer anderen Entwicklung musste ich eine komplexere Version des Ausschaltens der Maschine durchführen – über mein eigenes Ziel, um viele Aktionen ausführen zu können. Normalerweise wird empfohlen, einen Oneshot-Dienst mit der Option „RemainAfterExit“ zu erstellen. Dies verhindert jedoch, dass Sie ein interaktives Skript erstellen können.
Tatsache ist jedoch, dass die von der Option ExecOnStop gestarteten Befehle außerhalb des TTY ausgeführt werden! Das lässt sich leicht überprüfen: Fügen Sie den Befehl tty ein und speichern Sie die Ausgabe.
Daher habe ich die Abschaltung über mein Ziel implementiert. Ich behaupte nicht, 100 % richtig zu sein, aber es funktioniert!
Wie es gemacht wurde (allgemein):
Ich habe ein Ziel my_shutdown.target erstellt, das von niemandem abhängig ist:
my_shutdown.target
[Unit]
Description=my shutdown
AllowIsolate=yes
Wants=my_shutdown.service
Beim Aufrufen dieses Ziels (über systemctl isolate my_shutdwn.target) wurde der Dienst my_shutdown.service gestartet, dessen Aufgabe einfach ist: das Ausführen des Skripts 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
- Innerhalb dieses Skripts führe ich die notwendigen Aktionen aus. Aus Gründen der Flexibilität und Bequemlichkeit können Sie dem Ziel viele Skripte hinzufügen:
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
Notiz. Verwenden der Dateien /tmp/reboot und /tmp/shutdown. Sie können das Ziel nicht mit Parametern aufrufen. Es ist nur Service möglich.
Aber ich nutze Target, um Flexibilität bei der Arbeit und eine garantierte Reihenfolge der Aktionen zu haben.
Das Interessanteste kam jedoch später. Die Maschine muss ausgeschaltet/neu gestartet werden. Und es gibt 2 Möglichkeiten:
- Ersetzen Sie die Befehle „reboot“, „shutdown“ und andere Befehle (sie sind immer noch symbolische Links zu systemctl) durch Ihr Skript. Gehen Sie im Skript zu „my_shutdown.target“. Und die Skripte im Ziel rufen dann systemctl direkt auf, zum Beispiel systemctl reboot
- Eine einfachere Option, aber sie gefällt mir nicht. Rufen Sie in allen Schnittstellen nicht Shutdown/Reboot/Other auf, sondern rufen Sie direkt das Zielsystemctl Isolate my_shutdown.target auf
Ich habe mich für die erste Option entschieden. In systemd sind reboot (wie poweroff) symbolische Links zu systemd.
ls -l /sbin/poweroff
lrwxrwxrwx 1 root root 14 сен 30 18:23 /sbin/poweroff -> /bin/systemctl
Daher können Sie sie durch Ihre eigenen Skripte ersetzen:
rebooten
#!/bin/sh
touch /tmp/reboot
sudo systemctl isolate my_shutdown.target
fi
Source: habr.com