Sélection aléatoire d’OpenVPN dans systemd : pourquoi ExecStart échoue
systemd n’est pas un shell, donc une copie mélangée dans ExecStart meurt. Déplacez le choix vers ExecStartPre, ou laissez OpenVPN choisir lui-même un serveur distant.
Quarante fichiers .ovpn pour un pays arrivent d’un fournisseur, et le plan semble évident : copier un fichier au hasard à sa place, puis démarrer le tunnel. Écrit comme un pipeline dans ExecStart, ce plan meurt au démarrage avec un code d’état qui n’explique rien. Le fichier d’unité n’est pas un script, et la réparation consiste soit à faire entrer un shell explicitement, soit à cesser complètement de choisir des fichiers.
Le symptôme : une unité qui démarre et meurt aussitôt
L’état montre que le service a échoué en une fraction de seconde, et la ligne du journal nomme une commande qu’aucun shell n’aurait jamais reçue. Rien sur le tunnel n’apparaît, parce que le tunnel n’a jamais commencé.
Lire la ligne de commande exacte dans le journal est le moyen le plus rapide de le confirmer. systemd reproduit ce qu’il a exécuté, et la chaîne montrera les caractères du pipeline comme des arguments littéraux plutôt que comme des opérateurs.
Pourquoi ExecStart ne peut pas exécuter un pipeline
systemd découpe lui-même la ligne de commande, applique son propre guillemetage et sa propre substitution de variables, puis exécute le résultat directement. Les pipelines, la redirection, l’expansion de motifs par un shell et la substitution de commandes ne font tout simplement pas partie de cette grammaire. C’est un choix de conception et non un oubli : une unité est déclarative sur ce qu’il faut exécuter, et la logique de shell appartient à un script que l’unité appelle. Les deux réparations en découlent, et aucune des deux n’exige d’accorder plus de privilèges à l’unité.
Cela explique aussi le code de sortie trompeur. Le premier argument est traité comme le binaire, donc une ligne mal formée échoue avant que la moindre partie de votre logique ne s’exécute.
Faites la sélection dans ExecStartPre
La réparation directe est une étape de préparation qui appelle un shell explicitement, en gardant la sélection et le démarrage dans la même unité afin qu’une défaillance de l’une ne laisse pas l’autre tourner seule.
[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
Trois détails décident si cela survit au contact d’une vraie machine. Le nommage est le premier piège : systemd remplace %i pendant l’analyse de l’unité, bien avant que bash ne s’exécute, donc le spécificateur doit se trouver hors des guillemets simples. xargs -r empêche cp de s’exécuter sans argument quand le motif ne correspond à rien. Et une étape de préparation en échec arrête l’unité, ce qui est le bon résultat : démarrer avec la configuration d’hier est pire que ne pas démarrer. Une sélection aléatoire qui prend toujours le même fichier n’est généralement pas de la malchance. Vérifiez si le fichier copié est écrasé par une mise à jour du paquet, ou si le motif ne correspond qu’à une seule entrée obsolète.
Ou laissez OpenVPN choisir le point de terminaison
OpenVPN peut déjà contenir plusieurs points de terminaison dans un profil et en choisir un au hasard, ce qui retire complètement le shell, la copie et le droit d’écriture du chemin de démarrage.
client
remote-random
<connection>
remote au1.example.net 1194 udp
</connection>
<connection>
remote au2.example.net 1194 udp
</connection>
Cette forme garde le choix du serveur dans le fichier rédigé par le fournisseur, donc une rotation qui renomme les fichiers ne casse pas l’unité. Elle rend aussi la liste des candidats auditable, ce qu’un motif sur un répertoire n’est jamais. Le mélange garde néanmoins sa place quand les profils diffèrent par plus qu’un point de terminaison : certificats différents, directives de routage différentes, politique de chiffrement différente. Dans ce cas le choix se fait entre des configurations entières et une étape de shell est la façon honnête de l’exprimer.
Relisez l’instance et le fichier qu’elle a utilisé
Confirmez quelle unité tourne réellement avant de déboguer son contenu. Le service meta empaqueté et le modèle sont de l’infrastructure ; les unités qui correspondent à vos fichiers sont les instances nommées d’après eux.
systemctl list-units 'openvpn*'
systemctl cat [email protected]
journalctl -u openvpn@au -n 30 --no-pager
Une instance dont personne ne se souvient l’avoir activée remonte généralement à un fichier de configuration égaré dans le répertoire du paquet. Désactivez cette instance nommée plutôt que le modèle, sinon la prochaine mise à jour du paquet la ramènera. Après le changement, démarrez l’unité et lisez la ligne de commande que le journal rapporte, puis confirmez le chemin de configuration qu’elle a réellement ouvert. Ce seul contrôle sépare une sélection aléatoire qui fonctionne d’un service qui réutilise silencieusement un fichier du mois dernier.