PY ?= python3 VENV ?= .venv BIN := $(VENV)/bin IMAGE ?= awg-dash:dev COMPOSE_DEV := deploy/docker-compose.dev.yml COMPOSE_TEST := deploy/docker-compose.test.yml COMPOSE_STUB := deploy/docker-compose.stub.yml VERIFY := deploy/verify-dev-stand.sh COMPOSE ?= docker compose # Состояние dev-стенда: bind mount для /config и /data (AC-2 задачи #21). HOST_DIR ?= /opt/awg-dash-dev # Хост-порт стенда. На dev-хосте 8080 занят другим сервисом — по умолчанию 8180; # внутри контейнера приложение всегда на 8080. DASH_PORT ?= 8180 NETNS_ARGS ?= VERIFY_ARGS ?= MANIFEST_ARGS ?= # Режим входа в netns внутри тестового контейнера: auto|ip|nsenter (issue #56). NETNS_EXEC_MODE ?= auto .PHONY: help venv lint fmt test build dev-up dev-up-app dev-up-stub dev-down dev-logs dev-verify \ dev-verify-stub dev-reset test-netns test-netns-keep test-netns-ci test-manifests lint-shell \ test-deploy help: @grep -E '^[a-zA-Z_-]+:' $(MAKEFILE_LIST) | sed 's/:.*//' | sort -u venv: $(PY) -m venv $(VENV) $(BIN)/pip install -U pip $(BIN)/pip install -r requirements-dev.txt lint: $(BIN)/ruff check backend $(BIN)/mypy backend fmt: $(BIN)/ruff format backend $(BIN)/ruff check --fix backend test: $(BIN)/pytest build: @test -f deploy/Dockerfile || { echo 'deploy/Dockerfile ещё нет — задача #18'; exit 1; } docker build -f deploy/Dockerfile -t $(IMAGE) . # --- dev/staging стенд (задача #21) ------------------------------------------ # Пока FastAPI-каркас (#31) не реализован, API в образе не поднимается и AC-1 # (`curl :8080/api/v1/health` -> 200) недостижим. Чтобы гейт #21 проверялся на # живом стенде, `dev-up` автоматически добавляет оверрайд с health-заглушкой # (deploy/docker-compose.stub.yml), ПОКА файла приложения нет. После мержа #31 # оверрайд отключается сам. Явные режимы: dev-up-app / dev-up-stub. API_APP ?= backend/awg_api/app.py ifneq ($(wildcard $(API_APP)),) COMPOSE_DEV_EFFECTIVE := -f $(COMPOSE_DEV) STUB_MODE := 0 else COMPOSE_DEV_EFFECTIVE := -f $(COMPOSE_DEV) -f $(COMPOSE_STUB) STUB_MODE := 1 endif # deploy/Dockerfile приходит с задачей #18; до этого образ собирается отдельно, # поэтому --build не форсируем, если Dockerfile отсутствует в дереве. DEV_UP_BUILD := $(if $(wildcard deploy/Dockerfile),--build,) DEV_ENV := AWG_DASH_PORT=$(DASH_PORT) AWG_DASH_HOST_DIR=$(HOST_DIR) DEV_VERIFY_ARGS := $(if $(filter 1,$(STUB_MODE)),--stub,) dev-up: @test -f $(COMPOSE_DEV) || { echo '$(COMPOSE_DEV) ещё нет — задача #21'; exit 1; } @command -v docker >/dev/null 2>&1 || { echo 'нет docker: dev-стенд поднимается на Docker-хосте (см. deploy/dev-stand.md)'; exit 1; } @[ "$(STUB_MODE)" = "0" ] || echo 'dev-up: API (#31) ещё не в дереве — поднимаю стенд с health-заглушкой (deploy/docker-compose.stub.yml)' $(DEV_ENV) $(COMPOSE) $(COMPOSE_DEV_EFFECTIVE) up -d $(DEV_UP_BUILD) # Только реальный образ: проверка отказа API без заглушки / режим после #31. dev-up-app: @test -f $(COMPOSE_DEV) || { echo '$(COMPOSE_DEV) ещё нет — задача #21'; exit 1; } $(DEV_ENV) $(COMPOSE) -f $(COMPOSE_DEV) up -d $(DEV_UP_BUILD) # Только health-заглушка (проверка плумбинга стенда до #31). dev-up-stub: @test -f $(COMPOSE_STUB) || { echo '$(COMPOSE_STUB) ещё нет — задача #21'; exit 1; } $(DEV_ENV) $(COMPOSE) -f $(COMPOSE_DEV) -f $(COMPOSE_STUB) up -d $(DEV_UP_BUILD) dev-down: $(DEV_ENV) $(COMPOSE) $(COMPOSE_DEV_EFFECTIVE) down dev-logs: $(DEV_ENV) $(COMPOSE) $(COMPOSE_DEV_EFFECTIVE) logs -f --tail=100 # Проверка AC #21 на Docker-хосте: health 200 + состояние переживает restart. dev-verify: $(DEV_ENV) bash $(VERIFY) $(DEV_VERIFY_ARGS) $(VERIFY_ARGS) dev-verify-stub: $(DEV_ENV) bash $(VERIFY) --stub $(VERIFY_ARGS) # Останавливает стенд; каталоги состояния сохраняются (удалять вручную). dev-reset: $(DEV_ENV) $(COMPOSE) $(COMPOSE_DEV_EFFECTIVE) down --remove-orphans @echo "каталоги состояния сохранены: $(HOST_DIR)/config, $(HOST_DIR)/data (удалять вручную)" # --- netns-стенд (задача #21) ------------------------------------------------ test-netns: @test -d tests/netns || { echo 'tests/netns ещё нет — задача #21'; exit 1; } bash tests/netns/run.sh $(NETNS_ARGS) # То же, но топология и логи остаются для отладки (снести: tests/netns/topology.sh down). test-netns-keep: bash tests/netns/run.sh --keep-topology --keep-logs $(NETNS_ARGS) # Тот же стенд в привилегированном контейнере — путь для CI (#17). # Внутри контейнера `ip netns exec` падает с "mount of /sys failed: Operation # not permitted" (iproute2 6.x), поэтому харнесс сам переключается на # `nsenter --net=/var/run/netns/` (issue #56). AWG_NS_EXEC_MODE форсирует # режим: ip|nsenter|auto. test-netns-ci: AWG_DASH_IMAGE=$(IMAGE) AWG_NS_EXEC_MODE=$(NETNS_EXEC_MODE) $(COMPOSE) -f $(COMPOSE_TEST) run --rm netns # --- статические проверки манифестов и скриптов ------------------------------ test-manifests: $(PY) tests/deploy/validate_compose.py $(MANIFEST_ARGS) # Тесты стенда с покрытием: гейт DoD «тесты ≥70 %» для #21/#56. # Отдельный tests/deploy/pytest.ini обязателен: без него `pytest tests/deploy` # наследует addopts корневого pyproject.toml (--cov=awg_agent --cov=awg_api), # покрытие выходит 0.00 % и RC=1 — гейт приходилось гонять с --no-cov. test-deploy: @test -d tests/deploy || { echo 'tests/deploy ещё нет — задача #21'; exit 1; } $(PY) -m pytest tests/deploy -c tests/deploy/pytest.ini lint-shell: @command -v shellcheck >/dev/null 2>&1 || { echo 'shellcheck не установлен (apt-get install shellcheck)'; exit 1; } shellcheck -x -S style tests/netns/*.sh deploy/*.sh