Служба VPN падає на EXEC: порівняйте unit, бінарник і sudoers
Збій на кроці EXEC означає, що systemd не зміг запустити бінарник. Порівняйте шлях у unit, встановлений шлях і дозвіл у sudoers, перш ніж щось змінювати.
Учора тунель працював, а сьогодні вранці unit мертвий, у журналі Failed at step EXEC spawning /usr/local/bin/vpnclient. Облікові дані, маршрути й акаунт у це речення ніколи не потрапляють. systemd розв'язав ExecStart у шлях, попросив ядро його виконати, і ядро відмовило — найчастіше тому, що за цим шляхом нічого немає або файл не є виконуваним. Поки розібрана команда не доведе протилежне, вважайте це проблемою файлу й argv.
Чому EXEC падає до початку автентифікації
systemd не запускає ExecStart через оболонку. Він робить fork, будує вектор аргументів із рядка і викликає execve для першого елемента. Якщо цей виклик не вдався, служба ніколи не дійде до власного коду клієнта VPN, тож ні правка профілю, ні перевірка облікових даних не допоможуть.
Код стану відокремлює цю родину від звичайних падінь. Unit, що завершився зі status=203/EXEC, ніколи не запускав процес; unit, який стартував і потім завершився зі status=1 міг упасти на автентифікації, DNS або маршрутизації. Читайте код до журналу клієнта, бо в першому випадку журнал клієнта буде порожній.
Шлях у журналі — той, який використав systemd, а не обов'язково той, який редагували ви. Файли drop-in і вивід генератора можуть перекрити файл постачальника, а застарілий daemon-reload лишає старий рядок у пам'яті. Тому перша діагностика — запитати systemd, що він вважає командою, а не відкривати файл, який ви пам'ятаєте зміненим.
Три місця, де записано шлях до бінарника
Клієнт VPN, встановлений із пакета дистрибутива, скрипта постачальника й tarball релізу, може опинитися в трьох різних префіксах. Unit, правило привілеїв і інсталятор записали по одному з цих префіксів, і збій з'являється, коли два з трьох не збігаються.
| Record | Common value | How to read it |
|---|---|---|
| Unit or drop-in | /usr/local/bin/vpnclient | systemctl cat vpnclient.service |
| Installed binary | /usr/bin/vpnclient | command -v vpnclient, then ls -l the result |
| Sudoers grant | /opt/vpnclient/bin/vpnclient | sudo grep -r vpnclient /etc/sudoers.d/ |
Конкретна розбіжність виглядає так: скрипт постачальника ставить у /usr/local/bin, tarball розпаковано в /opt, а unit досі називає старий шлях /usr/bin із попереднього пакета. Існує лише один із цих файлів, і рідко це той, що в ExecStart.
Вирішіть, який шлях канонічний, перш ніж змінювати будь-який із файлів. Якщо бінарник належить пакету дистрибутива, канонічний шлях пакета, і unit має йти за ним. Якщо ним володіє скрипт постачальника, те саме правило діє для префікса постачальника. Копіювання бінарника між префіксами створює дві копії, які розійдуться під час наступного оновлення.
Коли команда в лапках стає назвою файлу
Другий режим збою ховається за тим самим рядком журналу. Хтось пише це в unit і очікує, що клієнт підключиться під час завантаження:
ExecStart=/usr/bin/"vpnclient connect office"systemd розбиває значення на слова, тож перше слово стає /usr/bin/vpnclient connect office, а лапки прибираються як синтаксис групування. Далі ядро шукає файл, у назві якого є пробіли, і такого немає. Помилка ідентична відсутньому бінарнику, хоча файл встановлено.
Підтримувана форма — справжній виконуваний файл, за яким ідуть окремі аргументи, наприклад ExecStart=/usr/bin/vpnclient connect office. Якщо робочому процесу справді потрібен конвеєр або ланцюжок, покладіть його в скрипт і викличте скрипт, бо ExecStart не має &&, не має конвеєрів і не має розкриття glob.
Лагодьте unit, а не файлову систему
Спокусливе виправлення — символічне посилання з очікуваного шляху на справжній бінарник. Воно працює, поки пакет не оновиться і посилання не вкаже на вилучений файл, або поки мітка SELinux не перестане відповідати цілі. Віддавайте перевагу редагуванню unit через drop-in, щоб файл постачальника лишався недоторканим, а оновлення — передбачуваними.
Правило привілеїв має відповідати тому самому шляху й тим самим підкомандам. Запис у sudoers для /usr/local/bin/vpnclient не дає прав на /usr/bin/vpnclient, а запис, що дозволяє будь-який аргумент, дозволяє будь-якому локальному процесу переналаштувати або зупинити тунель. Дайте два дієслова, потрібні unit, і тримайте демонтаж поза ним, якщо зупинка служби вже закриває інтерфейс.
Саме тут порівняння окупається: unit, база даних пакетів і sudoers мають називати один шлях. Коли так, помилка EXEC зникає без перезавантаження і без копії бінарника в третьому місці.
Доведіть розібрану команду до перезапуску
sudo systemctl daemon-reload
sudo systemctl cat vpnclient.service
systemctl show -p ExecStart vpnclient.service
systemd-analyze verify /etc/systemd/system/vpnclient.service
sudo systemctl restart vpnclient.service
systemctl status vpnclient.service --no-pagersystemctl show — вирішальний вивід, бо він друкує argv, який systemd справді розібрав, включно з будь-яким drop-in. systemd-analyze verify ловить відсутній або невиконуваний шлях до перезапуску, а команда status після перезапуску каже, чи існує процес тепер.
Якщо unit стартує і потім падає, ви залишили родину EXEC і увійшли в обробку помилок самого клієнта. Шукайте в журналі повідомлення клієнта, а не черговий збій execve. Тримайте diff unit і diff sudoers разом у записі про зміну, щоб наступне оновлення не повернуло розколотий шлях мовчки.