Предисловие

Этот инструмент появился не как идея “сделать проект”, а как попытка решить очень конкретную и довольно неприятную проблему.

Однажды мне нужно было разобраться с настройкой сети на домашнем сервере. К тому моменту я уже был сильно уставшим, но решил всё-таки довести задачу до конца. В итоге усталость сыграла свою роль — где-то по пути я сломал систему.

В тот момент я этого даже не заметил.

Вечером ничего не заработало, я просто ушёл спать. А на утро выяснилось, что проблема была не в сети, а в банальных ошибках в конфигурационных файлах — лишние символы, опечатки, которые я не заметил из-за усталости.

Исправление заняло несколько часов. И вот это было самое неприятное.
Не сама ошибка — с этим можно жить. А то, что на её исправление ушло непропорционально много времени.

В какой-то момент я поймал себя на мысли:

Почему у меня нет простого способа откатиться назад?

Система была относительно свежей — около пары месяцев после установки. Нормальные бэкапы я тогда ещё не настроил, да и, честно говоря, не чувствовал в этом острой необходимости.

Но в тот момент стало очевидно:

Мне не нужен backup. Мне нужен rollback.

Причём быстрый, простой и локальный.
Так начался поиск решения, который в итоге привёл к созданию собственного инструмента.

Проблема: rollback на Linux — неочевидная задача

На первый взгляд может показаться, что задача отката системы уже давно решена. Существуют бэкапы, снапшоты, различные файловые системы с поддержкой моментальных снимков.

Однако на практике всё упирается в одну деталь: в большинстве реальных машин используется ext4. Это стандартная файловая система по умолчанию, и в продакшене, и в домашних серверах. При этом у неё нет встроенного механизма снапшотов.

И именно здесь начинается проблема.

Backup ≠ Rollback

Резервное копирование и откат системы — это разные вещи, хотя их часто пытаются использовать взаимозаменяемо.

Backup решает задачу сохранности данных: он нужен на случай поломки диска, потери информации или необходимости восстановиться спустя длительное время. Это, как правило, тяжёлые операции, работа с большим объёмом данных и зачастую — с удалёнными хранилищами.

Rollback, наоборот, про другое. Это быстрый локальный возврат системы в предыдущее состояние — например, “как было вчера” или “до последних изменений”.

Технически backup можно использовать для rollback, но на практике это неудобно. Восстановление занимает много времени, требует работы с большим объёмом данных и часто оказывается избыточным для локальных проблем.

В результате получается, что инструмент есть, но он не подходит под задачу.

Где это начинает болеть

Проблема становится очевидной в самых обычных сценариях. Любые изменения в системе — от правки конфигурации до обновления пакетов — могут привести к тому, что система перестаёт работать корректно. Это может быть неочевидная ошибка в конфиге, некорректное обновление или просто неудачный эксперимент.

В таких случаях восстановление превращается в ручную работу: нужно искать, что именно сломалось, сравнивать конфигурации, проверять логи. И всё это занимает время.

В какой-то момент становится очевидно, что не хватает простого механизма: вернуть систему в состояние, в котором она точно работала час назад.

Почему готовые решения не всегда подходят

На первый взгляд, решения есть. Но у каждого из них есть ограничения.

Файловые системы вроде Btrfs или ZFS действительно предоставляют удобные снапшоты, но требуют использования конкретной FS. Это означает миграцию системы, что не всегда возможно или оправдано.

LVM-снапшоты тоже решают задачу, но предполагают, что система изначально построена на LVM. В реальности это встречается далеко не всегда, особенно в домашних и простых серверных установках.

Backup-инструменты вроде borg или restic решают другую задачу — хранение данных, а не быстрый откат. Они отлично подходят для резервного копирования, но избыточны для локального rollback.

В итоге получается парадокс: инструменты есть, но ни один из них не даёт простой, быстрый и удобный rollback в типичной системе на ext4.

Формулировка проблемы

Ситуация сводится к простой мысли: стандартная конфигурация Linux не даёт удобного механизма отката, а существующие решения либо требуют изменения инфраструктуры, либо решают другую задачу.

При этом сама потребность остаётся.

Нужен способ быстро и локально вернуть систему в предыдущее состояние, не меняя файловую систему и не поднимая тяжёлые инструменты.

Идея решения: rsync и hardlink вместо встроенных снапшотов

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

Ключевая идея оказалась довольно простой:

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

В качестве основы были выбраны два механизма:

  • rsync — для синхронизации состояния файлов

  • hardlink — для экономии дискового пространства

Почему именно rsync

rsync уже давно используется как универсальный инструмент для копирования и синхронизации файлов. Он умеет сравнивать директории, определять изменения и переносить только то, что действительно изменилось.

В контексте задачи rollback это даёт важное свойство:

Можно привести файловую систему к нужному состоянию, не копируя всё целиком

То есть rsync выступает не как “копировщик”, а как механизм приведения состояния.

Где появляется проблема

Если просто копировать систему целиком при каждом snapshot, это быстро становится неработающим:

  • слишком большой объём данных

  • медленная работа

  • неэффективное использование диска

Такой подход не подходит для регулярных снапшотов.

Hardlink как ключ к эффективности

Решение даёт механизм hardlink.

Hardlink — это не копия файла, а дополнительная ссылка на тот же самый inode в файловой системе. Это означает, что один и тот же файл может “существовать” в нескольких местах, занимая при этом место только один раз.

Именно это позволяет реализовать дедупликацию без дополнительной логики.

Если файл не изменился, он просто переиспользуется через hardlink. Если изменился — создаётся новая физическая копия.

Как это объединяется

Комбинация rsync и hardlink даёт нужный эффект.

Каждый новый snapshot создаётся на основе предыдущего:

rsync -aHAX --numeric-ids \ 
 --delete \
  --link-dest="${PREV}" \
  "${SRC}" "${DEST_TMP}/"  

Фактически вся логика сводится к одной команде: rsync сравнивает текущее состояние с предыдущим snapshot и не копирует неизменённые файлы, а переиспользует их через hardlink, записывая только изменения.

Как это реализовано на практике

После того как определена идея, следующий вопрос — как это выглядит в реальной системе.

Здесь важно было сохранить несколько принципов:

  • никаких скрытых механизмов

  • прозрачная структура

  • предсказуемое поведение

  • быстрый запуск и откат

В результате реализация свелась к довольно простой, но аккуратной схеме.

Структура хранения

Снапшоты хранятся как обычные директории:

.infra_snapshots/
├── system/
│   ├── 2026-03-01_00-30-00/
│   ├── 2026-03-02_00-30-00/
│   └── LATEST -> 2026-03-02_00-30-00/
├── docker/
│   ├── 2026-03-01_23-30-00/
│   └── LATEST -> 2026-03-01_23-30-00/

LATEST — это симлинк на последний snapshot, используемый по умолчанию.

Каждый снапшот — это отдельная папка с полной структурой файлов.

При этом важно:

Внешне это выглядит как полноценная копия системы, но фактически хранит только изменения

Создание снапшота

Процесс создания снапшота состоит из нескольких шагов.

Сначала создаётся временная директория вида:

.tmp-YYYY-MM-DD_HH-MM-SS

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

mv -T "${TMP_DEST}" "${FINAL_DEST}"
ln -sfn "${FINAL_DEST}" "${LATEST_LINK}"

Снапшот сначала собирается во временной директории, и только после успешного завершения атомарно “переключается” в финальное состояние.

Далее выполняется rsync с использованием --link-dest. На этом этапе происходит основная работа:

  • файлы сравниваются с предыдущим снапшотом

  • неизменённые переиспользуются через hardlink

  • изменённые записываются заново

После успешного завершения временная директория переименовывается в финальную.

Этот шаг делает снапшот атомарным с точки зрения пользователя:

  • либо снапшота нет

  • либо он полностью готов

Промежуточного состояния не существует.

LATEST

Для удобства используется симлинк LATEST, который всегда указывает на последний снапшот.

Это позволяет:

  • не искать вручную последний snapshot

  • использовать его по умолчанию при restore

  • упростить автоматизацию

Разделение system и docker

Отдельно стоит важное решение — разделение снапшотов.

Система и Docker обрабатываются независимо.

Это сделано по нескольким причинам.

Во-первых, у них разная природа данных. Системные файлы — это конфигурация и окружение, тогда как Docker содержит как инфраструктуру, так и пользовательские данные.

Во-вторых, это позволяет не смешивать rollback и хранение данных. Например, Docker volumes намеренно не входят в снапшоты.

В результате:

  • system snapshot отвечает за состояние системы

  • docker snapshot — за инфраструктуру

Такое разделение даёт более точный и безопасный restore.

Восстановление

Restore реализован через тот же rsync, но в обратную сторону.

Фактически это процесс синхронизации:

Текущее состояние системы приводится к состоянию выбранного снапшота

При этом используется режим dry-run, который показывает план изменений до их применения:

rsync -aHAX --numeric-ids --delete \
  --dry-run \
  "${SNAP}/" "/"

Это важно, потому что restore — потенциально опасная операция: перед выполнением можно увидеть, какие файлы будут изменены, удалены или добавлены.

Что в итоге получилось

Вся система сводится к понятной модели:

  • снапшоты — это обычные директории

  • изменения хранятся эффективно

  • restore — это синхронизация состояния

Без:

  • специальных файловых систем

  • сложных зависимостей

  • скрытых механизмов

Реализация получилась достаточно простой с точки зрения компонентов, но при этом даёт нужное поведение.

Вся система строится на стандартных инструментах Linux и остаётся полностью прозрачной для пользователя

Ограничения и подводные камни

Несмотря на то что подход с rsync и hardlink даёт удобный механизм отката, нужно понимать его ограничения. Это не “магические снапшоты” уровня файловых систем, а файловая модель со своими особенностями.

Нет атомарности на уровне всей системы

В отличие от Btrfs или ZFS, здесь нет мгновенного снимка всей системы в один момент времени.

Снапшот создаётся в процессе выполнения rsync. Это означает, что данные читаются последовательно, и система в этот момент может изменяться.

На практике это приводит к тому, что snapshot является согласованным на уровне файлов, но не обязательно отражает состояние системы в конкретную секунду.

Это, к слову, одна из причин, почему Docker volumes не входят в снапшоты: для активно изменяемых данных такая модель не всегда даёт корректный результат.

Хотя для большинства задач rollback этого достаточно, но это стоит учитывать.

Проблемы с активно изменяемыми данными

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

Например:

  • базы данных

  • журналы (logs)

  • файлы, которые постоянно перезаписываются

В таких случаях возможно, что snapshot зафиксирует “промежуточное” состояние.

Это не обязательно приведёт к проблемам, но в некоторых сценариях может потребовать дополнительного внимания.

Практический вывод здесь простой:

Для критичных сервисов лучше либо останавливать их перед snapshot, либо использовать отдельные стратегии резервного копирования

Это не backup

Хочу отдельно подчеркнуть: данный подход не заменяет полноценное резервное копирование.

Он не защищает:

  • от выхода из строя диска

  • от удаления самих снапшотов

  • от внешних проблем (например, повреждения файловой системы)

Snapshot — это инструмент локального отката, а не хранения данных.

Поведение restore

Restore — это операция синхронизации, а не “отмена изменений”.

Это означает, что:

  • новые файлы, которых нет в snapshot, будут удалены

  • изменённые файлы будут перезаписаны

  • текущее состояние системы будет приведено к прошлому

Это мощный механизм, но он требует понимания того, что именно будет изменено.

Именно поэтому в реализации используется обязательный dry-run перед применением.

Ограничения docker snapshot

Отдельный момент касается Docker.

Снапшоты намеренно не включают:

  • docker volumes

  • runtime-данные контейнеров

Это сделано для того, чтобы:

  • не раздувать объём снапшотов

  • не замедлять их создание

  • не смешивать rollback инфраструктуры с хранением пользовательских данных

В результате docker snapshot позволяет восстановить структуру окружения, но не сами данные внутри контейнеров.

Ограничения ли?

Все эти ограничения — не недостаток реализации, а следствие выбранного подхода.

Он сознательно ориентирован на:

  • простоту

  • предсказуемость

  • минимальные требования к системе

При понимании этих ограничений инструмент работает именно так, как от него ожидается.

Это не универсальное решение “на все случаи”, а практичный инструмент под конкретный класс задач

Заключение

В итоге задача оказалась не в том, чтобы найти “идеальный инструмент”, а в том, чтобы правильно сформулировать проблему.

Нужен был не backup, не сложная система снапшотов и не новая файловая система. Нужен был простой и предсказуемый механизм, который позволял бы быстро откатить систему в рабочее состояние.

В итоге решение оказалось значительно проще, чем ожидалось. Комбинации rsync и hardlink достаточно, чтобы получить поведение, близкое к снапшотам, без изменения инфраструктуры и без тяжёлых зависимостей.

При этом надо понимать границы такого подхода. Это не универсальная система хранения данных и не замена полноценным бэкапам. Это инструмент под конкретную задачу — быстрый локальный rollback.

О проекте

Изначально этот инструмент создавался как локальное решение под конкретную задачу. Он не задумывался как публичный проект, не был рассчитан на гибкость или удобство установки.

Со временем стало понятно, что подобная проблема возникает достаточно часто, а простого решения под ext4 не хватает. Поэтому инструмент был приведён в более универсальный вид и опубликован.

На данный момент он:

  • уже работает

  • может использоваться

  • решает поставленную задачу

Но при этом остаётся в стадии доработки:

  • не все сценарии учтены

  • конфигурация ещё может быть упрощена

  • часть решений пока ориентирована на конкретное окружение

GitHub

Если вам интересен сам инструмент или подход, исходный код доступен на GitHub:

https://github.com/W1spi/ext4-rollback-tool

P.S. Проект сейчас в активной разработке. Это не polished-решение.

Вместо вывода

Эта статья — не столько про инструмент, сколько про подход, который я неожиданно открыл для себя.

Иногда задача решается не внедрением сложных технологий, а переосмыслением того, что уже есть в системе.

В данном случае оказалось, что стандартных инструментов Linux достаточно, чтобы построить удобный и практичный механизм rollback, если правильно их использовать.

Upd

Пока писал статью, инструмент успел заметно эволюционировать и по сути дошёл до состояния первой полноценной версии.

По сути, из “скрипта под себя” он превратился в простой и воспроизводимый rollback-механизм для ext4, которым уже можно пользоваться на практике.

При этом сама идея осталась неизменной: никаких сложных зависимостей, никакой смены файловой системы — только стандартные инструменты Linux и понятная логика работы.

Если коротко: это тот инструмент, которого мне самому не хватало.

Если у вас есть идеи, вопросы или замечания — буду рад обсудить.