
Introdução
Ao desenvolver para Linux, surgem tarefas de criação de scripts interativos que são executados ao ligar ou desligar o sistema. No System V, isso era fácil, mas com o systemd, há ajustes. No entanto, ele possui seus próprios temporizadores.
Por que os targets são necessários
Frequentemente, diz-se que os targets são análogos aos runlevels no System V init. Discordo plenamente. Eles são mais numerosos e podem agrupar pacotes, permitindo, por exemplo, iniciar um grupo de serviços com um único comando e realizar ações adicionais. Além disso, não têm hierarquia, apenas dependências.
Exemplo de target ao ligar (visão geral das possibilidades) com o início de um script interativo
Descrição do próprio target:
cat installer.target
[Unit]
Description=Meu instalador
Requires=multi-user.target
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target
AllowIsolate=yes
Wants=installer.serviceEsse target será iniciado quando o multi-user.target for ativado e chamará o installer.service. Podem haver vários serviços desse tipo.
cat installer.service
[Unit]
# descrição
Description=diálogo interativo do instalador
[Service]
# Executar uma vez, quando o restante estiver iniciado
Type=idle
# Comando de inicialização - chamada do script
ExecStart=\/usr\/bin\/installer.sh
# Interação interativa com o usuário através do tty3
StandardInput=tty
TTYPath=\/dev\/tty3
TTYReset=yes
TTYVHangup=yes
[Install]
WantedBy=installer.targetE finalmente, um exemplo do script executado:
#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answerO mais importante é escolher o final.target - o target ao qual o sistema deve chegar durante a inicialização. Durante o processo de inicialização, o systemd percorrerá as dependências e iniciará tudo o que for necessário.
Existem diferentes maneiras de escolher o final.target; eu usei para isso uma opção do gerador de inicialização.
A inicialização final se parece com isto:
- O gerador de inicialização é iniciado
- O gerador começa a iniciar o firmware, passando o parâmetro final.target
- O systemd inicia o sistema. Ele segue sequencialmente para o installer.target ou work.target a partir do basic.target através de suas dependências (por exemplo, multi-user.target). Estas últimas levam o sistema a funcionar no modo desejado.
Preparação do firmware para o início
Ao criar firmwares, sempre surge a tarefa de restaurar o estado do sistema na inicialização e de mantê-lo ao desligar. O estado refere-se a arquivos de configuração, dumps de banco de dados, configurações de interfaces, etc.
O systemd inicia processos em um target em paralelo. Existem dependências que permitem determinar a sequência de execução dos scripts.
Como isso funciona no meu projeto ( )
- O sistema é iniciado
- O serviço settings_restore.service está sendo iniciado. Ele verifica a presença do arquivo settings.txt na partição de dados. Se não estiver presente, um arquivo padrão é colocado em seu lugar. Em seguida, as configurações do sistema são restauradas:
- senha de administrador
- hostname,
- fuso horário
- localidade
- Verificação se todo o dispositivo está sendo utilizado. Por padrão, o tamanho da imagem é pequeno — para facilitar a cópia e gravação no dispositivo. Ao iniciar, é verificado se há espaço não utilizado. Se houver, o disco será reorganizado.
- Geração do machine-id a partir do endereço MAC. Isso é importante para obter o mesmo endereço via DHCP
- Configurações de rede
- O tamanho dos logs é limitado
- Preparando o disco externo para uso (se a opção correspondente estiver ativada e o disco for novo)
- Iniciando o postgresq
- o serviço restore está sendo iniciado. Ele é necessário para preparar o Zabbix e seu banco de dados:
- Verifica se já existe um banco de dados Zabbix. Se não houver, ele é criado a partir dos dumps iniciais (que acompanham o Zabbix)
- uma lista de fusos horários é criada (necessária para a exibição na interface web)
- O IP atual é localizado e exibido na issue (convite para entrar no console)
- O prompt é alterado — a frase 'Ready to work' aparece
- A firmware está pronta para uso
Os arquivos de serviço são importantes, pois definem a sequência de inicialização
[Unit]
Description=restaurar configurações do 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 pode ser visto, coloquei as dependências para que meu script fosse executado primeiro, e somente depois a rede fosse levantada e o SGBD fosse iniciado.
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.targetAqui é um pouco mais complicado. A inicialização também está em multi-user.target, mas APÓS a inicialização do SGBD PostgreSQL e do meu settings_restore. Mas ANTES do início dos serviços do Zabbix.
Serviço com um timer para logrotate
O Systemd pode substituir o CRON. Sério. Além disso, a precisão não é até o minuto, mas até o segundo (caso precise). Pode-se criar um timer monotônico, acionado por um timeout de um evento.
Foi exatamente um timer monotônico, contando o tempo desde a inicialização da máquina, que eu criei.
Para isso, serão necessários 2 arquivos
logrotateTimer.service — a descrição do serviço em si:
[Unit]
Description=executar logrotate
[Service]
ExecStart=logrotate \/etc\/logrotate.conf
TimeoutSec=300É simples — descrição do comando de execução.
O segundo arquivo logrotateTimer.timer — ele define o funcionamento dos timers:
[Unit]
Description=Executar logrotate
[Timer]
OnBootSec=15min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.targetO que há aqui:
- descrição do timer
- Tempo para a primeira execução, a partir do carregamento do sistema
- período para execuções subsequentes
- Dependência do serviço de timers. Na verdade, esta linha é o que faz o timer funcionar
Script interativo na desligamento e seu target de desligamento
Em outro desenvolvimento, tive que criar uma variante mais complexa para desligar a máquina — através de meu próprio target, para executar várias ações. Normalmente, recomenda-se criar um serviço oneshot com a opção RemainAfterExit, mas isso não permite criar um script interativo.
O fato é que os comandos executados pela opção ExecOnStop são executados fora do TTY! É fácil verificar — insira o comando tty e salve sua saída.
Portanto, eu implementei o desligamento através do meu próprio target. Não reivindico 100% de precisão, mas funciona!
Como isso foi feito (de forma geral):
Criei o target my_shutdown.target, que não dependia de ninguém:
my_shutdown.target
[Unit]
Description=meu desligamento
AllowIsolate=yes
Wants=my_shutdown.service Ao mudar para esse target (via systemctl isolate my_shutdown.target), ele iniciava o serviço my_shutdown.service, cuja tarefa era simples — executar o script my_shutdown.sh:
[Unit]
Description=MEU desligamento
[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, realizo as ações necessárias. É possível adicionar muitos scripts ao target, para flexibilidade e conveniência:
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 dos arquivos \/tmp\/reboot e \/tmp\/shutdown. Não é possível chamar um target com parâmetros. Apenas serviços.
Mas eu uso um target para ter flexibilidade no trabalho e uma ordem garantida para a execução das ações.
No entanto, o mais interessante veio depois. Você precisa desligar/reiniciar a máquina. E aqui há 2 opções:
- Substituir os comandos reboot, shutdown e outros (que são links simbólicos para systemctl) pelo seu próprio script. Dentro do script — mudar para my_shutdown.target. E os scripts dentro do target chamam diretamente o systemctl, por exemplo, systemctl reboot
- Uma opção mais simples, mas que não me agrada. Em todas as interfaces, não invoque shutdown/reboot, mas chame diretamente o target systemctl isolate my_shutdown.target
Escolhi a primeira opção. No systemd, reboot (assim como poweroff) são links simbólicos para systemd.
ls -l \/sbin\/poweroff
lrwxrwxrwx 1 root root 14 set 30 18:23 \/sbin\/poweroff -> \/bin\/systemctlPortanto, podem ser substituídos por seus próprios scripts:
reiniciar
#!/bin/sh
touch /tmp/reboot
sudo systemctl isolate my_shutdown.target
fiFonte: habr.com
