platform: modularize api/gui, add docs-tests-web foundation, and refresh root config

This commit is contained in:
beckline
2026-03-26 22:40:54 +03:00
parent 0e2d7f61ea
commit 6a56d734c2
562 changed files with 70151 additions and 16423 deletions

View File

@@ -0,0 +1,184 @@
# D1 Документирование готовности ядра
Дата: 2026-03-02
Статус: in-progress
Владелец: Engineering
## 1) Цель
- Дать точный endpoint-level срез: что уже можно подключать в веб, что требует инфраструктурных условий, и где нужен отдельный UX-поток.
- Зафиксировать критерии перехода к веб-прототипу на базе фактического кода `selective-vpn-api`.
## 2) Легенда статусов
- `ready-read` — готово для веба как read endpoint.
- `ready-write` — готово для веба как write endpoint, но обязательно через auth/rbac/audit/proxy.
- `ready-async` — write endpoint готов, но требуется async UX (SSE progress/polling).
- `ready-interactive` — endpoint готов, но требует интерактивного flow (session state/action).
## 3) Глобальные условия для веба
- API должен ходить через proxy (unix socket + nginx/systemd socket) и не быть публично открыт напрямую на `127.0.0.1:8080`.
- Для всех mutating endpoints обязательны auth/rbac и аудит вызовов.
- Для SSE (`/api/v1/events/stream`) нужен корректный proxy режим (`proxy_buffering off`).
- Для async операций (`routes update`, `smartdns prewarm`, `dns benchmark`) веб должен слушать SSE/статусы и блокировать повторные конкурирующие действия.
- Архитектурный инвариант: один контракт `/api/v1` для `web + iOS + Android`, без платформенных форков backend-логики.
## 4) Матрица endpoint -> web-ready
| Endpoint | Method | Handler | Статус | Комментарий для веба |
| --- | --- | --- | --- | --- |
| `/healthz` | GET | `handleHealthz` | `ready-read` | Базовый liveness probe. |
| `/api/v1/events/stream` | GET | `handleEventsStream` | `ready-read` | SSE source для realtime UI. |
| `/api/v1/status` | GET | `handleGetStatus` | `ready-read` | Карточка статуса маршрутов. |
| `/api/v1/routes/status` | GET | `handleGetStatus` | `ready-read` | Алиас статуса. |
| `/api/v1/vpn/login-state` | GET | `handleVPNLoginState` | `ready-read` | Текущий state логина VPN. |
| `/api/v1/systemd/state` | GET | `handleSystemdState` | `ready-read` | Проверка unit state по query `unit`. |
| `/api/v1/routes/service/start` | POST | `makeRoutesServiceActionHandler("start")` | `ready-write` | Чувствительная операция systemd. |
| `/api/v1/routes/service/stop` | POST | `makeRoutesServiceActionHandler("stop")` | `ready-write` | Чувствительная операция systemd. |
| `/api/v1/routes/service/restart` | POST | `makeRoutesServiceActionHandler("restart")` | `ready-write` | Чувствительная операция systemd. |
| `/api/v1/routes/service` | POST | `handleRoutesService` | `ready-write` | Универсальный action endpoint. |
| `/api/v1/routes/update` | POST | `handleRoutesUpdate` | `ready-async` | Запуск async job; прогресс через SSE/events. |
| `/api/v1/routes/timer` | GET/POST | `handleRoutesTimer` | `ready-write` | GET состояние, POST переключение. |
| `/api/v1/routes/timer/toggle` | POST | `handleRoutesTimerToggle` | `ready-write` | Legacy toggle для совместимости. |
| `/api/v1/routes/rollback` | POST | `handleRoutesClear` | `ready-write` | Очистка nft/routes с риском влияния на трафик. |
| `/api/v1/routes/clear` | POST | `handleRoutesClear` | `ready-write` | Алиас rollback. |
| `/api/v1/routes/cache/restore` | POST | `handleRoutesCacheRestore` | `ready-write` | Восстановление из clear-cache. |
| `/api/v1/routes/precheck/debug` | POST | `handleRoutesPrecheckDebug` | `ready-async` | Debug flow + опциональный async restart. |
| `/api/v1/routes/fix-policy-route` | POST | `handleFixPolicyRoute` | `ready-write` | Ремонт policy route по status.json. |
| `/api/v1/routes/fix-policy` | POST | `handleFixPolicyRoute` | `ready-write` | Алиас fix-policy-route. |
| `/api/v1/traffic/mode` | GET/POST | `handleTrafficMode` | `ready-write` | Ключевой control-plane endpoint. |
| `/api/v1/traffic/mode/test` | GET/POST | `handleTrafficModeTest` | `ready-read` | Проверка health/probe для UI. |
| `/api/v1/traffic/advanced/reset` | POST | `handleTrafficAdvancedReset` | `ready-write` | Сброс advanced bypass state. |
| `/api/v1/traffic/interfaces` | GET | `handleTrafficInterfaces` | `ready-read` | Список интерфейсов и active iface. |
| `/api/v1/traffic/candidates` | GET | `handleTrafficCandidates` | `ready-read` | Подсказки subnet/unit/uid. |
| `/api/v1/traffic/appmarks` | GET/POST | `handleTrafficAppMarks` | `ready-write` | Runtime app marking, требует RBAC. |
| `/api/v1/traffic/appmarks/items` | GET | `handleTrafficAppMarksItems` | `ready-read` | Список текущих runtime marks. |
| `/api/v1/traffic/app-profiles` | GET/POST/DELETE | `handleTrafficAppProfiles` | `ready-write` | CRUD профилей приложений. |
| `/api/v1/traffic/audit` | GET | `handleTrafficAudit` | `ready-read` | Sanity-check и issues report. |
| `/api/v1/trace` | GET | `handleTraceTailPlain` | `ready-read` | Plain tail trace. |
| `/api/v1/trace-json` | GET | `handleTraceJSON` | `ready-read` | Structured trace view. |
| `/api/v1/trace/append` | POST | `handleTraceAppend` | `ready-write` | Write в trace; ограничить доступ и размер body. |
| `/api/v1/dns-upstreams` | GET/POST | `handleDNSUpstreams` | `ready-write` | Конфиг upstreams. |
| `/api/v1/dns/upstream-pool` | GET/POST | `handleDNSUpstreamPool` | `ready-write` | Конфиг pool. |
| `/api/v1/dns/status` | GET | `handleDNSStatus` | `ready-read` | Состояние DNS mode/runtime. |
| `/api/v1/dns/mode` | POST | `handleDNSModeSet` | `ready-write` | Переключение DNS resolver mode. |
| `/api/v1/dns/benchmark` | POST | `handleDNSBenchmark` | `ready-async` | Долгий бенчмарк; нужен UX ожидания. |
| `/api/v1/dns/smartdns-service` | POST | `handleDNSSmartdnsService` | `ready-write` | Start/stop/restart smartdns unit. |
| `/api/v1/smartdns/service` | GET/POST | `handleSmartdnsService` | `ready-write` | GET state + POST action. |
| `/api/v1/smartdns/runtime` | GET/POST | `handleSmartdnsRuntime` | `ready-write` | Runtime nftset hook toggle. |
| `/api/v1/smartdns/prewarm` | POST | `handleSmartdnsPrewarm` | `ready-async` | Потенциально долгий prewarm. |
| `/api/v1/domains/table` | GET | `handleDomainsTable` | `ready-read` | Таблица nft/ipset dump. |
| `/api/v1/domains/file` | GET/POST | `handleDomainsFile` | `ready-write` | File-backed editor; нужен ACL по name. |
| `/api/v1/smartdns/wildcards` | GET/POST | `handleSmartdnsWildcards` | `ready-write` | CRUD wildcard list. |
| `/api/v1/vpn/autoloop-status` | GET | `handleVPNAutoloopStatus` | `ready-read` | Статус autoloop parser. |
| `/api/v1/vpn/status` | GET | `handleVPNStatus` | `ready-read` | VPN state + unit state. |
| `/api/v1/vpn/autoconnect` | POST | `handleVPNAutoconnect` | `ready-write` | Управление adgvpn unit. |
| `/api/v1/vpn/locations` | GET | `handleVPNListLocations` | `ready-read` | Список location options. |
| `/api/v1/vpn/location` | POST | `handleVPNSetLocation` | `ready-write` | Смена desired location + unit restart. |
| `/api/v1/vpn/login/session/start` | POST | `handleVPNLoginSessionStart` | `ready-interactive` | Старт интерактивной PTY login session. |
| `/api/v1/vpn/login/session/state` | GET | `handleVPNLoginSessionState` | `ready-interactive` | Poll состояния и линий с `since`. |
| `/api/v1/vpn/login/session/action` | POST | `handleVPNLoginSessionAction` | `ready-interactive` | Команды open/check/cancel. |
| `/api/v1/vpn/login/session/stop` | POST | `handleVPNLoginSessionStop` | `ready-interactive` | Cleanup/stop interactive session. |
| `/api/v1/vpn/logout` | POST | `handleVPNLogout` | `ready-write` | Logout + refresh login state. |
## 5) Скриптовые smoke-тесты
- `tests/api_sanity.sh` — read endpoints + method guard + SSE headers.
- `tests/trace_append.sh` — append + readback (`/trace`, `/trace-json`).
- `tests/events_stream.py` — SSE stream + активный триггер `trace_append`.
- `tests/vpn_login_flow.py` — start/state/action/stop для login session.
- `tests/transport_flow_smoke.py` — API-flow `draft/validate/confirm/apply/rollback` для transport policy.
- `tests/transport_platform_compatibility_smoke.py` — проверка кроссплатформенного transport-контракта (`web/iOS/Android`) через capabilities + policy endpoints.
- `tests/transport_runbook_cli_smoke.sh` — smoke операционного runbook helper (`scripts/transport_runbook.py`) по lifecycle transport-клиента.
- `tests/transport_recovery_runbook_smoke.sh` — smoke recovery runbook (`scripts/transport_recovery_runbook.py`) для сценария `health->restart->fallback->diagnostics`.
- `tests/transport_systemd_real_e2e.py` — real-systemd e2e для `singbox/dnstt(+ssh)/phoenix` + проверка cleanup unit artifacts.
- `tests/transport_production_like_e2e.py` — production-like e2e для template-команд и `packaging_profile=bundled` на `systemd` backend.
- `tests/transport_singbox_e2e.py` — backend-first e2e для `singbox` lifecycle/guards.
- `tests/transport_dnstt_e2e.py` — backend-first e2e для `dnstt` lifecycle/guards (`ssh overlay`, template checks).
- `tests/transport_phoenix_e2e.py` — backend-first e2e для `phoenix` lifecycle/guards.
- `tests/run_all.sh` — общий запуск набора.
## 6) Факт прогона (2026-03-02)
- Команда: `API_URL=http://127.0.0.1:8080 ./tests/run_all.sh`.
- Результат: `api_sanity`, `trace_append`, `events_stream`, `vpn_login_flow``passed`.
- Дополнение (2026-03-07):
- Расширенный `run_all` с transport smoke/e2e и packaging smoke проходит `passed`;
- включён и проходит `tests/transport_platform_compatibility_smoke.py` (capabilities + policy contract);
- при запуске старого backend-процесса `transport_*_e2e` могут завершаться `SKIP` (без `provision/metrics`);
- после пересборки и перезапуска `selective-vpn-api` из текущего кода `transport_singbox_e2e`, `transport_dnstt_e2e`, `transport_phoenix_e2e` прошли без `SKIP`.
- в `run_all` включены и проходят `transport_systemd_real_e2e` + `transport_production_like_e2e` (template-commands + packaging profile checks на `runner=systemd`).
## 7) Вывод для веб-прототипа
- Срез по legacy-матрице (53 endpoint из Phase D):
- `ready-read`: 18
- `ready-write`: 27
- `ready-async`: 4
- `ready-interactive`: 4
- Дополнение 2026-03-07:
- В ядро добавлены `8` базовых mux-route `/api/v1/transport/*` + action-subpath `GET /api/v1/transport/clients/{id}/metrics` (контракт D4.1).
- Их контракт и rollout описаны в `docs/phase-e/E2_TRANSPORT_API_CONTRACT.md`.
- По API-ядру нет endpoint, который блокирует веб-прототип на уровне контракта.
- Ограничения не в ручках, а в обвязке: auth/rbac, proxy/CORS/CSRF, и UX для async/interactive сценариев.
- Практический старт веба можно делать сразу: read панели + write действия под admin scope + SSE realtime слой.
## 8) Условия переиспользования для смартфонов (iOS/Android)
- Серверная обвязка:
- gateway/proxy с TLS и токен-авторизацией;
- refresh/access токены и управление device sessions;
- аудит и rate-limit для mutating endpoints.
- Контракт:
- стабильные DTO и error contract (`ok`, `message`, `exitCode`, `stderr`);
- versioning через `/api/v1` и backward-compatible изменения.
- Realtime и фоновые ограничения:
- веб использует SSE постоянно;
- mobile использует SSE в foreground и polling fallback в background.
- Надежность мобильной сети:
- idempotency key для POST операций;
- retry policy с экспоненциальной паузой;
- correlation id для трассировки запросов.
- Безопасность клиента:
- iOS Keychain / Android Keystore для токенов;
- certificate pinning для production приложений.
## 9) D3: Последовательность запуска multi-client
- D3.1: Завершить gateway/auth слой (TLS, токены, RBAC, audit).
- D3.2: Подключить web-клиент к уже готовой матрице endpoint и SSE.
- D3.3: Ввести mobile transport profile (polling fallback + retry/idempotency).
- D3.4: Запустить iOS/Android клиенты на том же `/api/v1` без изменения бизнес-логики ядра.
## 10) D4: Transport Integration Backlog
- D4.1 `sing-box client`:
- Роль: универсальный клиент-транспорт для сценариев proxy/tun.
- Требование к ядру: запуск/остановка/health через backend-адаптер, без переноса логики в UI.
- Статус: `in-progress` (2026-03-07: в API введён минимальный общий backend-контракт lifecycle/health/metrics/errors, добавлен `GET /api/v1/transport/clients/{id}/metrics`).
- E2E шаг (backend-first): `tests/transport_singbox_e2e.py`:
- проверяет успешный lifecycle на `runner=mock` (`provision/start/health/restart/stop/metrics`);
- проверяет guard `runtime_mode=embedded` -> `TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED`;
- проверяет fail-fast `require_binary=true` для отсутствующего `singbox_bin`.
- Примечание: если в рантайме запущен старый процесс API без `provision/metrics`, тест завершится `SKIP`; для полного прогона нужен перезапуск backend из актуального кода.
- D4.2 `dnstt-client`:
- Роль: отдельный клиент для DNSTT-протокола (как самостоятельный backend).
- Требование к ядру: управление lifecycle + состояние/ошибки через общий API слой.
- Уточнение: поддержать режим `dnstt + ssh overlay` (туннель поверх SSH как единая система, аналогично phoenix UX-модели).
- Статус: `in-progress` (2026-03-07: в Go добавлены adapter foundation, `POST /api/v1/transport/clients/{id}/provision`, template-команды запуска, systemd restart/watchdog tuning, unit hardening-профили, `runtime_mode` foundation `exec|embedded|sidecar`, packaging profiles (`system|bundled`) и manual pinned updater/rollback scripts; `config.exec_start` работает как optional override).
- E2E шаг (backend-first): `tests/transport_dnstt_e2e.py`:
- проверяет успешный lifecycle на `runner=mock`;
- проверяет guard `ssh_overlay` конфигурации (`ssh_host` обязателен, `ssh_unit` валидируется);
- проверяет template-валидацию DNSTT при неполном config.
- Примечание: как и для singbox, при старом API-процессе без `provision` тест завершится `SKIP`; нужен перезапуск backend из текущего кода.
- D4.3 `phoenix -> slipstream`:
- Роль: отдельный backend-клиент для подключения к удаленному `slipstream` серверу.
- Требование к ядру: конфиг/статус/управление сессией через backend-адаптер и общий контракт.
- Статус: `in-progress` (2026-03-07: добавлен backend-first e2e тест `tests/transport_phoenix_e2e.py` для lifecycle/guards на API-контракте).
- E2E шаг (backend-first): `tests/transport_phoenix_e2e.py`:
- проверяет успешный lifecycle на `runner=mock` (`provision/start/health/restart/stop/metrics`);
- проверяет guard `runtime_mode=embedded` -> `TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED`;
- проверяет fail-fast `require_binary=true` для отсутствующего `phoenix_bin`.
- Примечание: при старом API-процессе без `provision/metrics` тест завершится `SKIP`; для полного прогона нужен перезапуск backend из актуального кода.
- D4.4 Общий инвариант:
- Для `web + iOS + Android` UI не знает о внутренней реализации backend-клиента; он работает с единым API ядра.
- Все transport-backend различия прячутся в Go-слое (feature flags, capability matrix, unified error contract).
## 11) D4.3: Матрица совместимости web + iOS + Android
- Отдельный артефакт: `docs/phase-d/D4_PLATFORM_COMPATIBILITY_MATRIX.md`.
- Зафиксировано:
- единый control-plane контракт `/api/v1` для всех платформ;
- transport-runtime работает только в backend (`exec`), UI работает через capabilities/policies flow;
- mobile использует тот же API, с fallback `polling` при недоступном SSE в background.

View File

@@ -0,0 +1,59 @@
# D4.3 Матрица совместимости (Web + iOS + Android)
Дата: 2026-03-07
Статус: done
Владелец: Engineering
## 1) Цель
- Зафиксировать единый контракт, чтобы `web`, `iOS`, `Android` переиспользовали одно Go-ядро (`/api/v1`) без платформенных форков бизнес-логики.
- Явно обозначить ограничения transport-runtime и ожидания к клиентам.
## 2) Инвариант архитектуры
- Все transport-клиенты (`singbox`, `dnstt`, `phoenix`) запускаются на стороне хоста с `selective-vpn-api` через backend-адаптеры.
- `web/iOS/Android` являются control-plane клиентами: вызывают API, читают состояние, запускают validate/apply flow.
- Платформы не должны управлять локальными `systemd`/бинарями напрямую.
## 3) Платформенная матрица API-контракта
| Контур | Web (`Vite + React + TS`) | iOS | Android |
| --- | --- | --- | --- |
| Базовый API | `GET/POST /api/v1/*` | `GET/POST /api/v1/*` | `GET/POST /api/v1/*` |
| Realtime | SSE (`/api/v1/events/stream`) в foreground | SSE в foreground, polling fallback в background | SSE в foreground, polling fallback в background |
| Transport capabilities | `GET /api/v1/transport/capabilities` | тот же endpoint | тот же endpoint |
| Policy flow | `GET /policies`, `POST /validate`, `POST /apply`, `POST /rollback` | тот же flow | тот же flow |
| Auth/TLS | gateway/proxy + token | gateway/proxy + token + secure storage | gateway/proxy + token + secure storage |
| Error contract | `ok/code/message/exitCode/stderr` | идентично | идентично |
## 4) Матрица transport backend vs платформы
| Transport backend (в Go-ядре) | Web UI | iOS UI | Android UI | Ограничения |
| --- | --- | --- | --- | --- |
| `singbox` | Поддержан через API | Поддержан через API | Поддержан через API | runtime на сервере, не в мобильном UI |
| `dnstt` (+ `ssh_overlay`) | Поддержан через API | Поддержан через API | Поддержан через API | SSH overlay оркестрируется backend-ом |
| `phoenix` -> `slipstream` | Поддержан через API | Поддержан через API | Поддержан через API | подключение/health/metrics только через backend |
## 5) Runtime/packaging ограничения
- Поддерживаемый runtime mode на текущем этапе: `exec=true`.
- `embedded=false`, `sidecar=false` (зарезервировано, без silent fallback).
- Packaging profiles: `system=true`, `bundled=true`.
- Практический вывод для платформ:
- UI показывает capabilities и ограничения из backend.
- UI не должен предполагать поддержку `embedded/sidecar` до явного включения в capabilities.
## 6) Проверка совместимости (smoke)
- Скрипт: `tests/transport_platform_compatibility_smoke.py`.
- Проверяет:
- наличие `singbox/dnstt/phoenix` в `/transport/capabilities`,
- `runtime_modes.exec=true`,
- `packaging_profiles.system=true`, `packaging_profiles.bundled=true`,
- рабочий policy-контракт (`/transport/policies`, `/validate`, `/conflicts`).
Команда запуска:
```bash
API_URL=http://127.0.0.1:8080 ./tests/transport_platform_compatibility_smoke.py
```
## 7) Итог D4.3
- Совместимость `web + iOS + Android` зафиксирована как единый API-контракт control-plane.
- Все transport-runtime различия остаются в Go-ядре.
- Это позволяет продолжать backend-first реализацию и затем переиспользовать ту же логику в desktop/web/mobile UI.

View File

@@ -0,0 +1,34 @@
# D5: NetNS Runtime Case (Ready)
Дата: 2026-03-09
## Что зафиксировано
- `SingBox` может работать в отдельном `netns` без влияния на основной VPN-контур.
- Для запуска внутри namespace используется адаптивный exec-режим:
- default: `nsenter --net=/var/run/netns/<name> -- ...`
- fallback: `ip netns exec <name> ...`
- Подготовка `netns` (veth/route/NAT) и запуск runtime используют единый механизм выбора exec-mode.
- GUI debug toggle (`Debug netns`) изменяет `netns_enabled` через API, делает `provision` и `restart` для активных клиентов.
## Рефакторинг модулей
- Go:
- `selective-vpn-api/app/transport_netns.go` — setup/cleanup/NAT/spec.
- `selective-vpn-api/app/transport_netns_exec.go` — exec-mode selection (`nsenter|ip`) и wrappers.
- GUI:
- `selective-vpn-gui/netns_debug.py` — вычисление состояния netns-кнопки и общий toggle pipeline (`patch -> provision -> restart`).
- `selective-vpn-gui/vpn_dashboard_qt.py` — только UI-обвязка и вызов helper-модуля.
## Минимальный runtime-check
1. `POST /api/v1/transport/clients/<id>/restart` -> `ok=true`.
2. `GET /api/v1/transport/clients/<id>/health` -> `status=up`.
3. `systemctl status <unit>` -> `active (running)`.
4. `systemctl show <unit> -p ExecStart` -> команда через `nsenter` (или `ip netns exec` при fallback).
## Полезные конфиг-ключи клиента
- `netns_enabled: bool`
- `netns_name: string`
- `netns_exec_mode: auto|nsenter|ip` (optional)
- `netns_nsenter_bin: /usr/bin/nsenter` (optional)
- `netns_ip_bin: /sbin/ip` (optional)
- `netns_setup_strict: bool` (optional)
- `netns_auto_cleanup: bool` (optional)