Quarenta arquivos .ovpn de um país chegam de um provedor, e o plano parece óbvio: copiar um aleatório para o seu lugar e depois iniciar o túnel. Escrito como um pipeline dentro de ExecStart, esse plano morre na inicialização com um código de estado que não explica nada. O arquivo de unidade não é um script, e a correção é ou trazer um shell explicitamente ou parar de escolher arquivos de vez.

O sintoma: uma unidade que inicia e morre imediatamente

O estado mostra que o serviço falhou em uma fração de segundo, e a linha do journal nomeia um comando que nenhum shell jamais teria recebido. Nada sobre o túnel aparece, porque o túnel nunca começou.

Ler a linha de comando exata do journal é a forma mais rápida de confirmar isso. O systemd repete o que executou, e a string mostrará os caracteres do pipeline como argumentos literais em vez de operadores.

Por que o ExecStart não consegue executar um pipeline

O systemd divide a linha de comando por conta própria, aplica a sua própria citação e substituição de variáveis e depois executa o resultado diretamente. Pipes, redirecionamento, expansão de padrões por um shell e substituição de comandos simplesmente não fazem parte dessa gramática. É uma escolha de projeto e não uma omissão: uma unidade declara o que executar, e a lógica de shell pertence a um script que a unidade invoca. Ambas as correções decorrem disso, e nenhuma exige dar mais privilégios à unidade.

Isso também explica o código de saída enganoso. O primeiro argumento é tratado como o binário, então uma linha malformada falha antes de qualquer parte da sua lógica ser executada.

Faça a seleção no ExecStartPre

A correção direta é um passo de preparação que chama um shell explicitamente, mantendo a seleção e o início na mesma unidade para que uma falha em um não deixe o outro rodando sozinho.

[Service]
Type=notify
ExecStartPre=/bin/bash -c "ls /etc/openvpn/profiles/au* | shuf -n 1 | xargs -r -I{} cp -f {} /etc/openvpn/%i.conf"
ExecStart=/usr/sbin/openvpn --config /etc/openvpn/%i.conf

Três detalhes decidem se isto sobrevive ao contato com uma máquina real. A nomenclatura é a primeira armadilha: o systemd substitui %i durante a análise da unidade, muito antes de o bash ser executado, então o especificador precisa ficar fora das aspas simples. O xargs -r impede que o cp seja executado sem argumento quando o padrão não corresponde a nada. E um passo de preparação falho para a unidade, o que é o resultado correto: iniciar com a configuração de ontem é pior do que não iniciar. Uma escolha aleatória que sempre pega o mesmo arquivo normalmente não é azar. Verifique se o arquivo copiado está sendo sobrescrito por uma atualização de pacote, ou se o padrão corresponde a apenas uma entrada obsoleta.

Ou deixe o OpenVPN escolher o endpoint

OpenVPN já consegue manter vários endpoints em um perfil e escolher entre eles aleatoriamente, o que remove completamente o shell, a cópia e o acesso de escrita do caminho de inicialização.

client
remote-random
<connection>
remote au1.example.net 1194 udp
</connection>
<connection>
remote au2.example.net 1194 udp
</connection>

Esta forma mantém a escolha do servidor dentro do arquivo que o provedor escreveu, então uma rotação que renomeia arquivos não quebra a unidade. Ela também torna a lista de candidatos auditável, o que um padrão sobre um diretório nunca é. O embaralhamento ainda merece seu lugar quando os perfis diferem em mais do que um endpoint: certificados diferentes, diretivas de roteamento diferentes, política de cifra diferente. Nesse caso a escolha é entre configurações inteiras, e um passo de shell é a maneira honesta de expressá-la.

Releia a instância e o arquivo que ela usou

Confirme qual unidade está realmente em execução antes de depurar o seu conteúdo. O serviço meta empacotado e o modelo são infraestrutura; as unidades que correspondem aos seus arquivos são as instâncias nomeadas em homenagem a eles.

systemctl list-units 'openvpn*'
systemctl cat [email protected]
journalctl -u openvpn@au -n 30 --no-pager

Uma instância que ninguém se lembra de ter ativado costuma remontar a um arquivo de configuração perdido no diretório do pacote. Desative essa instância nomeada em vez do modelo, senão a próxima atualização do pacote a trará de volta. Depois da mudança, inicie a unidade e leia a linha de comando que o journal relata, e então confirme o caminho de configuração que ela realmente abriu. Essa única verificação separa uma escolha aleatória que funciona de um serviço que reutiliza silenciosamente um arquivo do mês passado.

Artigos relacionados