Compare commits

..
2 Commits
Author SHA1 Message Date
ik d4ded77355 ci: schedule isolated production bootstrap
CI / backend-and-migrations (push) Canceled after 0s
CI / astro-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / compose-e2e (push) Canceled after 0s
2026-09-12 20:58:50 +07:00
ik f6e6211871 test: verify release schema upgrade 2026-09-12 20:57:58 +07:00
5 changed files with 94 additions and 2 deletions
+40
View File
@@ -0,0 +1,40 @@
name: Production bootstrap drill
on:
workflow_dispatch:
schedule:
- cron: "17 3 * * 6"
jobs:
isolated-production-bootstrap:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: apps/web/package-lock.json
- name: Install web dependencies
run: npm --prefix apps/web ci
- name: Run isolated production bootstrap
shell: bash
run: |
set -o pipefail
mkdir -p artifacts
./deploy/test-production-bootstrap.sh 2>&1 | tee artifacts/production-bootstrap.log
- name: Collect failure diagnostics
if: failure()
run: |
docker compose --env-file .env.production.example -f compose.production.yaml ps -a > artifacts/compose-ps.txt 2>&1 || true
cp -R apps/web/test-results artifacts/test-results 2>/dev/null || true
cp -R apps/web/playwright-report artifacts/playwright-report 2>/dev/null || true
- name: Upload drill diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: production-bootstrap-${{ github.run_id }}
path: artifacts
if-no-files-found: error
retention-days: 14
+4
View File
@@ -46,6 +46,10 @@ RF4DB/RF4-STAT/RF4MAP/RF4 Posts сначала принимаются в изо
Production release отделяет Alembic от runtime: одноразовый `migrate` должен успешно завершиться до запуска новой версии API. Перед изменением схемы создаётся backup; совместимый rollback возвращает предыдущие images, несовместимый — восстанавливает предрелизную копию данных вместо непроверенного `alembic downgrade`.
Путь обновления схемы проверяется изолированным `deploy/test-release-upgrade.sh`: предыдущая ревизия получает контрольную запись, обновляется до head, после чего проверяются версия, сохранность записи и новые колонки.
Тяжёлый production bootstrap вынесен в отдельный ручной/еженедельный CI workflow с 30-минутным timeout и сохраняемыми diagnostics; обычный push по-прежнему использует быстрый Compose E2E.
После повторных ошибок scheduler увеличивает паузу экспоненциально до 24 часов и возвращается к 30 минутам после успеха. Публичная страница `/status` показывает свежесть и состояние источников без URL запросов, внутренних ошибок и другой диагностической информации.
Подробный план и актуальные чекбоксы находятся в [`docs/ROADMAP.md`](docs/ROADMAP.md). Результаты проверки интерфейса и пять приоритетных UX-пакетов описаны в [`docs/UI_UX_AUDIT.md`](docs/UI_UX_AUDIT.md).
+8
View File
@@ -63,6 +63,12 @@ curl -fsS -H "Authorization: Bearer $ADMIN_TOKEN" -o rf4spotter-diagnostics.json
./deploy/test-production-bootstrap.sh
```
Отдельный upgrade-drill проверяет путь от предыдущей Alembic-ревизии к текущему head и сохранность контрольных данных:
```bash
./deploy/test-release-upgrade.sh
```
По умолчанию временно используются только loopback-порты `14321` и `18000`; PostgreSQL и MinIO наружу не публикуются. Контур и volumes удаляются после проверки. Drill успешно пройден 6 сентября 2026 года.
Одноразовый `migrate` применяет Alembic до rollout API; при ошибке новый runtime не запускается. API при старте выполняет только идемпотентный seed: в production он добавляет минимальные справочники и точки, а демонстрационные уловы жёстко отключены `SEED_DEMO_DATA=false`.
@@ -125,6 +131,8 @@ curl -fsS https://rf4spotter.ru/ready
Drill успешно пройден 6 сентября 2026 года. На целевом сервере всё равно проведите учебное восстановление с реальной зашифрованной копией перед приглашением пользователей. Затем настройте ежедневный запуск `backup.sh`, выгрузку копий во внешнее хранилище и уведомление при ошибке; храните минимум 7 ежедневных и 4 еженедельных копии.
Полный production bootstrap дополнительно запускается отдельным CI workflow вручную и еженедельно. Он не удваивает быстрый Compose E2E на каждом push; полный лог сохраняется всегда, а при ошибке — также Compose status и браузерные diagnostics.
## 7. Ежедневное обслуживание
После успешного backup сначала проверьте план очистки, затем примените его:
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
set -eu
repo=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
cd "$repo"
project="rf4-upgrade-$$"
export COMPOSE_PROJECT_NAME="$project"
export POSTGRES_PASSWORD=upgrade-drill-postgres-password
export DATABASE_URL=postgresql+psycopg://rf4:upgrade-drill-postgres-password@db:5432/rf4_spotter
compose="docker compose --env-file .env.production.example -f compose.production.yaml"
previous_revision=48094a7d1b92
head_revision=20260910_recovery
probe_id=00000000-0000-0000-0000-000000000099
cleanup() {
status=$?
if [ "$status" -ne 0 ]; then
$compose ps >&2 || true
$compose logs --no-color db migrate >&2 || true
fi
$compose down --volumes --remove-orphans >/dev/null 2>&1 || true
}
trap cleanup EXIT INT TERM
$compose up --build -d --wait db
$compose run --rm migrate alembic upgrade "$previous_revision"
$compose exec -T db psql -v ON_ERROR_STOP=1 -U rf4 -d rf4_spotter -c \
"insert into fish(id,slug,name_ru,trophy_weight_g) values ('$probe_id','upgrade-probe','Проверка обновления',1234)" >/dev/null
$compose run --rm migrate
db_revision=$($compose exec -T db psql -At -U rf4 -d rf4_spotter -c 'select version_num from alembic_version')
probe_count=$($compose exec -T db psql -At -U rf4 -d rf4_spotter -c "select count(*) from fish where id='$probe_id' and slug='upgrade-probe' and trophy_weight_g=1234")
recovery_columns=$($compose exec -T db psql -At -U rf4 -d rf4_spotter -c "select count(*) from information_schema.columns where table_schema='public' and ((table_name='submission_attempt' and column_name in ('idempotency_key','catch_report_id','payload_hash')) or (table_name='import_record_event' and column_name in ('changes','provenance')))")
test "$db_revision" = "$head_revision"
test "$probe_count" = "1"
test "$recovery_columns" = "5"
printf 'Release upgrade passed: %s -> %s, control data preserved\n' "$previous_revision" "$head_revision"
+2 -2
View File
@@ -44,7 +44,7 @@
- [x] **Q06 · Performance baseline.** На локальной production-сборке после оптимизации hero: performance 100, LCP 1,66 с, FCP 1,15 с, CLS 0,023, TBT 9 мс. Устранены найденные Lighthouse проблемы контраста и accessible name; методика и бюджеты записаны в [performance-baseline.md](performance-baseline.md). Полевой INP измеряется только после запуска.
- [x] **Q07 · Нагрузочная методика.** Добавлен read-only runner для activity, records, staging и moderation с warm-up, p50/p95/max, распределением HTTP-кодов и ограниченной concurrency. Методика фиксирует контекст запуска, ступени нагрузки и бюджеты, но не объявляет результатов до трёх прогонов на целевом сервере.
- [x] **Q08 · Политика MinIO.** Production bootstrap создаёт bucket и отдельную policy только с bucket location/list и get/put/delete его объектов. App credentials проверяются через bucket stat и отрицательную проверку глобального list; API больше не требует `ListAllMyBuckets` и не пытается создавать bucket. Root credentials остаются только у init-задачи.
- [ ] **Q09 · Release-процедура — код готов.** Миграции вынесены из API runtime в одноразовый `migrate` service; API запускается только после успешного Alembic upgrade. Документированы backup, rollout и два варианта отката. Осталось проверить upgrade с предыдущей ревизии на копии данных в изолированном Compose drill.
- [x] **Q09 · Release-процедура.** Миграции вынесены из API runtime в одноразовый `migrate` service; API запускается только после успешного Alembic upgrade. Документированы backup, rollout и два варианта отката. Изолированный drill поднимает предыдущую ревизию схемы, добавляет контрольные данные, обновляет до head и проверяет их сохранность и новые колонки.
- [ ] **Q10 · Документальная ревизия.** После каждого пакета обновлять этот файл и README, не возвращая закрытые R/A/T-задачи в активный backlog.
### Дополнения после ревизии PROJECT_AUDIT_2026-09-10
@@ -53,7 +53,7 @@
- [ ] **Q11 · Декомпозиция API.** Инкрементально разделить `apps/api/app/main.py` (638 строк, 26 маршрутов) на `APIRouter` по публичному каталогу/activity, submissions и admin/community. Сначала зафиксировать OpenAPI snapshot и сохранить URL, response models, middleware и dependency-поведение без функциональных изменений.
- [ ] **Q12 · Query-plan gate.** В рамках Q07 снять `EXPLAIN (ANALYZE, BUFFERS)` для activity, records, spot detail и public spot pages на реалистичном наборе данных. Существующие индексы миграции `0011_query_indexes` не дублировать; индекс с `fish_id`, SQL-агрегацию или materialized view добавлять только по измеренному плану и p95.
- [ ] **Q13 · Production bootstrap в CI.** Добавить отдельный ручной/плановый job для `deploy/test-production-bootstrap.sh`, не удваивая быстрый Compose E2E на каждом коммите; сохранять diagnostics при падении.
- [x] **Q13 · Production bootstrap в CI.** Отдельный workflow запускает `deploy/test-production-bootstrap.sh` вручную или раз в неделю, а не на каждом push. Вывод bootstrap всегда сохраняется 14 дней; при падении добавляются Compose status и Playwright diagnostics.
- [ ] **Q14 · Полная CSP.** Расширить текущую CSP (`frame-ancestors`, `base-uri`, `object-src`) до `default-src`, `script-src`, `style-src`, `img-src`, `connect-src` и `form-action`. Сначала инвентаризировать inline scripts/styles Astro, затем внедрить nonce/hash или безопасное вынесение; проверить report/admin/OG без ослабления до произвольных внешних origin.
- [ ] **Q15 · Частичная деградация главной.** Разделить получение activity, community signals и справочников так, чтобы отказ одного источника не превращал всю главную в общий 503. Для SSR не добавлять искусственный client-side loading/optimistic UI; показывать независимые `StatePanel` и корректный HTTP/cache статус.
- [ ] **Q16 · Контракт OpenAPI.** Генерировать OpenAPI artifact из приложения в CI и проверять осознанные изменения контракта при декомпозиции API; не поддерживать вручную редактируемую копию.