# niche-finder -- common tasks # Docker path is the default; `make local-*` targets run straight on the host. COMPOSE ?= docker compose IMAGE ?= niche-finder:latest # mcp[cli] (see backend/requirements.txt) needs Python 3.10+, but on macOS a # bare `python3` often resolves to the Xcode Command Line Tools' Python 3.9 -- # that silently breaks `local-install` (pip aborts on the first requirement, # so nothing after it gets installed either). Prefer a newer interpreter if one # is on PATH; override explicitly with `make local-install PYTHON=python3.11`. PYTHON ?= $(shell command -v python3.13 || command -v python3.12 || \ command -v python3.11 || command -v python3.10 || command -v python3) RUFF_VERSION ?= 0.16.9 RUFF ?= uvx ruff@$(RUFF_VERSION) .PHONY: help doctor build up up-db web open down logs worker mcp http shell test seed stats clean local-install local-test lint frontend-test local-run dev cli rotate-secrets api-docs doc-links lock-check extension-zip prod-up prod-down prod-logs prod-cli prod-backup doctor: ## проверить ключ, сеть и базу (начните отсюда) $(COMPOSE) run --rm mcp python cli.py doctor help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}' build: ## build the image (PREFETCH_MODEL=1 bakes in the embedding model) $(COMPOSE) build up: ## поднять дашборд и фоновый сборщик $(COMPOSE) up -d web worker @echo "Дашборд: http://localhost:$${WEB_PORT:-8080}" web: ## только дашборд $(COMPOSE) up -d web @echo "Дашборд: http://localhost:$${WEB_PORT:-8080}" open: ## открыть дашборд в браузере open "http://localhost:$${WEB_PORT:-8080}" down: ## stop everything $(COMPOSE) down logs: ## логи сборщика и дашборда $(COMPOSE) logs -f worker web mcp: ## run the MCP server on stdio (manual poke) $(COMPOSE) run --rm mcp http: ## expose the MCP server over HTTP on :8765 $(COMPOSE) --profile http up -d mcp-http shell: ## shell inside the image, with the models volume mounted docker run --rm -it --network niche-finder_default -v niche-finder-models:/models $(IMAGE) bash # Схема make_test переиспользуется между запусками, поэтому чистим её ПЕРЕД # прогоном: иначе данные прошлого раза ломают тесты, завязанные на свежий # discovered_at (make test был зелёным только при первом запуске). test: ## run the smoke tests inside the image, against postgres (needs: make up-db) @$(COMPOSE) exec -T postgres sh -c \ 'psql -q -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -c "DROP SCHEMA IF EXISTS make_test CASCADE"' >/dev/null docker run --rm --network niche-finder_default -e NICHE_DB_SCHEMA=make_test \ -e POSTGRES_HOST=postgres $(IMAGE) python tests/test_smoke.py seed: ## fill the database with synthetic demo data to try the tools (needs: make up-db) docker run --rm --network niche-finder_default -e POSTGRES_HOST=postgres \ $(IMAGE) python tests/seed_demo.py stats: ## what is in the database right now (needs: make up-db) docker run --rm --network niche-finder_default -e POSTGRES_HOST=postgres $(IMAGE) \ python -c "import json; from application import search as query; print(json.dumps(query.db_stats(user_id=None), indent=2, default=str))" up-db: ## just the database, e.g. before make test/seed/stats $(COMPOSE) up -d postgres cli: ## произвольная команда CLI: make cli ARGS="viral --period 24h" $(COMPOSE) run --rm mcp python cli.py $(ARGS) rotate-secrets: ## re-encrypt stored secrets with the current OWN_TOKENS_KEY (dry run; ARGS=--apply writes) $(COMPOSE) run --rm mcp python cli.py rotate-secrets $(ARGS) clean: ## delete the Postgres data volume (irreversible) docker volume rm niche-finder-postgres-data local-install: ## venv + deps, without Docker (needs Python 3.10+, auto-detected -- see PYTHON above) @ver=$$($(PYTHON) -c 'import sys; print("%d.%d" % sys.version_info[:2])'); \ major=$$(echo $$ver | cut -d. -f1); minor=$$(echo $$ver | cut -d. -f2); \ if [ "$$major" -lt 3 ] || { [ "$$major" -eq 3 ] && [ "$$minor" -lt 10 ]; }; then \ echo "ОШИБКА: $(PYTHON) -> Python $$ver, а пакету mcp нужен 3.10+."; \ echo "Поставьте современный Python (например: brew install python@3.12) и повторите,"; \ echo "либо укажите явно: make local-install PYTHON=python3.12"; \ exit 1; \ fi; \ echo "используется $$($(PYTHON) --version) ($(PYTHON))" cd backend && $(PYTHON) -m venv .venv \ && ./.venv/bin/pip install --upgrade pip \ && ./.venv/bin/pip install -r requirements.txt pytest # Тесты намеренно не читают .env (им не нужны ни ключ, ни сеть), # поэтому NICHE_DATABASE_URL достаём из .env здесь -- иначе хостовый прогон # уходит на localhost:5432 и падает с Connection refused. # Как в CI: весь tests/ одним процессом pytest. Изоляцию файлов друг от друга # даёт tests/conftest.py -- у Postgres-тестов своя свежая схема на файл, а # sqlite-двойники живут в отдельном мире модулей, не в sys.modules # (tests/module_doubles.py). Код выхода -- как у pytest. local-test: ## run all backend tests on the host in one pytest process @cd backend && export NICHE_DATABASE_URL="$${NICHE_DATABASE_URL:-$$(grep -E '^NICHE_DATABASE_URL=' ../.env 2>/dev/null | sed -E 's/^[^=]+=//; s/^"//; s/"$$//' | tr -d '\r')}"; \ ./.venv/bin/python -m pytest -q -p no:cacheprovider tests/ # Та же версия ruff, что в CI (.github/workflows/ci.yml); конфиг -- в # backend/pyproject.toml. uvx (из uv) скачивает ruff сам, в .venv он не нужен; # без uv: make lint RUFF=ruff (ruff==$(RUFF_VERSION) из pip). lint: ## lint the backend with ruff (same pinned version as CI) $(RUFF) check backend api-docs: ## regenerate docs/http-api.md from the HTTP routes (CI checks it is current) python3 scripts/gen_http_api_docs.py doc-links: ## check that relative links in the .md files reach tracked files and headings (CI runs it) python3 scripts/check_doc_links.py UV ?= uvx uv@0.13.5 lock-check: ## check backend/requirements.txt is what uv compiles from requirements.in (CI runs it) @cd backend && cp requirements.txt /tmp/nf-lock.txt \ && $(UV) pip compile requirements.in --python-version 3.10 --universal -o /tmp/nf-lock.txt -q \ && grep -v '^ *#' requirements.txt > /tmp/nf-lock.a && grep -v '^ *#' /tmp/nf-lock.txt > /tmp/nf-lock.b \ && if cmp -s /tmp/nf-lock.a /tmp/nf-lock.b; then echo "requirements.txt is uv-compiled"; \ else diff /tmp/nf-lock.a /tmp/nf-lock.b; echo "requirements.txt differs from uv pip compile: recompile it (backend/README.md, Running without Docker)"; exit 1; fi extension-zip: ## pack extension/ into dist/niche-finder-extension-v.zip (committed files only) sh scripts/package_extension.sh # Смоук-тест дашборда в headless Chromium (frontend/tests/test_smoke.py): свой # uvicorn на свободном порту + своя схема с демо-данными, рабочую базу не трогает. # Один раз: ./backend/.venv/bin/pip install -r frontend/tests/requirements.txt # ./backend/.venv/bin/python -m playwright install chromium frontend-test: ## browser smoke test of every dashboard screen (needs: make up-db) @cd backend && export NICHE_DATABASE_URL="$${NICHE_DATABASE_URL:-$$(grep -E '^NICHE_DATABASE_URL=' ../.env 2>/dev/null | sed -E 's/^[^=]+=//; s/^"//; s/"$$//' | tr -d '\r')}"; \ ./.venv/bin/python -m pytest -q -p no:cacheprovider ../frontend/tests local-run: ## run the MCP server on the host cd backend && ./.venv/bin/python server.py # Требует в .env строку NICHE_DATABASE_URL=postgresql://niches:niches@localhost:5433/niches # (база из compose опубликована на 5433, а дефолт в коде -- 5432) и свободный # :8080 -- при поднятом контейнере web сначала `docker compose stop web`, # иначе хостовый uvicorn перехватит порт и проброс контейнера тихо отвалится. dev: ## run the HTTP dashboard on the host, no Docker (needs: make local-install + NICHE_DATABASE_URL in .env) cd backend && ./.venv/bin/python -m uvicorn api:app --reload --host 127.0.0.1 --port 8080 # Сервер для других людей (docs/hosting.md): Caddy с настоящим сертификатом на # NF_DOMAIN, наружу только 80/443, вход по аккаунтам, бэкапы по расписанию. PROD = $(COMPOSE) -f docker-compose.yml -f docker-compose.prod.yml prod-up: ## production: everything behind Caddy on NF_DOMAIN, with backups (docs/hosting.md) $(PROD) up -d --build postgres web worker mcp-http proxy backup prod-down: ## production: stop everything (data and backups stay) $(PROD) down prod-logs: ## production: follow the logs $(PROD) logs -f --tail 100 web worker mcp-http proxy backup prod-cli: ## production: a CLI command, e.g. make prod-cli ARGS="create-user you@example.com --admin" $(PROD) exec web python cli.py $(ARGS) prod-backup: ## production: one backup right now, with the restore check $(PROD) run --rm backup once