MAATRIX / Блог / Наследие ручных правок: как найти всё, что разошлось с пакетными конфигами

Наследие ручных правок: как найти всё, что разошлось с пакетными конфигами

MAATRIX

Сервер достался вам с работающими сервисами и конфигами, в которые кто-то явно лазил руками — но какие строки правлены, а какие остались дефолтными из пакета, непонятно. Проблема всплывает в худший момент: apt upgrade или dnf update либо тихо затирает чужую правку дефолтом, либо оставляет рядом файл с непонятным расширением, а сервис работает на старой версии конфига. Разберём, как за один проход найти все места, где конфиг разошёлся с тем, что принёс пакет, и решить, что из расхождений сохранить, а что выкинуть как мусор.

Debian и Ubuntu: debsums и .dpkg-dist/.dpkg-old

Основной инструмент — debsums, он сверяет md5-суммы установленных файлов с теми, что были прописаны в пакете на момент установки:

apt install debsums
debsums -ce

Флаг -c — показывать только изменённые файлы, -e — ограничиться конфигурационными (conffiles). Вывод — прямой список путей, где содержимое разошлось с эталоном, без единого лишнего файла.

Ограничение: debsums работает только для пакетов, у которых сохранился файл контрольных сумм /var/lib/dpkg/info/<пакет>.md5sums. Некоторые пакеты (собранные без dpkg-dev, либо совсем старые) его не имеют — узнать список таких пакетов:

debsums -l

Для файлов из этого списка debsums бесполезен, сравнивать придётся вручную — способ описан в следующем разделе.

Если ставить отдельный пакет не хочется (минимальный контейнер), с dpkg версии 1.17.10 и новее есть встроенная проверка без установки чего-либо:

dpkg -V
# только конфиги конкретного пакета
dpkg -V nginx

По возможностям это чуть беднее debsums, но для быстрой проверки «есть ли вообще расхождения» хватает.

Отдельно от расхождений в содержимом стоит поискать файлы, которые сам dpkg уже оставил как метки конфликта при прошлых обновлениях:

find /etc -name '*.dpkg-dist' -o -name '*.dpkg-old' -o -name '*.dpkg-new'

Три расширения означают разное, и путать их — частая ошибка:

  • .dpkg-dist — новая версия конфига из пакета, которую dpkg не смог влить в изменённый локальный файл и оставил рядом, не трогая рабочий конфиг. Обновление пакета уже произошло, но вы ещё не смотрели, что изменилось в дефолте.
  • .dpkg-old — наоборот, ваша старая (изменённая) версия, которую dpkg заменил новым дефолтом и сохранил на всякий случай рядом. Локальная правка уже потеряна из рабочего файла — если она была нужна, её придётся восстанавливать из этой копии.
  • .dpkg-new — временный файл, остающийся только если обновление пакета прервалось (упал dpkg посреди распаковки); в норме его быть не должно, встретили — доустановите пакет.

Какой из первых двух вариантов получится при конфликте, зависит от политики apt при неинтерактивном обновлении (--force-confold оставляет ваш файл и создаёт .dpkg-dist, --force-confnew — наоборот) — подробнее в разделе про слияние правок ниже.

RHEL-семейство: rpm -Vf и .rpmnew/.rpmsave

Аналог debsums во всех дистрибутивах на rpm — команда rpm -V (verify), которая сравнивает файлы пакета с записанными в базе rpm контрольными суммами, размером, правами и владельцем:

rpm -Vf /etc/nginx/nginx.conf

Флаг -f — определить пакет по имени файла и верифицировать именно его, не весь пакет целиком. Строка вида S.5....T. c /etc/nginx/nginx.conf расшифровывается по колонкам: S — изменился размер, 5 — изменилась md5-сумма содержимого, T — не совпадает время модификации, c в конце — пометка «конфигурационный файл». Точка на месте буквы значит «здесь расхождения нет»: если после c идут одни точки, с точки зрения rpm файл не менялся, даже если mtime «свежий» — например, после touch без реального редактирования.

Проверить пакет целиком, а не один файл:

rpm -V nginx

Проверить вообще всё, что установлено, и отфильтровать только конфиги:

rpm -Va | grep ' c '

Это ощутимо медленнее (rpm -Va перепроверяет каждый файл каждого пакета в системе), поэтому на боевом сервере лучше сначала прогнать по конкретным пакетам, которые интересуют, а -Va оставить на разовую полную инвентаризацию.

Метки конфликта после обновления пакета — как и в Debian, две разные по смыслу:

find /etc -name '*.rpmnew' -o -name '*.rpmsave'
  • .rpmnew — новый дефолт из обновлённого пакета, положенный рядом, потому что ваш файл был изменён; рабочий конфиг остался прежним. Это происходит для файлов с флагом %config(noreplace) в спеке пакета — а таким флагом помечено большинство значимых конфигов (sshd_config, nginx.conf, httpd.conf), поэтому .rpmnew — самый частый случай.
  • .rpmsave — старая (изменённая) версия сохранена, а новый дефолт стал рабочим файлом. Так бывает для конфигов без %config(noreplace), таких меньше, но правка там теряется из рабочего файла молча, если не заметить вовремя.

В отличие от Debian, здесь поведение при конфликте прошито в самом пакете (флаг ставит мейнтейнер при сборке), а не настраивается локальной политикой — гибкости меньше, зато предсказуемее из версии в версию.

ЗадачаDebian/UbuntuRHEL-семейство
Найти все изменённые конфиги пакетовdebsums -ce`rpm -Va \grep ' c '`
Быстрая проверка без установки утилитыdpkg -Vrpm -V (уже часть базовой системы)
Кто владеет файломdpkg -S /etc/nginx/nginx.confrpm -qf /etc/nginx/nginx.conf
Новый дефолт, не влитый в текущий файл*.dpkg-dist*.rpmnew
Бэкап старого файла, перезаписанного дефолтом*.dpkg-old*.rpmsave
Кто решает, что создаётся при конфликтеполитика apt (--force-confold/--force-confnew)флаг %config(noreplace) в самом пакете

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Когда автоматической метки нет: сравниваем руками с эталоном пакета

Метки .dpkg-dist/.rpmnew появляются только в момент обновления пакета. Если пакет не обновлялся с момента правки конфига, никакого файла-подсказки рядом не будет — а узнать, что именно отличается от дефолта, всё равно нужно. Здесь помогает прямое извлечение эталонного файла из пакета, без его установки.

Debian/Ubuntu. Скачиваем .deb (не устанавливая) и достаём из него оригинальный файл:

apt-get download nginx-common
mkdir -p /tmp/pkg-extract && dpkg-deb -x nginx-common_*.deb /tmp/pkg-extract
diff -u /tmp/pkg-extract/etc/nginx/nginx.conf /etc/nginx/nginx.conf

apt-get download требует доступного в /etc/apt/sources.list репозитория с нужной версией пакета — обычно это тот же репозиторий, откуда его ставили.

RHEL-семейство. Аналогично, через dnf download и распаковку rpm без установки:

dnf install -y dnf-plugins-core   # если 'dnf download' ещё не доступен
dnf download nginx --destdir /tmp
cd /tmp
rpm2cpio nginx-*.rpm | cpio -idmv ./etc/nginx/nginx.conf
diff -u /tmp/etc/nginx/nginx.conf /etc/nginx/nginx.conf

Оба способа дают честный diff против ровно той версии пакета, что установлена сейчас — сравнение со случайно найденным в интернете «типовым конфигом nginx» ничего не докажет, версии дефолтов отличаются от релиза к релизу.

Решаем, что из правок сохранить, а что было костылём

Список расхождений на руках — дальше самое важное: не восстановить, а рассортировать. Слепо переносить всё в новую версию конфига при обновлении — такая же ошибка, как слепо всё затереть дефолтом.

Тип правкиПримерЧто делать при апдейте пакета
Хардненинг безопасностиssl_protocols TLSv1.2 TLSv1.3; вместо дефолтного набораСохранить, но сверить с новым дефолтом — версия пакета могла подтянуть тот же уровень защиты сама
Локальные пути и адресацияroot /var/www/custom;, свои upstream-блокиСохранить всегда — без этого сервис не запустится в этом окружении
Обход старого бага конкретной версииproxy_buffering off;, добавленный под известный баг старой сборкиПроверить changelog пакета: если баг закрыт в новой версии, попробовать вернуть дефолт и снять костыль
Тюнинг под конкретное железоworker_processes 8;, лимиты воркеров под старый CPUПересчитать заново под текущее железо, а не копировать значение — параметры удобно посчитать по методике из статьи про расчёт конфигурации сервера под нагрузку
Закомментированные эксперименты и мусор20 строк закомментированного блока «пробовал так, не взлетело»Удалить — не несёт функции, только раздувает diff и путает следующего инженера

Для каждой найденной правки, которую решаете сохранить, стоит зафиксировать не только «оставляем», но и почему — иначе через год это снова станет неопознанной строчкой. Проще всего писать причину прямо рядом с директивой:

# оставлено вручную: без этого таймаута падают долгие выгрузки отчётов
# из 1С, см. тикет INFRA-114 (правлено 2026-08)
proxy_read_timeout 300s;

Это не заменяет полноценную систему версионирования, но резко снижает шанс, что через два обновления пакета кто-то удалит правку просто потому, что не понял, зачем она здесь.

Сливаем правки: vimdiff, meld и защита от --force-confnew

Когда .dpkg-dist/.rpmnew уже лежит рядом с рабочим файлом, задача — не выбрать один из двух файлов целиком, а слить нужные куски. Для интерактивного сравнения удобны построчные дифф-редакторы:

vimdiff /etc/nginx/nginx.conf /etc/nginx/nginx.conf.dpkg-dist
# или, если есть графическое окружение / X-forwarding
meld /etc/nginx/nginx.conf /etc/nginx/nginx.conf.dpkg-dist

В vimdiff расхождения подсвечиваются построчно, do/dp переносят блок между окнами (do — взять из другого окна, dp — отдать в другое окно). После слияния файл-подсказку нужно убрать явно — dpkg и rpm сами их не удаляют, они лежат вечно, если не разобрать руками:

rm /etc/nginx/nginx.conf.dpkg-dist    # после того как нужное перенесено

Отдельная ловушка Debian/Ubuntu — какое поведение выбрано при неинтерактивном обновлении (unattended-upgrades, cloud-init). Если явно не настроено, apt может применить --force-confnew и заменить изменённый конфиг дефолтом, сохранив вашу правку лишь в .dpkg-old — а живым станет новый дефолт. Если правка была критичной (например, отключала уязвимый модуль), сервис на время окажется в незащищённом состоянии до следующего ручного вмешательства. Явно закрепить безопасное поведение — оставлять локальный файл и складывать новый дефолт рядом, а не наоборот:

cat >/etc/apt/apt.conf.d/local-confold <<'EOF'
Dpkg::Options {
   "--force-confdef";
   "--force-confold";
}
EOF

--force-confdef — использовать дефолтное поведение dpkg, если оно однозначно; --force-confold — в спорных случаях сохранять локальную версию и создавать .dpkg-dist. Это не решает слияние автоматически, но гарантирует, что апдейт никогда не сотрёт правку без следа.

Процесс на будущее: не разово, а при каждом обновлении

Разовая инвентаризация закрывает вопрос «что сейчас разошлось», но следующее обновление снова создаст новые .dpkg-dist/.rpmnew. Смысл в том, чтобы проверка расхождений стала обязательным шагом каждого апдейта, а не отдельным расследованием раз в год.

Практический порядок для планового обновления пакета с конфигом:

  1. До обновления — debsums -ce (или rpm -Vf) по интересующему пакету, чтобы знать, что вообще может быть задето.
  2. Обновление пакета как обычно (apt upgrade / dnf update).
  3. Сразу после — поиск свежих .dpkg-dist/.rpmnew этого апдейта (find /etc -newer /var/log/apt/history.log -name '*.dpkg-dist' или аналогично по времени последней транзакции).
  4. Слияние по таблице категорий из раздела выше, с комментарием о причине для каждой сохранённой правки.
  5. Фиксация итога — если на сервере уже настроен etckeeper (методика в статье про восстановление истории правок, ссылка выше), достаточно закоммитить результат внятным сообщением; если etckeeper ещё нет, самое время его завести, пока диффы свежие в голове.

Если серверов с одной и той же болью становится много, локальные правки стоит вынести из ручного режима — держать в репозитории и применять пайплайном, а не точечно по SSH. Подход на примере VPN-сервера разобран в статье про GitOps-подход к конфигам: там конфликт с обновлением решается на уровне процесса, а не постфактум диффом.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли просто удалить все .dpkg-dist/.rpmnew, не разбираясь, раз рабочий конфиг и так работает?

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

Debsums показывает расхождение в файле, который я точно не трогал — как так?

Возможные причины: пакет переустанавливался с другой ревизией той же версии, файл менял другой процесс автоматически (служба дописывает в свой конфиг при старте), либо конфиг генерируется шаблонизатором при установке (debconf на этапе postinst) и потому официально отличается от «сырого» файла в пакете. Сверьтесь с dpkg -L <пакет> — не создаётся ли файл динамически.

Что делать, если %config(noreplace) не спас и .rpmsave уже перезаписал мою правку?

Правка не потеряна — она в .rpmsave рядом. Сравните его с новым рабочим файлом через diff и перенесите нужные куски по той же логике категоризации, что и для .dpkg-dist/.rpmnew.

Стоит ли гнаться за нулём расхождений с пакетным дефолтом?

Нет, это не самоцель. Ноль расхождений означает либо сервер без специфики, либо что все правки перенесли в другое место (systemd override, отдельный include-файл, конфиг-менеджмент) — хорошо, если сделано осознанно, но не единственно верный путь.

Инструменты вроде debsums/rpm -V ловят права доступа и владельца или только содержимое?

И то, и другое, но раздельно по колонкам вывода (в rpm -V — буквы U/G/M для владельца/группы/прав, debsums права не проверяет вовсе, только содержимое). Если конфиг с виду не менялся, но сервис не может его прочитать — это отдельная проверка stat на права, а не про содержимое.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →