DealDocumentScreening/Makefile
febux 6f22b0368a
Some checks failed
ci / Lint & typecheck (push) Successful in 33s
ci / Unit tests (push) Successful in 1m7s
ci / Integration tests (push) Failing after 25s
Fix CI workflows. Extend make commands. Fix Nomad deploy doc.
2026-09-13 23:54:06 +03:00

353 lines
20 KiB
Makefile

# «Контракт-чек» — everyday commands (uv + docker)
# Usage: make <target> (see `make help`)
.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 services-ps \
full-up full-down full-logs full-ps \
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 bot-webhook-path \
seed-token jwt-secret jwt-token jwt-verify health shell-api shell-bot \
shell-db admin-promote admin-list \
nomad-validate nomad-plan nomad-deploy nomad-status nomad-logs nomad-scale nomad-revert \
clean dev dev-obs stop stop-obs
# ─────────────────────────────────────────────────────────────────────────────
# Help
# ─────────────────────────────────────────────────────────────────────────────
help: ## Show available commands
@echo "Usage: make <target>"
@grep -E '^[a-zA-Z0-9_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
# ─────────────────────────────────────────────────────────────────────────────
# Local dev (uv)
# ─────────────────────────────────────────────────────────────────────────────
install: ## Sync dev dependencies (uv)
uv sync --group dev
lint: ## Run ruff linter + format check
uv run ruff check src tests
uv run ruff format --check src tests
lint-fix: ## Run ruff autofix + format + isort fix
uv run ruff check --fix src tests
uv run ruff format src tests
uv run isort src tests
isort: ## Run isort (sort imports in-place)
uv run isort src tests
isort-check: ## Run isort in check-only mode
uv run isort --check-only src tests
typecheck: ## Run ty type checker
uv run ty check src
test: ## Run all tests (unit + integration)
uv run pytest
test-unit: ## Run unit tests only (fast)
uv run pytest -m "not integration"
test-cov: ## Run unit tests with coverage report (enforces threshold)
uv run pytest --cov=src/contract_check --cov-branch --cov-report=term-missing --cov-report=html --cov-fail-under=59 -m "not integration" tests/unit
test-integration: ## Run integration tests (needs docker infra)
uv run pytest -m integration
# ─────────────────────────────────────────────────────────────────────────────
# Docker: infrastructure (postgres + redis + rabbitmq + minio)
# ─────────────────────────────────────────────────────────────────────────────
infra-up: ## Start infra containers
docker compose up -d
infra-down: ## Stop infra containers
docker compose down
infra-logs: ## Tail infra logs
docker compose logs -f
# ─────────────────────────────────────────────────────────────────────────────
# Docker: api + workers (no bot)
# ─────────────────────────────────────────────────────────────────────────────
services-up: ## Start api + worker services (needs infra running; no bot)
docker compose --profile services up -d --build --remove-orphans
services-down: ## Stop api + worker services
docker compose --profile services down
services-logs: ## Tail api + worker logs
docker compose --profile services logs -f
services-ps: ## Show api + worker containers
docker compose --profile services ps
# ─────────────────────────────────────────────────────────────────────────────
# 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
full-down: ## Stop full stack: api + workers + bot
docker compose --profile services --profile bot down
full-logs: ## Tail full stack logs (api + workers + bot)
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)
# ─────────────────────────────────────────────────────────────────────────────
obs-up: ## Start Grafana/Loki/Prometheus containers
docker compose --profile obs up -d --build --remove-orphans
obs-down: ## Stop Grafana/Loki/Prometheus containers
docker compose --profile obs down
obs-logs: ## Tail Grafana/Loki/Prometheus containers logs
docker compose --profile obs logs -f
obs-url: ## Print Grafana URL and default credentials
@echo "Direct: http://localhost:$${GRAFANA_PORT:-3000}"
@echo "Via nginx: $${GRAFANA_ROOT_URL:-http://localhost}/grafana/"
@echo "User: $${GRAFANA_ADMIN_USER:-admin}"
@echo "Password: $${GRAFANA_ADMIN_PASSWORD:-admin}"
@echo "Dashboard: $${GRAFANA_ROOT_URL:-http://localhost}/grafana/d/contract-check-logs"
# ─────────────────────────────────────────────────────────────────────────────
# Docker: OpenObserve collector stack
# ─────────────────────────────────────────────────────────────────────────────
observer-up: ## Start OpenObserve + Vector containers
docker compose --profile observer up -d --build --remove-orphans
observer-down: ## Stop OpenObserve + Vector containers
docker compose --profile observer down
observer-logs: ## Tail OpenObserve + Vector containers logs
docker compose --profile observer logs -f
obs-reset: ## Reset Grafana and Loki volumes (wipes dashboards/logs data)
docker compose --profile obs down -v
rm -rf deploy/observability/grafana/dashboards/*.json.tmp
# ─────────────────────────────────────────────────────────────────────────────
# Docker: nginx edge (reverse proxy)
# ─────────────────────────────────────────────────────────────────────────────
nginx-up: ## Start nginx
docker compose --profile edge up -d --build --remove-orphans
nginx-down: ## Stop nginx
docker compose --profile edge down
nginx-logs: ## Tail nginx logs
docker compose --profile edge logs -f
nginx-ps: ## Show running containers
docker compose --profile edge ps
# ─────────────────────────────────────────────────────────────────────────────
# Individual services
# ─────────────────────────────────────────────────────────────────────────────
api: ## Start/restart API service
docker compose --profile services up -d --build --remove-orphans api
api-logs: ## Tail API logs
docker compose logs -f api
bot: bot-up ## Alias for bot-up
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)
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)
# ─────────────────────────────────────────────────────────────────────────────
bot-remote-up: ## Start bot on a separate server (set API_URL to central API)
docker compose -f docker-compose.bot.yml up -d --build --remove-orphans
bot-remote-down: ## Stop remote bot
docker compose -f docker-compose.bot.yml down
bot-remote-logs: ## Tail remote bot logs
docker compose -f docker-compose.bot.yml logs -f
bot-remote-ps: ## Show remote bot container status
docker compose -f docker-compose.bot.yml ps
bot-webhook-path: ## Print the secret-derived Telegram webhook path (reads BOT_TOKEN + BOT_WEBHOOK_SECRET_TOKEN from .env)
@uv run python -c "import os; from dotenv import load_dotenv; load_dotenv(); \
from src.contract_check.bot.webhook import derive_webhook_path; \
print(derive_webhook_path(os.environ['BOT_TOKEN'], os.environ['BOT_WEBHOOK_SECRET_TOKEN']))"
worker-extract: ## Start/restart extract worker
docker compose --profile services up -d --build --remove-orphans worker-extract
worker-analyze: ## Start/restart analyze worker
docker compose --profile services up -d --build --remove-orphans worker-analyze
worker-analyze-logs: ## Tail analyze worker logs
docker compose logs -f worker-analyze
worker-prescreen: ## Start/restart prescreen worker
docker compose --profile services up -d --build --remove-orphans worker-prescreen
worker-prescreen-logs: ## Tail prescreen worker logs
docker compose logs -f worker-prescreen
worker-notify: ## Start/restart notify worker
docker compose --profile services up -d --build --remove-orphans worker-notify
worker-notify-logs: ## Tail notify worker logs
docker compose logs -f worker-notify
# ─────────────────────────────────────────────────────────────────────────────
# Database
# ─────────────────────────────────────────────────────────────────────────────
migrate: ## Run Alembic migrations (inside api container)
docker compose --profile services build api
docker compose --profile services run --rm api alembic upgrade head
shell-db: ## Open psql inside postgres container
docker compose exec postgres psql -U contract_check -d contract_check
# ─────────────────────────────────────────────────────────────────────────────
# Admin panel (/admin — manage users; needs role = 'admin')
# ─────────────────────────────────────────────────────────────────────────────
admin-promote: ## Grant admin role to a user (usage: make admin-promote EMAIL=a@b.c)
@if [ -z "$(EMAIL)" ]; then \
echo "Usage: make admin-promote EMAIL=a@b.c"; \
exit 1; \
fi
@docker compose exec -T postgres psql -U contract_check -d contract_check \
-c "UPDATE users SET role = 'admin' WHERE email = '$(EMAIL)';" \
-c "SELECT id, email, role FROM users WHERE email = '$(EMAIL)';"
admin-list: ## List current admin users
@docker compose exec -T postgres psql -U contract_check -d contract_check \
-c "SELECT id, email, telegram_id, role, is_active FROM users WHERE role = 'admin';"
# ─────────────────────────────────────────────────────────────────────────────
# Auth / tokens
# ─────────────────────────────────────────────────────────────────────────────
seed-token: ## Generate bot service token (prints bearer token)
docker compose --profile services exec api python -m src.contract_check.api seed-token bot-prod bot
jwt-secret: ## Generate a fresh JWT_SECRET for .env
@openssl rand -hex 32
jwt-token: ## Exchange a telegram_id for a user JWT (usage: make jwt-token TG_ID=123456)
@if [ -z "$(TG_ID)" ]; then \
echo "Usage: make jwt-token TG_ID=123456"; \
exit 1; \
fi
@TOKEN=$$(grep -E '^BOT_SERVICE_TOKEN=' .env | cut -d= -f2); \
if [ -z "$$TOKEN" ]; then \
echo "BOT_SERVICE_TOKEN not found in .env"; \
exit 1; \
fi; \
curl -s -X POST -H "Authorization: Bearer $$TOKEN" \
-H "Content-Type: application/json" \
-d '{"telegram_id":$(TG_ID)}' \
http://localhost:8000/api/v1/auth/telegram/bot | jq .
jwt-verify: ## Introspect a user JWT (usage: make jwt-verify JWT=eyJ...)
@if [ -z "$(JWT)" ]; then \
echo "Usage: make jwt-verify JWT=eyJ..."; \
exit 1; \
fi
@curl -s -H "Authorization: Bearer $(JWT)" \
http://localhost:8000/api/v1/auth/me | jq .
# ─────────────────────────────────────────────────────────────────────────────
# Health / diagnostics
# ─────────────────────────────────────────────────────────────────────────────
health: ## Check API health endpoint
@echo "API health:"
@curl -s http://localhost:8000/healthz | jq . 2>/dev/null || curl -s http://localhost:8000/healthz
@echo ""
@echo "RabbitMQ: http://localhost:15672 (guest/guest → contract_check/contract_check)"
@echo "MinIO: http://localhost:9001 (contract_check/contract_check)"
@echo "Postgres: localhost:15432 (contract_check/contract_check)"
shell-api: ## Open shell inside API container
docker compose --profile services exec api /bin/sh
shell-bot: ## Open shell inside bot container
docker compose --profile bot exec bot /bin/sh
# ─────────────────────────────────────────────────────────────────────────────
# Nomad (production app services; full runbook: deploy/nomad/README.md)
# CLI env (NOMAD_ADDR/CACERT/TOKEN) is sourced from /root/.nomadrc on the VPS.
# ─────────────────────────────────────────────────────────────────────────────
NOMAD_JOB := deploy/nomad/contract-check.nomad.hcl
NOMAD_RUN := bash -c '. /root/.nomadrc 2>/dev/null; IMAGE_TAG=$${IMAGE_TAG:-manual} nomad "$$@"' --
nomad-validate: ## Validate the Nomad job file
@$(NOMAD_RUN) job validate $(NOMAD_JOB)
nomad-plan: ## Dry-run diff of the next deployment
@$(NOMAD_RUN) job plan $(NOMAD_JOB)
nomad-deploy: ## Deploy to Nomad (usage: IMAGE_TAG=<git-sha> make nomad-deploy)
@$(NOMAD_RUN) job run $(NOMAD_JOB)
nomad-status: ## Job status: groups, allocations, deployments
@$(NOMAD_RUN) job status contract-check
nomad-logs: ## Tail a group's logs (usage: make nomad-logs G=api)
@alloc=$$($(NOMAD_RUN) job allocs -json contract-check | python3 -c \
"import json,sys; a=[x for x in json.load(sys.stdin) if x['TaskGroup']=='$(G)' and x['ClientStatus']=='running']; print(a[0]['ID'] if a else '')"); \
[ -n "$$alloc" ] && $(NOMAD_RUN) alloc logs -f $$alloc || echo "no running alloc in group $(G)"
nomad-scale: ## Scale a group (usage: make nomad-scale G=worker-extract N=3)
@$(NOMAD_RUN) job scale contract-check $(G) $(N)
nomad-revert: ## Revert job to a prior version (usage: make nomad-revert V=2)
@$(NOMAD_RUN) job revert contract-check $(V)
# ─────────────────────────────────────────────────────────────────────────────
# Cleanup
# ─────────────────────────────────────────────────────────────────────────────
clean: ## Remove containers, volumes, caches
docker compose --profile services down -v
docker compose --profile bot down -v
docker compose down -v
rm -rf .pytest_cache .ruff_cache
uv cache clean
# ─────────────────────────────────────────────────────────────────────────────
# Full workflow shortcuts
# ─────────────────────────────────────────────────────────────────────────────
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
stop: services-down bot-down infra-down ## Stop everything
stop-obs: obs-down services-down bot-down infra-down ## Stop everything including observability