fix(ops): бэкап без выгрузки в S3 падает, а не рапортует успех #3086
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3086
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/3085-backups-fail-loud"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Зачем
Проверка хостов за шесть дней до переезда: на Poincare пустой crontab — ни бэкапов, ни сторожей, ни конфигов. Сегодня это неважно, базы ещё на Beget, где всё работает и сентинелы свежие. 30.08 базы переезжают — и бэкапы там просто не начнутся.
Хуже, что отказ был бы тихим: выгрузка в S3 была опциональной, без четырёх переменных скрипт писал
backup stays local onlyи выходил с кодом 0. Не ошибка — успешный прогон. Сторож свежести на новом хосте тоже не установлен, промолчали бы оба.Что меняется
Отсутствие любой из
S3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY/S3_SECRET_KEYроняет прогон с ненулевым кодом — до снятия дампа, потому что незачем тратить время и место на дамп, которому некуда уехать. Образец взят изops/backup-forgejo.sh, где так было изначально.BACKUP_ALLOW_LOCAL_ONLY=1оставляет прежний путь для машины без ключей.На Beget все четыре переменные заданы (проверено по SSH), поэтому там поведение не меняется вообще.
Что нашло ревью
Первая версия форточки возвращала ровно ту дыру, которую чинит PR. Локальный прогон всё равно писал сентинел и выходил нулём → сторож свежести оставался зелёным, а единственным следом был WARNING в логе, который никто не тailит. Достаточно раскомментировать одну строку в
/etc/default— и «настроено» стало бы неотличимо от рабочего. Теперь такой прогон сентинел не пишет и сразу зовётnotify().Попутно найден живой баг. В
ops/lib-backup.sh:54внутри блока после||стояло литеральное\n— шелл разбирал его как командуn, поэтомуnotify_fallback_mailне вызывался никогда. При недоступности Telegram мы теряли уведомления молча — тот же класс тишины, что и основной дефект.Восстановимость
ops/restore-drill.shпоставлен в расписание Poincare. Он существовал, но не запускался нигде — восстановимость дампов автоматически не проверялась ни разу.gzip -tи трейлерpg_dumpговорят «файл не битый», а это не то же самое, что «база поднимается».У самой дрели своего сторожа нет и
MAILTOв cron не задан, поэтому её падение тоже уходит вnotify(). Заодноdocker rm -fполучил-v: при регулярном запуске иначе оставался бы dangling-том размером с базу до воскресной чистки, а на Poincare это конкурирует с ночным дампом за диск.Мелочь
Имя бакета в примере исправлено на
gendsgn-backups— на хостах настроен именно он, forgejo пишет туда же под префиксомforgejo/.Что намеренно НЕ закрыто
elif: если появится/etc/default/tradein-backupбез S3-переменных, прогон упадёт вместо чтения общего файла. Под новой политикой это желаемое поведение, а не дефект.gendesign:restore-drill.shне умеет батч, расширять его здесь — смешивать задачи.Test plan
bash -nна всех четырёх изменённых скриптахbackup-tradein-db.shбез переменных S3 → три строки ОШИБКА,rc=1, до строки «Dumping»ops/backup.shсBACKUP_ALLOW_LOCAL_ONLY=1→ WARNING, затем доходит до дампаОтдельно, требует человека
Оба ключа S3 оказались write-only:
PutObjectесть,List,GetиDelete— AccessDenied. Как защита это правильно, но следствия три: этими ключами нельзя восстановиться (403 наGetObject), нельзя проверить содержимое бакета с хостов, и ретенцию нельзя сделать скриптами — только lifecycle-политикой на бакете. Нужен отдельный read-ключ, хранимый не на хостах.Refs #3085, #3008