Files
awg-dash/deploy/api_stub.py
T
embeddevio da1c750297 feat(#21): dev-стенд — compose-файлы, health-заглушка до #31
- 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
2026-10-21 23:49:11 +03:00

85 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())