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:
- O bootloader inicia
- O bootloader inicia o lançamento do firmware passando o parâmetro final.target
- 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 (
- O sistema inicia
- 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)
- Iniciar postgresq
- 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)
- O convite muda - aparece a frase Pronto para trabalhar
- 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