fix(ops): проверка трейлера дампа падала на grep — ведущие -- принимались за опции (#2203) #3013

Merged
lekss361 merged 1 commit from fix/backup-trailer-grep-dashdash into main 2026-08-20 20:57:32 +00:00
Collaborator

Что было

verify_dump_integrity() в обоих бэкап-скриптах делала:

gunzip -c "$file" | tail -5 | grep -qF "$trailer"

$trailer = "-- PostgreSQL database dump complete"... cluster dump complete для globals) — строка начинается с --. GNU grep трактует ведущие -- в аргументе как конец списка опций/саму опцию, и без разделителя перед паттерном падает:

grep: unrecognized option '-- PostgreSQL database dump complete'

Проверка из-за этого всегда возвращала «трейлера нет» — не потому что дамп оборван, а потому что сама grep-команда не могла выполниться. Вызывающий код удалял только что созданный валидный дамп и завершался с ошибкой; ретеншен не успевал отработать (ранний exit) — свежие бэкапы не создавались никогда, старые копии оставались молча (тихая заморозка бэкапов).

Воспроизведение (дословно, с прода)

$ bash /opt/gendesign/tradein-mvp/deploy/backup-tradein-db.sh

удалил свежий дамп с сообщением «дамп оборван?» — на самом деле дамп был валиден, упал именно grep.

Почему не поймали раньше

Ни один тест не исполнял verify_dump_integrity() на реальном gzip-потоке — код только читался/ревьюился глазами, а grep -qF "текст, начинающийся с --" выглядит безобидно, пока не запущен.

Фикс

-    if ! gunzip -c "$file" 2>/dev/null | tail -5 | grep -qF "$trailer"; then
+    if ! gunzip -c "$file" 2>/dev/null | tail -5 | grep -qF -- "$trailer"; then

В обоих файлах: ops/backup.sh:74, tradein-mvp/deploy/backup-tradein-db.sh:59. Никакой другой логики не менял, bash -n на обоих зелен.

Регрессионный тест

backend/tests/ops/test_2203_backup_trailer_grep_dashdash.py — извлекает тело verify_dump_integrity() из каждого скрипта и реально исполняет его (gunzip -c | tail -5 | grep -qF -- "$trailer") на настоящих gzip-потоках: валидный дамп с трейлером → rc=0, оборванный без трейлера → rc=1, плюс отдельная проверка что grep не падает с unrecognized option.

Проверено локально:

  • на добаговой версии (временно откатывал --) тест падает 4/6 с тем же grep: unrecognized option, что и на проде;
  • на исправленной версии — 6/6 зелёных.

Гейт bash -n в .forgejo/workflows/ci.yml (scripts/*.sh, ops/*.sh, ops/**/*.sh) покроет ops/backup.sh, но не tradein-mvp/deploy/backup-tradein-db.sh — этот путь под маску не попадает. Регрессионный тест лежит в backend/tests/ops/ и гарантированно попадает в backend-tests job (paths-filter backend/**), так что реальное исполнение обоих скриптов покрыто именно pytest-ом, а не синтаксическим гейтом. CI не трогал.

## Что было `verify_dump_integrity()` в обоих бэкап-скриптах делала: ``` gunzip -c "$file" | tail -5 | grep -qF "$trailer" ``` `$trailer` = `"-- PostgreSQL database dump complete"` (и `... cluster dump complete` для globals) — строка начинается с `--`. GNU grep трактует ведущие `--` в аргументе как конец списка опций/саму опцию, и без разделителя перед паттерном падает: ``` grep: unrecognized option '-- PostgreSQL database dump complete' ``` Проверка из-за этого **всегда** возвращала «трейлера нет» — не потому что дамп оборван, а потому что сама grep-команда не могла выполниться. Вызывающий код удалял только что созданный **валидный** дамп и завершался с ошибкой; ретеншен не успевал отработать (ранний exit) — свежие бэкапы не создавались никогда, старые копии оставались молча (тихая заморозка бэкапов). ## Воспроизведение (дословно, с прода) ``` $ bash /opt/gendesign/tradein-mvp/deploy/backup-tradein-db.sh ``` удалил свежий дамп с сообщением «дамп оборван?» — на самом деле дамп был валиден, упал именно grep. ## Почему не поймали раньше Ни один тест не исполнял `verify_dump_integrity()` на реальном gzip-потоке — код только читался/ревьюился глазами, а `grep -qF "текст, начинающийся с --"` выглядит безобидно, пока не запущен. ## Фикс ```diff - if ! gunzip -c "$file" 2>/dev/null | tail -5 | grep -qF "$trailer"; then + if ! gunzip -c "$file" 2>/dev/null | tail -5 | grep -qF -- "$trailer"; then ``` В обоих файлах: `ops/backup.sh:74`, `tradein-mvp/deploy/backup-tradein-db.sh:59`. Никакой другой логики не менял, `bash -n` на обоих зелен. ## Регрессионный тест `backend/tests/ops/test_2203_backup_trailer_grep_dashdash.py` — извлекает тело `verify_dump_integrity()` из каждого скрипта и реально исполняет его (`gunzip -c | tail -5 | grep -qF -- "$trailer"`) на настоящих gzip-потоках: валидный дамп с трейлером → rc=0, оборванный без трейлера → rc=1, плюс отдельная проверка что grep не падает с `unrecognized option`. Проверено локально: - на добаговой версии (временно откатывал `--`) тест падает 4/6 с тем же `grep: unrecognized option`, что и на проде; - на исправленной версии — 6/6 зелёных. Гейт `bash -n` в `.forgejo/workflows/ci.yml` (`scripts/*.sh`, `ops/*.sh`, `ops/**/*.sh`) покроет `ops/backup.sh`, но **не** `tradein-mvp/deploy/backup-tradein-db.sh` — этот путь под маску не попадает. Регрессионный тест лежит в `backend/tests/ops/` и гарантированно попадает в `backend-tests` job (paths-filter `backend/**`), так что реальное исполнение обоих скриптов покрыто именно pytest-ом, а не синтаксическим гейтом. CI не трогал.
bot-backend added 1 commit 2026-08-20 20:39:47 +00:00
fix(ops): проверка трейлера дампа падала на grep — ведущие -- принимались за опции (#2203)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m59s
CI / backend-tests (pull_request) Successful in 17m7s
70f3c0a88a
verify_dump_integrity() в ops/backup.sh и tradein-mvp/deploy/backup-tradein-db.sh
делала `grep -qF "$trailer"`, где $trailer = "-- PostgreSQL database dump
complete". Ведущие -- в аргументе grep трактует как конец опций/флаг, без
разделителя команда падает: `grep: unrecognized option '-- PostgreSQL...'`.

Проверка из-за этого ВСЕГДА возвращала "трейлера нет" — не потому что дамп
оборван, а потому что сама проверка не могла выполниться. Вызывающий код
удалял только что созданный валидный дамп и завершался с ошибкой; ретеншен
не успевал отработать (ранний exit) — свежие бэкапы не создавались никогда,
старые копии оставались молча.

Воспроизведено вручную на проде: bash
/opt/gendesign/tradein-mvp/deploy/backup-tradein-db.sh удалил свежий дамп с
сообщением "дамп оборван?".

Фикс: `grep -qF -- "$trailer"` — `--` явно завершает список опций grep.

Регрессионный тест (backend/tests/ops/test_2203_backup_trailer_grep_dashdash.py)
исполняет РЕАЛЬНУЮ verify_dump_integrity() из обоих скриптов на настоящем
gzip-потоке через gunzip|tail|grep — не читает исходник текстом. Проверено
локально: падает на добаговой версии с тем же "unrecognized option", зелен
на исправленной.
lekss361 merged commit 63bc576c40 into main 2026-08-20 20:57:32 +00:00
lekss361 deleted branch fix/backup-trailer-grep-dashdash 2026-08-20 20:57:33 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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#3013
No description provided.