feat(ops): у волта Obsidian появился автоматический бэкап #3090

Merged
lekss361 merged 1 commit from feat/3057-couchdb-backup into main 2026-08-25 05:44:17 +00:00
Owner

Summary

  • У волта Obsidian (CouchDB) не было автоматического бэкапа вообще — Site Finder, МЕРА и Forgejo закрыты, CouchDB не закрывал никто. Единственная копия — ручной месячный прогон на машине владельца.
  • ops/backup-couchdb.sh: останавливает контейнер (документация CouchDB требует file-level копию с остановленного узла), архивирует оба тома, поднимает обратно, проверяет целостность, выгружает в S3, пишет сентинел.
  • Подъём висит на trap EXIT — остановленный и не поднятый волт хуже отсутствия бэкапа. После старта проверяется /_up, иначе notify().
  • S3 обязателен и проверяется до остановки контейнера. Отдельный ключ не требуется: при пустых COUCHDB_S3_* берутся S3_* основного бэкапа под своим префиксом couchdb/.
  • Фолбэк читается в подоболочке — прямой source затёр бы LOCAL_BACKUP_DIR / SENTINEL_FILE / KEEP и обновил бы сентинел основного бэкапа, ослепив его сторожа свежести.
  • ops/*.cron добавлен в paths deploy-infra.yml: строка была только в deploy.yml, и после разъезда хостов правка crontab-beget.cron не доезжала бы до хоста, где живёт волт.

Test plan

  • bash -n, YAML валиден
  • на боевом Beget: без S3-настроек → rc=1, контейнер не тронут (Up 12 hours)
  • на боевом Beget, полный прогон ×2: снимок данных 53 219 001 байт (15 файлов), конфиг 788 байт (3 файла), контейнер поднят и healthy, сентинел записан, весь прогон 103 с
  • изоляция подоболочки на подставном конфиге: S3-значения подхвачены, LOCAL_BACKUP_DIR / SENTINEL_FILE / KEEP не затёрты
  • владелец архивов после правки — gendesign:gendesign (был root:root)
  • объекты в бакете подтверждены независимым читающим ключом: couchdb/couchdb-data_*.tar.gz 50.8 MiB, couchdb/couchdb-config_*.tar.gz
  • на хосте: crontab /opt/gendesign/ops/crontab-beget.cron (запись 04:45 + сторож)

Refs #3057, #3085

## Summary - У волта Obsidian (CouchDB) **не было автоматического бэкапа вообще** — Site Finder, МЕРА и Forgejo закрыты, CouchDB не закрывал никто. Единственная копия — ручной месячный прогон на машине владельца. - `ops/backup-couchdb.sh`: останавливает контейнер (документация CouchDB требует file-level копию с остановленного узла), архивирует оба тома, поднимает обратно, проверяет целостность, выгружает в S3, пишет сентинел. - Подъём висит на `trap EXIT` — остановленный и не поднятый волт хуже отсутствия бэкапа. После старта проверяется `/_up`, иначе `notify()`. - S3 обязателен и проверяется **до** остановки контейнера. Отдельный ключ не требуется: при пустых `COUCHDB_S3_*` берутся `S3_*` основного бэкапа под своим префиксом `couchdb/`. - Фолбэк читается в подоболочке — прямой `source` затёр бы `LOCAL_BACKUP_DIR` / `SENTINEL_FILE` / `KEEP` и обновил бы сентинел **основного** бэкапа, ослепив его сторожа свежести. - `ops/*.cron` добавлен в `paths` `deploy-infra.yml`: строка была только в `deploy.yml`, и после разъезда хостов правка `crontab-beget.cron` не доезжала бы до хоста, где живёт волт. ## Test plan - [x] `bash -n`, YAML валиден - [x] на боевом Beget: без S3-настроек → rc=1, контейнер не тронут (`Up 12 hours`) - [x] на боевом Beget, полный прогон ×2: снимок данных 53 219 001 байт (15 файлов), конфиг 788 байт (3 файла), контейнер поднят и `healthy`, сентинел записан, весь прогон 103 с - [x] изоляция подоболочки на подставном конфиге: S3-значения подхвачены, `LOCAL_BACKUP_DIR` / `SENTINEL_FILE` / `KEEP` не затёрты - [x] владелец архивов после правки — `gendesign:gendesign` (был `root:root`) - [x] объекты в бакете подтверждены **независимым читающим ключом**: `couchdb/couchdb-data_*.tar.gz` 50.8 MiB, `couchdb/couchdb-config_*.tar.gz` - [ ] на хосте: `crontab /opt/gendesign/ops/crontab-beget.cron` (запись 04:45 + сторож) Refs #3057, #3085
lekss361 added 1 commit 2026-08-25 05:37:33 +00:00
feat(ops): у волта Obsidian появился автоматический бэкап
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
0111b32cb2
Site Finder закрывает backup.sh, МЕРУ — backup-tradein-db.sh, Forgejo —
backup-forgejo.sh. CouchDB не закрывал никто: автоматического бэкапа волта не
существовало вообще, единственной копией был ручной месячный прогон на машине
владельца. При этом в волте вся накопленная база знаний проекта — решения,
разборы инцидентов, ограничения, — и восстановить её из кода нельзя.

Контейнер на время снимка останавливается. Файлы CouchDB пишутся append-only,
но копия работающего узла может застать запись между обновлением данных и
обновлением заголовка базы, а .shards/ и _dbs.couch — рассогласованными между
собой; документация прямо требует снимать file-level копию с остановленного
узла. Данных около 85 МБ, простой измерен и составляет примерно минуту, окно
ночное, а LiveSync к обрывам связи устойчив по своей природе.

Оставить волт лежать — отказ хуже отсутствия бэкапа, поэтому старт висит на
trap EXIT и отрабатывает при любом выходе, включая ошибку tar и нехватку
места. После подъёма проверяется, что /_up отвечает, иначе notify().

S3 обязателен, как в backup-forgejo.sh и в backup.sh после #3085: без ключей
скрипт падает ДО остановки контейнера. Ронять волт ради копии, которой некуда
уехать, незачем.

Отдельный ключ для волта не требуется. Если COUCHDB_S3_* не заданы, берутся
S3_* из конфига основного бэкапа — того же ключа, которым уже выгружается
Site Finder, но под своим префиксом couchdb/. Требовать узкий ключ как условие
запуска значило бы оставить волт без бэкапа до того дня, когда до ключа дойдут
руки; завести его никто не мешает, файл-образец для этого есть.

Фолбэк читается в ПОДОБОЛОЧКЕ, наружу выносятся ровно четыре значения. Прямой
source был бы тихой диверсией: конфиг основного бэкапа задаёт свои
LOCAL_BACKUP_DIR, KEEP и SENTINEL_FILE, они затёрли бы вычисленные для волта.
Архивы легли бы в чужой каталог, а write_sentinel обновил бы сентинел
ОСНОВНОГО бэкапа — его сторож свежести зеленел бы по чужому прогону, и
пропущенный дамп базы перестал бы замечаться.

Владелец архивов выравнивается внутри контейнера: читать том надо от root,
поэтому наружу файлы выходили root:root, в отличие от соседних серий. Ротацию
это не ломало, но ставило каталог в зависимость от отсутствия sticky-бита.

ops/*.cron добавлен в paths deploy-infra.yml: эта строка была только в
deploy.yml, и после разъезда хостов правка crontab-beget.cron доезжала бы
лишь до продового хоста — то есть не туда, где живёт волт.

Проверено на боевом Beget, дважды подряд:
- без S3-настроек — код возврата 1, контейнер не тронут (Up 12 hours);
- полный прогон — контейнер остановлен, снят снимок 53 219 001 байт (15
  файлов) и конфиг 788 байт (3 файла), контейнер поднят и healthy, оба архива
  выгружены, сентинел записан, весь прогон 103 с;
- изоляция подоболочки прогнана на подставном конфиге: S3-значения
  подхватываются, LOCAL_BACKUP_DIR / SENTINEL_FILE / KEEP не затираются;
- объекты в бакете подтверждены НЕЗАВИСИМЫМ читающим ключом:
  couchdb/couchdb-data_*.tar.gz 50.8 MiB и couchdb/couchdb-config_*.tar.gz.

Refs #3057, #3085
lekss361 merged commit bffec49434 into main 2026-08-25 05:44:17 +00:00
lekss361 deleted branch feat/3057-couchdb-backup 2026-08-25 05:44:19 +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#3090
No description provided.