fix(ops): бэкап не теряет роли, не глотает ошибки и умеет уезжать с машины (#2203) #3004

Merged
lekss361 merged 4 commits from chore/backup-hardening-2203 into main 2026-08-20 19:44:22 +00:00
Owner

Что чинит (acceptance #2203)

  • Роли/GRANT не терялись бы при restore, а не дампились вовсе. pg_dump по определению не включает роли/GRANT — оба скрипта (ops/backup.sh, tradein-mvp/deploy/backup-tradein-db.sh) теперь дополнительно гонят pg_dumpall --globals-only в отдельный файл <db>_globals_<ts>.sql.gz рядом с основным дампом, с той же ретенцией (раздельные серии, чтобы gendesign_globals_* не попадал в общий glob с gendesign_*) и той же S3-выгрузкой.
  • Ошибки pg_dump больше не глотаются. Убран 2>/dev/null у pg_dump в обоих скриптах — текст ошибки теперь идёт в лог, а не исчезает молча (set -o pipefail и раньше ронял пайп при сбое, просто причина была не видна).
  • Целостность дампа проверяется по существу, не только "не пустой". Truncated-посреди-записи дамп (диск кончился, OOM-kill, оборвался docker exec) может дать структурно валидный, не крошечный .gz — ни -s, ни существующий MIN_DUMP_BYTES это не ловят. Добавлена verify_dump_integrity(): gzip -t + проверка, что в распакованном потоке есть трейлер (-- PostgreSQL database dump complete для основного дампа, -- PostgreSQL database cluster dump complete для globals — у pg_dumpall он называется иначе). Проверяется через gunzip -c | tail -N, без распаковки всего файла на диск. При провале любой проверки — файл удаляется, причина логируется, выход ненулевой, ретенция НЕ трогается (инвариант "не затирать хорошие дампы из-за неудачного прогона" сохранён).
  • Диск (75% занято, 12 ГБ дампов на одном томе с БД) — выгрузка в S3. tradein-mvp/deploy/backup-tradein-db.sh раньше вообще не умел выгружать в S3 — добавлен тот же блок, что в ops/backup.sh (4 переменные, docker run --rm amazon/aws-cli). Источник переменных: /etc/default/tradein-backup, если файла нет — фолбэк на общий /etc/default/gendesign-backup. Без переменных — поведение прежнее (только локально, лог об этом предупреждает).
  • Минимальный порог размера дампа теперь и в tradein-скрипте, env-переопределяемый (MIN_DUMP_BYTES, дефолт 10 KiB — там дампы кратно меньше, чем main DB).
  • Новый ops/restore-drill.sh — реальный restore-тест, который НЕ трогает прод. Поднимает одноразовый postgis/postgis:16-3.4 со случайным именем и docker-assigned портом, без монтирования прод-томов. Заливает globals (best-effort — на свежем контейнере конфликт с ролью postgres ожидаем и не фатален) + сам дамп (ON_ERROR_STOP=1 — это и есть настоящая проверка целостности). Проверяет postgis_full_version(), число таблиц в public, и построчный count по списку ключевых таблиц (для tradein: listings, listing_sources, deals, houses, trade_in_estimates; для gendesign: rosreestr_deals, cad_opportunity_parcels, domrf_snapshots, trade_in_estimates, parcel_user_status — выбраны как представительные central-таблицы разных доменов схемы, можно переопределить через RESTORE_DRILL_TABLES). Контейнер гарантированно удаляется в конце (trap ... EXIT). В шапке — предупреждение заглавными буквами, что ops/restore.sh — это восстановление В БОЕВУЮ базу и для учений не годится.
  • ops/gendesign-backup.default.example дополнен комментариями про globals, про то, что этот файл теперь ещё и fallback-источник S3-кредов для tradein-backup, и про ops/restore-drill.sh. Реальных значений не добавлял.

Что остаётся за человеком

  • Создать S3-бакет (Selectel или другой S3-compatible) — сейчас его нет, поэтому даже с этим PR выгрузка не заработает сама по себе.
  • Записать 4 переменные в /etc/default/gendesign-backup (и опционально отдельно в /etc/default/tradein-backup, если нужны разные креды/бакет для tradein) на проде — root-owned, chmod 600, НЕ в git. Шаблон — ops/gendesign-backup.default.example.
  • Ничего из этого PR не выполнялось на проде и не будет запущено этой сессией — только правки файлов в репозитории.

Verify

  • bash -n на всех трёх изменённых/новых скриптах — чисто.
  • shellcheck недоступен в этом окружении (не установлен, docker daemon не запущен) — сделан ручной review на типовые SC-паттерны (quoting, SC2012/SC2164/SC2155 и т.п.), явных проблем не найдено.

Refs #2203

## Что чинит (acceptance #2203) - **Роли/GRANT не терялись бы при restore, а не дампились вовсе.** `pg_dump` по определению не включает роли/`GRANT` — оба скрипта (`ops/backup.sh`, `tradein-mvp/deploy/backup-tradein-db.sh`) теперь дополнительно гонят `pg_dumpall --globals-only` в отдельный файл `<db>_globals_<ts>.sql.gz` рядом с основным дампом, с той же ретенцией (раздельные серии, чтобы `gendesign_globals_*` не попадал в общий glob с `gendesign_*`) и той же S3-выгрузкой. - **Ошибки `pg_dump` больше не глотаются.** Убран `2>/dev/null` у `pg_dump` в обоих скриптах — текст ошибки теперь идёт в лог, а не исчезает молча (`set -o pipefail` и раньше ронял пайп при сбое, просто причина была не видна). - **Целостность дампа проверяется по существу, не только "не пустой".** Truncated-посреди-записи дамп (диск кончился, OOM-kill, оборвался `docker exec`) может дать структурно валидный, не крошечный `.gz` — ни `-s`, ни существующий `MIN_DUMP_BYTES` это не ловят. Добавлена `verify_dump_integrity()`: `gzip -t` + проверка, что в распакованном потоке есть трейлер (`-- PostgreSQL database dump complete` для основного дампа, `-- PostgreSQL database cluster dump complete` для globals — у `pg_dumpall` он называется иначе). Проверяется через `gunzip -c | tail -N`, без распаковки всего файла на диск. При провале любой проверки — файл удаляется, причина логируется, выход ненулевой, ретенция НЕ трогается (инвариант "не затирать хорошие дампы из-за неудачного прогона" сохранён). - **Диск (75% занято, 12 ГБ дампов на одном томе с БД) — выгрузка в S3.** `tradein-mvp/deploy/backup-tradein-db.sh` раньше вообще не умел выгружать в S3 — добавлен тот же блок, что в `ops/backup.sh` (4 переменные, `docker run --rm amazon/aws-cli`). Источник переменных: `/etc/default/tradein-backup`, если файла нет — фолбэк на общий `/etc/default/gendesign-backup`. Без переменных — поведение прежнее (только локально, лог об этом предупреждает). - **Минимальный порог размера дампа теперь и в tradein-скрипте**, env-переопределяемый (`MIN_DUMP_BYTES`, дефолт 10 KiB — там дампы кратно меньше, чем main DB). - **Новый `ops/restore-drill.sh` — реальный restore-тест, который НЕ трогает прод.** Поднимает одноразовый `postgis/postgis:16-3.4` со случайным именем и docker-assigned портом, без монтирования прод-томов. Заливает globals (best-effort — на свежем контейнере конфликт с ролью `postgres` ожидаем и не фатален) + сам дамп (`ON_ERROR_STOP=1` — это и есть настоящая проверка целостности). Проверяет `postgis_full_version()`, число таблиц в `public`, и построчный count по списку ключевых таблиц (для tradein: `listings, listing_sources, deals, houses, trade_in_estimates`; для gendesign: `rosreestr_deals, cad_opportunity_parcels, domrf_snapshots, trade_in_estimates, parcel_user_status` — выбраны как представительные central-таблицы разных доменов схемы, можно переопределить через `RESTORE_DRILL_TABLES`). Контейнер гарантированно удаляется в конце (`trap ... EXIT`). В шапке — предупреждение заглавными буквами, что `ops/restore.sh` — это восстановление В БОЕВУЮ базу и для учений не годится. - `ops/gendesign-backup.default.example` дополнен комментариями про globals, про то, что этот файл теперь ещё и fallback-источник S3-кредов для tradein-backup, и про `ops/restore-drill.sh`. Реальных значений не добавлял. ## Что остаётся за человеком - **Создать S3-бакет** (Selectel или другой S3-compatible) — сейчас его нет, поэтому даже с этим PR выгрузка не заработает сама по себе. - **Записать 4 переменные** в `/etc/default/gendesign-backup` (и опционально отдельно в `/etc/default/tradein-backup`, если нужны разные креды/бакет для tradein) на проде — root-owned, chmod 600, НЕ в git. Шаблон — `ops/gendesign-backup.default.example`. - Ничего из этого PR не выполнялось на проде и не будет запущено этой сессией — только правки файлов в репозитории. ## Verify - `bash -n` на всех трёх изменённых/новых скриптах — чисто. - `shellcheck` недоступен в этом окружении (не установлен, docker daemon не запущен) — сделан ручной review на типовые SC-паттерны (quoting, SC2012/SC2164/SC2155 и т.п.), явных проблем не найдено. Refs #2203
lekss361 added 2 commits 2026-08-20 19:16:00 +00:00
- ops/backup.sh и tradein-mvp/deploy/backup-tradein-db.sh теперь дампят
  globals (pg_dumpall --globals-only) отдельным файлом с той же ретенцией
  и той же S3-выгрузкой — pg_dump по определению не включает роли/GRANT.
- Обе выгрузки проходят gzip -t + проверку трейлера дампа перед тем как
  считаться успешными; при провале файл удаляется, ретенция не трогается,
  выход ненулевой.
- Убран 2>/dev/null у pg_dump в обоих скриптах — ошибка дампа теперь
  видна в логе, а не глотается молча.
- tradein-backup.sh получил S3-выгрузку (по образцу ops/backup.sh, те же
  4 переменные, тот же способ через aws-cli контейнер) и env-переопределяемый
  порог минимального размера дампа; источник переменных —
  /etc/default/tradein-backup с фолбэком на /etc/default/gendesign-backup.
- Новый ops/restore-drill.sh — учебное восстановление в одноразовый
  postgis-контейнер без прод-томов, никогда не трогает боевую БД (в отличие
  от ops/restore.sh, который восстанавливает В БОЕВУЮ базу).
chore(ops): восстановить +x на ops/restore-drill.sh
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
7691db8d2c
core.filemode=false на Windows-чекауте молча создал файл как 100644;
остальные ops/*.sh — 100755, restore-drill.sh должен быть исполняемым
так же (cron/deploy конвенция ops/backup.sh).
bot-backend added 1 commit 2026-08-20 19:24:12 +00:00
fix(ops): деплой триггерится на любой ops/*.sh, а не только docker-prune.sh (#2203)
All checks were successful
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m6s
CI / backend-tests (pull_request) Successful in 17m11s
2d2336cd51
paths-filter в deploy.yml знал только про ops/docker-prune.sh (#2887) — правка
ops/backup.sh или новый ops/restore-drill.sh из этого же PR не долетели бы до
/opt/gendesign: деплой не триггерится -> git reset --hard origin/main не
исполняется -> cron на VM месяцами крутит старую версию, молча.

Точечный список сам по себе и есть баг: #2887 добавил только тот файл, о
котором тогда шла речь, и следующий новый ops-скрипт (backup.sh) остался
за бортом. Глоб ops/*.sh закрывает класс целиком — не матчит подпути
(ops/db-bootstrap/**, ops/glitchtip-auth-forwarder/**), у них свои explicit
триггеры уже есть, дублирования нет.
bot-backend added 1 commit 2026-08-20 19:25:42 +00:00
docs(rules): deploy.md отражает ops/*.sh глоб вместо точечного docker-prune.sh (#2203)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m0s
CI / backend-tests (pull_request) Successful in 17m6s
e909dbcada
Оба утверждения в разделе Path triggers устарели ровно из-за коммита
2d2336cd в этой же ветке: список больше не содержит ops/docker-prune.sh
(теперь ops/*.sh), а предупреждение «добавлять в paths явно» для
любого нового ops/<name>.sh больше не верно — глоб их подхватывает сам.

Осталась одна деталь, о которой правда надо помнить: одиночная звёздочка
не пересекает /, так что новый ПОДКАТАЛОГ внутри ops/ (как db-bootstrap/,
glitchtip-auth-forwarder/) под глоб не попадает и всё ещё требует своей
строки в paths — иначе тот же класс бага (#2887 / #2203) повторится для
подкаталога.
lekss361 merged commit 2b083290cf into main 2026-08-20 19:44:22 +00:00
lekss361 deleted branch chore/backup-hardening-2203 2026-08-20 19:44:23 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3004
No description provided.