Skip to main content
PostgreSQL — единственное долговременное хранилище Omniflow. Valkey держит кэш, ограничения частоты запросов, блокировки и одноразовое состояние, поэтому его потеря не стоит ничего, кроме прогретого кэша. Всё на этой странице поэтому касается PostgreSQL.

Что такое резервная копия

Резервная копия — это вывод pg_dump --format=custom --no-owner --no-privileges --compress=6, зашифрованный AES-256-GCM и записанный в APP_BACKUP_DIR. Omniflow вызывает pg_dump и pg_restore как внешние программы, а не переписывает их заново, поэтому резервную копию всегда можно восстановить штатными средствами PostgreSQL, даже если сам Omniflow не запускается. Шифрование кадровое, а не один запечатанный блок, поэтому многогигабайтный дамп никогда не обязан помещаться в память:
Размер кадра — 1 MiB. Nonce каждого кадра — это базовый nonce XOR его индекса, и каждый кадр аутентифицирует собственный индекс и признак того, завершает ли он файл. Переставленный, продублированный, потерянный или обрезанный кадр не открывается вместо того, чтобы выдать правдоподобно выглядящий частичный дамп, — а это единственный исход хуже, чем полное отсутствие резервной копии. Файл, который не является резервной копией Omniflow, отвергается уже на магических байтах. Файлы называются omniflow-<kind>-<UTC timestamp>.dump.enc, например omniflow-scheduled-20260811T030000Z.dump.enc. kind — это scheduled или manual и ничего больше: на столбце стоит check-ограничение, поэтому третий вид потребует миграции. Имя уникально, а файл создаётся эксклюзивно, поэтому коллизия приводит к ошибке, а не к перезаписи.

Кто её создаёт

Операторская панель в отношении резервных копий работает только на чтение. Нет ни маршрута панели, ни маршрута /v1/admin, который создаёт или восстанавливает копию; единственные два пути записи в продукте — это расписание воркера и поверхность /ops у бота. Это сделано намеренно: восстановление разрушительно, и оно живёт за явно перечисленным списком аккаунтов Telegram, а не за правом доступа, которое можно выдать шести ролям.

Включение резервных копий

Храните APP_BACKUP_ENCRYPTION_KEY там, куда сама резервная копия базы не достаёт, — в менеджере паролей, в хранилище секретов или в запечатанном конверте. Ключ, который существует только внутри того, что он расшифровывает, — не ключ.Это другой ключ, не APP_DATA_ENCRYPTION_KEY. Сохранить нужно оба: ключ резервных копий, чтобы прочитать дамп, и ключ данных, чтобы прочитать внутри него зашифрованные контактные значения, секреты TOTP и учётные данные провайдеров. Восстановление с неправильным ключом данных даёт базу, которая загружается, но чьи запечатанные значения не открываются.
Образы бота и воркера поставляются с соответствующими клиентскими утилитами PostgreSQL 18. В compose-стеке оба монтируют общий том backup-data в /var/lib/omniflow/backups, поэтому пересборка контейнера никогда не выбрасывает резервные копии. Пароль базы данных никогда не попадает в argv дочернего процесса, где любой локальный пользователь мог бы прочитать его из списка процессов. Вместо этого DSN раскладывается на PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD и PGSSLMODE в окружении дочернего процесса.

Проверка

Резервная копия записывается как completed только после того, как её прочитали обратно: файл расшифровывается от начала до конца, а его SHA-256-дайджест сравнивается с дайджестом, посчитанным при записи. Копия, не прошедшая любую из этих проверок, удаляется и записывается как failed с классифицированным кодом ошибки. Это выполняется при каждой резервной копии, а не только по требованию. Непроверенная резервная копия — это догадка.

Управление из Telegram

Telegram несёт только операторские уведомления и действия по резервным копиям и восстановлению. Все остальные административные органы управления живут в веб-панели.
Аккаунт, отсутствующий в этом списке, не получает на /ops вообще никакого ответа — даже отказа, который подтвердил бы существование команды. Команда также игнорируется вне приватного чата с ботом. /ops показывает пять последних резервных копий с именем файла, статусом (и кодом ошибки, если он записан), размером и меткой времени в UTC, а также:
  • 💾 Сделать копию сейчас — немедленно создаёт ручную резервную копию. Если одна уже выполняется, ответом будет «A backup is already running», а не запуск второй.
  • ♻️ Восстановить последнюю… — предлагается только при наличии завершённой копии. Подтверждение в два шага: первое нажатие лишь показывает предупреждение, а второе несёт идентификатор той резервной копии, которую оператор видел.
  • 🔄 Обновить — перерисовывает список.
Панель показывает только размеры и метки времени: никаких параметров подключения, путей к файлам и материала шифрования. Оба действия пишут события журнала аудита, доступные только на добавление, с указанием аккаунта Telegram, действия, резервной копии и причины: backup.created для ручной копии, затем backup.restore_requested и backup.restored вокруг восстановления. Причина восстановления записывается как «restore requested from Telegram by operator <id>».

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

Восстановление сначала проверяет резервную копию, затем запускает pg_restore --clean --if-exists --no-owner --no-privileges --single-transaction с расшифрованным потоком на stdin. Оно намеренно прямолинейно, и именно поэтому настаивает, чтобы вызывающая сторона подтвердила разрушительное намерение и чтобы было записано, кто попросил.
Восстановление заменяет текущее содержимое базы данных. Все изменения, сделанные с момента создания резервной копии, теряются — включая оформленные заказы, проведённые платежи и полученные сообщения поддержки за этот промежуток.
Оно отказывает, а не выполняется наполовину, в следующих случаях: Перед восстановлением:
  1. Остановите api, bot и worker. Живой воркер продолжит выдавать доступ поверх состояния, которое вот-вот будет заменено.
  2. Сделайте свежую резервную копию текущей базы, даже если вы считаете её сломанной. Это единственный путь назад из ошибочного восстановления.
  3. Убедитесь, что статус целевой резервной копии — completed.
После восстановления:
  1. Запустите сначала worker и следите за /readyz и топиком исполнения заказов.
  2. Запустите api и bot.
  3. Сверьтесь с Remnawave. Выдача доступа, случившаяся после точки резервной копии, всё ещё живёт в панели, но отсутствует в Omniflow. Плановое обнаружение расхождений сообщит о несоответствиях в течение пятнадцати минут, и каждое из них решается из представления расхождений.
  4. Перепроверьте платежи в ожидании. Платёж, проведённый после точки резервной копии, подхватывается опросом сверки или повторной доставкой вебхука провайдера. Оба идемпотентны, поэтому ничто не списывается и не зачисляется дважды.
Восстановление выполняется в процессе bot, когда оно запущено из /ops, поэтому именно в его логе описан сбой, хотя сообщение на экране упоминает воркер.

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

Бинарник воркера заодно работает как фильтр расшифровки, потому что аварийное восстановление не должно зависеть от способности остального стека запуститься. Ему не нужно ничего, кроме бинарника, ключа и файла:
--decrypt-backup должен быть первым аргументом, а ключ читается из окружения до загрузки какой-либо конфигурации — не требуется ни база данных, ни Valkey, ни учётные данные Remnawave. Он падает с APP_BACKUP_ENCRYPTION_KEY is required to decrypt a backup или APP_BACKUP_ENCRYPTION_KEY must be base64-encoded 32 bytes, а при неверном ключе или повреждённом файле падает громко, а не выдаёт частичный вывод.
Atlas записывает применённые миграции в собственную схему atlas_schema_revisions, а не в public. Дамп, снятый Omniflow, несёт всю базу целиком, поэтому обычное восстановление переносит эту историю вместе с ней. Если вы пересобираете базу иным способом, перенесите эту схему тоже, иначе сервис migrate попытается применить историю заново, а диагностический комплект сообщит о нуле применённых миграций.

Срок хранения и очистка

Каждая строка резервной копии при создании помечается retain_until — на APP_BACKUP_RETENTION_DAYS вперёд. После каждого планового запуска воркер очищает до 100 просроченных копий: файл удаляется, а строка помечается как pruned. Строка сохраняется, поэтому история того, какие копии существовали, остаётся пригодной для аудита после того, как файлы исчезли. Файл, который не удалось удалить, логируется и пропускается, а его строка сохраняет свой статус, поэтому следующий проход пробует снова, а не теряет его из виду.

Просмотр истории

Резервная копия, которую никто никогда не восстанавливал, — это копия, о которой никто не знает, работает ли она; именно поэтому история восстановлений находится рядом со списком копий, а не на отдельном экране.

Обновление Omniflow

Миграции применяются только вперёд, и их накатывает сервис migrate до старта любого прикладного процесса.
  1. Сделайте резервную копию и убедитесь, что она completed.
  2. Загрузите новые образы. Закрепляйте явный тег; плавающие теги делают откат неоднозначным.
  3. Выполните docker compose up -d. Сервис migrate запускает atlas migrate apply, и все остальные сервисы ждут его успешного завершения.
  4. Следите за /readyz на каждом процессе.
Повторное применение миграций ничего не делает: Atlas помнит, что уже выполнено. CI доказывает, что каждая поддерживаемая база — пустая база плюс каждый префикс истории миграций — обновляется до головной версии, и что повторное применение головной ничего не меняет.
Сервис migrate в compose использует жёстко заданную строку подключения и не читает APP_DATABASE_URL. Если вы поменяли учётные данные базы, поменяйте их и в compose.yaml, иначе мигратор подключится не к той базе.

Откат

Omniflow не поставляет обратных миграций. Откат схемы — это восстановление:
  1. Остановите api, bot и worker.
  2. Восстановите резервную копию, снятую перед обновлением.
  3. Закрепите предыдущие теги образов и запустите стек.
  4. Сверьтесь с Remnawave, как описано выше.
Именно поэтому первый шаг обновления — резервная копия, и именно поэтому копия проверяется, прежде чем ей начинают доверять.

Обновление PostgreSQL

Omniflow поддерживает PostgreSQL 18. Чтобы перейти между мажорными версиями:
  1. Остановите api, bot и worker; postgres оставьте работающим.
  2. Сделайте резервную копию и проверьте её.
  3. Остановите postgres и запустите новую мажорную версию поверх пустого каталога данных.
  4. Восстановите резервную копию.
  5. Запустите прикладные процессы.
Не направляйте новую мажорную версию PostgreSQL на старый каталог данных. pg_upgrade — альтернатива, если вы предпочитаете обновление на месте, но путь «дамп и восстановление» — тот, под который спроектирован этот формат резервных копий. Помните, что pg_dump и pg_restore должны соответствовать мажорной версии сервера; поставляемые образы фиксируют клиентские утилиты PostgreSQL 18 именно по этой причине.

Учения по аварийному восстановлению

Проводите их ежеквартально. Непроверенная резервная копия — это предположение.
Скрипт — это сама процедура, а не её описание. Он берёт последнюю резервную копию со статусом completed, расшифровывает её через worker --decrypt-backup, восстанавливает в одноразовый экземпляр PostgreSQL в сети самого стека, сравнивает число строк, открывает запечатанное значение ключом APP_DATA_ENCRYPTION_KEY и снова удаляет экземпляр. Он никогда не пишет в боевую базу и завершается с ненулевым кодом, когда учения провалились, — поэтому запуск по расписанию что-то значит. Передайте COMPOSE, если стек использует файлы переопределения, и TABLES, чтобы сравнить другой набор:
Что он сообщает и как это читать: Шаг с запечатанным значением пропускают чаще всего, и он же чаще всего проваливается: база, восстановленная без её ключа шифрования данных, поднимается безупречно, а секреты в ней нечитаемы. Скрипт открывает запечатанный клиентский секрет customer OIDC в одноразовом контейнере, поэтому на хосте ничего устанавливать не нужно. Если в установке не настроен ни один OIDC-провайдер, запечатанного значения для проверки нет — скрипт скажет об этом, а не пройдёт молча; проверьте вручную секрет TOTP или учётные данные провайдера, прежде чем доверять такой копии. Записывайте дату и результат.

Больше, чем база данных: полное аварийное восстановление

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

Потеря всего хоста

Восстановление pg_dump возвращает каждую строку. Оно не возвращает то, что всегда жило только вне базы данных:
  • .env, либо то, что хранит его в вашем менеджере секретов.
  • APP_DATA_ENCRYPTION_KEY. Это то, что важнее всего восстановить в первую очередь. Каждое запечатанное значение — секреты TOTP, клиентские секреты OIDC, учётные данные провайдеров, контактные значения клиентов — навсегда недоступно без него, и пути повторного шифрования не существует. См. Безопасность.
  • APP_BACKUP_ENCRYPTION_KEY, хранимый отдельно от резервных копий, которые он расшифровывает, — копия, несущая оба ключа, это единственный артефакт, который расшифровывает сам себя.
  • APP_OPERATOR_TOKEN, поскольку новый никто не выпускает; утраченный токен означает повторную инициализацию поверхности с bearer-токеном.
  • Конфигурация обратного прокси и материал TLS перед стеком, которые живут в вашем собственном хозяйстве, а не в Omniflow. См. Обратный прокси.
  • DNS, указывающий на то, что заменяет утраченный хост.
Ничего из этого не входит в формат резервной копии на этой странице, потому что ничто из этого не строки в PostgreSQL. Храните это там же, где вы храните секреты, которые должны пережить то, что они защищают, — никогда внутри самой резервной копии базы данных.

Потеря Valkey

Это вообще не требует восстановления. Valkey хранит записи кэша, счётчики ограничения частоты запросов, блокировки и токены однократного использования — ничего долговременного, — поэтому утраченный экземпляр Valkey — это просто новый, запущенный пустым. См. Развёртывание. Автоматическое определение режима обслуживания следит за Valkey наравне с PostgreSQL и Remnawave, поэтому сбой приостанавливает новые покупки после той же серии отказов, что и любая другая зависимость, вместо того чтобы клиенты сталкивались с противоречивыми отказами ограничения частоты запросов и защиты от повтора посреди оформления заказа.

RPO и RTO

Изложено на основе того, что реально настраивается, а не обещанной цифры, которую эта страница не может гарантировать:
  • Целевая точка восстановления (RPO) определяется APP_BACKUP_INTERVAL_HOURS — по умолчанию 24 часа между плановыми резервными копиями, поэтому в худшем случае можно потерять активность за этот период. Уменьшите интервал ради более узкого RPO; ручная резервная копия из /ops перед рискованной операцией сужает его до нуля на этот момент.
  • Целевое время восстановления (RTO) определяется прежде всего временем восстановления, которое растёт вместе с размером базы данных, плюс окном сверки после возвращения стека — расхождения с Remnawave устраняются в течение пятнадцати минут после планового прохода сверки, а платежи в процессе рассчитываются на следующем тике сверятеля. Здесь нет фиксированной цифры, которую можно обещать: используйте продолжительность собственных учений как ориентир для вашей установки.

Связанные страницы

Развёртывание

Топология compose, тома и путь обновления, которые предполагают эти процедуры.

Диагностика неисправностей

Что означает каждый код ошибки резервного копирования и где читать лог.