Bot setup was fixed with service profile and update docs.

This commit is contained in:
febux 2026-09-05 00:25:25 +03:00
parent b45274e01b
commit 8da39ebae1
8 changed files with 174 additions and 80 deletions

View file

@ -20,8 +20,9 @@ BOT_SERVICE_TOKEN=
# Example: https://contract-check.example.com # Example: https://contract-check.example.com
API_URL= API_URL=
# Optional: Redis URL for shared rate-limit state. # Redis URL for rate-limit state.
# Leave empty for the in-memory backend (recommended for a single bot instance). # Default in docker-compose.bot.yml is the local Redis container (redis://redis:6379/0).
# If set, Redis must be reachable from this host over a private network/VPN; # Set to empty to use the in-memory backend (fine for a single bot instance).
# never expose Redis to the public internet. # If you point this at an external Redis, it must be reachable over a private
REDIS_URL= # network/VPN; never expose Redis to the public internet.
REDIS_URL=redis://redis:6379/0

View file

@ -2,11 +2,16 @@
# Usage: make <target> (see `make help`) # Usage: make <target> (see `make help`)
.PHONY: help install lint lint-fix isort isort-check typecheck test test-unit test-integration migrate \ .PHONY: help install lint lint-fix isort isort-check typecheck test test-unit test-integration migrate \
infra-up infra-down infra-logs services-up services-down services-logs \ infra-up infra-down infra-logs services-up services-down services-logs services-ps \
services-obs obs-up obs-down obs-logs obs-url obs-reset \ full-up full-down full-logs full-ps \
api api-logs bot bot-logs worker-extract worker-analyze worker-notify \ services-obs services-observer services-nginx \
obs-up obs-down obs-logs obs-url obs-reset \
observer-up observer-down observer-logs \
nginx-up nginx-down nginx-logs nginx-ps \
api api-logs bot bot-up bot-down bot-logs bot-ps worker-extract worker-analyze worker-notify \
bot-remote-up bot-remote-down bot-remote-logs bot-remote-ps \
seed-token jwt-secret jwt-token jwt-verify health shell-api shell-bot \ seed-token jwt-secret jwt-token jwt-verify health shell-api shell-bot \
shell-db admin-promote admin-list clean shell-db admin-promote admin-list clean dev dev-obs stop stop-obs
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Help # Help
@ -64,28 +69,46 @@ infra-logs: ## Tail infra logs
docker compose logs -f docker compose logs -f
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Docker: all services (api + workers + bot) # Docker: api + workers (no bot)
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
services-up: ## Start all services (needs infra running) services-up: ## Start api + worker services (needs infra running; no bot)
docker compose --profile services up -d --build --remove-orphans docker compose --profile services up -d --build --remove-orphans
services-down: ## Stop all services services-down: ## Stop api + worker services
docker compose --profile services down docker compose --profile services down
services-logs: ## Tail all service logs services-logs: ## Tail api + worker logs
docker compose --profile services logs -f docker compose --profile services logs -f
services-ps: ## Show running containers services-ps: ## Show api + worker containers
docker compose --profile services ps docker compose --profile services ps
services-obs: ## Start services + Grafana/Loki/Prometheus observability # ─────────────────────────────────────────────────────────────────────────────
docker compose --profile services --profile obs up -d --build --remove-orphans # Docker: full stack (api + workers + bot on the same host)
# ─────────────────────────────────────────────────────────────────────────────
full-up: ## Start full stack: api + workers + bot (needs infra running)
docker compose --profile services --profile bot up -d --build --remove-orphans
services-observer: ## Start services + OpenObserve collector stack full-down: ## Stop full stack: api + workers + bot
docker compose --profile services --profile observer up -d --build --remove-orphans docker compose --profile services --profile bot down
services-nginx: ## Start with nginx reverse proxy full-logs: ## Tail full stack logs (api + workers + bot)
docker compose --profile services --profile edge up -d --build --remove-orphans docker compose --profile services --profile bot logs -f
full-ps: ## Show full stack containers (api + workers + bot)
docker compose --profile services --profile bot ps
# ─────────────────────────────────────────────────────────────────────────────
# Docker: combined with observability / edge
# ─────────────────────────────────────────────────────────────────────────────
services-obs: ## Start api + workers + bot + Grafana/Loki/Prometheus
docker compose --profile services --profile bot --profile obs up -d --build --remove-orphans
services-observer: ## Start api + workers + bot + OpenObserve collector stack
docker compose --profile services --profile bot --profile observer up -d --build --remove-orphans
services-nginx: ## Start full stack + nginx reverse proxy
docker compose --profile services --profile bot --profile edge up -d --build --remove-orphans
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Docker: observability (Grafana + Loki logs) # Docker: observability (Grafana + Loki logs)
@ -146,12 +169,20 @@ api: ## Start/restart API service
api-logs: ## Tail API logs api-logs: ## Tail API logs
docker compose logs -f api docker compose logs -f api
bot: ## Start/restart Telegram bot (same host as the api stack) bot: bot-up ## Alias for bot-up
docker compose --profile services up -d --build --remove-orphans bot
bot-up: ## Start/restart Telegram bot (same host as the api stack)
docker compose --profile bot up -d --build --remove-orphans bot
bot-down: ## Stop Telegram bot
docker compose --profile bot down
bot-logs: ## Tail bot logs (same host as the api stack) bot-logs: ## Tail bot logs (same host as the api stack)
docker compose logs -f bot docker compose logs -f bot
bot-ps: ## Show bot container status
docker compose --profile bot ps
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Docker: bot on a separate server (HTTP-only adapter to central API) # Docker: bot on a separate server (HTTP-only adapter to central API)
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
@ -261,13 +292,14 @@ shell-api: ## Open shell inside API container
docker compose --profile services exec api /bin/sh docker compose --profile services exec api /bin/sh
shell-bot: ## Open shell inside bot container shell-bot: ## Open shell inside bot container
docker compose --profile services exec bot /bin/sh docker compose --profile bot exec bot /bin/sh
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Cleanup # Cleanup
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
clean: ## Remove containers, volumes, caches clean: ## Remove containers, volumes, caches
docker compose --profile services down -v docker compose --profile services down -v
docker compose --profile bot down -v
docker compose down -v docker compose down -v
rm -rf .pytest_cache .ruff_cache rm -rf .pytest_cache .ruff_cache
uv cache clean uv cache clean
@ -275,10 +307,10 @@ clean: ## Remove containers, volumes, caches
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Full workflow shortcuts # Full workflow shortcuts
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
dev: install infra-up migrate services-up ## Bootstrap full dev environment dev: install infra-up migrate services-up bot-up ## Bootstrap full dev environment (with bot)
dev-obs: install infra-up migrate services-obs ## Bootstrap full dev environment with observability dev-obs: install infra-up migrate services-obs ## Bootstrap full dev environment with observability
stop: services-down infra-down ## Stop everything stop: services-down bot-down infra-down ## Stop everything
stop-obs: obs-down services-down infra-down ## Stop everything including observability stop-obs: obs-down services-down bot-down infra-down ## Stop everything including observability

View file

@ -138,16 +138,18 @@ uv run python -m contract_check.bot # Telegram-бот
```bash ```bash
cp .env.example .env # заполнить секреты (DB, Rabbit, MinIO, LLM, BOT_TOKEN, JWT_SECRET, ...) cp .env.example .env # заполнить секреты (DB, Rabbit, MinIO, LLM, BOT_TOKEN, JWT_SECRET, ...)
docker compose up -d # только инфра с healthchecks docker compose up -d # только инфра с healthchecks
docker compose --profile services up -d --build # + api, worker-extract, worker-prescreen, worker-analyze, worker-notify, worker-billing, bot docker compose --profile services up -d --build # + api, worker-extract, worker-prescreen, worker-analyze, worker-notify, worker-billing (без бота)
docker compose --profile bot up -d --build # + bot (можно запускать отдельно или на другом хосте)
``` ```
Или через `make`: `make dev` (install + infra + migrate + services), `make help` — полный список Или через `make`: `make dev` (install + infra + migrate + api + workers + bot), `make help` — полный список
целей (lint, typecheck, test, seed-token, jwt-token, admin-promote, логи/шеллы сервисов и т.п.). целей (lint, typecheck, test, seed-token, jwt-token, admin-promote, логи/шеллы сервисов и т.п.).
Профиль `services` собирает 6 образов из `srv/<service>/Dockerfile` и поднимает их Профили:
с `depends_on: condition: service_healthy`. Edge-прокси (Nginx + certbot) доступен - `services` — api + worker-ы (без бота).
профилем `edge` (`deploy/nginx/`, `docs/DEPLOY.md §13`). Observability (Prometheus/Grafana/Tempo/OTel) - `bot` — Telegram-бот; можно поднять на этом же хосте или на отдельном сервере (`docs/DEPLOY.md` §14).
— за будущим профилем `obs`. - `edge` — Nginx + certbot (`deploy/nginx/`, `docs/DEPLOY.md` §13).
- `obs` / `observer` — observability (Grafana/Loki/Prometheus или OpenObserve + OTel collector).
Порты на хосте (смещены, чтобы не конфликтовать): Postgres `15432`, Redis `17379`, Порты на хосте (смещены, чтобы не конфликтовать): Postgres `15432`, Redis `17379`,
RabbitMQ AMQP `5672` / UI `15672`, MinIO `9000` / console `9001`, api `8000` / metrics `9100`, RabbitMQ AMQP `5672` / UI `15672`, MinIO `9000` / console `9001`, api `8000` / metrics `9100`,

View file

@ -27,11 +27,13 @@ services:
BOT_TOKEN: ${BOT_TOKEN} BOT_TOKEN: ${BOT_TOKEN}
API_URL: ${API_URL} API_URL: ${API_URL}
BOT_SERVICE_TOKEN: ${BOT_SERVICE_TOKEN} BOT_SERVICE_TOKEN: ${BOT_SERVICE_TOKEN}
# Optional Redis for shared rate-limit state across bot replicas. # Local Redis for rate-limit state. Set to empty to use the in-memory
# Leave empty (or omit) to use the in-memory backend; safe for a single # backend (fine for a single bot instance). The Redis container below is
# bot instance. If set, Redis must be reachable from this host (private # not exposed outside the host; only the bot container can reach it.
# network / VPN — do NOT expose Redis to the public internet). REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
REDIS_URL: ${REDIS_URL:-} depends_on:
redis:
condition: service_healthy
healthcheck: healthcheck:
test: test:
- CMD-SHELL - CMD-SHELL
@ -40,3 +42,19 @@ services:
timeout: 5s timeout: 5s
retries: 3 retries: 3
start_period: 15s start_period: 15s
redis:
image: redis:8-alpine
container_name: contract_check-redis
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
volumes:
redisdata:

View file

@ -1,9 +1,11 @@
# «Контракт-чек» — infrastructure (Step 1). # «Контракт-чек» — infrastructure (Step 1).
# #
# Default (`docker compose up`) starts ONLY infra: postgres + redis + rabbitmq # Default (`docker compose up`) starts ONLY infra: postgres + redis + rabbitmq
# + minio (+ minio-init). Service containers (api, worker-extract, worker-analyze, # + minio (+ minio-init). Service containers are added behind profiles:
# bot) and the observability/edge stacks are added behind profiles in later steps # `services` -> api + workers (no bot)
# (docs/ARCHITECTURE.md §20). # `bot` -> Telegram bot adapter (can run on a separate host)
# `obs` / `observer` / `edge` -> observability and reverse proxy
# (docs/ARCHITECTURE.md §20, docs/DEPLOY.md §14).
# #
# Durability posture (§10): quorum-ready. Postgres is configured # Durability posture (§10): quorum-ready. Postgres is configured
# wal_level=replica + WAL archiving (replica/PITR-ready). RabbitMQ quorum queues # wal_level=replica + WAL archiving (replica/PITR-ready). RabbitMQ quorum queues
@ -295,19 +297,19 @@ services:
- "9103:9103" - "9103:9103"
# Telegram bot adapter (aiogram 3, HTTP-only to api). Per docs/ARCHITECTURE.md §17 # Telegram bot adapter (aiogram 3, HTTP-only to api). Per docs/ARCHITECTURE.md §17
# the bot holds no DB/MQ/S3 credentials — it depends on `api` being healthy, # the bot holds no DB/MQ/S3 credentials — it talks only to the API.
# not on the infra containers directly, enforcing the hexagonal boundary even # It is intentionally isolated in profile `bot` so api + workers can start
# in dependency ordering. # without it; the bot can also run on a different host. See docs/DEPLOY.md §14.
bot: bot:
profiles: ["services"] profiles: ["bot"]
build: build:
context: . context: .
dockerfile: srv/bot/Dockerfile dockerfile: srv/bot/Dockerfile
container_name: contract_check-bot container_name: contract_check-bot
restart: unless-stopped restart: unless-stopped
depends_on: # Note: no depends_on api. The bot healthchecks the API at runtime and
api: # restarts via `restart: unless-stopped` if the API is not yet ready.
condition: service_healthy # This keeps the bot profile self-contained and deployable on a separate host.
environment: environment:
ENV: ${ENV:-dev} ENV: ${ENV:-dev}
LOG_LEVEL: ${LOG_LEVEL:-INFO} LOG_LEVEL: ${LOG_LEVEL:-INFO}

View file

@ -1277,10 +1277,13 @@ dev = [{ include-group = "api" }, { include-group = "extract" },
### docker-compose.yml structure ### docker-compose.yml structure
One file, profiles. Default (`docker compose up`) = infra only. Services One file, profiles. Default (`docker compose up`) = infra only.
behind `--profile services`. Two observability stacks are mutually exclusive: - `services` profile = api + workers (no bot).
`--profile obs` (Grafana + Loki + Prometheus) or `--profile observer` - `bot` profile = Telegram bot adapter; can run on the same host or a separate
(OpenObserve + OTel Collector). Edge behind `--profile edge`. server (`docs/DEPLOY.md` §14).
- `obs` (Grafana + Loki + Prometheus) and `observer` (OpenObserve + OTel
Collector) are mutually exclusive observability stacks.
- `edge` = Nginx + certbot.
``` ```
services: services:
@ -1299,7 +1302,10 @@ services:
worker-analyze: build srv/worker-analyze/Dockerfile; depends_on pg/rabbit/minio-init healthy; 9102 worker-analyze: build srv/worker-analyze/Dockerfile; depends_on pg/rabbit/minio-init healthy; 9102
worker-billing: build srv/worker-billing/Dockerfile; depends_on pg/minio-init healthy; 9105 worker-billing: build srv/worker-billing/Dockerfile; depends_on pg/minio-init healthy; 9105
worker-notify: build srv/worker-notify/Dockerfile; depends_on rabbit healthy; 9103 worker-notify: build srv/worker-notify/Dockerfile; depends_on rabbit healthy; 9103
bot: build srv/bot/Dockerfile; depends_on api healthy (NOT pg/rabbit)
# ── BOT (profile: bot) ──
bot: build srv/bot/Dockerfile; no depends_on so it can run on a
separate host; healthchecks API at runtime
# ── OBSERVABILITY — GRAFANA STACK (profile: obs) ── # ── OBSERVABILITY — GRAFANA STACK (profile: obs) ──
loki: grafana/loki; filesystem-backed single-node log store loki: grafana/loki; filesystem-backed single-node log store

View file

@ -80,12 +80,20 @@ uv run alembic upgrade head
docker compose --profile services run --rm api alembic upgrade head docker compose --profile services run --rm api alembic upgrade head
``` ```
### 2.4 Полный стек (api + workers + bot) ### 2.4 Сервисы (api + workers) и бот
```bash ```bash
# api + workers (без бота):
docker compose --profile services up -d --build docker compose --profile services up -d --build
# Telegram-бот отдельно (можно на этом же или на другом хосте):
docker compose --profile bot up -d --build
# Или сразу весь стек:
docker compose --profile services --profile bot up -d --build
# Ждём healthy: # Ждём healthy:
docker compose --profile services ps docker compose --profile services --profile bot ps
``` ```
--- ---
@ -190,7 +198,8 @@ make shell-db # UPDATE plans SET price_kopecks = 59000 WHERE code = 'lite';
| Профиль | Что поднимает | | Профиль | Что поднимает |
|---|---| |---|---|
| *(default)* | `postgres`, `redis`, `rabbitmq`, `minio`, `minio-init` | | *(default)* | `postgres`, `redis`, `rabbitmq`, `minio`, `minio-init` |
| `services` | `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-billing`, `worker-notify`, `bot` | | `services` | `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-billing`, `worker-notify` |
| `bot` | `bot` — Telegram-адаптер; можно поднять на этом же хосте или на отдельном сервере |
| `obs` | `loki`, `promtail`, `grafana`, `prometheus` (logs + metrics; traces — позже) | | `obs` | `loki`, `promtail`, `grafana`, `prometheus` (logs + metrics; traces — позже) |
| `observer` | `openobserve`, `otel-collector` (logs + metrics + traces через collector) | | `observer` | `openobserve`, `otel-collector` (logs + metrics + traces через collector) |
| `edge` | `nginx`, `certbot` | | `edge` | `nginx`, `certbot` |
@ -199,24 +208,32 @@ make shell-db # UPDATE plans SET price_kopecks = 59000 WHERE code = 'lite';
# Инфра: # Инфра:
docker compose up -d docker compose up -d
# + сервисы (сборка + запуск): # api + workers (без бота):
docker compose --profile services up -d --build docker compose --profile services up -d --build
# Только бот (например, на отдельном сервере или после api + workers):
docker compose --profile bot up -d --build
# Полный стек на одном хосте:
docker compose --profile services --profile bot up -d --build
# + observability Grafana/Loki/Prometheus (логи + метрики): # + observability Grafana/Loki/Prometheus (логи + метрики):
docker compose --profile services --profile obs up -d --build docker compose --profile services --profile bot --profile obs up -d --build
# + observability OpenObserve (логи + метрики + трейсы через otel-collector): # + observability OpenObserve (логи + метрики + трейсы через otel-collector):
docker compose --profile services --profile observer up -d --build docker compose --profile services --profile bot --profile observer up -d --build
# + edge (nginx reverse proxy + TLS; deploy/nginx готов): # + edge (nginx reverse proxy + TLS; deploy/nginx готов):
docker compose --profile services --profile observer --profile edge up -d --build docker compose --profile services --profile bot --profile observer --profile edge up -d --build
``` ```
### 4.2 depends_on и healthchecks ### 4.2 depends_on и healthchecks
`api` ждёт `postgres`, `rabbitmq`, `minio-init` (healthy / completed). `api` ждёт `postgres`, `rabbitmq`, `minio-init` (healthy / completed).
`bot` ждёт только `api` (healthy) — не инфра напрямую, соблюдая hexagonal boundary.
Workers ждут `postgres`, `rabbitmq`, `minio-init`. Workers ждут `postgres`, `rabbitmq`, `minio-init`.
`bot` не имеет `depends_on` — он находится в отдельном профиле `bot` и может
запускаться на другом хосте. На старте бот проверяет доступность API через
встроенный healthcheck и перезапускается при необходимости (`restart: unless-stopped`).
--- ---
@ -274,9 +291,9 @@ asyncio.run(seed())
### 5.3 Перезапуск бота с новым токеном ### 5.3 Перезапуск бота с новым токеном
```bash ```bash
docker compose --profile services restart bot docker compose --profile bot restart bot
# Проверка логов: # Проверка логов:
docker compose --profile services logs -f bot docker compose --profile bot logs -f bot
``` ```
--- ---
@ -348,8 +365,8 @@ docker compose exec postgres psql -U contract_check -d contract_check \
### 7.2 Запуск ### 7.2 Запуск
```bash ```bash
docker compose --profile services up -d bot docker compose --profile bot up -d --build
docker compose --profile services logs -f bot docker compose --profile bot logs -f bot
``` ```
Ожидаемый вывод при `/start`: Ожидаемый вывод при `/start`:
@ -437,10 +454,13 @@ MinIO хранит raw-документы и extracted `.txt`. ILM-правил
# 1. Pull изменений # 1. Pull изменений
git pull origin main git pull origin main
# 2. Rebuild + recreate (Compose пересоздаёт только изменённые контейнеры) # 2. Rebuild + recreate api + workers (Compose пересоздаёт только изменённые контейнеры)
docker compose --profile services up -d --build docker compose --profile services up -d --build
# 3. Миграции (если есть новые) # 3. Пересоздать бота (если он запущен на этом хосте)
docker compose --profile bot up -d --build
# 4. Миграции (если есть новые)
docker compose --profile services run --rm api alembic upgrade head docker compose --profile services run --rm api alembic upgrade head
# 4. Проверка: # 4. Проверка:
@ -543,8 +563,11 @@ docker compose exec postgres psql -U contract_check -d contract_check \
### 12.6 Бот не отвечает ### 12.6 Бот не отвечает
```bash ```bash
# Проверка логов # Проверка логов (если бот на этом хосте)
docker compose --profile services logs -f bot docker compose --profile bot logs -f bot
# Если бот на отдельном сервере — смотрите логи там:
# docker compose -f docker-compose.bot.yml logs -f
# Проверка polling: # Проверка polling:
# Бот использует polling по умолчанию (aiogram). Если webhook установлен — # Бот использует polling по умолчанию (aiogram). Если webhook установлен —
@ -563,14 +586,17 @@ NGINX_SERVER_NAME=contract-check.example.com
``` ```
> **Важно:** nginx зависит от `api` (`service_healthy`), а `api` находится в > **Важно:** nginx зависит от `api` (`service_healthy`), а `api` находится в
> профиле `services` — поэтому nginx/certbot всегда запускайте с обоими > профиле `services` — поэтому nginx/certbot всегда запускайте хотя бы с
> профилями: `docker compose --profile services --profile edge ...`. > профилями `services` и `edge`. Бот (`profile: bot`) можно добавить по желанию.
> Запуск только `--profile edge` падает с `service "nginx" depends on > Запуск только `--profile edge` падает с `service "nginx" depends on
> undefined service "api"`. > undefined service "api"`.
```bash ```bash
# Запуск edge вместе со стеком (после получения первого сертификата, см. §13.2) # Запуск edge вместе со стеком api + workers (без бота):
docker compose --profile services --profile edge up -d docker compose --profile services --profile edge up -d
# Запуск edge вместе с полным стеком (api + workers + бот на этом хосте):
docker compose --profile services --profile bot --profile edge up -d
``` ```
### 13.2 Первый запуск и получение сертификата ### 13.2 Первый запуск и получение сертификата
@ -585,7 +611,8 @@ Nginx **не может стартовать без сертификата** —
nginx стартует. Всё это делает `certbot-init.sh`: nginx стартует. Всё это делает `certbot-init.sh`:
```bash ```bash
# 1. Запускаем основной стек (nginx пока НЕ стартуем) # 1. Запускаем основной стек (nginx пока НЕ стартуем).
# Добавьте --profile bot, если бот должен работать на этом же хосте.
docker compose --profile services up -d docker compose --profile services up -d
# 2. Получаем первый сертификат (standalone, порт 80) и стартуем nginx + renew-sidecar # 2. Получаем первый сертификат (standalone, порт 80) и стартуем nginx + renew-sidecar
@ -608,8 +635,8 @@ curl https://contract-check.example.com/healthz
Grafana под путём `/grafana`: Grafana под путём `/grafana`:
```bash ```bash
# Открыть логи через основной домен (нужен профиль obs + edge) # Открыть логи через основной домен (нужны профили services/bot/obs/edge)
docker compose --profile services --profile obs --profile edge up -d docker compose --profile services --profile bot --profile obs --profile edge up -d
# https://contract-check.example.com/grafana/d/contract-check-logs # https://contract-check.example.com/grafana/d/contract-check-logs
``` ```
@ -651,6 +678,7 @@ docker compose --profile services run --rm api alembic stamp head
```bash ```bash
# Удалить ВСЕ данные (тома + контейнеры): # Удалить ВСЕ данные (тома + контейнеры):
docker compose --profile services down -v docker compose --profile services down -v
docker compose --profile bot down -v
docker compose down -v docker compose down -v
# Затем пересоздать с нуля: §2 + §5 # Затем пересоздать с нуля: §2 + §5
``` ```
@ -705,9 +733,13 @@ docker compose --profile services exec api python -m src.contract_check.api seed
BOT_TOKEN=123456789:ABCDEF... # тот же токен из @BotFather BOT_TOKEN=123456789:ABCDEF... # тот же токен из @BotFather
BOT_SERVICE_TOKEN=bot-prod-secret-xxx # должен совпадать с service_tokens.name='bot-prod' BOT_SERVICE_TOKEN=bot-prod-secret-xxx # должен совпадать с service_tokens.name='bot-prod'
API_URL=https://contract-check.example.com # публичный адрес центрального API, без trailing slash API_URL=https://contract-check.example.com # публичный адрес центрального API, без trailing slash
REDIS_URL= # пусто → in-memory rate limiter; достаточно для одного инстанса REDIS_URL=redis://redis:6379/0 # локальный Redis из docker-compose.bot.yml
``` ```
`docker-compose.bot.yml` поднимает собственный Redis-контейнер (только для
rate-limit, не для очереди). Чтобы использовать in-memory бэкенд, задайте
`REDIS_URL=` (пустое значение).
3. Запустите: 3. Запустите:
```bash ```bash
@ -767,7 +799,7 @@ make bot-remote-ps
- [ ] `.env` заполнен (включая `TELEGRAM_BOT_TOKEN`, `JWT_SECRET`) - [ ] `.env` заполнен (включая `TELEGRAM_BOT_TOKEN`, `JWT_SECRET`)
- [ ] `BOT_SERVICE_TOKEN` засеян в `service_tokens` - [ ] `BOT_SERVICE_TOKEN` засеян в `service_tokens`
- [ ] Миграции накатаны (`alembic upgrade head`) - [ ] Миграции накатаны (`alembic upgrade head`)
- [ ] `docker compose --profile services ps` показывает все healthy - [ ] `docker compose --profile services --profile bot ps` показывает все healthy (или `services` — если бот не на этом хосте)
- [ ] `/healthz` и `/readyz` отвечают 200 - [ ] `/healthz` и `/readyz` отвечают 200
- [ ] Telegram-бот отвечает на `/start` - [ ] Telegram-бот отвечает на `/start`
- [ ] Тестовый PDF проходит pipeline: upload → extract → analyze → report - [ ] Тестовый PDF проходит pipeline: upload → extract → analyze → report

View file

@ -29,7 +29,7 @@
| `worker_notify/` | done | `consumer.py`/`handler.py`/`__main__.py`: consume `NotificationMessage` → SMTP (password reset, magic link) или dev-лог при пустом `SMTP_HOST`. | | `worker_notify/` | done | `consumer.py`/`handler.py`/`__main__.py`: consume `NotificationMessage` → SMTP (password reset, magic link) или dev-лог при пустом `SMTP_HOST`. |
| `bot/` — Telegram adapter | done | `client.py`/`config.py`/`handlers.py`/`__main__.py`: `/start`, upload→`POST /documents`, poll→deliver; граница импортов проверяется `tests/unit/test_bot_boundary.py`. | | `bot/` — Telegram adapter | done | `client.py`/`config.py`/`handlers.py`/`__main__.py`: `/start`, upload→`POST /documents`, poll→deliver; граница импортов проверяется `tests/unit/test_bot_boundary.py`. |
| Dockerfile-ы | done | `srv/{api,worker-extract,worker-prescreen,worker-analyze,worker-notify,bot,prototype}/Dockerfile` — все 7 (deps-группы PEP 735 заточены на сервис). | | Dockerfile-ы | done | `srv/{api,worker-extract,worker-prescreen,worker-analyze,worker-notify,bot,prototype}/Dockerfile` — все 7 (deps-группы PEP 735 заточены на сервис). |
| Docker Compose | done | Инфра (default) + профиль `services` (api, 3 worker-а, bot) + профиль `edge` (nginx+certbot) с `depends_on: service_healthy`. Профиль `obs` — позже. | | Docker Compose | done | Инфра (default) + профиль `services` (api + worker-ы) + профиль `bot` (Telegram-адаптер, можно на отдельном хосте) + профиль `edge` (nginx+certbot) с `depends_on: service_healthy`. Профили `obs`/`observer` — позже. |
| Observability | in_progress | Prometheus-метрики (`/metrics`), Sentry, OpenTelemetry SDK — в коде. Полный стек Prom/Grafana/Tempo/OTel-collector — позже. | | Observability | in_progress | Prometheus-метрики (`/metrics`), Sentry, OpenTelemetry SDK — в коде. Полный стек Prom/Grafana/Tempo/OTel-collector — позже. |
| Stage 3 — B2B API | done | `api/routes/b2b.py`, `core/api_keys.py`, `core/rate_limit.py`, `core/redis_client.py`, миграция `0002_api_keys.py`, `tests/integration/test_b2b_api.py`, `tests/unit/test_rate_limit.py`; `X-API-Key` auth + token-bucket rate-limit. | | Stage 3 — B2B API | done | `api/routes/b2b.py`, `core/api_keys.py`, `core/rate_limit.py`, `core/redis_client.py`, миграция `0002_api_keys.py`, `tests/integration/test_b2b_api.py`, `tests/unit/test_rate_limit.py`; `X-API-Key` auth + token-bucket rate-limit. |
@ -73,7 +73,7 @@
## Этап 1 — Production refactor (api + core + workers + bot) ## Этап 1 — Production refactor (api + core + workers + bot)
> Архитектура: hexagonal, RabbitMQ pipeline (`extract.q``analyze.q`), MinIO, Postgres, Redis. > Архитектура: hexagonal, RabbitMQ pipeline (`extract.q``analyze.q`), MinIO, Postgres, Redis.
> Профили Docker Compose: default = инфра; `services` = api + workers + bot. > Профили Docker Compose: default = инфра; `services` = api + workers; `bot` = Telegram-адаптер.
### T-E1-001 — Ядро `contract_check.core` ### T-E1-001 — Ядро `contract_check.core`
**Статус:** done · **Оценка:** L **Статус:** done · **Оценка:** L
@ -120,8 +120,9 @@ consume `DocumentExtracted` из `analyze.q` → скачать `.txt` → chunk
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-004, T-E1-005, T-E1-006 **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-004, T-E1-005, T-E1-006
`docker-compose.yml`: default-профиль = инфра (`postgres`, `redis`, `rabbitmq`, `minio`, `minio-init`); `docker-compose.yml`: default-профиль = инфра (`postgres`, `redis`, `rabbitmq`, `minio`, `minio-init`);
профиль `services` = `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-notify`, `bot` с `depends_on: service_healthy`; профиль `services` = `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-notify` с `depends_on: service_healthy`;
профиль `edge` = nginx+certbot. Все 7 Dockerfile-ов в `srv/`. Профиль `obs` добавляет `loki` + `promtail` + `grafana` для логов (сделано в T-E1-010a). профиль `bot` = `bot` (Telegram-адаптер), можно поднять на этом же или на отдельном хосте;
профиль `edge` = nginx+certbot. Все 7 Dockerfile-ов в `srv/`. Профили `obs`/`observer` добавляют observability-стек.
### T-E1-008 — Оплата (ЮKassa) и пополнение кредитов ### T-E1-008 — Оплата (ЮKassa) и пополнение кредитов
**Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003, T-E1-006 **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003, T-E1-006