From da1c750297d928d9f68620a3700993d3acff37c1 Mon Sep 17 00:00:00 2001 From: embeddevio Date: Wed, 21 Oct 2026 23:49:11 +0300 Subject: [PATCH] =?UTF-8?q?feat(#21):=20dev-=D1=81=D1=82=D0=B5=D0=BD=D0=B4?= =?UTF-8?q?=20=E2=80=94=20compose-=D1=84=D0=B0=D0=B9=D0=BB=D1=8B,=20health?= =?UTF-8?q?-=D0=B7=D0=B0=D0=B3=D0=BB=D1=83=D1=88=D0=BA=D0=B0=20=D0=B4?= =?UTF-8?q?=D0=BE=20#31?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-compose.dev.yml: отдельный project awg-dash-dev, порт 8080 в контейнере (наружу AWG_DASH_PORT, по умолчанию 8180 — 8080 занят на dev-хосте), bind mount /opt/awg-dash-dev/{config,data} -> /config,/data, healthcheck /api/v1/health, NET_ADMIN/NET_RAW/SYS_MODULE, /dev/net/tun, ip_forward, лимит логов; - docker-compose.stub.yml + api_stub.py — DEV-ONLY заглушка health: AC-1 требует FastAPI-каркаса (#31), до него проверяем плумбинг стенда без обмана отчёта (в ответе ивно стоит "stub": true); - docker-compose.test.yml: привилегированный netns-раннер (CAP_SYS_ADMIN нужен для `ip netns add`) + сервис app для API-тестов. Refs #21 Refs #15 --- deploy/api_stub.py | 84 ++++++++++++++++++++++++++++++++++ deploy/docker-compose.dev.yml | 68 +++++++++++++++++++++++++++ deploy/docker-compose.stub.yml | 23 ++++++++++ deploy/docker-compose.test.yml | 70 ++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 deploy/api_stub.py create mode 100644 deploy/docker-compose.dev.yml create mode 100644 deploy/docker-compose.stub.yml create mode 100644 deploy/docker-compose.test.yml diff --git a/deploy/api_stub.py b/deploy/api_stub.py new file mode 100644 index 0000000..848922d --- /dev/null +++ b/deploy/api_stub.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""api_stub.py — DEV-ONLY заглушка health-эндпоинта (задача #21). + +Существует ровно для одной цели: до реализации FastAPI-каркаса (#31) проверить +плумбинг dev-стенда — маппинг порта, healthcheck контейнера, тома /config и +/data, переживание `docker compose restart`. + +Отдаёт: + GET /api/v1/health -> 200 {"status":"ok","stub":true,...} + GET /healthz -> 200 (алиас для отладки) + всё остальное -> 404 {"error":"not_implemented","issue":"#31"} + +Чего НЕ делает: никакой логики шлюза, никакого доступа к awg/iptables. +В staging/prod запускать запрещено (см. deploy/dev-stand.md). +""" + +from __future__ import annotations + +import argparse +import json +import os +import time +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +STARTED_AT = time.time() +STUB_ENV = os.environ.get("AWG_API_STUB", "0") + + +class Handler(BaseHTTPRequestHandler): + server_version = "awg-dash-stub/0.1" + protocol_version = "HTTP/1.1" + + def _json(self, code: int, payload: dict) -> None: + body = (json.dumps(payload, sort_keys=True) + "\n").encode() + self.send_response(code) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def do_GET(self) -> None: # noqa: N802 (HTTP-метод) + path = self.path.split("?", 1)[0] + if path in ("/api/v1/health", "/healthz"): + self._json( + 200, + { + "status": "ok", + "stub": True, + "issue": "#31", + "note": "заглушка dev-стенда: реальный API появится в #31", + "uptime_s": round(time.time() - STARTED_AT, 3), + "stub_env": STUB_ENV, + }, + ) + return + self._json(404, {"error": "not_implemented", "issue": "#31", "path": path}) + + def do_HEAD(self) -> None: # noqa: N802 + self.send_response(200 if self.path.split("?", 1)[0] in ("/api/v1/health", "/healthz") else 404) + self.send_header("Content-Length", "0") + self.end_headers() + + def log_message(self, fmt: str, *args) -> None: # лог в stdout контейнера + print(f"[api-stub] {self.address_string()} {fmt % args}", flush=True) + + +def main() -> int: + ap = argparse.ArgumentParser(description="DEV-ONLY заглушка health для dev-стенда (#21)") + ap.add_argument("--host", default="0.0.0.0") + ap.add_argument("--port", type=int, default=8080) + ap.add_argument("--domain", default="/config", help="каталог, доступность которого проверяем (для лога)") + args = ap.parse_args() + + print( + f"[api-stub] DEV-ONLY заглушка на {args.host}:{args.port}; каталог {args.domain} " + f"{'доступен' if os.path.isdir(args.domain) else 'НЕ доступен'}", + flush=True, + ) + ThreadingHTTPServer((args.host, args.port), Handler).serve_forever() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/deploy/docker-compose.dev.yml b/deploy/docker-compose.dev.yml new file mode 100644 index 0000000..1097bd3 --- /dev/null +++ b/deploy/docker-compose.dev.yml @@ -0,0 +1,68 @@ +# dev/staging стенд awg-dash (задача #21, эпик #15). +# +# Отдельный compose-project `awg-dash-dev` — не пересекается с контейнерами PoC +# (amneziawg-client) и другими сервисами dev-хоста. +# +# Запуск: make dev-up (поднимает стек с образом awg-dash:dev) +# Проверка AC: make dev-verify (health 200 + переживание restart для /config /data) +# Логи: make dev-logs +# Остановка: make dev-down (тома/каталоги состояния сохраняются) +# +# AC-1 (health 200) выполняется только после реализации FastAPI-каркаса (#31): +# до этого контейнер отдаёт healthcheck unhealthy, а `/config` `/data` уже +# проверяются скриптом deploy/verify-dev-stand.sh. Заглушка для ранней проверки +# стенда — deploy/docker-compose.stub.yml + `make dev-up-stub`. + +name: ${COMPOSE_PROJECT_NAME:-awg-dash-dev} + +services: + awg-dash: + image: ${AWG_DASH_IMAGE:-awg-dash:dev} + build: + context: .. + dockerfile: ${AWG_DASH_DOCKERFILE:-deploy/Dockerfile} + args: + AMNEZIAWG_GO_REF: ${AMNEZIAWG_GO_REF:-master} + AMNEZIAWG_TOOLS_REF: ${AMNEZIAWG_TOOLS_REF:-master} + container_name: awg-dash-dev + restart: unless-stopped + # Порт приложения внутри образа всегда 8080 (EXPOSE/AWG_API_PORT). + # На dev-хосте 192.168.1.147 порт 8080 занят (drawio) — по умолчанию 8180. + ports: + - "${AWG_DASH_PORT:-8180}:8080" + volumes: + # Состояние вынесено на хост, чтобы правки конфигурации переживали + # `docker compose restart` (AC задачи #21) и пересоздание контейнера. + - ${AWG_DASH_HOST_DIR:-/opt/awg-dash-dev}/config:/config + - ${AWG_DASH_HOST_DIR:-/opt/awg-dash-dev}/data:/data + devices: + - /dev/net/tun:/dev/net/tun + cap_add: + # NET_ADMIN — iptables/ipset/ip rule; NET_RAW — ping/socket-проверки; + # SYS_MODULE — sysctl net.ipv4.ip_forward и модули ядра (как в ТЗ). + - NET_ADMIN + - NET_RAW + - SYS_MODULE + sysctls: + net.ipv4.ip_forward: "1" + net.ipv4.conf.all.src_valid_mark: "1" + environment: + AWG_CONFIG_DIR: /config + AWG_DATA_DIR: /data + AWG_RUN_DIR: /run/awg + AWG_API_HOST: 0.0.0.0 + AWG_API_PORT: "8080" + AWG_LOG_LEVEL: ${AWG_LOG_LEVEL:-info} + AWG_DEV_STAND: "1" + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/api/v1/health >/dev/null || exit 1"] + interval: 15s + timeout: 5s + start_period: 30s + retries: 4 + stop_grace_period: 20s + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" diff --git a/deploy/docker-compose.stub.yml b/deploy/docker-compose.stub.yml new file mode 100644 index 0000000..936efb3 --- /dev/null +++ b/deploy/docker-compose.stub.yml @@ -0,0 +1,23 @@ +# Оверрайд для dev-стенда: health-заглушка вместо ещё не реализованного API. +# +# Зачем: AC-1 задачи #21 (`curl :8080/api/v1/health` -> 200) физически не может +# выполниться до появления FastAPI-каркаса (#31, Sprint 2). Чтобы проверить +# плумбинг стенда (порт, healthcheck, тома, restart) уже сейчас, этот оверрайд +# подменяет entrypoint на минимальный HTTP-сервер из deploy/api_stub.py. +# +# Это НЕ приложение: заглушка отдаёт 200 только на /api/v1/health и явно +# помечает ответ `"stub": true`. В prod/staging не использовать. +# +# Запуск: docker compose -f deploy/docker-compose.dev.yml -f deploy/docker-compose.stub.yml up -d +# (или make dev-up-stub / make dev-verify-stub) +# +# После мержа #31 оверрайд не нужен: `make dev-up` поднимает реальный API. + +services: + awg-dash: + entrypoint: ["python3", "/opt/awg-dash-stub/api_stub.py"] + command: ["--host", "0.0.0.0", "--port", "8080", "--domain", "/config"] + volumes: + - ../deploy/api_stub.py:/opt/awg-dash-stub/api_stub.py:ro + environment: + AWG_API_STUB: "1" diff --git a/deploy/docker-compose.test.yml b/deploy/docker-compose.test.yml new file mode 100644 index 0000000..da01e80 --- /dev/null +++ b/deploy/docker-compose.test.yml @@ -0,0 +1,70 @@ +# Тестовый стенд awg-dash (задача #21): netns-топология + мок WireGuard-endpoint. +# +# Использование: +# docker compose -f deploy/docker-compose.test.yml run --rm netns # прогон стенда +# docker compose -f deploy/docker-compose.test.yml run --rm netns --keep-topology +# docker compose -f deploy/docker-compose.test.yml up -d app # приложение для API-тестов +# (make test-netns-ci — обёртка над первым вызовом) +# +# Контейнеру `netns` нужен --privileged: внутри создаются сетевые namespace +# (`ip netns add`) и veth-пары, для этого требуются CAP_SYS_ADMIN + CAP_NET_ADMIN. +# Репозиторий монтируется только для чтения; артефакты пишутся в /artifacts и /tmp. + +name: ${COMPOSE_PROJECT_NAME:-awg-dash-test} + +services: + netns: + image: ${AWG_DASH_IMAGE:-awg-dash:dev} + build: + context: .. + dockerfile: ${AWG_DASH_DOCKERFILE:-deploy/Dockerfile} + container_name: awg-dash-test-netns + privileged: true + working_dir: /repo + entrypoint: ["/bin/bash", "/repo/tests/netns/run.sh"] + command: ["--report", "/artifacts/netns-report.json"] + volumes: + - ..:/repo:ro + - ${AWG_DASH_TEST_ARTIFACTS:-./artifacts}:/artifacts + environment: + AWG_NETNS_WORKDIR: /tmp/awg-netns + AWG_NETNS_PREFIX: ${AWG_NETNS_PREFIX:-awgt21} + # nft в образе приложения нет — там iptables (см. tests/netns/README.md) + AWG_FW_BACKEND: ${AWG_FW_BACKEND:-auto} + tmpfs: + - /tmp:mode=1777 + logging: + driver: json-file + options: + max-size: "5m" + max-file: "2" + + app: + image: ${AWG_DASH_IMAGE:-awg-dash:dev} + container_name: awg-dash-test-app + restart: "no" + volumes: + - awg-dash-test-config:/config + - awg-dash-test-data:/data + devices: + - /dev/net/tun:/dev/net/tun + cap_add: + - NET_ADMIN + - NET_RAW + - SYS_MODULE + environment: + AWG_CONFIG_DIR: /config + AWG_DATA_DIR: /data + AWG_API_HOST: 0.0.0.0 + AWG_API_PORT: "8080" + AWG_TEST_STAND: "1" + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/api/v1/health >/dev/null || exit 1"] + interval: 10s + timeout: 5s + start_period: 20s + retries: 5 + +volumes: + awg-dash-test-config: + awg-dash-test-data: