
Introduzione
Nello sviluppo su linux si presentano compiti per la creazione di script interattivi, eseguiti all'accensione o allo spegnimento del sistema. In system V questo era semplice, ma con systemd necessitano alcune modifiche. Tuttavia, essa supporta anche i propri timer.
A cosa servono i target
Viene spesso scritto che i target sono l'equivalente dei runlevel in system V -init. Personalmente non sono d'accordo. Ce ne sono di più e si possono raggruppare i pacchetti in categorie, e ad esempio, avviare con un solo comando un gruppo di servizi e compiere ulteriori azioni. Inoltre, non hanno gerarchie, ma 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 verrà avviato quando sarà attivato multi-user.target e invocherà installer.service. Possono esserci più di questi servizi.
cat installer.service
[Unit]
# descrizione
Description=dialogo interattivo dell’installer
[Service]
# Esegui una sola volta, quando sarà avviato il resto
Type=idle
# Comando di avvio - chiamata dello 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, esempio di uno script eseguito:
#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answerLa cosa più importante è scegliere final.target - il target a cui il sistema deve arrivare all'avvio. Durante il processo di avvio, systemd seguirà le dipendenze e avvierà tutto ciò che è necessario.
Il final.target può essere scelto in vari modi, io ho utilizzato l'opzione del caricatore.
L'avvio finale appare così:
- Il caricatore si avvia
- Il caricatore inizia l'avvio del firmware, passando il parametro final.target
- Systemd inizia l'avvio del sistema. Procede in sequenza verso installer.target o work.target partendo da basic.target attraverso le loro dipendenze (ad esempio, multi-user.target). Questi ultimi portano il sistema a funzionare nella modalità desiderata.
Preparazione del firmware per l'avvio
Nella creazione dei firmware, sorge sempre il compito 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 unico target in parallelo. Ci sono dipendenze che permettono di determinare l'ordine di esecuzione degli script.
Come funziona nel mio progetto ( )
- Il sistema si avvia
- Il servizio settings_restore.service si avvia. Controlla la presenza del file settings.txt nella sezione dati. Se non è presente, viene sostituito con il file di riferimento. Successivamente avviene il ripristino delle impostazioni di sistema:
- password dell'amministratore
- hostname,
- fuso orario
- locale
- Determinazione se l'intero supporto è utilizzato. Per impostazione predefinita, le dimensioni dell'immagine sono ridotte, per facilitare la copia e la scrittura sul supporto. All'avvio, viene controllato se c'è ancora spazio non utilizzato. Se disponibile, il disco viene ripartizionato.
- Generazione di machine-id dall'indirizzo MAC. Questo è importante per ottenere lo stesso indirizzo tramite DHCP
- Impostazioni di rete
- Viene limitata la dimensione dei log
- Preparazione del disco esterno per il funzionamento (se è attivata l'opzione corrispondente e il disco è nuovo)
- Avvio di postgresq
- Il servizio restore si avvia. È necessario per preparare Zabbix e il suo database:
- Controlla se esiste già un database Zabbix. Se non esiste, viene creato da dump di inizializzazione (forniti insieme a Zabbix)
- Viene creato un elenco dei fusi orari (necessario per la loro visualizzazione nell'interfaccia web)
- Viene trovata l'attuale IP, che viene visualizzata nel messaggio (invito per accedere alla console)
- Cambia il prompt — appare la frase Pronto per lavorare
- Il firmware è pronto per l'uso
I file di servizio sono importanti, poiché determinano l'ordine di avvio
[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.targetCome si può vedere, ho impostato le dipendenze affinché prima venga eseguito il mio script e solo dopo si avvii la rete e il DBMS.
E il secondo servizio (preparazione 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. Si avvia anche nel 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. Inoltre, la precisione non è fino al minuto, ma fino al secondo (e magari è necessario). È possibile anche creare un timer monotono, attivato tramite timeout da un evento.
Ho creato proprio un timer monotono, che conta il tempo dall'avvio della macchina.
Per questo sono necessari 2 file
logrotateTimer.service — è la descrizione del servizio:
[Unit]
Description=run logrotate
[Service]
ExecStart=logrotate /etc/logrotate.conf
TimeoutSec=300È tutto semplice — è la descrizione del comando di avvio.
Il secondo file logrotateTimer.timer — è questo che imposta il funzionamento dei timer:
[Unit]
Description=Run logrotate
[Timer]
OnBootSec=15min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.targetCosa c'è qui:
- descrizione del timer
- Tempo della prima esecuzione, a partire dal caricamento del sistema
- periodo delle successive esecuzioni
- Dipendenza dal servizio timer. In effetti, questa è la riga e crea il timer
Script interattivo durante lo spegnimento e il proprio target di spegnimento
In un altro sviluppo ho dovuto implementare una versione di spegnimento più complessa — attraverso un target proprio, per eseguire molte azioni. Si raccomanda di creare un servizio oneshot con l'opzione RemainAfterExit, ma questo non consente di creare uno script interattivo.
Il problema è che i comandi eseguiti con l'opzione ExecOnStop vengono eseguiti al di fuori di TTY! Puoi controllarlo facilmente — inserisci il comando tty e salva il suo output.
Quindi ho implementato lo spegnimento tramite il mio target. Non pretendo che sia al 100% corretto, ma funziona!
Come è stato fatto (in linea generale):
Ho creato il target my_shutdown.target, che non dipende da nessuno:
my_shutdown.target
[Unit]
Description=il mio spegnimento
AllowIsolate=yes
Wants=my_shutdown.service Quando si passa a 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=IL mio spegnimento
[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. Puoi 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 di file \/tmp\/reboot e \/tmp\/shutdown. Non è possibile chiamare il target con parametri. È possibile solo il servizio.
Ma io uso il target per avere flessibilità nel lavoro e un ordine garantito nell'esecuzione delle azioni.
Tuttavia, la parte più interessante è arrivata dopo. La macchina deve essere spenta\/riavviata. E qui ci sono 2 opzioni:
- Sostituire i comandi reboot, shutdown e altri (sono comunque collegamenti simbolici a systemctl) con il proprio script. All'interno dello script — passare a my_shutdown.target. 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 non chiamare shutdown\/reboot\/altri, ma chiamare direttamente il target systemctl isolate my_shutdown.target
Ho scelto la prima opzione. In systemd reboot (come poweroff) sono collegamenti simbolici a systemd.
ls -l \/sbin\/poweroff
lrwxrwxrwx 1 root root 14 set 30 18:23 \/sbin\/poweroff -> \/bin\/systemctlQuindi possono essere sostituiti con i propri script:
reboot
#!/bin/sh
touch /tmp/reboot
sudo systemctl isolate my_shutdown.target
fiFonte: habr.com
