
Эта статья — кросспост из моего блога. Передо мной встала задача: сделать так, чтобы при неудачном обновлении Debian можно было быстро объяснить по телефону, как вернуть систему в рабочее состояние. В итоге пришлось собрать собственный аналог механизма отката из openSUSE на базе Btrfs, Snapper и grub-btrfs.
Я не планировал публиковать этот текст где-то, кроме камерного блога, но коллеги убедили, что если мне пришлось несколько часов искать решение, то оно, вероятно, может пригодиться кому-нибудь ещё. Ну что ж…
Настало время офигительных историй. И сегодня вас ждёт сказ о том, как потратить восемь часов собственной жизни, чтоб привнести в Debian немножечко openSUSE, а может быть даже Arch’а. Но обо всём по порядку.
Итак, речь пойдёт о том, как сделать так, чтоб при неудачном обновлении Debian testing или даже unstable было очень легко откатиться до рабочей системы, буквально за 3 минуты. И это я потратил 8 часов на поиск рецепта, а уже с готовым рецептом сделал это на других ноутах за 30 минут, 25 из которых ставился и обновлялся Debian.
Я не буду приводить тут тексты скриптов, реализующих все описанные далее шаги, я залил их в репозиторий на Github. Так что тут я просто опишу, какие шаги надо сделать, чтоб достичь желаемого.
Итак, откат до рабочей системы за 3 минуты. И поможет нам в этом деле файловая система btrfs
Btrfs (B-tree File System) — это современная файловая система для Linux на базе технологии Copy-on-Write (копирование при записи), которая объединяет функции отказоустойчивого хранилища и логического управления дисками. Благодаря архитектуре CoW она исключает повреждение данных при внезапном сбое питания, а также поддерживает создание мгновенных снимков (snapshots) и разделение пространства на изолированные подтома (subvolumes) без жесткого ограничения их размера. Встроенное сжатие данных «на лету» (например, ZSTD) оптимизирует место на накопителе, а постоянный контроль хеш-сумм позволяет автоматически обнаруживать и исправлять ошибки чтения, что делает Btrfs идеальной основой для систем восстановления вроде Snapper.
То есть неким образом я могу иметь снимки состояния своего хранилища с файловой системой btrfs, при этом снимок будет не копией всех данных, а только набором отличий снимка от текущего состояния. Что как-то сразу намекает на то, что снимка домашнего каталога с музычкой, видео и прочими закачками или билдами лучше не делать. Так что начнём мы с установки.
Точнее начнём с разметки разделов. Поскольку я использую linux последние 15 лет, и это всегда был debian с небольшим перерывом на ubuntu, то у меня предсказуемо уже всё давно настроено, все конфиги есть, я совершенно не желаю всё это терять при переустановке системы и даже не хочу таскать туда-обратно на флешке. Так что /home у меня уже давно отдельным разделом (для тех, кто на винде, это некий аналог вашего отдельного диска D:). Так что /home оставляем как есть отдельным разделом на ext4, и это будет хорошо. Но кроме него (и раздела подкачки) нам понадобятся ещё два раздела: EFI и собственно / на btrfs. Отдельно стоит упомянуть, что если комп старый, типа ноута 2012 года, который в efi нормально не грузится, и приходится использовать режим legacy, то раздел должен быть не EFI (в настройке раздела это прямо отдельный пункт, хотя в реальности это FAT32), а ext4, смонтированный в /boot. И EFI, и /boot должны быть первым разделом на диске.
Считаем, что Debian поставился (хотя могут быть нюансы), пора приступать к настройке. Я обычно ставлю stable (сейчас это релиз номер 13 с кодовым именем trixie), а потом поднимаю его до testing, потому что у testing инсталлятор бывает глючный, но обнаруживается это ближе к концу установки. Debian в версии testing - это некий rolling-release большую часть времени, а стабильный выпуск тихо устаревает до следующего выпуска, получая только обновления безопасности, а обновления софта только принудительно, только некоторого, и только если вдруг очень захотелось. Если захочется оставить stable, то дальше вообще читать не надо, он не обновляется (почти) и не ломается от слова совсем, там все эти ухищрения просто не имеют смысла.
Для тех, кто хочет большего, надо пойти в файл /etc/apt/source.list и поменять там trixie на testing, при том должны остаться только строчки с testing и testing-security, никаких testing-backports не существует. После этого делается
$ sudo apt-get update && sudo apt-get dist-upgrade -y && sudo reboot
И после перезагрузки уже имеем testing.
Дальше вспоминаем про то, что снимки хранят разницу между состоянием системы текущим и тем, когда сделали снимок. Нас не очень интересует состояние /var/cache и /var/tmp, там хранится либо то, что системе не нужно после перезагрузки, либо то, что она отлично обновит и перестроит сама в тот момент, когда понадобится.
Сабтома (Subvolumes) в Btrfs— это независимо монтируемые изолированные пространства внутри единого пула файловой системы, которые внешне выглядят как обычные директории, но обладают свойствами отдельных дисковых разделов. В отличие от классических разделов LVM или ext4, сабтома не имеют фиксированного размера и динамически делят между собой всё доступное пространство накопителя. Именно эта архитектура позволяет мгновенно создавать их снимки (snapshots), гибко настраивать параметры монтирования (например, индивидуальное сжатие для разных каталогов) и изолировать системные файлы от пользовательских данных, что критически важно для корректной работы утилит отката системы вроде Snapper.
Вот в этими-то субтомами и можно разделить то, что мы будем “снимать”, и то, что мы не хотим “снимать”. Так что выделяем /var/cache и /var/tmp в отдельные субтома, чтобы потом не делать их снимки. Фактически этот шаг можно делать ещё до превращения системы в testing, но особой разницы реально нет.
Вторым шагом необходимо поставить snapper, а так же очень удобно будет иметь btrfs-assistant. Можно, конечно, всё делать из командной строки, но я стал слишком ленив, чтоб запоминать кучу команд, которые возможно понадобятся мне раз в год. Так что в данном случае я выбрал графический интерфейс. Кстати, snapper тоже можно настроить с его помощью.
Этот же скрипт инициализирует первый конфиг снаппера, отключает создание снапшотов по расписанию и включает создание снимков при каждой загрузке. Я не вижу причин фоткать состояние системы каждый час. Успешная загрузка - это достаточно хорошая точка, чтоб сделать снимок.
А ещё этот скрипт запускает вспомогательный скрипт, который позволяет включить или отключить автоматическое создание снапшотов при установке пакетов. И вот на этой штуке стоит остановиться отдельно. Snapper при каждой установке или удалении пакетов с помощью apt-get или apt может делать пару снимков, прямо перед изменениями и сразу после. Нельзя заставить его делать что-то одно из пары, только два. И он не будет делать никаких снимков, если работать напрямую с dpkg. А все графические менеджеры пакетов делают именно так. С flatpak и snap история ещё более мутная (хотя snap в debian и не приветствуется, упомянуть о нём стоит). Поэтому я такую возможность предпочёл отключить и полагаться на снимки при загрузке. Может быть потом сделаю просто ежедневные снимки “по крону”. Хотя теперь уже по сервисам и таймерам systemd, но это в другой раз.
До успеха осталось несколько шагов. Третий шаг - это поставить grub-btrfs.
grub-btrfs —это специализированный скрипт для Linux, который автоматически интегрирует снимки Btrfs (snapshots) прямо в загрузочное меню GRUB. При каждом создании нового снимка системы утилита генерирует для него отдельные пункты загрузки, позволяя пользователю загрузиться в рабочее состояние ОС прямо из меню перезагрузки в случае сбоя. В связке со Snapper это превращает загрузчик в полноценный инструмент аварийного восстановления, где откат к «рабочему snapshot» занимает считанные секунды и не требует использования Live-USB.
А после этого как раз и начинается основная магия. Дело в том, что grub-btrfs сам по себе только строит меню GRUB с включением туда снимков. Но он отслеживает изменения через Inotify, а встроенные механизмы ядра Linux не генерируют inotify-события для btrfs-сабволюмов, когда Snapper создает их через свои API-вызовы.
Из-за этого архитектурного ограничения ядра Linux штатный демон автообновления меню (grub-btrfsd или служба grub-btrfs.path) оказывается абсолютно «слепым». Он может быть включен в системе, но при создании новых снимков меню загрузчика будет оставаться пустым. Сигнал от ядра не придёт никогда.
Однако есть обходной путь, основанный на особенностях работы самого Snapper. Дело в том, что при создании любого снимка Snapper выполняет два действия:
Создает read-only сабволюм btrfs (на который Inotify не реагирует).
Записывает обычный текстовый файл info.xml с метаданными снимка внутри каталога /.snapshots/N/.
И вот на создание обычного XML-файла подсистема Inotify ядра Linux реагирует отлично! Если создать собственную службу grub-btrfs-watcher.path для Systemd с параметром "PathChanged=/.snapshots", то служба будет наблюдать за всем каталогом, и как только там появляется новый xml с метаданными, она пинает свой сервис, который принудительно вызывает пересборку меню grub, пользуясь скриптами из состава grub-btrfs.
Ну отлично, теперь снимки есть, меню пересобирается, счастье есть… Или нет?
Как выяснилось, нет. Потому что snapper создаёт снимки read-only, а Debian в отличии от openSUSE и ArchLinux не имеет встроенных механизмов для загрузки в такие снимки. Он хочет писать на диск уже при загрузке. И можно, конечно, методично выносить в сабволы один каталог за другим, пытаясь собрать коллекцию, не попадающую в снимки, но можно пойти и по другому пути.
В принципе ничего не мешает загрузиться с live-флешки, смонтировать свой том и переставить метку активного сабвола на нужный снимок. Но погодите, ведь мы точно знаем, что система, которая стартует с флешки, точно так же хочет чего-то куда-то писать ещё со старта. И мы точно знаем, что на флешку это не пишется (на live-cd точно не писалось). А значит что? Она создаёт виртуальную файловую систему в памяти. Давайте хотеть так же, только система будет грузиться не с флешки или диска, а с read-only снимка.
И в этом нам поможет утилита overlayroot. Работает она по принципу многослойного пирога (OverlayFS). При загрузке ядро монтирует наш read-only снимок в качестве нижнего, неприкосновенного слоя, а поверх него разворачивает виртуальную файловую систему прямо в оперативной памяти (tmpfs) как верхний слой для записи. Для самой операционной системы этот процесс абсолютно прозрачен. Debian считает, что он загрузился на обычный полноценный диск: systemd спокойно пишет свои логи, инициализируются сокеты, обновляются временные файлы в /var. А физически вся эта суета происходит в оперативке.
Но главное тут сделать так, чтоб файловая система переезжала в память только тогда, когда мы грузим снимок, а не основную систему. Надо настроить overlayroot так, чтобы при загрузке в основную систему он спал и никак себя не проявлял.
Для этого мы оставляем конфигурационный файл утилиты пустым, но прописываем специальный триггер "overlayroot=tmpfs" в параметры генерации меню grub-btrfs. Теперь магия включится только тогда, когда происходит загрузка снимка. Для того, чтоб этот трюк сработал, скрипт обновляет образы initramfs (стартовую рамфайловую систему, куда overlayroot внедряет свои хуки). Пакетный менеджер Debian устроен так, что при каждом последующем обновлении ядер эти хуки будут пересобираться автоматически.
И так, у нас всё готово. Перезагружаемся, ставим какой-нибудь пакет, и после этого снова уходим в перезагрузку. А вот тут главное не зевать, в меню GRUB появилась дополнительная строчка (последняя, обычно "Debian GNU/Linux snapshots"). При выборе этой строчки открывается следующий список. И там наблюдаются все уже сделанные снимки системы (возможно, там будет что-то ещё, но с этим можно разобраться потом). Главное, что там есть хотя бы один снимок, в который можно загрузиться. И после загрузки можно поисследовать систему, поковыряться в логах, главное помнить, что всё, что тут сейчас с системой будет сделано, происходит только в оперативке. И основная цель - это запустить btrfs-assistant (ну или сказать snapper rollback), выбрать к какому снапшоту хочется откатить систему (не обязательно к тому, в который сейчас система загружена), и после выбора НЕМЕДЛЕННО перезагрузиться. Это не значит очень быстро, это значит больше никаких действий не совершать, теперь только перезагрузка.
И после отката в какой-то снапшот (да хоть в последний, там пакет ещё не стоял) и обычной перезагрузки можно обнаружить, что пакета в системе нет.
Магия btrfs+snapper+grub-btrfs+overlayroot+кастомизация наконец-то работает!
Оригинал статьи находится тут