
Introduzione
Nello sviluppo su Linux si presentano compiti legati alla creazione di script interattivi da eseguire all'accensione o allo spegnimento del sistema. Con System V questo era semplice, ma con systemd ci sono delle modifiche. Tuttavia, supporta i propri timer.
A cosa servono i target
Spesso si scrive che i target sono l'equivalente dei runlevel in System V init. Non sono affatto d'accordo. Ce ne sono di più e possono essere suddivisi in gruppi, ad esempio avviando un gruppo di servizi con un solo comando, eseguendo azioni aggiuntive. Inoltre, non hanno una gerarchia, solo dipendenze.
Esempio di target all'accensione (panoramica delle funzionalità) con l'avvio di uno script interattivo
Descrizione del target stesso:
cat installer.target
[Unit]
Description=Il mio installer
Requires=multi-user.target
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target
AllowIsolate=yes
Wants=installer.serviceQuesto target si avvierà quando viene avviato multi-user.target e chiamerà installer.service. Possono esserci più di questi servizi.
cat installer.service
[Unit]
# descrizione
Description=dialogo interattivo di installazione
[Service]
# Esegui una volta, quando tutto il resto sarà avviato
Type=idle
# Comando di avvio - chiamata allo script
ExecStart=/usr/bin/installer.sh
# Interazione interattiva con l'utente tramite tty3
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes
[Install]
WantedBy=installer.targetE infine, un esempio di script eseguibile:
#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answerLa cosa più importante è scegliere final.target — il target al quale il sistema deve arrivare all'avvio. Durante l'avvio, systemd seguirà le dipendenze e avvierà tutto il necessario.
Final.target può essere selezionato in vari modi; io ho usato l'opzione del bootloader per questo.
L'avvio finale appare così:
- Si avvia il bootloader
- Il bootloader inizia ad avviare il firmware, passando il parametro final.target
- Systemd inizia ad avviare il sistema. Procede in modo sequenziale verso installer.target o work.target da basic.target attraverso le loro dipendenze (ad esempio, multi-user.target). Queste ultime portano il sistema a funzionare nella modalità desiderata.
Preparazione del firmware per l'avvio
Durante la creazione dei firmware si pone sempre il problema di ripristinare lo stato del sistema all'avvio e di conservarlo allo spegnimento. Per stato si intendono i file di configurazione, i dump del database, le impostazioni delle interfacce, ecc.
Systemd avvia i processi in un bersaglio in parallelo. Ci sono dipendenze che permettono di determinare la sequenza di avvio degli script.
Come funziona nel mio progetto ( )
- Il sistema si avvia
- Si avvia il servizio settings_restore.service, che verifica la presenza del file settings.txt nella sezione dati. Se non è presente, viene posizionato un file di riferimento al suo posto. Successivamente avviene il ripristino delle impostazioni del sistema:
- password dell'amministratore
- hostname,
- fuso orario
- locale
- Determinazione se tutto il supporto è utilizzato. Di default, la dimensione dell'immagine è piccola — per facilitare la copia e la scrittura sul supporto. All'avvio si verifica se c'è ancora spazio inutilizzato. Se c'è, il disco viene ridimensionato.
- Generazione dell'machine-id dall'indirizzo MAC. Questo è importante per ottenere lo stesso indirizzo tramite DHCP.
- Impostazioni di rete
- Dimensione dei log limitata
- Preparazione del disco esterno per il lavoro (se l'opzione corrispondente è attivata e il disco è nuovo)
- Avvio di postgresq
- Il servizio di ripristino si avvia. È necessario per preparare Zabbix e il suo database:
- Controllo se esiste già un database zabbix. Se non esiste, viene creato dai dump di inizializzazione (inclusi nella fornitura di Zabbix)
- Viene creata una lista di fusi orari (necessaria per la loro visualizzazione nell'interfaccia web)
- Attuale IP trovato, viene visualizzato nell'issue (invito ad accedere alla console)
- L'invito cambia: appare la frase 'Pronto per lavorare'
- Il firmware è pronto per l'uso
I file dei servizi sono importanti, in quanto stabiliscono l'ordine del loro avvio
[Unit]
Description=Ripristino delle impostazioni di sistema
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.targetCome si può notare, ho impostato le dipendenze affinché il mio script venga eseguito prima che la rete venga avviata e il DBMS parta.
E il secondo servizio (preparazione di 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.targetQui è un po' più complicato. L'avvio avviene anch'esso in multi-user.target, ma DOPO l'avvio del DBMS postgresql e del mio setting_restore. Ma PRIMA dell'avvio dei servizi zabbix.
Servizio con timer per logrotate
Systemd può sostituire CRON. Sul serio. E con una precisione non al minuto, ma al secondo (e se mai ne avrete bisogno). Inoltre, è possibile creare un timer monotono, attivato tramite timeout di un evento.
Proprio il timer monotono, che conta il tempo dall'avvio della macchina, ho creato.
Per questo sono necessari 2 file
logrotateTimer.service — la descrizione del servizio:
[Unit]
Description=eseguire logrotate
[Service]
ExecStart=logrotate /etc/logrotate.conf
TimeoutSec=300È semplice: la descrizione del comando di avvio.
Il secondo file logrotateTimer.timer — è proprio questo che definisce il funzionamento dei timer:
[Unit]
Description=Eseguire logrotate
[Timer]
OnBootSec=15min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.targetCosa c'è qui:
- la descrizione del timer
- il tempo del primo avvio, contando dall'accensione del sistema
- il periodo per i futuri avvii
- la dipendenza dal servizio timer. In pratica, questa riga definisce il timer
uno script interattivo durante lo spegnimento e il proprio target di spegnimento
In un'altra implementazione ho dovuto realizzare una versione più complessa dello spegnimento della macchina — tramite un target proprio, per eseguire numerose azioni. Di solito si raccomanda di creare un servizio oneshot con l'opzione RemainAfterExit, ma questo non consente di creare uno script interattivo.
Il fatto è che i comandi eseguiti con l'opzione ExecOnStop vengono eseguiti al di fuori del TTY! Puoi semplicemente verificarlo: inserisci il comando tty e salva il suo output.
Per questo ho implementato lo spegnimento tramite il mio target. Non pretendo di avere il 100% di certezza, ma funziona!
Come è stato fatto (in breve):
Ho creato il target my_shutdown.target, che non dipendeva da nessuno:
my_shutdown.target
[Unit]
Description=my shutdown
AllowIsolate=yes
Wants=my_shutdown.service Quando vengo eseguito in questo target (attraverso systemctl isolate my_shutdown.target), avvia il servizio my_shutdown.service, il cui compito è semplice: eseguire lo script 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- All'interno di questo script eseguo le azioni necessarie. Posso aggiungere molti script al target, per flessibilità e comodità:
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/
$commandNota: Utilizzo dei file /tmp/reboot e /tmp/shutdown. Non puoi chiamare il target con parametri. Puoi solo utilizzare il servizio.
Ma utilizzo il target per avere flessibilità nel lavoro e un ordine garantito nell'esecuzione delle azioni.
Tuttavia, la parte più interessante è arrivata dopo. È necessario spegnere/riavviare la macchina. E qui ci sono 2 opzioni:
- Sostituire i comandi reboot, shutdown e altri (sono tutti link simbolici a systemctl) con il proprio script. All'interno dello script — passaggio a my_shutdown.target. E gli script all'interno del target poi chiamano direttamente systemctl, ad esempio, systemctl reboot.
- Un'opzione più semplice, ma che non mi piace. In tutte le interfacce, invocare non shutdown/reboot/altro, ma chiamare direttamente il target systemctl isolate my_shutdown.target.
Ho scelto la prima opzione. In systemd, reboot (come anche poweroff) sono link simbolici a systemd.
ls -l /sbin/poweroff
lrwxrwxrwx 1 root root 14 set 30 18:23 /sbin/poweroff -> /bin/systemctlPertanto, possono essere sostituiti dai propri script:
riavvia
#!/bin/sh
touch /tmp/reboot
sudo systemctl isolate my_shutdown.target
fiFonte: habr.com
