gendesign/tradein-mvp/DEPLOY.md
bot-backend feff55bea9
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 11s
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
CI Trade-In / backend-tests (pull_request) Successful in 2m33s
chore(tradein/geocoder): удалить остатки скриптов Яндекс-геокодера, часть 3 (#2593)
Удалены мёртвые ops-скрипты Yandex Geocoder (уже недостижимы после #2593 частей 1-2):
- scripts/_yandex_reverse.py, scripts/audit_address_mismatch.py, scripts/backfill_house_coords.py
- их тесты + осиротевшая фикстура tests/fixtures/yandex_geocode_sample.json
- осиротевшие SQL-хелперы scripts/audit_address_sample.sql, scripts/address_audit_report.sql (использовались только audit_address_mismatch.py)

Обновлена документация (осиротевшие упоминания YANDEX_GEOCODER_API_KEY / удалённых
скриптов): scripts/README.md, tradein-mvp/DEPLOY.md, docs/Secrets_Rotation_Policy.md.

Добавлен tests/test_geocoder_nominatim_lookup.py — покрывает _nominatim_lookup
(единственный живой внешний геокодер) на предмет реальной передачи city_hint в
исходящий HTTP-запрос к Nominatim; закрывает дыру в coverage, оставленную
удалёнными yandex-тестами.

Refs #2593
2026-07-31 23:16:19 +03:00

239 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Trade-In MVP — деплой в gendesign репо
## Архитектура
```
lekss361/-gendesign/
├── backend/ ← gendesign Python backend (НЕ ТРОГАЕМ)
├── frontend/ ← gendesign Next.js (НЕ ТРОГАЕМ)
├── docker-compose.prod.yml ← gendesign-стек
├── Caddyfile ← основной Caddy → ВКЛЮЧАЕТ tradein-mvp/deploy/Caddyfile.tradein-fragment
├── .github/workflows/
│ ├── deploy.yml ← существующий gendesign deploy
│ └── deploy-tradein.yml ← НАШ новый, триггерится только на tradein-mvp/**
└── tradein-mvp/ ← НАША подпапка (изолированный subproject)
├── backend/
├── frontend/
├── deploy/
│ ├── Caddyfile.tradein-fragment ← импортится в основной Caddyfile
│ └── cron-scrape.sh
├── docker-compose.yml ← локальный dev
├── docker-compose.prod.yml ← production overlay (использует GHCR images)
└── .github/workflows/deploy-tradein.yml
```
## Прод URL routes
| URL | Сервис |
|----------------------------------|-------------------------|
| `gendsgn.ru/trade-in/` | tradein-frontend:3000 |
| `gendsgn.ru/trade-in/api/v1/...` | tradein-backend:8000 |
Caddy в gendesign-стеке проксирует через `handle_path /trade-in/api/*` (вырезает префикс) и `handle /trade-in/*` (Next.js basePath).
## Прод docker stacks
- `docker compose -p gendesign` — основной gendesign (postgres + backend + frontend + caddy + couchdb)
- `docker compose -p gendesign-tradein` — наш (tradein-postgres + tradein-backend + tradein-frontend)
Оба подключены к `gendesign_shared` external network — туда смотрит Caddy.
## Что нужно положить в основной Caddyfile gendesign
В блок `gendsgn.ru { ... }` **ПЕРЕД** универсальным `handle { reverse_proxy frontend:3000 }`:
```caddyfile
import /opt/gendesign/tradein-mvp/deploy/Caddyfile.tradein-fragment
```
Или вставить содержимое фрагмента inline.
## Что нужно положить в `.env.runtime` (на сервере, НЕ в git)
Два файла:
1. `/opt/gendesign/tradein-mvp/.env.runtime` — переменные для docker compose
substitution (`${TRADEIN_POSTGRES_PASSWORD}` и т.п. в compose-файле). Читается
shell-скриптом deploy через `source .env.runtime` перед `compose up`.
2. `/opt/gendesign/tradein-mvp/backend/.env.runtime` — переменные внутри
контейнера `tradein-backend` (читаются через `env_file:` в compose). Сюда
попадают `COOKIE_ENCRYPTION_KEY` и остальные application-секреты внутри
контейнера.
```bash
# /opt/gendesign/tradein-mvp/.env.runtime
TRADEIN_POSTGRES_USER=tradein
TRADEIN_POSTGRES_PASSWORD=<сгенерировать openssl rand -hex 32>
TRADEIN_CONTACT_EMAIL=tradein@gendsgn.ru
# Encryption key for Cian session cookies (pgp_sym_encrypt / Stage 9 Calculator).
# Empty = Valuation Calculator scraper disabled + /api/v1/cookies/upload returns 503.
# Generate once on VPS:
# openssl rand -hex 32
COOKIE_ENCRYPTION_KEY=<64-char hex>
```
```bash
# /opt/gendesign/tradein-mvp/backend/.env.runtime — те же ключи которые
# читаются ВНУТРИ container'а (app/*, scripts/*.py).
# Может быть симлинком на ../.env.runtime если переменные совпадают:
# ln -s ../.env.runtime /opt/gendesign/tradein-mvp/backend/.env.runtime
COOKIE_ENCRYPTION_KEY=<64-char hex>
GENDESIGN_FDW_PASSWORD=<password или пусто>
GLITCHTIP_DSN=<dsn или пусто>
# DaData on-demand enrichment в /estimate flow (PR Q1).
# Demo tier: 100 req/день. Пусто = enrichment disabled (graceful).
# Регистрация ключей: https://dadata.ru/api/clean/
DADATA_API_TOKEN=<token или пусто>
DADATA_API_SECRET=<secret или пусто>
# Telegram support-bot bridge (сервис tgbot, docker-compose.prod.yml).
# Long-polling воркер (app/tgbot_main.py), тот же образ что backend/scraper,
# отдельный контейнер tradein-tgbot. Пусто TELEGRAM_BOT_TOKEN = бот НЕ падает
# и НЕ рестарт-лупится — процесс стартует, уходит в idle-блокировку и просто
# висит (это норма для окружений без токена, не сбой; см. tgbot_main.py).
#
# 1. TELEGRAM_BOT_TOKEN — токен от @BotFather (/newbot). Пусто = бот выключен
# (idle, не polling).
TELEGRAM_BOT_TOKEN=<token или пусто>
# 2. TELEGRAM_SUPPORT_CHAT_ID — id супергруппы-форума (Topics включены в
# настройках группы), вида -100XXXXXXXXXX. Получить: добавить бота в группу,
# отправить любое сообщение в любой топик, дернуть
# https://api.telegram.org/bot<token>/getUpdates — в ответе
# message.chat.id (для супергруппы всегда отрицательный, начинается с -100).
TELEGRAM_SUPPORT_CHAT_ID=<-100... или пусто>
# 3. TELEGRAM_SUPPORT_TOPIC_ID — id конкретного топика (thread) внутри группы,
# куда падают support-обращения. Открыть нужный топик в Telegram Desktop/Web →
# в URL топика (t.me/c/<chat>/<topic_id>) последнее число — это topic_id.
# Либо взять message_thread_id из того же getUpdates-ответа (п.2), отправив
# тестовое сообщение именно в целевой топик.
TELEGRAM_SUPPORT_TOPIC_ID=<topic_id или пусто>
```
Оба файла создаются вручную при первом деплое.
### Деплой / рестарт `tgbot`
```bash
# .env.runtime читается на старте container — `compose restart` НЕ перечитывает.
docker compose -p gendesign-tradein -f docker-compose.prod.yml \
up -d --force-recreate --no-deps tgbot
```
`restart: unless-stopped` + `stop_grace_period: 120s` в compose (см. `docker-compose.prod.yml`)
— автозапуск после ребута VPS гарантирован (`on-failure` сюда не годится: код выхода
контейнера при ребуте — гонка с long-poll таймаутом 30с, `unless-stopped`/`always`
не зависят от exit-кода). 120s grace даёт time докрутить long-poll + отработать
кооперативный drain (`_DRAIN_TIMEOUT_S=100s` в `tgbot_main.py`) до docker SIGKILL —
паттерн скопирован с `scraper` (см. комментарий там же).
### После изменения `backend/.env.runtime`
```bash
# .env.runtime читается на старте container — `compose restart` НЕ перечитывает.
docker compose -p gendesign-tradein -f docker-compose.prod.yml \
up -d --force-recreate --no-deps backend
```
См. `.claude/rules/deploy.md` — main backend pattern идентичный.
### Обновление `COOKIE_ENCRYPTION_KEY` на существующем VPS
```bash
# На VPS, если COOKIE_ENCRYPTION_KEY ещё не задан:
echo "COOKIE_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> /opt/gendesign/tradein-mvp/.env.runtime
# Применить без полного рестарта стека:
cd /opt/gendesign/tradein-mvp
docker compose -p gendesign-tradein -f docker-compose.prod.yml up -d --force-recreate --no-deps backend
```
## GitHub Secrets (уже должны быть от gendesign deploy)
- `DEPLOY_HOST` — IP сервера (94.228.121.73)
- `DEPLOY_USER` — root
- `DEPLOY_SSH_KEY` — приватный SSH ключ
- `DEPLOY_PORT` — 22 (или другой если меняли)
## Первый деплой — пошагово
### 1. На анто́новой машине: подготовить tradein-mvp/ к слиянию
```bash
# clone gendesign репо (с PAT)
git clone https://<PAT>@github.com/lekss361/-gendesign.git
cd -gendesign
# скопировать tradein-mvp/ как подпапку
cp -r /Users/anton/Птица/tradein-mvp ./tradein-mvp
rm -rf ./tradein-mvp/postgres-data # на всякий
rm -rf ./tradein-mvp/.git # не нужен внутри monorepo
# скопировать workflow в правильное место
mkdir -p .github/workflows
cp ./tradein-mvp/.github/workflows/deploy-tradein.yml .github/workflows/
git add tradein-mvp/ .github/workflows/deploy-tradein.yml
git commit -m "feat: add tradein-mvp subproject + deploy workflow"
git push origin main
```
### 2. На сервере: первый раз вручную добавить Caddy include
```bash
ssh -i ~/.ssh/id_bot_server root@94.228.121.73
cd /opt/gendesign
# git reset --hard origin/main подтянет tradein-mvp/ автоматически
# Добавить в Caddyfile (ровно одну строку в блок gendsgn.ru { ... })
nano Caddyfile
# вставить ПЕРЕД последним handle { ... }:
# import /opt/gendesign/tradein-mvp/deploy/Caddyfile.tradein-fragment
# Создать .env.runtime — два файла:
# 1. tradein-mvp/.env.runtime — для compose substitution на host'е
cat > tradein-mvp/.env.runtime <<EOF
TRADEIN_POSTGRES_USER=tradein
TRADEIN_POSTGRES_PASSWORD=$(openssl rand -hex 32)
TRADEIN_CONTACT_EMAIL=tradein@gendsgn.ru
EOF
chmod 600 tradein-mvp/.env.runtime
# 2. tradein-mvp/backend/.env.runtime — для env_file внутри container'а.
# Симлинк работает если переменные одни и те же; иначе создать отдельный файл.
ln -sf ../.env.runtime tradein-mvp/backend/.env.runtime
# Запустить stack первый раз вручную (после этого GitHub Actions сам)
docker network inspect gendesign_shared >/dev/null 2>&1 || docker network create gendesign_shared
cd tradein-mvp
set -a; source .env.runtime; set +a
docker compose -p gendesign-tradein -f docker-compose.prod.yml pull
docker compose -p gendesign-tradein -f docker-compose.prod.yml up -d
# Reload Caddy
cd /opt/gendesign
docker compose -p gendesign -f docker-compose.prod.yml exec -T caddy caddy reload --config /etc/caddy/Caddyfile
# Проверить
curl -sS https://gendsgn.ru/trade-in/api/health
curl -sI https://gendsgn.ru/trade-in/
```
### 3. Дальше — автодеплой
После первого ручного запуска все следующие `git push origin main` с изменениями в `tradein-mvp/**` будут:
1. Триггерить `.github/workflows/deploy-tradein.yml`
2. Билдить и пушить новые образы в `ghcr.io/lekss361/gendesign-tradein-*`
3. SSH в сервер → `docker compose pull``up -d` → Caddy reload
4. Health check
## Откат
```bash
ssh root@94.228.121.73
cd /opt/gendesign/tradein-mvp
# Откатиться на предыдущий SHA-тег
docker compose -p gendesign-tradein -f docker-compose.prod.yml down
IMAGE_TAG=<previous-sha> docker compose -p gendesign-tradein -f docker-compose.prod.yml up -d
```