4. Serviços oneshot, dependências, ordenação e falhas
4.1 Processo persistente ou tarefa finita?
Até agora, nosso exemplo executa indefinidamente. Nem todos os serviços devem funcionar assim. Um programa que prepara um GPIO, migra um banco de dados ou gera um relatório pode executar e terminar corretamente.
É exatamente o cenário de Type=oneshot.
Contraste importante: em um serviço persistente, o processo principal normalmente permanece em execução. Num oneshot, o sucesso é indicado pela conclusão da tarefa. Depois de concluída, a Unit pode aparecer inactive (dead) e ainda assim ter executado corretamente.
4.2 Criando um serviço oneshot de sistema
Crie um script simples:
sudo install -d -m 755 /usr/local/libexec
sudo nano /usr/local/libexec/preparar-coletor.sh
Conteúdo:
#!/usr/bin/env bash
set -euo pipefail
/usr/bin/logger -t preparar-coletor "Preparação iniciada"
/usr/bin/test -d /opt/coletor
/usr/bin/logger -t preparar-coletor "Preparação concluída"
Instale com permissões adequadas:
sudo chmod 755 /usr/local/libexec/preparar-coletor.sh
Crie /etc/systemd/system/preparar-coletor.service:
[Unit]
Description=Preparação única do coletor
[Service]
Type=oneshot
ExecStart=/usr/local/libexec/preparar-coletor.sh
[Install]
WantedBy=multi-user.target
Teste:
sudo systemd-analyze verify /etc/systemd/system/preparar-coletor.service
sudo systemctl daemon-reload
sudo systemctl start preparar-coletor.service
systemctl status preparar-coletor.service
sudo journalctl -u preparar-coletor.service -n 30
Se terminou com sucesso, inactive (dead) pode ser o estado correto.
4.3 Entendendo RemainAfterExit
Podemos declarar:
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/libexec/preparar-coletor.sh
Nesse caso, após a execução bem-sucedida, o systemd considera a Unit ativa (exited), embora nenhum processo permaneça rodando.
É apropriado quando a tarefa configura um estado que deve ser considerado aplicado, como determinada preparação de dispositivo. Para tarefas executadas repetidamente por timer, em geral não use RemainAfterExit=yes: uma Unit ainda considerada ativa não costuma ser reiniciada a cada disparo normal do timer.
4.4 Dependência versus ordenação
Este é um dos pontos mais sutis do systemd:
After=preparar-coletor.service
não ativa a Unit de preparação. Ele apenas impõe ordenação se ambas participarem da transação.
Já:
Requires=preparar-coletor.service
After=preparar-coletor.service
solicita a dependência e ordena a inicialização. Uma falha de preparação pode impedir o serviço dependente de iniciar.
Exemplo de /etc/systemd/system/coletor.service:
[Unit]
Description=Coletor dependente de preparação
Requires=preparar-coletor.service
After=preparar-coletor.service
[Service]
Type=exec
User=coletor
Group=coletor
WorkingDirectory=/opt/coletor
ExecStart=/usr/bin/python3 /opt/coletor/coletor.py
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Observe que os caminhos e as permissões precisam continuar corretos para a aplicação.
4.5 Serviços que devem iniciar juntos
Além de Wants= e Requires=, há opções como PartOf= e BindsTo= que tratam propagação de paradas e relações mais fortes com ciclos de vida de outras Units. Elas são úteis, por exemplo, para componentes associados à presença de hardware.
Não use essas opções automaticamente: primeiro decida o comportamento desejado quando o recurso associado falha, desaparece ou é reiniciado.
4.6 Execução idempotente
Um conceito essencial para oneshot é idempotência: executar a tarefa mais de uma vez não deveria criar danos, inconsistências ou duplicações indesejadas.
Exemplo simples:
#!/usr/bin/env bash
set -euo pipefail
install -d -m 750 /var/lib/minha-aplicacao
O comando não pressupõe que o diretório esteja ausente. Em operações mais complexas, a idempotência exige verificar o estado atual antes de aplicar mudanças.
Porém, cuidado com scripts que modificam dispositivos, bancos de dados ou partições: a idempotência deve ser projetada e testada, não presumida.
4.7 Controlando falhas e excesso de reinicializações
Para serviços persistentes:
[Unit]
StartLimitIntervalSec=60s
StartLimitBurst=5
[Service]
Restart=on-failure
RestartSec=5s
A configuração limita o número de tentativas de inicialização em uma janela de tempo. Se o serviço exceder o limite, novas tentativas automáticas poderão ser bloqueadas até que o limite seja liberado.
Para investigar:
systemctl status coletor.service
systemctl show coletor.service -p Result -p NRestarts -p ExecMainStatus
sudo journalctl -u coletor.service -b --no-pager
Depois de corrigir a causa de uma falha, quando necessário:
sudo systemctl reset-failed coletor.service
sudo systemctl start coletor.service
reset-failed não corrige a aplicação; apenas limpa determinados estados e contadores administrativos.
4.8 Timeouts e parada ordenada
[Service]
TimeoutStopSec=20s
KillSignal=SIGTERM
A aplicação deve tratar o sinal, finalizar operações pendentes e liberar recursos. Se não encerrar dentro do tempo configurado, o systemd poderá recorrer a encerramento forçado, conforme KillMode=, SendSIGKILL= e outras políticas.
No Linux embarcado, uma parada previsível é especialmente importante para evitar arquivos corrompidos, buffers não enviados e estados inseguros de atuadores.
Ponto de aprendizagem
oneshot é para trabalho finito, não um daemon que por acaso encerrou. Dependência não é ordenação, e políticas de reinício devem ser combinadas com tratamento correto de falhas e procedimentos seguros de parada.