Scelta casuale di OpenVPN in systemd: perché ExecStart fallisce
systemd non è una shell, quindi una copia mescolata dentro ExecStart muore. Spostate la scelta in ExecStartPre, o lasciate che OpenVPN scelga da sé un server remoto.
Quaranta file .ovpn per un paese arrivano da un fornitore, e il piano sembra ovvio: copiarne uno a caso al suo posto, poi avviare il tunnel. Scritto come pipeline dentro ExecStart, quel piano muore all’avvio con un codice di stato che non spiega nulla. Il file di unità non è uno script, e la riparazione consiste nel portare esplicitamente una shell oppure nel smettere del tutto di scegliere file.
Il sintomo: un’unità che parte e muore subito
Lo stato mostra che il servizio è fallito in una frazione di secondo, e la riga del journal nomina un comando che nessuna shell avrebbe mai ricevuto. Nulla sul tunnel compare, perché il tunnel non è mai iniziato.
Leggere la riga di comando esatta dal journal è il modo più rapido per confermarlo. systemd ripete ciò che ha eseguito, e la stringa mostrerà i caratteri della pipeline come argomenti letterali invece che come operatori.
Perché ExecStart non può eseguire una pipeline
systemd divide da sé la riga di comando, applica le proprie virgolette e la propria sostituzione di variabili, e poi esegue il risultato direttamente. Pipe, ridirezione, espansione di pattern da parte di una shell e sostituzione di comandi semplicemente non fanno parte di quella grammatica. È una scelta di progetto e non una dimenticanza: un’unità dichiara cosa eseguire, e la logica di shell appartiene a uno script che l’unità richiama. Entrambe le riparazioni derivano da questo, e nessuna delle due richiede di dare più privilegi all’unità.
Spiega anche il codice di uscita fuorviante. Il primo argomento è trattato come il binario, quindi una riga malformata fallisce prima che qualunque parte della vostra logica venga eseguita.
Fate la scelta in ExecStartPre
La riparazione diretta è un passo di preparazione che chiama una shell esplicitamente, tenendo la scelta e l’avvio nella stessa unità così che un guasto in uno non possa lasciare l’altro in esecuzione da solo.
[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
Tre dettagli decidono se questo sopravvive al contatto con una macchina reale. La denominazione è la prima trappola: systemd sostituisce %i durante l’analisi dell’unità, molto prima che bash venga eseguito, quindi lo specificatore deve stare fuori dalle virgolette singole. xargs -r impedisce a cp di essere eseguito senza argomento quando il pattern non corrisponde a nulla. E un passo di preparazione fallito ferma l’unità, che è il risultato corretto: partire con la configurazione di ieri è peggio che non partire. Una scelta casuale che prende sempre lo stesso file di solito non è sfortuna. Verificate se il file copiato viene sovrascritto da un aggiornamento del pacchetto, o se il pattern corrisponde a una sola voce obsoleta.
Oppure lasciate che OpenVPN scelga l’endpoint
OpenVPN può già contenere più endpoint in un profilo e sceglierne uno a caso, il che rimuove del tutto la shell, la copia e l’accesso in scrittura dal percorso di avvio.
client
remote-random
<connection>
remote au1.example.net 1194 udp
</connection>
<connection>
remote au2.example.net 1194 udp
</connection>
Questa forma tiene la scelta del server dentro il file scritto dal fornitore, così una rotazione che rinomina i file non rompe l’unità. Rende anche la lista dei candidati verificabile, cosa che un pattern su una directory non è mai. Il mescolamento conserva comunque il suo posto quando i profili differiscono per più di un endpoint: certificati diversi, direttive di routing diverse, politica di cifratura diversa. In quel caso la scelta è tra configurazioni intere e un passo con la shell è il modo onesto di esprimerlo.
Rileggete l’istanza e il file che ha usato
Confermate quale unità è realmente in esecuzione prima di eseguire il debug del suo contenuto. Il servizio meta confezionato e il modello sono infrastruttura; le unità che corrispondono ai vostri file sono le istanze che portano il loro nome.
systemctl list-units 'openvpn*'
systemctl cat [email protected]
journalctl -u openvpn@au -n 30 --no-pager
Un’istanza che nessuno ricorda di aver abilitato di solito risale a un file di configurazione smarrito nella directory del pacchetto. Disattivate quell’istanza con nome invece del modello, altrimenti il prossimo aggiornamento del pacchetto la riporta indietro. Dopo la modifica, avviate l’unità e leggete la riga di comando che il journal riporta, poi confermate il percorso di configurazione che ha realmente aperto. Quell’unico controllo separa una scelta casuale che funziona da un servizio che riutilizza silenziosamente un file del mese scorso.