
Introducción
Al desarrollar para Linux, surgen tareas relacionadas con la creación de scripts interactivos que se ejecutan al encender o apagar el sistema. En System V, esto se hacía fácilmente, pero con systemd hay que hacer ajustes. Sin embargo, ahora cuenta con sus propios temporizadores.
¿Para qué sirven los targets?
A menudo se dice que los targets son análogos a los runlevels en System V -init. No estoy de acuerdo en absoluto. Hay más de ellos y se pueden dividir los paquetes en grupos y, por ejemplo, iniciar un grupo de servicios con un solo comando, realizar acciones adicionales. Además, no tienen jerarquía, solo dependencias.
Ejemplo de un target al encender (vista general de la funcionalidad) con el lanzamiento de un script interactivo.
Descripción del target en sí:
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.serviceEste target se iniciará cuando se active multi-user.target y llamará a installer.service. Además, puede haber varios de estos servicios.
cat installer.service
[Unit]
# descripción
Description=installer interactive dialog
[Service]
# Iniciar una vez, cuando todo lo demás esté iniciado
Type=idle
# Comando de inicio - llamada al script
ExecStart=\/usr\/bin\/installer.sh
# Interacción interactiva con el usuario a través de tty3
StandardInput=tty
TTYPath=\/dev\/tty3
TTYReset=yes
TTYVHangup=yes
[Install]
WantedBy=installer.targetY, por último, un ejemplo de un script ejecutable:
#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answerLo más importante es elegir final.target, el target al que el sistema debe llegar al iniciarse. Durante el arranque, systemd seguirá las dependencias y lanzará todo lo necesario.
Se puede elegir final.target de diferentes maneras; yo utilicé para esto una opción del cargador de arranque.
El arranque final se ve así:
- El cargador de arranque se inicia.
- El cargador de arranque comienza a iniciar el firmware, pasando el parámetro final.target.
- Systemd comienza a iniciar el sistema. Va progresivamente a installer.target o work.target desde basic.target a través de sus dependencias (por ejemplo, multi-user.target). Estas últimas llevan al sistema a funcionar en el modo adecuado.
Preparación del firmware para el arranque.
Al crear firmwares, siempre surge la tarea de restaurar el estado del sistema al inicio y su conservación al apagarse. Por estado se entiende los archivos de configuración, volcado de bases de datos, configuraciones de interfaces, etc.
Systemd ejecuta procesos dentro de un mismo target en paralelo. Hay dependencias que permiten definir la secuencia de inicio de los scripts.
Así es como funciona en mi proyecto ( )
- El sistema se inicia.
- Se inicia el servicio settings_restore.service. Verifica la existencia del archivo settings.txt en la sección de datos. Si no existe, se coloca un archivo de referencia en su lugar. A continuación, se lleva a cabo la restauración de la configuración del sistema:
- contraseña de administrador
- hostname,
- zona horaria
- localidad
- Determinando si se utiliza todo el medio. Por defecto, el tamaño de la imagen es pequeño, para facilitar la copia y grabación en el medio. Al inicio, se verifica si hay espacio no utilizado. Si lo hay, se vuelve a particionar el disco.
- Generación de machine-id a partir de la dirección MAC. Esto es importante para obtener la misma dirección a través de DHCP
- Configuraciones de red
- Se limita el tamaño de los registros
- Se prepara un disco externo para trabajar (si la opción correspondiente está habilitada y el disco es nuevo)
- Se inicia postgresq
- se inicia el servicio restore. Es necesario para preparar zabbix y su base de datos:
- Se verifica si ya existe la base de datos zabbix. Si no existe, se crea a partir de los volcado iniciales (se incluyen con la entrega de zabbix)
- se crea una lista de zonas horarias (necesaria para su visualización en la interfaz web)
- Se encuentra la IP actual, que se muestra en el issue (invitación para ingresar en la consola)
- Se cambia el prompt, aparece la frase Listo para trabajar
- El firmware está listo para funcionar
Los archivos de servicios son importantes, ya que determinan el orden de su inicio
[Unit]
Description=restaurar configuraciones del 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.targetComo se puede ver, he establecido las dependencias para que mi script se ejecute primero, y solo después se levante la red y se inicie la base de datos.
Y el segundo servicio (preparación de 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.targetAquí es un poco más complicado. La ejecución también ocurre en multi-user.target, pero DESPUÉS del inicio de la base de datos postgresql y mi setting_restore. Pero ANTES de iniciar los servicios de zabbix.
Servicio con temporizador para logrotate
Systemd puede reemplazar a CRON. En serio. De hecho, la precisión no es hasta el minuto, sino hasta el segundo (por si acaso). También se puede crear un temporizador monótono, llamado por tiempo de espera a partir de un evento.
El temporizador monótono que cuenta el tiempo desde el inicio de la máquina es el que he creado.
Para esto se necesitan 2 archivos
logrotateTimer.service — descripción del servicio:
[Unit]
Description=ejectuar logrotate
[Service]
ExecStart=logrotate \/etc\/logrotate.conf
TimeoutSec=300Todo es simple: descripción del comando de inicio.
El segundo archivo logrotateTimer.timer — es el que determina el funcionamiento de los temporizadores:
[Unit]
Description=Ejecutar logrotate
[Timer]
OnBootSec=15min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target¿Qué hay aquí?
- Descripción del temporizador
- Tiempo del primer arranque, a partir de la carga del sistema
- Periodo de arranques posteriores
- Dependencia del servicio de temporizadores. De hecho, esta es la línea que hace funcionar el temporizador
Script interactivo al apagarse y su objetivo de apagado
En otro desarrollo, tuve que crear una versión más complicada de apagado de la máquina — a través de un objetivo propio, para llevar a cabo múltiples acciones. Generalmente se recomienda crear un servicio oneshot con la opción RemainAfterExit, pero esto no permite crear un script interactivo.
El asunto es que los comandos ejecutados con la opción ExecOnStop se ejecutan fuera de TTY. Es fácil de verificar: inserte el comando tty y guarde su salida.
Por lo tanto, implementé el apagado a través de mi propio objetivo. No afirmo que sea 100% correcto, ¡pero funciona!
Cómo se hizo (en términos generales):
Creé el objetivo my_shutdown.target, que no dependía de nadie:
my_shutdown.target
[Unit]
Description=mi apagado
AllowIsolate=yes
Wants=my_shutdown.service Al cambiar a este objetivo (a través de systemctl isolate my_shutdown.target), se activaba el servicio my_shutdown.service, cuya tarea es sencilla: ejecutar el script my_shutdown.sh:
[Unit]
Description=MI apagado
[Service]
Type=oneshot
ExecStart=\/usr\/bin\/my_shutdown.sh
StandardInput=tty
TTYPath=\/dev\/tty3
TTYReset=yes
TTYVHangup=yes
WantedBy=my_shutdown.target- Dentro de este script realizo las acciones necesarias. Se pueden agregar muchos scripts al objetivo, para flexibilidad y conveniencia:
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. Uso de los archivos \/tmp\/reboot y \/tmp\/shutdown. No se puede invocar un objetivo con parámetros. Solo servicio.
Pero utilizo el objetivo para tener flexibilidad en el trabajo y un orden de ejecución garantizado.
Sin embargo, lo más interesante fue después. La máquina debe ser apagada/ reiniciada. Y aquí hay 2 opciones:
- Reemplazar los comandos reboot, shutdown y otros (que son enlaces simbólicos a systemctl) por mi propio script. Dentro del script, se dirigirá a my_shutdown.target. Y luego los scripts dentro del objetivo llaman directamente a systemctl, por ejemplo, systemctl reboot.
- Una opción más sencilla, pero que no me gusta. En todas las interfaces, llamar no a shutdown/reboot/otros, sino directamente al objetivo systemctl isolate my_shutdown.target.
Elegí la primera opción. En systemd, reboot (al igual que poweroff) son enlaces simbólicos a systemd.
ls -l \/sbin\/poweroff
lrwxrwxrwx 1 root root 14 sep 30 18:23 \/sbin\/poweroff -> \/bin\/systemctlPor lo tanto, pueden ser reemplazados por mis propios scripts:
reiniciar
#!/bin/sh
touch /tmp/reboot
sudo systemctl isolate my_shutdown.target
fiFuente: habr.com
