# «Контракт-чек» — everyday commands (uv + docker) # Usage: make (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 clean dev dev-obs stop stop-obs # ───────────────────────────────────────────────────────────────────────────── # Help # ───────────────────────────────────────────────────────────────────────────── help: ## Show available commands @echo "Usage: make " @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 # ───────────────────────────────────────────────────────────────────────────── # 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