ExecStart — не shell: виправлення допоміжного юніта VPN
ExecStart запускає програму з аргументами, а не конвеєр shell. Використовуйте явний інтерпретатор або справжній скрипт, потім перевірте юніт і прочитайте журнал.
Скопіюйте робочий командний рядок у ExecStart, перезавантажте, і юніт падає або тихо завершується, бо аргументи вважаються іменами файлів. У systemd нічого не зламано. ExecStart — це список аргументів, а не shell, тому конвеєри, переспрямування та умови втрачають сенс тієї миті, коли залишають ваш термінал. Вирішіть, де має бути shell, і напишіть юніт, який точно каже, що треба виконати.
Чому конвеєр у ExecStart нічого не робить
Коли команда вводиться в терміналі, shell спершу розбирає її. Він будує конвеєр, відкриває файли для переспрямування і розгортає змінні ще до запуску будь-якої програми. systemd нічого цього не робить: він ділить ExecStart за власними правилами і виконує отриману програму з цими аргументами. Тому > приходить як буквальний аргумент, а | передається програмі замість з'єднання двох процесів. Юніт поводиться не неправильно; синтаксис означав інше.
Це зроблено навмисно, а не через недбалість, бо передбачувана обробка аргументів і робить юніти придатними до перевірки. Це дивує лише тих, хто припускає наявність shell.
Напишіть юніт, який точно каже, що запускається
Почніть із невеликого юніта, призначення якого видно в журналі, щоб проблеми розбору залишалися окремо від проблем тунелю. Цей лише повідомляє версію двійкового файлу.
[Unit]
Description=Report the installed OpenVPN version
[Service]
Type=oneshot
ExecStart=/usr/sbin/openvpn --version
StandardOutput=journal
StandardError=journalШлях — це заповнювач, тому підставте двійковий файл, який справді встановлено. Type=oneshot підходить для команди, що працює до завершення, а налаштування журналу розміщують її вивід там, де ви можете прочитати його без термінала. Розділу встановлення навмисно немає, тому юніт викликається вручну, а не вмикається під час завантаження. Перевірте файл перед запуском, бо одруківка в назві директиви виявляється інакше, ніж одруківка в шляху.
systemd-analyze verify /etc/systemd/system/openvpn-version.service
systemctl start openvpn-version.serviceТримайте логіку shell у скрипті
Коли помічнику справді потрібна умовна логіка, покладіть цю логіку в скрипт і викличте його явно. Скрипту потрібні рядок інтерпретатора, право на виконання і коди виходу, які служба може повідомити. Запуск /bin/sh -c з командою в лапках справді працює, але ховає правила цитування всередині юніта і ускладнює перевірку. Іменований скрипт можна перевірити вручну і згодом замінити, не редагуючи юніт. Не покладайтеся на профіль shell інтерактивного користувача, бо служба стартує зі своїм оточенням.
Задайте WorkingDirectory, коли важливі відносні шляхи, і передавайте оточення через підтримувані директиви юніта, а не експортуйте його в терміналі. Секрети належать захищеним файлам або обліковим даним, а не спискам аргументів, які журнал може записати.
Перевірте юніт, який справді існує
Файли drop-in змінюють поведінку, не торкаючись файлу, який ви пам'ятаєте відредагованим. Прочитайте, що завантажив менеджер і що повідомив останній запуск.
systemctl cat vpn-helper.service
journalctl -u vpn-helper.service -n 20 --no-pagerДамп юніта включає файли drop-in у порядку завантаження, і зазвичай саме звідти береться неочікуваний ExecStart. Після будь-якої зміни перезавантажте менеджер і підтвердьте, що нове визначення — це те, що друкує systemctl cat. Журнал показує точний командний рядок і статус виходу, що відокремлює проблему аргументів від відсутнього двійкового файлу або помилки прав. Потім вирішіть, чи помічник належить до наявного життєвого циклу VPN, чи має залишитися ручною діагностикою, бо другий фоновий процес тунелю створює конкуруючого власника.
Юніт, який на запит друкує правильний вивід, — гарний перший етап. Це ще не тунель, і він не повинен вдавати, що це так.