Systemd, script interattivi e timer

Systemd, script interattivi e timer

Introduzione

Quando si sviluppa per Linux, si pone il compito di creare script interattivi che vengono eseguiti all'accensione o allo spegnimento del sistema. Nel sistema V questo era facile, ma con systemd apporta modifiche. Ma può avere i propri timer.

Perché abbiamo bisogno di obiettivi?

Viene spesso scritto che target funge da analogo del runlevel nel sistema V -init. Fondamentalmente non sono d'accordo. Ce ne sono di più e puoi dividere i pacchetti in gruppi e, ad esempio, avviare un gruppo di servizi con un comando ed eseguire azioni aggiuntive. Inoltre, non hanno gerarchia, solo dipendenze.

Esempio di target quando abilitato (panoramica delle funzionalità) con l'esecuzione di script interattivo

Descrizione dell'obiettivo stesso:

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

Questo target verrà avviato quando viene avviato multi-user.target e chiama installer.service. Tuttavia, potrebbero esserci diversi servizi di questo tipo.

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

E infine, un esempio dello script in esecuzione:

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

La cosa più importante è selezionare final.target, il target a cui il sistema dovrebbe arrivare all'avvio. Durante il processo di avvio, systemd esaminerà le dipendenze e avvierà tutto ciò di cui ha bisogno.
Esistono diversi modi per selezionare final.target, per questo ho utilizzato l'opzione caricatore.

Il lancio finale è simile al seguente:

  1. Si avvia il bootloader
  2. Il bootloader inizia ad avviare il firmware passando il parametro final.target
  3. Systemd inizia ad avviare il sistema. Va in sequenza a installer.target o work.target da basic.target attraverso le relative dipendenze (ad esempio, multi-user.target). Questi ultimi portano il sistema a funzionare nella modalità desiderata

Preparazione del firmware per il lancio

Quando si crea il firmware, si pone sempre il compito di ripristinare lo stato del sistema all'avvio e di salvarlo allo spegnimento. Stato indica file di configurazione, dump del database, impostazioni dell'interfaccia, ecc.

Systemd esegue i processi nella stessa destinazione in parallelo. Esistono dipendenze che consentono di determinare la sequenza di avvio degli script.

Come funziona nel mio progetto ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)

  1. Il sistema si avvia
  2. Viene avviato il servizio settings_restore.service che verifica la presenza del file settings.txt nella sezione dati. Se non è presente, al suo posto viene inserito un file di riferimento e successivamente vengono ripristinate le impostazioni di sistema:
    • password dell'amministratore
    • Nome host,
    • fuso orario
    • locale
    • Determina se vengono utilizzati tutti i media. Per impostazione predefinita, la dimensione dell'immagine è piccola, per facilitare la copia e la registrazione su supporto. All'avvio controlla se c'è ancora spazio inutilizzato. Se esiste, il disco viene ripartizionato.
    • Generazione dell'ID macchina dall'indirizzo MAC. Questo è importante per ottenere lo stesso indirizzo tramite DHCP
    • Impostazioni di rete
    • Limita la dimensione dei log
    • L'unità esterna viene preparata per il lavoro (se l'opzione corrispondente è abilitata e l'unità è nuova)
  3. Inizia postgresq
  4. Viene avviato il servizio di ripristino. È necessario per preparare zabbix stesso e il suo database:
    • Controlla se esiste già un database zabbix. In caso contrario, viene creato dai dump di inizializzazione (inclusi con zabbix)
    • viene creato un elenco di fusi orari (necessario per visualizzarli nell'interfaccia web)
    • Viene trovato l'IP corrente, viene visualizzato in questione (invito ad accedere alla console)
  5. L'invito cambia: appare la frase Pronto a lavorare
  6. Il firmware è pronto per l'uso

I file di servizio sono importanti, sono loro che stabiliscono la sequenza del loro 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.target

Come puoi vedere, ho installato le dipendenze in modo che il mio script funzionasse prima e solo allora la rete si collegasse e il DBMS si avviasse.

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.target

Qui è un po’ più complicato: anche il lancio avviene in multi-user.target, ma DOPO aver avviato il DBMS postgresql e il mio setting_restore. Ma PRIMA di avviare i servizi zabbix.

Servizio timer per logrotate

Systemd può sostituire CRON. Sul serio. Inoltre, la precisione non è fino al minuto, ma fino al secondo (e se fosse necessario), oppure puoi creare un timer monotono, richiamato da un timeout da un evento.
Era il timer monotono che conta il tempo dall'avvio della macchina che ho creato.
Ciò richiederà 2 file
logrotateTimer.service - la descrizione effettiva del servizio:

[Unit]
Description=run logrotate

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

È semplice: descrizione del comando di avvio.
Il secondo file logrotateTimer.timer è dove funzionano i timer:

[Unit]
Description=Run logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

Cosa c'è qui:

  • descrizione del timer
  • Prima ora di avvio, a partire dall'avvio del sistema
  • periodo di ulteriori lanci
  • Dipendenza dal servizio timer. In effetti, questa è la stringa che costituisce il timer

Script interattivo allo spegnimento e obiettivo di spegnimento

In un altro sviluppo, ho dovuto eseguire una versione più complessa dello spegnimento della macchina, attraverso il mio obiettivo, per eseguire molte azioni. Di solito è consigliabile creare un servizio one-shot con l'opzione RemainAfterExit, ma ciò impedisce di creare uno script interattivo.

Ma il fatto è che i comandi lanciati dall'opzione ExecOnStop vengono eseguiti al di fuori del TTY! È facile controllare: incolla il comando tty e salva il suo output.

Pertanto, ho implementato l'arresto tramite il mio obiettivo. Non pretendo di essere corretto al 100%, ma funziona!
Come è stato fatto (in termini generali):
Ho creato un target my_shutdown.target, che non dipendeva da nessuno:
mio_spegnimento.target

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

Quando si accede a questo target (tramite systemctl isolate my_shutdwn.target), viene avviato 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. Puoi aggiungere molti script alla destinazione per flessibilità e comodità:

mio_spegnimento.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

Nota. Utilizzando i file /tmp/reboot e /tmp/shutdown. Non è possibile chiamare target con parametri. È possibile solo il servizio.

Ma utilizzo Target per avere flessibilità nel lavoro e un ordine di azioni garantito.

La cosa più interessante però è arrivata dopo. La macchina deve essere spenta/riavviata. E ci sono 2 opzioni:

  • Sostituisci reboot, shutdown e altri comandi (sono ancora collegamenti simbolici a systemctl) con il tuo script. All'interno dello script, vai su my_shutdown.target. E gli script all'interno del target chiamano direttamente systemctl, ad esempio systemctl reboot
  • Un'opzione più semplice, ma non mi piace. In tutte le interfacce, non chiamare shutdown/reboot/other, ma chiamare direttamente il target systemctl isolate my_shutdown.target

Ho scelto la prima opzione. In systemd, il riavvio (come lo spegnimento) sono collegamenti simbolici a systemd.

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

Pertanto, puoi sostituirli con i tuoi script:
reboot

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

Fonte: habr.com

Aggiungi un commento