O mesmo comando liga a partir de uma shell e morre sob cron, por isso a conclusão natural é que o túnel ou o certificado tem culpa. Repare no que as duas execuções têm em comum antes de acreditar nisso: o binário, a configuração e as credenciais são idênticos, e só o ambiente do processo mudou. Quando o programa nem sequer é encontrado, nenhuma quantidade de depuração TLS ajuda. A correção costuma estar num caminho, num diretório de trabalho e num punhado de variáveis de ambiente, não na VPN.

command -v openvpn na shell que funciona imprime a resposta, e é muito frequente ser /usr/sbin/openvpn, um diretório que o cron não põe no seu caminho de pesquisa.

O túnel nunca arrancou, por isso o túnel não é o problema

As tarefas iniciadas pelo cron herdam um ambiente mínimo, convencionalmente PATH=/usr/bin:/bin, e nenhum terminal de controlo. Tudo o que está instalado em /usr/sbin ou /usr/local/sbin está simplesmente ausente dessa lista, e a linha de registo diz openvpn: not found em vez de um erro TLS. O subprocess do Python falha de outra forma: herda o ambiente do processo do interpretador, que pode ser mais rico do que o do cron mas continua a não ter uma shell que expanda um til. Ambas as falhas acontecem antes de um único pacote sair do anfitrião.

Uma forma rápida de os distinguir é ler o primeiro erro em vez do último. Um binário em falta produz uma exceção da shell ou do Python, uma configuração má produz uma mensagem de utilização do OpenVPN, e uma falha real de aperto de mão produz marcas temporais e endereços de pares. Só o terceiro é um problema de túnel.

Caminhos absolutos e listas de argumentos vencem as strings de shell

Passe o caminho resolvido explicitamente e salte a shell por completo. subprocess.run(["/usr/sbin/openvpn", "--config", config, "--verb", "4"], capture_output=True, text=True, timeout=90) dá-lhe um código de retorno, stdout e stderr sem pedir a uma shell que interprete um nome de ficheiro. Construa o caminho da configuração a partir de os.environ["HOME"] ou de pathlib.Path.home(), para que um til nunca tenha de ser expandido. Quando precisar mesmo de sintaxe de shell, use shlex.split, porque dividir uma string com .split() quebra em qualquer caminho que contenha um espaço. Leia também o stderr, já que o OpenVPN escreve lá a maioria das suas falhas precoces.

A mesma regra aplica-se ao cron: a linha da tarefa não é uma shell de início de sessão, por isso nada carrega o seu perfil e nada define os seus aliases.

O que um crontab herda na realidade

Uma entrada de crontab recebe o ambiente que o crontab declara mais alguns valores predefinidos herdados, por isso escreva o caminho por extenso ou envolva a chamada num script que o faça. Uma linha como PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin no topo do ficheiro cobre os locais de instalação habituais, e o redirecionamento com >> cron.log 2>&1 é o que torna a falha seguinte legível. Um perfil protegido por frase-passe não tem forma de pedir o seu segredo sob cron, já que não existe terminal onde perguntar. Uma ligação dessas pertence a uma unidade systemd que consiga ler um ficheiro de credenciais, não a uma tarefa que finge ser interativa.

Os caminhos relativos são a segunda armadilha. Um teste acontece muitas vezes a partir da pasta que guarda o perfil, e uma tarefa de cron começa noutro sítio completamente, por isso --config office.ovpn resolve-se num ficheiro que não está lá e a mensagem lê-se como um ficheiro em falta e não como um túnel em falta.

Prove-o despindo o ambiente

Reproduza o ambiente mínimo diretamente: env -i PATH=/usr/bin:/bin /usr/sbin/openvpn --config office.ovpn --verb 4. Uma queixa de caminho ou de terminal dessa execução significa que o problema está acima da VPN. Se ligar, os suspeitos restantes são a conta com que corre, o diretório em que arranca e a credencial que apresenta. Qualquer dos dois resultados é melhor do que mais uma ronda de verificação de certificados.

Depois torne o invólucro auto-relator. Saia com um estado distinto para cada fase alcançada, registe os dois fluxos com uma marca temporal, e a falha seguinte dirá se morreu na resolução do caminho, na autenticação ou depois de o túnel já estar levantado.

Uma unidade também precisa da ordem correta: Wants=network-online.target mais After=network-online.target impedem o cliente de arrancar antes de existir um endereço ou uma rota predefinida, o que de outra forma aparece como erros de certificado ou de TLS que desaparecem num reinício manual.

Artigos relacionados