VENV ?= $(HOME)/.py_envs/robotex PYTHON := $(VENV)/bin/python PIP := $(PYTHON) -m pip WHEELS_DIR := wheels REQUIREMENTS := requirements.txt ver ?= latest MAKECMDGOALS_TARGETS := install freeze wheels sync sync-offline clean help build release sync-dev test test-browser up down logs smoke key-create key-list key-revoke stats push dev dev-deps dev-key PKG := $(filter-out $(MAKECMDGOALS_TARGETS),$(MAKECMDGOALS)) .PHONY: install freeze wheels sync sync-offline clean help venv build release sync-dev test test-browser up down logs smoke key-create key-list key-revoke stats push dev dev-deps dev-key # образы сервисов: api — без Chrome, worker — Chrome + Xvfb; rabbitmq и redis — сторонние, кладутся в релиз REGISTRY := docker.sokolab.xyz API_IMAGE := robotex-api WORKER_IMAGE := robotex-worker DEPS_IMAGES := rabbitmq:4.1-alpine redis:8.2-alpine postgres:17-alpine API_URL ?= http://localhost:8000 API_KEY ?= # make dev (#90): rabbitmq, redis, postgres — в Docker с портами на 127.0.0.1 (docker-compose.dev.yml), api и worker — # на хосте, Chrome открывается на рабочем столе. Порты нестандартные: 5672/5432/8000 часто заняты другими проектами. DEV_COMPOSE := docker compose -f docker-compose.yml -f docker-compose.dev.yml DEV_API_PORT ?= 8010 DEV_RABBITMQ_PORT ?= 5673 DEV_REDIS_PORT ?= 6380 DEV_POSTGRES_PORT ?= 5433 # окон Chrome на рабочем столе; потоков dramatiq — вдвое больше, как в Dockerfile DEV_SLOTS ?= 2 RABBITMQ_PASSWORD ?= robotex POSTGRES_PASSWORD ?= robotex export DEV_RABBITMQ_PORT DEV_REDIS_PORT DEV_POSTGRES_PORT RABBITMQ_PASSWORD POSTGRES_PASSWORD DEV_ENV := RABBITMQ_URL=amqp://robotex:$(RABBITMQ_PASSWORD)@localhost:$(DEV_RABBITMQ_PORT)/ \ REDIS_URL=redis://localhost:$(DEV_REDIS_PORT)/0 \ DATABASE_URL=postgresql+asyncpg://robotex:$(POSTGRES_PASSWORD)@localhost:$(DEV_POSTGRES_PORT)/robotex \ BROWSER_SLOTS=$(DEV_SLOTS) \ LOG_LEVEL=DEBUG help: @echo "make install [ ...] - установить пакет(ы) в $(VENV), обновить $(REQUIREMENTS), собрать wheel в $(WHEELS_DIR)/" @echo "make sync - установить все пакеты из $(REQUIREMENTS) (из индекса)" @echo "make sync-offline - установить все пакеты из $(REQUIREMENTS) только из $(WHEELS_DIR)/, без обращения к индексу" @echo "make freeze - перезаписать $(REQUIREMENTS) текущим состоянием окружения" @echo "make wheels - собрать wheel для всех пакетов из $(REQUIREMENTS)" @echo "make clean - удалить $(WHEELS_DIR)/" @echo "make build [ver=latest] - собрать образы $(API_IMAGE): и $(WORKER_IMAGE):" @echo "make push ver= - собрать образы и отправить в $(REGISTRY)" @echo "make release ver= [out=registry|file] - собрать образы и пакет для деплоя в build// (compose, .env);" @echo " образы: registry — push в $(REGISTRY) (по умолчанию), file — архив .tar.gz" @echo "make up - собрать и запустить локально: api, worker, rabbitmq, redis" @echo "make down - остановить локальный стенд" @echo "make logs - логи api и worker" @echo "make dev - api и worker на хосте с видимым Chrome, rabbitmq/redis/postgres в Docker; API на :$(DEV_API_PORT)" @echo "make dev-key name=<клиент> - выпустить API-ключ клиенту при make dev" @echo "make smoke API_KEY=... [API_URL=...] - проверить запущенный стенд: ключ, /v1/solve, /v2/tasks, удержание окна" @echo "make key-create name=<клиент> - выпустить API-ключ клиенту (в запущенном compose)" @echo "make key-list - клиенты и их ключи (без самих ключей)" @echo "make key-revoke id= - отозвать ключ клиента" @echo "make stats [days=7] - статистика: запросы, их время, время работы браузера по клиентам" @echo "make sync-dev - установить зависимости для тестов (requirements-dev.txt)" @echo "make test - юнит-тесты без браузера" @echo "make client-build - собрать wheel клиента robotex в client/dist/" @echo "make test-browser [ver=latest] - собрать образ и прогнать сквозные тесты с Chrome в Docker (нужна сеть)" venv: @test -d $(VENV) || python3 -m venv $(VENV) install: venv ifeq ($(strip $(PKG)),) $(error Укажите пакет: make install , например make install redis) endif $(PIP) install --upgrade $(PKG) $(PIP) freeze > $(REQUIREMENTS) @mkdir -p $(WHEELS_DIR) $(PIP) wheel $(PKG) -w $(WHEELS_DIR) @echo "Готово: $(PKG) установлен, $(REQUIREMENTS) обновлён, wheel-пакеты в $(WHEELS_DIR)/" sync: venv $(PIP) install -r $(REQUIREMENTS) sync-offline: venv $(PIP) install --no-index --find-links=$(WHEELS_DIR) -r $(REQUIREMENTS) freeze: venv $(PIP) freeze > $(REQUIREMENTS) wheels: venv @mkdir -p $(WHEELS_DIR) $(PIP) wheel -r $(REQUIREMENTS) -w $(WHEELS_DIR) clean: rm -rf $(WHEELS_DIR) build: docker build -f Dockerfile.api -t $(API_IMAGE):$(ver) . docker build -t $(WORKER_IMAGE):$(ver) . push: build for img in $(API_IMAGE) $(WORKER_IMAGE); do \ docker tag $$img:$(ver) $(REGISTRY)/$$img:$(ver) && docker push $(REGISTRY)/$$img:$(ver) || exit 1; \ done up: build docker compose up -d down: docker compose down logs: docker compose logs -f api worker # контейнерные api и worker останавливаются: иначе worker в Docker тоже берёт задачи из общей очереди и выполняет их невидимо dev-deps: -docker compose stop api worker $(DEV_COMPOSE) up -d --wait rabbitmq redis postgres # Ctrl+C останавливает оба процесса; инфраструктура остаётся запущенной (make down — остановить) # api и worker — в фоне: фоновые процессы sh не получают SIGINT от Ctrl+C, поэтому при выходе (Ctrl+C, закрытие # терминала, kill) trap останавливает оба и ждёт их — иначе осиротевший worker остаётся работать (#110) dev: dev-deps cd src && export $(DEV_ENV) && \ { $(PYTHON) -m uvicorn main:app --reload --port $(DEV_API_PORT) & api=$$!; \ $(PYTHON) -m dramatiq worker.actors --processes 1 --threads $$(( $(DEV_SLOTS) * 2 )) & worker=$$!; \ trap 'trap - EXIT INT TERM HUP; kill $$api $$worker 2>/dev/null; wait' EXIT INT TERM HUP; \ wait $$worker; } dev-key: dev-deps cd src && $(DEV_ENV) $(PYTHON) -m db.cli create "$(name)" smoke: $(PYTHON) tests/smoke.py $(API_URL) $(API_KEY) key-create: docker compose exec api python -m db.cli create "$(name)" key-list: docker compose exec api python -m db.cli list key-revoke: docker compose exec api python -m db.cli revoke $(id) days ?= 7 stats: docker compose exec api python -m db.cli stats --days $(days) sync-dev: venv $(PIP) install -r requirements-dev.txt test: $(PYTHON) -m pytest client-build: venv rm -rf client/dist client/build $(PIP) wheel ./client -w client/dist --no-deps @echo "Готово: client/dist/ — pip install client/dist/robotex-*.whl" test-browser: build docker run --rm --shm-size=2g \ -v $(CURDIR)/tests:/app/tests:ro -v $(CURDIR)/pytest.ini:/app/pytest.ini:ro \ -v $(CURDIR)/requirements-dev.txt:/app/requirements-dev.txt:ro \ -w /app $(WORKER_IMAGE):$(ver) sh -c 'pip install -q --user -r requirements-dev.txt && \ xvfb-run -a --server-args="-screen 0 1920x1080x24" python -m pytest -m browser -p no:cacheprovider -s' RELEASE_DIR := build/$(ver) # куда деваются образы при make release: registry — push в $(REGISTRY), file — архив .tar.gz в $(RELEASE_DIR)/ out ?= registry ifneq ($(filter release push,$(MAKECMDGOALS)),) ifeq ($(ver),latest) $(error Укажите версию: make release ver=v0.0.2) endif ifeq ($(filter $(out),registry file),) $(error out=registry или out=file, а не "$(out)") endif endif ifeq ($(out),file) RELEASE_RUN := docker load -i robotex-$(ver).tar.gz else RELEASE_RUN := docker compose pull endif release: $(if $(filter file,$(out)),build,push) @echo "make release $(ver) [out=$(out)] -> $(RELEASE_DIR)/" @mkdir -p $(RELEASE_DIR) sed -e 's|image: .*$(API_IMAGE):.*|image: $(REGISTRY)/$(API_IMAGE):$(ver)|' \ -e 's|image: .*$(WORKER_IMAGE):.*|image: $(REGISTRY)/$(WORKER_IMAGE):$(ver)|' \ docker-compose.prod.yml > $(RELEASE_DIR)/docker-compose.yml cp dot_env $(RELEASE_DIR)/.env ifeq ($(out),file) # сторонние образы (rabbitmq, redis, postgres) кладутся в архив; при out=registry сервер берёт их с Docker Hub for img in $(DEPS_IMAGES); do docker image inspect $$img >/dev/null 2>&1 || docker pull $$img; done docker tag $(API_IMAGE):$(ver) $(REGISTRY)/$(API_IMAGE):$(ver) docker tag $(WORKER_IMAGE):$(ver) $(REGISTRY)/$(WORKER_IMAGE):$(ver) docker save $(REGISTRY)/$(API_IMAGE):$(ver) $(REGISTRY)/$(WORKER_IMAGE):$(ver) $(DEPS_IMAGES) | gzip > $(RELEASE_DIR)/robotex-$(ver).tar.gz endif @printf '%s\n' \ '# Robotex $(ver)' \ '' \ '```bash' \ '$(RELEASE_RUN)' \ 'docker compose up -d' \ '```' \ '' \ 'Настройки (порт, слоты, CPU и память воркера, пароли RabbitMQ и Postgres) — в .env.' \ 'Ключ клиенту: docker compose exec api python -m db.cli create <имя>' > $(RELEASE_DIR)/README.md @echo "Готово: $(RELEASE_DIR)/" # позволяет передавать имя пакета как аргумент: make install redis %: @: