All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
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
Четвёртый пункт приёмки #2203. В дампах есть колонки под pgp_sym_encrypt, ключ к ним лежит на самой машине и никуда не уезжает: потеря машины означает «дампы есть, расшифровать нечем». Ради этой связки шифрование в базе и заводилось. КУДА И ПОЧЕМУ ИМЕННО ТУДА. В тот же S3, отдельным префиксом env/, и только зашифрованным. Разобранные варианты: - рядом с дампами открытым — нельзя: ключ рядом с шифротекстом обнуляет шифрование, одна утечка доступа к бакету отдаёт и то и другое; - на соседний хост по SSH — потребовало бы завести доверие между машинами, которого нет (проверено: Permission denied (publickey)), то есть РАСШИРИТЬ периметр ровно тогда, когда #3075 его сужает; - отдельный бакет с отдельными ключами — правильнее всего, но требует новых учётных данных. Выбран единственный исполнимый без расширения доступа: шифротекст в S3, парольная фраза — вне S3. FAIL-CLOSED. Без фразы скрипт не выгружает файл открытым, а падает с явным сообщением и алертом. Молчаливая выгрузка ключа в бакет с дампами хуже отсутствия копии: создаёт ложное чувство защищённости. Фраза уходит через дескриптор, а не аргументом — иначе видна в ps любому пользователю машины. После шифрования файл проверяется обратной расшифровкой: без этого можно годами возить нечитаемый мусор и узнать в тот момент, когда он понадобился. Прогон на хосте 27.08 (подставной исходник, боевой не трогался): без фразы → код 1, файлов создано 0 с фразой → «Зашифровано и проверено расшифровкой», 110 байт своей фразой читается, чужой — нет ОДИН ШАГ ЗА ВЛАДЕЛЬЦЕМ, и он неустраним: фраза обязана жить там, где переживёт смерть машины, иначе копия бесполезна — расшифровать будет нечем. Сгенерировать её здесь и оставить на хосте нельзя по построению. Инструкция — в шапке скрипта. Пять тестов сторожат ровно те свойства, ради которых всё сделано. Прогон: 85 ops-тестов зелёные, ruff чист. Refs #2203
98 lines
9.7 KiB
Text
98 lines
9.7 KiB
Text
# crontab ПРИНИМАЮЩЕГО хоста (Selectel Poincare, 188.124.37.140) — после 30.08.
|
||
#
|
||
# Устанавливать СРАЗУ после того, как стек поднялся и смоук прошёл:
|
||
# mkdir -p /opt/gendesign/logs
|
||
# crontab /opt/gendesign/ops/crontab-poincare.cron
|
||
# crontab -l # сверить
|
||
#
|
||
# Перед установкой в /etc/default/ должны лежать (root-owned, chmod 600,
|
||
# НЕ в git, переносятся руками с предыдущего хоста или создаются заново):
|
||
# /etc/default/gendesign-backup — S3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY/S3_SECRET_KEY
|
||
# (см. ops/gendesign-backup.default.example)
|
||
# /etc/default/tradein-backup — свои S3_*, либо отсутствует (тогда
|
||
# backup-tradein-db.sh падает на общий
|
||
# /etc/default/gendesign-backup выше)
|
||
# Без них ops/backup.sh и backup-tradein-db.sh теперь падают громко (#3085),
|
||
# а не молча пишут локальный бэкап.
|
||
#
|
||
# ⚠️ САМЫЙ ОПАСНЫЙ ПРОПУСК ВСЕГО ПЕРЕЕЗДА. Если этот файл не установить,
|
||
# на новом хосте НЕ БУДЕТ НИ ОДНОГО БЭКАПА — ни tradein, ни gendesign. И узнать
|
||
# об этом будет неоткуда: сторож пропущенных прогонов (check-backup-staleness)
|
||
# сам живёт в этом же crontab'е, то есть его тоже не будет. Отсутствие бэкапов
|
||
# обнаружится в момент, когда они понадобятся.
|
||
#
|
||
# Времена оставлены те же, что были на Beget — чтобы не гадать, изменилось ли
|
||
# поведение из-за переезда или из-за нового расписания. Подгонять окна имеет
|
||
# смысл ПОСЛЕ того, как всё поедет стабильно.
|
||
|
||
# ── Бэкапы обоих кластеров ──────────────────────────────────────────────────
|
||
# Off-box выгрузка в S3 включается через /etc/default/tradein-backup и
|
||
# /etc/default/gendesign-backup — файлы НЕ переезжают сами (в git их нет и не
|
||
# должно быть), их нужно перенести руками. Без них оба скрипта падают ГРОМКО
|
||
# (exit 1, дамп не снимается вовсе) — см. #3085; они больше НЕ пишут молча
|
||
# локальный бэкап.
|
||
30 3 * * * bash /opt/gendesign/ops/backup.sh >> /opt/gendesign/logs/gendesign-backup.log 2>&1
|
||
30 4 * * * bash /opt/gendesign/tradein-mvp/deploy/backup-tradein-db.sh >> /opt/gendesign/logs/tradein-backup.log 2>&1
|
||
|
||
# Сторожа пропущенных прогонов, порог 26ч. Едут вместе со своими бэкапами:
|
||
# сторож должен жить там же, где sentinel, иначе он сторожит файл, который
|
||
# никто не обновляет, и кричит вечно.
|
||
0 * * * * bash /opt/gendesign/ops/check-backup-staleness.sh /opt/gendesign/backups/.last_success 26 "gendesign main backup" >> /opt/gendesign/logs/backup-staleness.log 2>&1
|
||
0 * * * * bash /opt/gendesign/ops/check-backup-staleness.sh /opt/gendesign/backups/tradein/.last_success 26 "tradein backup" >> /opt/gendesign/logs/backup-staleness.log 2>&1
|
||
|
||
# ── Restore-дрель, раз в месяц (#3085) ───────────────────────────────────────
|
||
# Восстановимость дампа никогда не проверялась автоматически — только
|
||
# ручными прогонами. 1-е число месяца, 02:15 — до ночных бэкапов (03:30/04:30)
|
||
# и до обогащения/геокодинга (05:00-05:45), не пересекается ни с чем.
|
||
# Берёт САМЫЙ СВЕЖИЙ на тот момент main-дамп; ops/restore-drill.sh сам найдёт
|
||
# рядом лежащий globals-файл и сам снесёт свой временный контейнер — прод не
|
||
# трогает никогда (см. заголовок скрипта).
|
||
15 2 1 * * bash -c 'f=$(ls -t /opt/gendesign/backups/gendesign_[0-9]*.sql.gz 2>/dev/null | head -1); if [ -n "$f" ]; then bash /opt/gendesign/ops/restore-drill.sh "$f"; else echo "no gendesign dump found to drill"; exit 1; fi' >> /opt/gendesign/logs/restore-drill.log 2>&1
|
||
|
||
# Та же дрель для базы tradein — она НЕ покрывалась строкой выше (#2203).
|
||
# Строка выше берёт только `gendesign_*`, то есть восстановимость базы
|
||
# клиентских оценок автоматически не проверялась вовсе: ни листинги, ни дома,
|
||
# ни сами оценки. Замер 27.08: дамп 343 МБ разворачивается за 58 c и сходится
|
||
# с боевой базой построчно (houses 10400, listings 109978, deals 108623 —
|
||
# расхождения только по строкам, созданным ПОСЛЕ снятия дампа).
|
||
#
|
||
# Еженедельно, а не раз в месяц: две минуты работы против месяца, в течение
|
||
# которого битый дамп остаётся незамеченным. Понедельник 03:00 — после
|
||
# монтажной дрели gendesign (02:15 первого числа, ей хватает форы даже когда
|
||
# первое выпадает на понедельник) и до ночных бэкапов в 03:30.
|
||
0 3 * * 1 bash -c 'f=$(ls -t /opt/gendesign/backups/tradein/tradein-[0-9]*.sql.gz 2>/dev/null | head -1); if [ -n "$f" ]; then bash /opt/gendesign/ops/restore-drill.sh "$f"; else echo "no tradein dump found to drill"; exit 1; fi' >> /opt/gendesign/logs/restore-drill.log 2>&1
|
||
|
||
# ── Off-box копия рантайм-конфига (#2203) ────────────────────────────────────
|
||
# Четвёртый пункт приёмки #2203. В дампах есть колонки под pgp_sym_encrypt;
|
||
# ключ к ним лежит на самой машине и никуда не уезжает. Потеря машины =
|
||
# дампы есть, расшифровать нечем.
|
||
#
|
||
# 04:10 — после обоих ночных бэкапов (03:30 и 04:30 идёт tradein, но он берёт
|
||
# свой дамп независимо), до обогащения в 05:00. Ежедневно, потому что копия
|
||
# должна соответствовать дампу того же дня: конфиг меняют редко, но если
|
||
# поменяли, старая копия не расшифрует свежий дамп.
|
||
#
|
||
# Скрипт fail-closed: без ENV_OFFBOX_PASSPHRASE в /etc/default/gendesign-backup
|
||
# он НЕ выгружает файл открытым, а падает с явным сообщением и алертом.
|
||
# Открытым в тот же бакет, где лежат дампы, класть нельзя — это обнулило бы
|
||
# шифрование колонок (ключ рядом с шифротекстом).
|
||
10 4 * * * bash /opt/gendesign/ops/backup-env-offbox.sh >> /opt/gendesign/logs/env-offbox.log 2>&1
|
||
|
||
# ── Обогащение и геокодирование (ходят в tradein-backend) ───────────────────
|
||
0 5 * * * cd /opt/gendesign/tradein-mvp && docker exec tradein-backend python -m scripts.backfill_houses_dadata --limit 100 --priority both >> /opt/gendesign/logs/dadata-backfill.log 2>&1
|
||
30 5 * * * cd /opt/gendesign/tradein-mvp && docker exec tradein-backend python -m scripts.geocode_deals_from_houses --limit 50000 >> /opt/gendesign/logs/deals-geocode.log 2>&1
|
||
45 5 * * * cd /opt/gendesign/tradein-mvp && docker exec tradein-backend python -m scripts.geocode_deals_nominatim --limit 5000 >> /opt/gendesign/logs/deals-nominatim.log 2>&1
|
||
|
||
# ── Уборка докера ───────────────────────────────────────────────────────────
|
||
# СВОЯ запись, не «переехавшая»: на Beget она чистит тома CI-раннеров, здесь —
|
||
# образы продуктового стека. Нужны обе, на обоих хостах.
|
||
0 4 * * 0 bash /opt/gendesign/ops/docker-prune.sh >> /opt/gendesign/logs/docker-prune.log 2>&1
|
||
|
||
# ── Логи: /opt/gendesign/logs, а не /tmp ────────────────────────────────────
|
||
# На Beget половина записей писала в /tmp — он чистится при перезагрузке, и
|
||
# именно поэтому историю отказов бэкапов там восстановить не удавалось.
|
||
# Каталог создать до установки crontab: mkdir -p /opt/gendesign/logs
|
||
#
|
||
# ── ЧЕГО ЗДЕСЬ НЕТ ──────────────────────────────────────────────────────────
|
||
# backup-forgejo.sh + его сторож — Forgejo остаётся на Beget
|
||
# (см. ops/crontab-beget.cron)
|