Systemd, scripts interativos e temporizadores

Systemd, scripts interativos e temporizadores

Introdução

Ao desenvolver para Linux, surge a tarefa de criar scripts interativos que são executados quando o sistema é ligado ou desligado. No sistema V isso foi fácil, mas com o systemd ele faz ajustes. Mas pode ter seus próprios temporizadores.

Por que precisamos de metas?

Muitas vezes é escrito que o alvo serve como um análogo do nível de execução no sistema V -init. Eu discordo fundamentalmente. Existem mais deles e você pode dividir os pacotes em grupos e, por exemplo, iniciar um grupo de serviços com um comando e realizar ações adicionais. Além disso, eles não possuem hierarquia, apenas dependências.

Exemplo de destino quando ativado (visão geral dos recursos) com script interativo em execução

Descrição do próprio alvo:

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

Este destino será iniciado quando multi-user.target for iniciado e chamar installer.service. No entanto, pode haver vários desses serviços.

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 por fim, um exemplo do script sendo executado:

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

O mais importante é selecionar final.target - o destino ao qual o sistema deve chegar na inicialização. Durante o processo de inicialização, o systemd irá percorrer as dependências e lançar tudo o que precisa.
Existem diferentes maneiras de selecionar final.target, usei a opção loader para isso.

O lançamento final é assim:

  1. O bootloader inicia
  2. O bootloader inicia o lançamento do firmware passando o parâmetro final.target
  3. Systemd começa a iniciar o sistema. Vai sequencialmente para installer.target ou work.target de basic.target por meio de suas dependências (por exemplo, multi-user.target). Estes últimos fazem o sistema funcionar no modo desejado

Preparando o firmware para lançamento

Ao criar firmware, sempre surge a tarefa de restaurar o estado do sistema na inicialização e salvá-lo ao desligar. Estado significa arquivos de configuração, dumps de banco de dados, configurações de interface, etc.

Systemd executa processos no mesmo destino em paralelo. Existem dependências que permitem determinar a sequência de inicialização dos scripts.

Como funciona no meu projeto ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)

  1. O sistema inicia
  2. O serviço settings_restore.service é iniciado e verifica a presença do arquivo settings.txt na seção de dados. Se não estiver lá, em seu lugar será colocado arquivo de referência... Em seguida, as configurações do sistema são restauradas:
    • senha do administrador
    • nome de anfitrião,
    • fuso horário
    • localidade
    • Determina se toda a mídia está sendo usada. Por padrão, o tamanho da imagem é pequeno - para facilitar a cópia e gravação na mídia. Na inicialização, ele verifica se ainda há espaço não utilizado. Se houver, o disco será reparticionado.
    • Gerando ID de máquina a partir do endereço MAC. Isto é importante para obter o mesmo endereço via DHCP
    • Configurações de rede
    • Limita o tamanho dos logs
    • A unidade externa está sendo preparada para funcionar (se a opção correspondente estiver habilitada e a unidade for nova)
  3. Iniciar postgresq
  4. O serviço de restauração é iniciado. É necessário preparar o próprio zabbix e seu banco de dados:
    • Verifica se já existe um banco de dados zabbix. Caso contrário, ele será criado a partir de dumps de inicialização (incluídos no zabbix)
    • uma lista de fusos horários é criada (necessária para exibi-los na interface da web)
    • O IP atual é encontrado, é exibido em questão (convite para fazer login no console)
  5. O convite muda - aparece a frase Pronto para trabalhar
  6. O firmware está pronto para uso

Os arquivos de serviço são importantes, são eles que definem a sequência de seu lançamento

[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

Como você pode ver, instalei dependências para que meu script funcionasse primeiro, e só então a rede subisse e o SGBD iniciasse.

E o segundo serviço (preparação do 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

Aqui é um pouco mais complicado, o lançamento também é em multi-user.target, mas DEPOIS de iniciar o SGBD postgresql e meu setting_restore. Mas ANTES de iniciar os serviços do zabbix.

Serviço de temporizador para logrotate

Systemd pode substituir o CRON. Seriamente. Além disso, a precisão não é de minuto, mas de segundo (e se for necessário) Ou você pode criar um cronômetro monótono, chamado por um tempo limite de um evento.
Foi o cronômetro monótono que conta o tempo desde o início da máquina que criei.
Isso exigirá 2 arquivos
logrotateTimer.service - a descrição real do serviço:

[Unit]
Description=run logrotate

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

É simples - descrição do comando de lançamento.
O segundo arquivo logrotateTimer.timer é onde os temporizadores funcionam:

[Unit]
Description=Run logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

O que há aqui:

  • descrição do temporizador
  • Primeira hora de inicialização, a partir da inicialização do sistema
  • período de novos lançamentos
  • Dependência do serviço de timer. Na verdade, esta é a string que faz o timer

Script interativo ao desligar e seu destino de desligamento

Em outro desenvolvimento, tive que fazer uma versão mais complexa de desligar a máquina - através do meu próprio alvo, para realizar diversas ações. Geralmente é recomendado criar um serviço oneshot com a opção RemainAfterExit, mas isso impede a criação de um script interativo.

Mas o fato é que os comandos lançados pela opção ExecOnStop são executados fora do TTY! É fácil verificar: cole o comando tty e salve sua saída.

Portanto, implementei o desligamento através do meu alvo. Não pretendo estar 100% correto, mas funciona!
Como foi feito (em termos gerais):
Criei um target my_shutdown.target, que não dependia de ninguém:
meu_desligamento.target

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

Ao ir para este alvo (via systemctl isolar my_shutdown.target), ele lançou o serviço my_shutdown.service, cuja tarefa é simples - executar o 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

  • Dentro deste script eu realizo as ações necessárias. Você pode adicionar muitos scripts ao destino para flexibilidade e conveniência:

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

Observação. Usando os arquivos /tmp/reboot e /tmp/shutdown. Você não pode chamar o destino com parâmetros. Somente o serviço é possível.

Mas utilizo o target para ter flexibilidade no trabalho e uma ordem de ações garantida.

Porém, o mais interessante veio depois. A máquina precisa ser desligada/reiniciada. E existem 2 opções:

  • Substitua os comandos reboot, shutdown e outros comandos (eles ainda são links simbólicos para systemctl) pelo seu script.Dentro do script, vá para my_shutdown.target. E os scripts dentro do destino chamam systemctl diretamente, por exemplo, systemctl reboot
  • Uma opção mais simples, mas não gosto. Em todas as interfaces, não chame shutdown/reboot/other, mas chame diretamente o systemctl alvo isolado my_shutdown.target

Eu escolhi a primeira opção. No systemd, reinicializar (como desligar) são links simbólicos para o systemd.

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

Portanto, você pode substituí-los por seus próprios scripts:
reinicialização

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

Fonte: habr.com

Adicionar um comentário