- 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
This commit is contained in:
@@ -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())
|
||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
@@ -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:
|
||||||
Reference in New Issue
Block a user