Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

01 - Estrutura (Unit, Service, Install)

video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

A anatomia interna de um Unit File do systemd é fundamentada em uma sintaxe declarativa derivada do formato INI. O parser do systemd (escrito em C) lê esses arquivos em busca de seções (denotadas por chaves []) que agrupam pares de chave-valor (diretivas). Ao contrário dos scripts imperativos do SysVinit, o systemd não executa este arquivo de cima para baixo; ele o compila em uma estrutura de dados orientada a objetos na memória.

A estrutura de um arquivo .service canônico é universalmente dividida em três domínios semânticos distintos: [Unit] (ontologia e topologia), [Service] (comportamento de execução do binário) e [Install] (regras de persistência e ativação estática). Compreender a interação ortogonal entre essas seções é o pré-requisito para a orquestração avançada do espaço de usuário.


1. A Seção [Unit]: Meta-Contexto e Topologia no DAG

A seção [Unit] é genérica e aplica-se a quase todos os 11 tipos de unidades do systemd. Sua responsabilidade arquitetural dupla é fornecer metadados descritivos e, criticamente, posicionar a unidade dentro do Grafo Acíclico Dirigido (DAG) de inicialização.

O erro mais comum na engenharia de Unit Files é a confusão entre dependência de existência (Requirement) e dependência temporal (Ordering). No systemd, estes dois eixos são estritamente ortogonais.

1.1. Dependências de Requisito (Existence)

Estas diretivas determinam se outras unidades devem ser ativadas em conjunto com a unidade atual. Elas não ditam ordem. Se A requer B, e ambas forem chamadas, o systemd iniciará as duas no mesmo exato milissegundo.

  • Wants=: A dependência fraca (Soft Dependency). Se a unidade atual for iniciada, as unidades listadas em Wants= também serão enfileiradas para início. Se qualquer uma das unidades requeridas falhar em iniciar, a unidade atual continua sua execução incólume. É o mecanismo padrão e recomendado para maximizar a resiliência.
  • Requires=: A dependência forte (Hard Dependency). Similar ao Wants=, mas impõe uma restrição de sucesso. Se a unidade requerida falhar durante a inicialização, a transição da unidade atual é abortada imediatamente. Contudo, se a unidade requerida for terminada ou crashar subitamente muito tempo após o boot, a unidade atual não será parada automaticamente.
  • BindsTo=: A restrição mais severa. Adiciona à semântica do Requires= a propagação de estado de morte. Se a unidade referenciada morrer, falhar ou for interrompida manualmente a qualquer momento do ciclo de vida do sistema operacional, a unidade atual será imediatamente terminada pelo PID 1. Indispensável para acoplar daemons a interfaces de rede efêmeras (ex: BindsTo=sys-subsystem-net-devices-eth0.device).
  • PartOf=: Propaga ações manuais do administrador, mas não falhas. Se a unidade referenciada for parada ou reiniciada (via systemctl stop/restart), esta unidade sofrerá a mesma ação.
  • Conflicts=: O antônimo da dependência. Define exclusão mútua. Se a unidade atual for iniciada, as unidades listadas aqui serão paradas. Se a unidade atual e a conflitante forem instruídas a iniciar simultaneamente, o systemd abortará a operação por impasses insolúveis no DAG.

1.2. Dependências Temporais (Ordering)

Para resolver condições de corrida (ex: o banco de dados deve estar com socket aberto antes do servidor web subir), o systemd introduz o eixo temporal.

  • **Before= e After=**: Se a unidade atual possui After=network.target e ambas as unidades forem enfileiradas para início, o systemd reterá a unidade atual na fila de jobs até que a unidade network.target reporte o status de active.
  • Ortogonalidade Crítica: Declarar After=B não instrui o systemd a iniciar B. Se apenas a unidade atual for solicitada e ela tiver After=B, mas não tiver Wants=B, o systemd a iniciará imediatamente, ignorando o After. O ordenamento só se aplica se ambas as unidades estiverem presentes na mesma transação no DAG.

1.3. Predicados Condicionais

Antes de instanciar a unidade, o parser avalia sentenças lógicas.

  • ConditionPathExists=: A unidade só prossegue se o arquivo especificado existir. Útil para condicionar a inicialização de um daemon à existência de seu arquivo de configuração primário.
  • ConditionVirtualization=: Avalia se o sistema roda em metal nativo, KVM, LXC ou chroot, permitindo que serviços específicos de hardware abortem graciosamente sem gerar falhas (estado condition failed, não failed).

2. A Seção [Service]: A Máquina de Execução e o Cgroup

Restrita exclusivamente aos arquivos .service, esta seção é o coração operacional do sistema. Ela mapeia o objeto declarativo para um processo real (ou árvore de processos) no espaço de usuário e orquestra a chamada de sistema execve().

2.1. O Modelo de Execução (Type=)

O valor de Type= altera fundamentalmente como o PID 1 define o que significa "iniciar com sucesso" para a unidade. Este é o parâmetro arquitetural mais complexo de um .service.

  • Type=simple (O Padrão Otimista): O systemd realiza o fork(), executa o binário definido em ExecStart= e, no milissegundo seguinte, marca a unidade como active (running). Ele não aguarda o daemon alocar memória, ler configurações ou abrir portas. Se o processo morrer um segundo depois por erro de sintaxe na configuração, a unidade mudará para failed. Isso proporciona paralelismo extremo, mas exige que outros serviços usem Socket Activation para não falharem ao tentar conectar muito cedo.
  • Type=exec: Similar ao simple, mas o systemd aguarda especificamente o retorno da syscall execve() dentro do processo filho. Isso garante que o binário principal foi pelo menos localizado no disco e as permissões de execução foram validadas antes de marcar a unidade como ativa.
  • Type=forking (Retrocompatibilidade SysV): Desenhado para daemons tradicionais C/C++ que executam double-fork e rodam em background. O systemd invoca o ExecStart= e espera que o processo pai morra (retorne saída 0). Quando o pai morre, o systemd marca o serviço como ativo.
  • Restrição: Como o PID original foi descartado, o systemd precisa deduzir qual processo é o daemon principal dentro do cgroup. O uso da diretiva PIDFile=/var/run/daemon.pid torna-se quase mandatório para evitar ambiguidades se o processo filho fizer forks adicionais rapidamente.

  • Type=oneshot: Destinado a scripts de inicialização efêmeros, não a daemons residentes (ex: aplicar regras de firewall via iptables). O systemd marca o estado como activating, aguarda o processo terminar a execução com código de saída 0 e, em seguida, marca a unidade como inactive (dead) (ou active (exited) se a diretiva RemainAfterExit=yes for utilizada). As unidades que tiverem After= para este serviço ficarão bloqueadas até que o comando termine completamente.

  • Type=dbus: O serviço é considerado inicializado apenas quando adquire um nome específico (declarado na diretiva BusName=) no barramento D-Bus do sistema. Indispensável para daemons como o NetworkManager ou polkit.
  • Type=notify (O Padrão Moderno): A evolução definitiva do gerenciamento de estado. O binário executa no modelo simple, mas o systemd o mantém no estado activating. O processo C/Go/Python do daemon, após terminar sua rotina interna (ler configs, instanciar memória), invoca ativamente a biblioteca libsystemd (chamada sd_notify(0, "READY=1")) através de um socket IPC injetado no ambiente pelo PID 1 ($NOTIFY_SOCKET). Só então o systemd marca a unidade como ativada e libera as unidades que dependem dela. Elimina completamente as condições de corrida cegas.

2.2. Diretivas de Invocação e Controle

  • ExecStart=: O comando absoluto (com caminho completo, ex: /usr/sbin/sshd -D) a ser executado. O systemd não processa variáveis shell ou operadores como > e | por razões de segurança. Para usar recursos shell, deve-se invocar ExecStart=/bin/bash -c "comando | filtro". Pode ser declarado múltiplas vezes em tipos oneshot.
  • ExecStop=: Opcional. O comando a executar antes do encerramento forçado. Se omitido, o systemd não lê arquivos PID; ele localiza o cgroup atrelado ao serviço e envia um SIGTERM para absolutamente todas as threads internas simultaneamente. Após TimeoutStopSec= (padrão 90s), envia um SIGKILL destrutivo.
  • ExecReload=: O comando acionado via systemctl reload. Geralmente envia um sinal ao processo principal utilizando a variável dinâmica injetada: ExecReload=/bin/kill -HUP $MAINPID.
  • **Environment= e EnvironmentFile=**: Injeta variáveis de ambiente estéreis diretamente no contexto do execve(), ignorando a poluição do perfil do usuário (~/.bashrc).

2.3. Resiliência e Sandboxing

  • Restart=: A política de ressurreição do daemon. Pode assumir valores como on-failure (reinicia apenas se o código de saída for diferente de 0, se receber segfault ou sofrer timeout do watchdog) ou always (reinicia até mesmo se for encerrado com saída 0).
  • RestartSec=: O tempo de backoff (dormência) antes da tentativa de ressurreição, para evitar a exaustão da CPU (tight loop) se o binário estiver corrompido.
  • Controle de Usuário: User= e Group= forçam a queda de privilégios (setuid/setgid) antes da invocação do ExecStart=. O processo jamais roda como root, mitigando vetores críticos de RCE (Execução Remota de Código).

3. A Seção [Install]: A Ancoragem de Estado (Enablement)

A arquitetura do systemd distingue rigidamente a execução transitória (Start) da execução persistente (Enable). Iniciar um serviço apenas injeta a unidade na transação atual do DAG em memória. Se a máquina reiniciar, o serviço não voltará, pois não há registro em disco dessa intenção de design.

A seção [Install] não é lida pelo PID 1 durante o boot. Ela é processada exclusivamente pela ferramenta systemctl (rodando em espaço de usuário interativo) quando o administrador executa o comando systemctl enable ou systemctl disable.

3.1. A Semântica do WantedBy= e RequiredBy=

Para que um serviço inicie durante o boot, ele precisa ser tracionado por uma unidade que já faz parte do caminho crítico de inicialização (como o multi-user.target). A seção [Install] declara as dependências inversas.

Quando o Unit File declara WantedBy=multi-user.target, e o comando systemctl enable é invocado, o systemctl executa uma operação simétrica no sistema de arquivos estático (em /etc/systemd/system/):

  1. Ele localiza o diretório auxiliar do alvo (ex: /etc/systemd/system/multi-user.target.wants/). Se não existir, ele o cria.
  2. Ele gera um link simbólico (symlink) neste diretório apontando para o arquivo original do serviço.
  3. No próximo boot, ao carregar a unidade multi-user.target, o parser do systemd varrerá automaticamente a pasta .wants/ associada. Ele injetará um Wants= implícito para cada symlink encontrado ali.

O WantedBy= estabelece uma dependência fraca reversa persistida fisicamente no disco. O RequiredBy= gera o symlink no diretório homônimo .requires/, estabelecendo uma dependência forte reversa persistente.

3.2. Aliasing e Corolários

  • Alias=: Cria nomes alternativos persistentes. Ao executar o enable, cria-se um symlink com o nome alternativo apontando para a unidade real. Permite que o comando systemctl restart meubanco.service atue sobre postgresql-14.service, facilitando scripts de automação e transições de versão.
  • Also=: Ao habilitar esta unidade, instrui o systemctl a habilitar silenciosamente e simultaneamente as outras unidades listadas, gerenciando conjuntos de unidades (como acoplar a habilitação de um .socket junto ao .service que o consome).