Перейти к содержимому

Бэкапы

Minishop умеет автоматически собирать ZIP-бэкапы в worker-контейнере, хранить последние архивы на сервере, отправлять их в Telegram и восстанавливать compose-папку из админки. БД восстанавливается отдельной командой обслуживания с остановкой backend и worker.

Архив создается в BACKUP_DIR, по умолчанию data/backups внутри volume shop-data.

Типовой файл называется так:

minishop-20260527-12-00.zip

Внутри:

  • database/<POSTGRES_DB>.dump - pg_dump в custom format для pg_restore;
  • database/tariffs.json - каталог тарифов из TARIFFS_CONFIG_PATH, если файл существует;
  • compose/ - snapshot папки с docker-compose.yml, .env и соседними конфигами;
  • manifest.json — дата создания, minishop_version, database_metadata.migration_ids (полный список применённых миграций), версия PostgreSQL, сведения о БД, compose snapshot и предупреждения.

Дамп и список миграций используют один экспортированный снимок PostgreSQL: миграционная история описывает именно данные архива. Версия формата ZIP и версия приложения — разные поля. Старые ZIP без новых метаданных поддерживаются: миграционная история читается из восстановленного дампа.

Если compose-папка не смонтирована или недоступна, worker не роняет весь бэкап: архив будет создан с дампом БД и предупреждением в manifest.json.

Основные параметры доступны в админке: Система -> Настройки -> Бэкапы.

Минимальный .env, если LOG_CHAT_ID уже задан и подходит для бэкапов:

BACKUP_ENABLED=True

Если бэкапы нужно отправлять в отдельный чат или topic/thread, добавьте только нужные переменные:

BACKUP_CHAT_ID=-1001234567890
BACKUP_THREAD_ID=123

Остальные backup-переменные обычно не нужны в .env: BACKUP_INTERVAL_SECONDS=3600 запускает бэкапы ровно на границе часа 12:00, 13:00 и т.д.; BACKUP_LOCAL_RETENTION=100 хранит 100 последних ZIP-архивов; BACKUP_COMPOSE_ENABLED=True, COMPOSE_BACKUP_SOURCE=. и COMPOSE_RESTORE_MODE=rw уже совпадают со стандартным compose-сценарием.

BACKUP_CHAT_ID задает чат Telegram для отправки архивов. Если он пустой, используется LOG_CHAT_ID. Для topic/thread можно указать BACKUP_THREAD_ID; если он пустой, используется LOG_THREAD_ID.

Каждый архив содержит manifest.json с SHA-256 и размером каждого файла. Это позволяет проверить, что архив не поврежден и его содержимое не отличается от manifest.

Архив не привязан к текущему инстансу, BOT_TOKEN или серверу. Его можно загрузить и восстановить на другом сервере, если формат архива поддерживается и проверки целостности проходят.

В стандартных compose-файлах есть два mount:

  • worker: ${COMPOSE_BACKUP_SOURCE:-.}:/app/compose-source:ro - только читает папку для создания snapshot;
  • backend: ${COMPOSE_BACKUP_SOURCE:-.}:/app/compose-source:${COMPOSE_RESTORE_MODE:-rw} - читает список архивов и может восстановить compose-папку из админки.

COMPOSE_BACKUP_SOURCE=. означает папку рядом с текущим docker-compose.yml. Если compose лежит в другом месте, укажите абсолютный host-путь.

Ручное создание бэкапа из админки выполняется в backend-контейнере, а автоматический backup по расписанию - в worker-контейнере. Оба контейнера должны видеть /app/compose-source. Если ручной backup содержит compose-папку, а автоматический нет, пересоздайте worker после обновления compose:

docker compose up -d --force-recreate worker
docker compose exec worker ls -la /app/compose-source

Контейнеры migrate, backend и worker также должны видеть один и тот же источник данных в /app/data. Админка читает текущий compose-файл и показывает ошибку конфигурации data_mount_mismatch, если source volume или bind mount у этих сервисов различается либо у одного из них отсутствует. До исправления предупреждения не меняйте тарифы, переводы и другие файловые настройки: разные процессы могут читать разные версии данных.

Если нужно запретить восстановление compose-файлов из контейнера, задайте:

COMPOSE_RESTORE_MODE=ro

В этом режиме восстановление БД останется доступным, а восстановление compose-папки вернет понятную ошибку о недоступной записи.

Откройте Система -> Бэкапы. В разделе можно:

  • создать новый backup вручную, не дожидаясь следующего запуска по расписанию;
  • выбрать архив, уже лежащий в data/backups;
  • загрузить ZIP-архив вручную;
  • отметить, что восстанавливать: БД, compose-папка или оба варианта;
  • ввести точное имя выбранного архива и запустить восстановление после подтверждения.

Ручное создание использует тот же механизм, что и расписание: делает pg_dump, добавляет compose snapshot, сохраняет ZIP в BACKUP_DIR, отправляет архив в Telegram и применяет локальный retention. На время ручного запуска используется общий Redis lock, поэтому он не пересечется с плановым backup или restore.

Восстановление БД из работающего HTTP-запроса запрещено: backend не может остановить сам себя и worker, продолжая надёжно выполнять восстановление. При выборе БД админка показывает инструкцию для команды обслуживания ниже. Загрузка, скачивание и создание архивов остаются доступны. Compose можно восстановить отдельно; сочетание БД и compose через HTTP отклоняется до любых изменений.

Compose-файлы восстанавливаются поверх текущей папки. Перед заменой backend создает pre-restore snapshot текущего compose-каталога рядом с остальными архивами:

minishop-pre-restore-YYYYMMDD-HH-MM.zip

После восстановления compose-папки перезапустите нужные сервисы, чтобы изменения docker-compose.yml, .env, Caddyfile/Angie/Nginx-конфигов и других файлов реально применились:

docker compose up -d --build backend worker
docker compose ps

Если менялись proxy-конфиги, перезапустите соответствующий сервис (caddy, angie, nginx, newt).

Backend валидирует архив до восстановления:

  • файл должен быть валидным ZIP;
  • manifest.json должен принадлежать remnawave-minishop и иметь поддерживаемую версию формата;
  • SHA-256 и размер каждого файла должны совпадать с manifest;
  • выбранный server-side файл должен лежать внутри BACKUP_DIR, путь вида ../backup.zip отклоняется;
  • пути внутри ZIP не могут быть абсолютными, содержать .., \, пустые сегменты или дубли;
  • архивы с подозрительно большим числом файлов, размером или zip-bomb compression ratio отклоняются;
  • для восстановления БД нужен database/*.dump или database/*.backup;
  • для восстановления compose нужны файлы внутри compose/;
  • compose restore стартует только если целевая папка существует и доступна на запись;
  • backup/restore защищены одним Redis lock, чтобы две операции не выполнялись одновременно.
  • восстановление требует точного повторного ввода имени выбранного архива;
  • БД восстанавливается только в пустую отдельную базу; текущая база сохраняется до и после переключения.

Это защищает от случайной загрузки мусорного файла, zip-slip-архивов и поврежденных ZIP. Проверка специально не привязана к секретам инстанса, чтобы архивы можно было использовать для переноса между серверами. Это не проверка доверенного источника: не восстанавливайте архивы, происхождение которых вы не контролируете.

Для переноса БД между инстансами:

  1. Создайте backup на старом сервере или возьмите ZIP из Telegram.
  2. На новом сервере загрузите архив в Система -> Бэкапы.
  3. Запомните имя загруженного архива. Если нужны .env и proxy-конфиги, перенесите их отдельно и проверьте настройки целевого сервера.
  4. На новом сервере выполните команду обслуживания ниже.
  5. Проверьте healthcheck и сохраните предыдущую БД и журнал операции до окончания проверки.

Если переносите compose-папку, проверьте домены, токены, WEBHOOK_BASE_URL, SUBSCRIPTION_MINI_APP_URL, bind-порты и volume/mount пути: на новом сервере они могут отличаться. Не восстанавливайте старый compose автоматически вместе с БД: он может запустить старые образы поверх уже обновлённой схемы.

Используйте образы целевой версии и скрипт из того же релиза. Архив должен находиться в BACKUP_DIR общего volume. Запускайте из каталога установки на Docker Compose хосте (нужны Bash и flock). Если установка без Git checkout, сохраните scripts/restore-backup.sh из целевого релиза в каталог установки под тем же путём.

bash scripts/restore-backup.sh minishop-YYYYMMDD-HH-MM.zip
# Для нестандартного compose:
bash scripts/restore-backup.sh minishop-YYYYMMDD-HH-MM.zip --env-file .env -f docker-compose.yml

Порядок действий:

  1. Проверить ZIP и контрольные суммы до остановки приложения.
  2. Остановить backend, worker и migrate. Убедиться, что к текущей БД больше нет подключений.
  3. Запретить новые подключения к текущей БД и создать отдельную пустую БД из template0.
  4. Проверить дамп через pg_restore --list и восстановить его с --single-transaction --exit-on-error --no-owner --no-privileges, без --clean и без наложения на текущую схему.
  5. Сверить миграционную историю с manifest, если она есть. Отклонить неизвестные миграции (включая миграции отсутствующих плагинов); downgrade не поддерживается. Выполнить только отсутствующие миграции Core и установленных плагинов.
  6. Проверить наличие таблиц и колонок моделей, индексы и ограничения PostgreSQL; нормализовать serial sequences. Данные восстанавливаются до создания новых таблиц и применения миграций.
  7. Подготовить каталог тарифов из архива; при его отсутствии сохранить текущий. Атомарно переименовать обе БД в одной транзакции. Прежняя остаётся под minishop_previous_<id> с запрещёнными подключениями. Обновлённая получает прежнее имя подключения.
  8. Только после успеха запустить backend/worker из настроенных образов и дождаться healthcheck.

Нужны права владельца БД и CREATEDB (стандартный compose-пользователь ими обладает), доступ к служебной БД postgres и место для двух полных БД. Внешние клиенты и отдельные реплики приложения тоже должны быть остановлены. PgBouncer и нестандартные роли/права требуют отдельной проверки конфигурации. Успех не означает проверку семантики всех данных сторонних плагинов.

При ошибке сервисы остаются остановленными. До переключения исходная БД не меняется; неудачный кандидат сохраняется для диагностики. Команда пишет BACKUP_DIR/restore-<id>.json с именами баз, состоянием операции и путями файлов. Прежний каталог тарифов сохраняется рядом. При аварийном завершении процесса сверяйте журнал с pg_database: запись журнала и операции PostgreSQL не образуют общую транзакцию. Не запускайте приложение, пока не проверены и БД, и соответствующий ей каталог тарифов.

Для отката после успешного переключения остановите backend/worker/migrate, сохраните новую БД, переименуйте её в свободное имя, а previous_database из журнала — в исходное имя. Делайте оба переименования одной транзакцией из служебной БД postgres, без активных подключений; разрешите подключения к возвращённой БД (ALTER DATABASE ... ALLOW_CONNECTIONS true). Верните previous_tariffs (либо удалите каталог тарифов, если его прежде не было), выберите соответствующие исходной БД образы и только затем запустите сервисы. Старую и неудачную БД удаляйте вручную только после проверки результата — скрипт их не удаляет.

Полный справочник лежит в переменных окружения. Основные ключи:

Переменная Назначение
BACKUP_ENABLED Включает периодические бэкапы.
BACKUP_CHAT_ID / BACKUP_THREAD_ID Куда отправлять архивы в Telegram.
BACKUP_INTERVAL_SECONDS Периодичность, по умолчанию 3600.
BACKUP_LOCAL_RETENTION Сколько последних архивов хранить на сервере.
BACKUP_DIR Каталог ZIP-архивов.
BACKUP_COMPOSE_ENABLED Добавлять compose snapshot.
COMPOSE_BACKUP_SOURCE Host-путь compose-папки для mount в контейнеры.
COMPOSE_RESTORE_MODE rw для восстановления compose из админки, ro для запрета записи.
BACKUP_PG_DUMP_PATH / BACKUP_PG_RESTORE_PATH Пути к pg_dump и pg_restore внутри контейнеров.

Резервная копия включает WEBAPP_THEMES_DIR в config/themes/: каталог, версии пакетов, настройки владельца и ручные темы. Временные импорты исключены. При восстановлении базы темы восстанавливаются после проверки пакетов; старые архивы без этого раздела не меняют темы. Подробнее: хранение тем.