Сорок файлов .ovpn для одной страны приходят от провайдера, и план кажется очевидным: скопировать случайный на его место, затем запустить туннель. Записанный как конвейер внутри ExecStart, этот план умирает при загрузке с кодом состояния, который ничего не объясняет. Файл юнита — не скрипт, и исправление состоит либо в том, чтобы явно подключить оболочку, либо в том, чтобы полностью отказаться от выбора файлов.

Симптом: юнит, который запускается и сразу умирает

Статус показывает, что служба упала за долю секунды, а строка журнала называет команду, которую ни одна оболочка никогда бы не получила. О туннеле ничего не появляется, потому что туннель так и не начался.

Прочитать точную командную строку из журнала — самый быстрый способ это подтвердить. systemd повторяет то, что выполнил, и в строке символы конвейера будут показаны как буквальные аргументы, а не как операторы.

Почему ExecStart не может выполнить конвейер

systemd сам разбирает командную строку, применяет свои правила кавычек и подстановки переменных, а затем выполняет результат напрямую. Конвейеры, перенаправление, подстановка шаблонов оболочкой и подстановка команд просто не входят в эту грамматику. Это осознанный выбор, а не упущение: юнит декларативно говорит, что запускать, а логика оболочки принадлежит скрипту, который юнит вызывает. Оба исправления следуют отсюда, и ни одно не требует давать юниту больше прав.

Это также объясняет вводящий в заблуждение код выхода. Первый аргумент считается бинарным файлом, поэтому неправильная строка терпит неудачу ещё до того, как запустится хоть какая-то часть вашей логики.

Сделайте выбор в ExecStartPre

Прямое исправление — это подготовительный шаг, который явно вызывает оболочку и держит выбор и запуск в одном юните, чтобы сбой в одном не оставил другой работать в одиночку.

[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

Три детали решают, переживёт ли это встречу с реальной машиной. Именование — первая ловушка: systemd подставляет %i ещё при разборе юнита, задолго до запуска bash, поэтому спецификатор должен стоять вне одинарных кавычек. xargs -r не даёт cp запуститься без аргумента, когда шаблон ни с чем не совпадает. А неудачный подготовительный шаг останавливает юнит, и это правильный исход: начинать со вчерашней конфигурацией хуже, чем не начинать. Случайный выбор, который всегда берёт один и тот же файл, обычно не невезение. Проверьте, не перезаписывает ли скопированный файл обновление пакета, и не совпадает ли шаблон всего с одной устаревшей записью.

Или позвольте OpenVPN выбрать конечную точку

OpenVPN уже может держать несколько конечных точек в одном профиле и случайно выбирать среди них, что полностью убирает оболочку, копирование и право записи из пути загрузки.

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

Эта форма держит выбор сервера внутри файла, который создал провайдер, поэтому ротация, переименовывающая файлы, не ломает юнит. Она также делает список кандидатов проверяемым, чем подстановка шаблона по каталогу никогда не бывает. Перемешивание всё ещё оправдано, когда профили различаются не только конечной точкой: другие сертификаты, другие директивы маршрутизации, другая политика шифров. В этом случае выбор идёт между целыми конфигурациями, и шаг с оболочкой — честный способ это выразить.

Прочитайте обратно экземпляр и файл, который он использовал

Прежде чем отлаживать содержимое, убедитесь, какой юнит действительно работает. Пакетная мета-служба и шаблон — это инфраструктура; юниты, соответствующие вашим файлам, — это экземпляры, названные в их честь.

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

Экземпляр, который никто не помнит, что включал, обычно ведёт к заблудшему файлу конфигурации в каталоге пакета. Отключите именно этот именованный экземпляр, а не шаблон, иначе следующее обновление пакета вернёт его. После изменения запустите юнит и прочитайте командную строку, которую сообщает журнал, затем подтвердите путь конфигурации, который он действительно открыл. Эта единственная проверка отделяет работающий случайный выбор от службы, которая тихо использует файл прошлого месяца.

Похожие статьи