Каталог .d рядом с юнитом — единственная правка, которую не сотрёт пакетный менеджер
Знакомая ситуация: в юнит сервиса аккуратно вписана нужная строка, всё работает, а после очередного апдейта сервис ведёт себя так, будто правки не было. Так и есть — файл переписан установщиком. У Xray это /etc/systemd/system/xray.service, и разработчики честно оставили предупреждение внутри своего же вспомогательного файла: «Or all changes you made will be lost!». С юнитом Docker, приезжающим из пакета, произойдёт ровно то же самое.
Копировать чужой юнит к себе и жить с копией — плохая стратегия: апстрим будет чинить свой файл, а у вас останется снимок двухлетней давности, о расхождении вы узнаете случайно. В systemd для этого предусмотрен отдельный механизм — drop-in, крошечный .conf, который меняет только интересующие вас строки.
Из чего складывается конфигурация сервиса
Разобрав основной файл foo.service, менеджер идёт в одноимённый каталог foo.service.d/ и дочитывает оттуда всё, что оканчивается на .conf. Таких каталогов система знает три, и вес у них разный — от пакетного к пользовательскому: /usr/lib/systemd/system/, затем /run/systemd/system/, затем /etc/systemd/system/. Формулировка из systemd.unit(5): «Drop-in files in /etc/ take precedence over those in /run/ which in turn take precedence over those in /usr/lib/».
А вот дальше начинается то, что регулярно упускают. Каталог не решает, кто применится позже: сортировка идёт по имени файла — «Multiple drop-in files with different names are applied in lexicographic order, regardless of which of the directories they reside in». Отсюда практическое следствие: файл override.conf из-под systemctl edit окажется поверх установочного 10-donot_touch_single_conf.conf, потому что в ASCII цифра меньше буквы.
Схема по документации systemd.unit(5)
ℹ️ Справка
Именами дело не ограничивается. Есть верхнеуровневый service.d/, который цепляется ко всем сервисам разом. Инстанс шаблона читает сначала foo@bar.service.d/, потом foo@.service.d/. А если в имени юнита есть дефисы, к списку добавляются укороченные каталоги — например foo-.service.d/.
Случай первый: прокси для докерового демона
Файл ~/.docker/config.json демону не указ — он смотрит на переменные окружения собственного процесса. Поэтому в документации Docker описан именно drop-in:
Убедиться в результате помогает systemctl show --property=Environment docker — ожидаемый ответ содержит все три пары в одной строке. Ничего не вывелось? Либо промахнулись каталогом, либо забыли про daemon-reload. Rootless-инсталляции работают по тому же принципу, но каталог лежит в домашней директории — ~/.config/systemd/user/docker.service.d/, а sudo меняется на флаг --user.
Случай второй: другая команда запуска и пустая строка перед ней
На этом месте спотыкаются чаще всего. Директива ExecStart= относится к накапливающимся: ваша строка не вытеснит исходную, а встанет рядом с ней. При этом в systemd.service(5) записано жёсткое условие — «Unless Type= is oneshot, exactly one command must be given». Для обычного сервиса второй команды достаточно, чтобы юнит отказался загружаться.
Лечится это обнулением списка перед новым значением, что документация подтверждает прямо: «If the empty string is assigned to this option, the list of commands to start is reset». Выглядит так:
[Service]
ExecStart=
ExecStart=/usr/local/bin/xray run -confdir /usr/local/etc/xray/
Ровно этот приём использует и сам установщик Xray в своём drop-in — сперва пустое присваивание, следом рабочая команда. Правило универсально: ExecStartPre=, Environment=, After= ведут себя точно так же.
⛔ Так делать нельзя
Каталог /usr/lib/systemd/system/ отдан пакетному менеджеру — редактировать лежащие там юниты бессмысленно. Второй антипаттерн мягче, но вреднее вдолгую: тянуть весь юнит целиком через systemctl edit --full ради одной директивы. Полная копия перестаёт получать апстримные исправления, и обнаружится это на первом же релизе, который что-то поменял внутри.
Случай третий: лимиты и рестарт — обнуление не требуется
Такие директивы одиночные, побеждает последнее присвоенное значение.
Отдельная команда перезагрузки после systemctl edit не нужна: выход из редактора уже делает действие, «equivalent to systemctl daemon-reload». Смотреть при этом стоит не в файл, а на то, что получил менеджер: systemctl show -p LimitNOFILE -p Restart xray.service. Кстати, применительно к Xray дескрипторы трогать смысла мало — установщик и так задаёт LimitNOFILE=1000000 вместе с LimitNPROC=10000; куда чаще правят Restart=on-failure, переводя его в always.
✅ Как правильно
После каждой правки — три команды:
systemctl cat xray.service — основной файл плюс все drop-in с путями, в порядке применения;
systemd-analyze verify xray.service — синтаксис до того, как сервис не поднимется;
systemd-delta — общесистемная картина: что чем перекрыто.
⚠️ Осторожно
Команда systemctl revert xray.service возвращает вендорское состояние, но вместе с этим уносит весь каталог xray.service.d/ — включая файлы, которые положили не вы. Применительно к Xray это потеря 10-donot_touch_*.conf с путём к конфигу. Чтобы убрать только собственную правку, достаточно удалить свой файл и выполнить daemon-reload.
Есть ещё режим, о котором вспоминают редко, — флаг --runtime. Изменение записывается в /run/systemd/system/ и живёт до перезагрузки. Пригодится, когда сервис нужно на полчаса перевести в Restart=no и при этом ничего после себя не оставить.
Источники
systemd.unit(5) — каталоги drop-in, приоритет и лексикографический порядок
Как у вас организованы правки юнитов — отдельные drop-in или всё же копия файла целиком? И случалось ли, что обновление пакета молча вернуло вендорский .service, а вы узнали об этом по упавшему сервису?
Вы можете написать сейчас и зарегистрироваться позже.
Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.
Знакомая ситуация: в юнит сервиса аккуратно вписана нужная строка, всё работает, а после очередного апдейта сервис ведёт себя так, будто правки не было. Так и есть — файл переписан установщиком. У Xray это
/etc/systemd/system/xray.service, и разработчики честно оставили предупреждение внутри своего же вспомогательного файла: «Or all changes you made will be lost!». С юнитом Docker, приезжающим из пакета, произойдёт ровно то же самое.Копировать чужой юнит к себе и жить с копией — плохая стратегия: апстрим будет чинить свой файл, а у вас останется снимок двухлетней давности, о расхождении вы узнаете случайно. В systemd для этого предусмотрен отдельный механизм — drop-in, крошечный
.conf, который меняет только интересующие вас строки.Из чего складывается конфигурация сервиса
Разобрав основной файл
foo.service, менеджер идёт в одноимённый каталогfoo.service.d/и дочитывает оттуда всё, что оканчивается на.conf. Таких каталогов система знает три, и вес у них разный — от пакетного к пользовательскому:/usr/lib/systemd/system/, затем/run/systemd/system/, затем/etc/systemd/system/. Формулировка изsystemd.unit(5): «Drop-in files in /etc/ take precedence over those in /run/ which in turn take precedence over those in /usr/lib/».А вот дальше начинается то, что регулярно упускают. Каталог не решает, кто применится позже: сортировка идёт по имени файла — «Multiple drop-in files with different names are applied in lexicographic order, regardless of which of the directories they reside in». Отсюда практическое следствие: файл
override.confиз-подsystemctl editокажется поверх установочного10-donot_touch_single_conf.conf, потому что в ASCII цифра меньше буквы.ℹ️ Справка
Именами дело не ограничивается. Есть верхнеуровневый
service.d/, который цепляется ко всем сервисам разом. Инстанс шаблона читает сначалаfoo@bar.service.d/, потомfoo@.service.d/. А если в имени юнита есть дефисы, к списку добавляются укороченные каталоги — напримерfoo-.service.d/.Случай первый: прокси для докерового демона
Файл
~/.docker/config.jsonдемону не указ — он смотрит на переменные окружения собственного процесса. Поэтому в документации Docker описан именно drop-in:Убедиться в результате помогает
systemctl show --property=Environment docker— ожидаемый ответ содержит все три пары в одной строке. Ничего не вывелось? Либо промахнулись каталогом, либо забыли проdaemon-reload. Rootless-инсталляции работают по тому же принципу, но каталог лежит в домашней директории —~/.config/systemd/user/docker.service.d/, а sudo меняется на флаг--user.Случай второй: другая команда запуска и пустая строка перед ней
На этом месте спотыкаются чаще всего. Директива
ExecStart=относится к накапливающимся: ваша строка не вытеснит исходную, а встанет рядом с ней. При этом вsystemd.service(5)записано жёсткое условие — «Unless Type= is oneshot, exactly one command must be given». Для обычного сервиса второй команды достаточно, чтобы юнит отказался загружаться.Лечится это обнулением списка перед новым значением, что документация подтверждает прямо: «If the empty string is assigned to this option, the list of commands to start is reset». Выглядит так:
Ровно этот приём использует и сам установщик Xray в своём drop-in — сперва пустое присваивание, следом рабочая команда. Правило универсально:
ExecStartPre=,Environment=,After=ведут себя точно так же.⛔ Так делать нельзя
Каталог
/usr/lib/systemd/system/отдан пакетному менеджеру — редактировать лежащие там юниты бессмысленно. Второй антипаттерн мягче, но вреднее вдолгую: тянуть весь юнит целиком черезsystemctl edit --fullради одной директивы. Полная копия перестаёт получать апстримные исправления, и обнаружится это на первом же релизе, который что-то поменял внутри.Случай третий: лимиты и рестарт — обнуление не требуется
Такие директивы одиночные, побеждает последнее присвоенное значение.
Отдельная команда перезагрузки после
systemctl editне нужна: выход из редактора уже делает действие, «equivalent to systemctl daemon-reload». Смотреть при этом стоит не в файл, а на то, что получил менеджер:systemctl show -p LimitNOFILE -p Restart xray.service. Кстати, применительно к Xray дескрипторы трогать смысла мало — установщик и так задаётLimitNOFILE=1000000вместе сLimitNPROC=10000; куда чаще правятRestart=on-failure, переводя его вalways.✅ Как правильно
После каждой правки — три команды:
systemctl cat xray.service— основной файл плюс все drop-in с путями, в порядке применения;systemd-analyze verify xray.service— синтаксис до того, как сервис не поднимется;systemd-delta— общесистемная картина: что чем перекрыто.⚠️ Осторожно
Команда
systemctl revert xray.serviceвозвращает вендорское состояние, но вместе с этим уносит весь каталогxray.service.d/— включая файлы, которые положили не вы. Применительно к Xray это потеря10-donot_touch_*.confс путём к конфигу. Чтобы убрать только собственную правку, достаточно удалить свой файл и выполнитьdaemon-reload.Есть ещё режим, о котором вспоминают редко, — флаг
--runtime. Изменение записывается в/run/systemd/system/и живёт до перезагрузки. Пригодится, когда сервис нужно на полчаса перевести вRestart=noи при этом ничего после себя не оставить.Источники
ExecStart=пустым присваиваниемedit,--drop-in=,--runtime,revert,cat💬 Вопрос к сообществу
Как у вас организованы правки юнитов — отдельные drop-in или всё же копия файла целиком? И случалось ли, что обновление пакета молча вернуло вендорский
.service, а вы узнали об этом по упавшему сервису?TOP HOSTERS: KAMATERA (30 дней бесплатного теста!)
Универсальный хостер №1 - 4VPS.su (2Гб\с сервера) - 10% скидка на первый заказ или 15% бонус на первое пополнение