💾 Play-API — резервное копирование и восстановление

О чём это

После переезда в Oracle Cloud база play перестала быть локальной, и старый бэкап её потерял. Здесь — как устроена текущая схема, почему она именно такая, и как проверить, что бэкап живой.

Направление: тянем, а не пушим

Postgres на Oracle намеренно не публикует порт — в docker-compose.yaml прямо сказано «No ports are published: it is reachable only as db:5432». Поставить туда агент домашнего бэкап-сервера тоже нельзя: агент ходит к серверу домой, а домашний NAT входящие соединения не пропускает.

Поэтому направление развёрнуто — aurora тянет с Oracle:

oracle (play-api)
   ▲ ssh, forced command
   │
aurora ──▶ ~/db-backups/ ──▶ BBS-агент ──▶ Borg-репозиторий на dexus
         (pre-backup hook)      01:00

Так у публичной VM нет ни ключей от хранилища, ни маршрута в домашнюю сеть: её компрометация не даёт доступа к архивам.

Почему не VPN

Обсуждали site-to-site между домашним MikroTik ax3 и Oracle. Отвергли: туннель дал бы публичной VM маршрут в домашнюю LAN, не решив ничего, чего не решает pull по SSH.

Что именно забирается

На Oracle стоит /usr/local/bin/play-backup-export, привязанный к ключу через command="…",restrict — shell по этому ключу недоступен, принимаются только три глагола:

ГлаголКомандаФайл на aurora
db (и пустая команда)pg_dump -U play -Fc -Z0 playplay.dump
globalspg_dumpall -U play --globals-onlyplay.globals.sql
cfgtar с .env + docker-compose.yamlplay-config.tar (права 600)

Три причины, почему именно так:

  • -Z0 (без сжатия) — чтобы Borg дедуплицировал дамп против предыдущих ночей. Сжатый дамп меняется целиком от любого различия и ложится в репозиторий заново каждый раз.
  • globals отдельноpg_dump не выгружает роли. Кластер, восстановленный из одного play.dump, поднимется вообще без логинов.
  • cfg.env самая невосстановимая часть стека: этих значений нет ни в репозитории, ни в документации (см. предупреждение в заметке о переезде про забытые переменные).

Схема play, а не public

Таблицы лежат в схеме play

Это уже подводило дважды. pg_dump -t bots без схемы тихо возвращает no matching tables were found, а проверка information_schema.tables where table_schema='public' показывает 0 таблиц на полностью корректном восстановлении.

Правильно: -t play.bots, а для подсчёта — select count(*) from play.games либо агрегат по всем несистемным схемам.

Это же делает неприменимым встроенный плагин BBS «PostgreSQL Backup/Restore»: его рекомендованная роль выдаёт гранты на public, то есть дамп получился бы без данных. Плюс плагин требует TCP-доступа к базе (пришлось бы выставлять Postgres в интернет) и сжимает дамп, ломая дедупликацию. Осознанно остались на Shell Script Hook.

Проверка, что бэкап живой

Зелёный лог ≠ рабочий бэкап

С 27 июля по 2 августа 2026 play.dump не обновлялся шесть суток, при этом агент каждую ночь писал Pre-script completed successfully (exit 0), а Borg исправно архивировал протухший файл как свежий.

Причина составная: удалённый fetch намеренно не фатален (сбой в сети не должен ронять локальные дампы), stderr уходил в /dev/null, а единственный сигнал — push status=down в Uptime Kuma — остался незамеченным.

Что теперь ловит такую ситуацию:

  1. check_fresh в backup.sh — артефакт старше 36 часов считается провалом. Возраст файла переживает любую причину сбоя, в отличие от кода возврата.
  2. ~/verify-play-restore.sh (crontab пользователя jegors, воскресенье 04:00 UTC) — поднимает одноразовый Postgres, реально восстанавливает play.dump, проверяет схему и падение числа строк относительно прошлого прогона. Лог — ~/verify-play-restore.log.
  3. stderr больше не глушится и попадает в лог-строку backup.sh, а оттуда в /var/log/bbs-agent.log.

Восстановление вручную

# 1. Достать артефакты из Borg (или взять свежие из ~/db-backups на aurora)
# 2. Роли — первыми, иначе объекты не к кому привязать
docker exec -i <pg> psql -U postgres -d postgres < play.globals.sql
 
# 3. Сама база
docker exec -i <pg> pg_restore -U postgres -d play --no-owner --no-privileges < play.dump
 
# 4. Проверка — именно по схеме play
docker exec <pg> psql -U postgres -d play -Atc "select count(*) from play.games"
 
# 5. Конфиг
tar -xf play-config.tar -C /opt/play-cloud

Проверено вживую 2026-08-02: восстановление дало 8 таблиц и 53 709 партий.

Где что лежит

ЧтоГде
Экспортёр (forced command)Oracle: /usr/local/bin/play-backup-export
Ключ доступаaurora: ~/.ssh/oracle-play-backup
Pre-backup hookaurora: ~/backup.sh (запускает bbs-agent от root в 01:00)
Артефактыaurora: ~/db-backups/
Проверка восстановленияaurora: ~/verify-play-restore.sh
Лог агентаaurora: /var/log/bbs-agent.log (не journal; время в UTC)
Архивыdexus: /mnt/backup, репозиторий dexus-backup-hdd

Полная домашняя картина (PBS, BBS, расписания всех клиентов) — в вики homelab, заметка «Глобальная стратегия резервного копирования».