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,100 @@
# B1 Проверка ядра и внешних зависимостей
Дата: 2026-02-27
Статус: draft
Владелец: Engineering
## 1) Цель
- Убедиться, что Go-ядро имеет централизованную логику для работы с nftables, policy routing, systemd, cgroup и SmartDNS, и что эти зависимости отражены в конфигурациях/документации.
- Выявить потенциальные отверстия (например, части логики, которые напрямую обращаются к shell-скриптам `routes_update`, `autoloop`, `trace`) перед тем, как стартовать задачи для веба.
## 2) Критерии завершения
- Описание работы ключевых модулей ({`routes_update.go`, `traffic_mode.go`, `events_bus.go`, `watchers.go`, `resolver.go`, `smartdns_runtime.go`, `vpn_handlers.go`}) подготовлено, включая внешние зависимости (nftables, SmartDNS, systemd, AdGuard VPN API).
- Сформулирован список требований (root, обязательные сервисы, долгие операции, возможные блокировки) на случай запуска через веб-консоль.
- Уточнено, какие данные уже сохраняются в `stateDir`, `appmark` и `routes` файлах, чтобы веб мог отображать информацию без дополнительного API.
## 3) Задачи
- Пройти `routes_update.go`, `routes_units.go`, `routes_cache.go`, `nft_update.go` и описать, как обновляются маршруты и кэшируются состояния.
- Проанализировать `traffic_mode.go`, `traffic_appmarks.go`, `traffic_app_profiles.go`, `traffic_audit.go`: что именно меняют (fwmark, cgroup, nft sets) и какие проверки выполняются.
- Прописать взаимодействие с DNS/SmartDNS (`dns_settings.go`, `smartdns_runtime.go`, `smartdns_wildcards_store.go`, `resolver.go`), включая init/restore/caching жизненный цикл.
- Проверить `watchers.go`, `events_bus.go`, `trace_handlers.go`: какие события стримятся, какие веб-интерфейсы они смогут потреблять, и нужны ли дополнительные фильтры или буферизация.
- Отметить, какие части кода требуют root-доступ и как это влияет на веб (например, API будет работать под сервис-аккаунтом и выполнять nft/update через internal API, но сама служба должна запускаться с нужными привилегиями).
- Собрать стартовый список фактов по каждому модулю: с какими `systemd` unit'ами, `nft` set'ами, SmartDNS/AdGuard VPN API и `stateDir` работает, и какие ограничения это накладывает.
- Задокументировать потенциальные точки отказа (недоступность nftables/systemd, SmartDNS/AdGuard API) и предложить стратегию контроля ошибок для веб-интерфейса.
## 4) Модули и зависимости
### `routes_update.go`
- Основной контроллер обновления маршрутов: строит policy routes, nftables-цепочки `agvpn`/`agvpn4`/`agvpn_dyn4`, запускает `runResolverJob`, пишет список доменов/IP в `stateDir` и сохраняет `status.json`.
- Поддерживает прогресс через `events.push("routes_nft_progress", ...)`, пишет heartbeat-файлы и требует доступа к `iproute2`, `nft`, `stateDir`, `trace.log` и воркеру резольвера.
- Обрабатывает `force`/`auto` конфигурации из `TrafficModeState` и фиксирует `trafficEval` в статусе, что важно для отображения в вебе.
### `routes_units.go`, `routes_cache.go`, `nft_update.go`
- `routes_units.go` разрешает `systemd` имя (`routesServiceUnitName`, `routesTimerUnitName`) и используется в `/routes/service`/`timer`.
- `routes_cache.go` кеширует `domains`, `ips`, `domains` state; можно использовать для расчёта прогресса и восстановления.
- `nft_update.go` держит “умный” апдейтер, который управляет правилами и sets; важно документировать, какие команды выполняются и как проверяется успех (`runNFTUpdate`, `progressCb`) для веба.
### `traffic_mode.go` + `traffic_appmarks*`
- Управляют состоянием (`stateDir/state-traffic-mode.json`), применяют флаги fwmark и policy rules (`applyTrafficMode`), читают/пишут список принудительных субнетов/UID/cgroup.
- `traffic_appmarks.go` работает с cgroup v2, создаёт маркеры `MARK_APP`/`MARK_DIRECT` и задаёт TTL (через systemd scopes) для runtime управления per-app traffic.
- `traffic_app_profiles.go` хранит профили в `stateDir` и предоставляет CRUD, что пригодится вебу для создания shortcut-профилей.
- `traffic_audit.go` проверяет состояние nft/route и формирует `TrafficAudit` issues, полезные для мониторинга.
### `dns_settings.go` + `resolver.go`
- Хранит конфигурацию `dns-upstreams.conf`, режим (`dns_mode.json`) и pool upstreams; отвечает за benchmark, SmartDNS control (`smartdns` service start/stop) и режим `ViaSmartDNS`.
- `resolver.go` запускает Go-резольвер, читает `domains.txt`, `meta-special.txt`, `static-ips.txt`, использует кеши (`domain-cache.json`, `ptr-cache.json`) и пишет результат в `stateDir`.
- Сервисы SmartDNS (`smartdns_runtime.go`, `smartdns_wildcards_store.go`) управляют дополнительными wildcard-базами, runtime stats и prewarm, что потребует отображения статуса SmartDNS в веб.
### `vpn_handlers.go` + `vpn_login_session.go`
- Интеграция с AdGuard VPN API: `autoloop`, `autoconnect`, `locations`, `set location`, `logout` и status založený na HTTP-запросах к локальному `adguardvpn` сервису.
- `vpn_login_session.go` создаёт PTY-сессию (через `runCommand` + `systemd-run --user`?), сохраняет состояния в `loginStatePath` и выпускает события (`events.push`) для SSE; вебу потребуется поддерживать эти пользователи.
### `events_bus.go`, `watchers.go`, `trace_handlers.go`
- `startWatchers` запускает наблюдение за `status.json`, `loginState`, autoloop логом, `trace.log`, state traffic appmarks TTL и systemd unitами (routes service/timer, VPN unit, SmartDNS).
- Все watcher-изменения отправляются в `events` и поступают клиенту через `handleEventsStream`, что даёт вебу источник realtime данных.
- `trace_handlers.go` читает `trace.log`, `trace-json` и принимает append, что позволяет вебу показывать live trace и записывать дополнительные строки.
### `routes_handlers.go`
- Управление systemd-unit'ами (`routes_service`, `routes_timer`), ручной rollback/clear, fixing policy route, переключение режимов/advanced config.
- Служит фасадом для CLI (как `routes-update`, `routes-clear`, `autoloop`), следовательно API может использоваться как `POST /api/v1/routes/service` и `POST /api/v1/routes/update`.
### Корневые ограничения и привилегии
- `routes_update`/`routes_handlers` запускаются как root (nft, ip, systemctl); веб-интерфейс должен вызывать API, а не повторять команды, сохраняя сервис privileged.
- SmartDNS требует запуска `smartdns-local.service`; VPN команды ждут доступ к AdGuard VPN (возможно, `adguardvpn.service`).
- `stateDir` и `domains` файлы должны быть доступны API, а вебу важно понять, какие endpoints кешируют/читают эти файлы. Например, `routes timer enable` хранится в `routes_timer_state.json`.
- Потенциальные точки отказа:
- `nft` команды падут, если kernel не поддерживает nftables или service не запущен — API должен возвращать `CmdResult` с `stderr` и `exitCode`.
- `systemd` unit может быть недоступна (не установлен `routes-service`), мониторинг через watchers должен отправлять `unit_state_changed`.
- SmartDNS runtime может не стартовать: нужно отразить ошибку через `events.push("smartdns_error"... )` и вернуть `CmdResult.ok=false`.
- AdGuard VPN API или PTY могут быть недоступны; API должен возвращать ошибки и веб должен приступать к повторной попытке.
## 5) Матрица зависимостей
| Модуль | http/cli | Зависимости | Состояния/файлы | Потенциальные ошибки |
| --- | --- | --- | --- | --- |
| `events_bus.go`, `watchers.go`, `events_handlers.go` | `/api/v1/events/stream` | in-memory queue, watcher goroutines | `status.json`, `login_state.json`, `trace.log`, systemd unit polling, `autoloop.log` | SSE disconnected, poll errors, buffer overflow |
| `routes_update.go` | `POST /api/v1/routes/update`, CLI `routes-update` | `iproute2`, `nft`, `runResolverJob`, `SmartDNS` | `stateDir/{domains,ips}*`, `status.json`, `trace.log`, `heartbeat` | Нет VPN-интерфейса, резольвер падает, `nft` пишет ошибки |
| `routes_units.go` | `/api/v1/routes/service`, `/routes/timer`, CLI `routes-clear` | `systemd` (service/timer names) | `routes_timer_state.json`, unit name env | Unit не установлена, systemctl возвращает error |
| `routes_cache.go`, `nft_update.go` | внутренняя логика routes update | `nft`, temp files | `/var/run/selective-vpn` temp files | `nft` прогресс в stderr, temp-файлы не создаются |
| `traffic_mode.go` + `traffic_appmarks*` | `/api/v1/traffic/*` | `nft`, policy routing, `cgroup v2`, `systemd` | `stateDir/traffic-mode.json`, `appmarks.json`, cgroup scopes | Некорректная конфигурация, `nft` не применяет правила |
| `traffic_app_profiles.go` | `/api/v1/traffic/app-profiles` | файловые операции | `stateDir/app-profiles.json` | Файл повреждён, не сохраняется |
| `dns_settings.go` + `resolver.go` | `/api/v1/dns-*` | `resolv.conf`, SmartDNS binary | `dns-upstreams.conf`, `dns_mode.json`, `domain-cache.json`, `ptr-cache.json` | Нет upstream, benchmark таймаут |
| `smartdns_runtime.go`, `smartdns_wildcards_store.go` | `/api/v1/smartdns/*` | `smartdns-local.service`, wildcard files | `smartdns.conf`, `wildcards.json` | Service fail, wildcard file read-only |
| `vpn_handlers.go`, `vpn_login_session.go` | `/api/v1/vpn/*` | AdGuard VPN API, PTY, systemd-user | `login_state.json`, `autoloop.log` | API недоступен, PTY не стартует |
| `trace_handlers.go` | `/api/v1/trace*` | доступ к `trace.log` | `trace.log` | Файл недоступен, append write error |
Эта матрица показывает, какие компоненты требуют привилегий и как ошибка отражается на UI. В Phase D можно перенести эти строки в таблицу `web-ready` и отметить `status` (готово/требует/blocked).
### Технические state-файлы и кеши
- `stateDir` (по умолчанию `/var/lib/selective-vpn`) содержит:
- `domains.txt`, `ips-*.txt`, `ipmap-*.txt` — сериализация выходных данных resolver, используются trace/expand logic.
- `state-traffic-mode.json`, `appmarks.json`, `app-profiles.json` — основные состояния traffic mode/appmarks/profiles, читаются `traffic_mode.go`, `traffic_appmarks.go`, `traffic_app_profiles.go`.
- `domain-cache.json`, `ptr-cache.json` — кеш resolver, обновляется из `runResolverJob` и влияет на `routes_update` (limit/ttl).
- `status.json`, `heartbeat` — пишутся `routes_update`. `watchStatusFile` читает file, `events.push("status_changed")` доставляет web info (`iface`, `table`, `healthy`).
- `login_state.json` — создаётся `vpn_login_session.go`, SSE `login_state_changed` отражает current state; при `CmdResult.ok=false` пишется `error` в file/event.
- `trace.log` — задействован `trace_handlers.go` и `watchFileChange`, SSE `trace_changed` парсит tail. `trace_append` endpoint пишет новые строки (CmdResult). Web должна ограничить size (<=1<<20) и показывать stderr/exitCode.
### Watchers и события
- `watchStatusFile`, `watchLoginFile`, `watchAutoloop`, `watchTrafficAppMarksTTL`, `watchSystemdUnit*` запускаются на `startWatchers` и публикуют события (`status_changed`, `login_state_changed`, `autoloop_status_changed`, `unit_state_changed`, `appmarks_ttl`).
- `events_bus` буферизует последние `SVPN_EVENTS_CAP` событий (config via env). SSE `events/stream` читает их `since`/`Last-Event-ID`. При ошибок (blocked mutex, dropped events) нужно логировать `selective-vpn-api` и отображать `status_error`.
- `events.push` используется для `routes_nft_progress`, `smartdns`, `trace_changed`. Вебу важно знать ключи, чтобы фильтровать события по статус/trace/health.

View File

@@ -0,0 +1,76 @@
# B2 Resolver: разница ролей и план улучшения
Дата: 2026-03-07
Статус: planned
Владелец: Engineering
## 1) Цель
- Зафиксировать, чем отличается текущий системный resolver selective-vpn от DNS-модуля `sing-box`.
- Зафиксировать roadmap доработки нашего resolver, чтобы он оставался основой для `selective`-маршрутизации.
## 2) Роли компонентов
- `System resolver` (Go + SmartDNS + nftset):
- источник истины для PBR (`routes/update`, nft sets, wildcard/static fallback),
- системный контроль резолва для selective-режима.
- `SingBox DNS`:
- DNS-часть transport-профиля `sing-box`,
- управляет резолвом внутри конкретного engine/profile.
Правило слоя:
- В текущей архитектуре authoritative для маршрутизации остаётся системный resolver.
- `SingBox DNS` используется как transport-level capability и не заменяет системный PBR resolver.
## 3) Матрица различий
| Область | System resolver (текущий) | SingBox DNS |
| --- | --- | --- |
| Основная роль | Системная selective-маршрутизация | DNS внутри transport engine |
| Связь с nft/PBR | Прямая (`agvpn4/agvpn_dyn4`, `routes/update`) | Непрямая, через поведение engine |
| Wildcard runtime | Да (SmartDNS nftset + merge в `routes/update`) | Нет прямого контроля системных nft sets |
| Static fallback | Да (`static-ips.txt`, policy merge) | Через raw/typed config профиля |
| Единая картина для всех клиентов | Да, через Go state/API | Нет, scoped к конкретному профилю |
| Риск рассинхрона | Низкий при single source of truth | Высокий, если использовать как отдельный source of truth |
## 4) Основные gap-ы текущего resolver
- Недостаточная наблюдаемость по качеству резолва (latency/error-rate/NX/servfail per upstream).
- Нет отдельного endpoint'а с полным health snapshot resolver pipeline.
- Нужна более строгая TTL/refresh-стратегия для mixed источников (cache + wildcard runtime + static).
- Нужен формализованный negative caching и rate-limit на проблемные домены при деградации upstream.
- Нужна формализация owner-map для доменов в multi-client сценариях (без дублей и гонок).
## 5) План улучшения resolver (приоритеты)
### R1 Надёжность и консистентность
- Ввести unified resolver state snapshot (`updated_at`, `stale`, `refresh_in_progress`, `last_error`, `next_retry_at`, counters).
- Усилить single-flight на expensive refresh-path и добавить доменный backoff.
- Нормализовать merge-порядок источников: `static fallback -> wildcard runtime -> resolver cache`.
### R2 Качество резолва
- Добавить adaptive upstream scoring (latency/success/NX/timeout/servfail).
- Добавить sticky-preferred upstream с безопасным failover.
- Ввести отдельную политику negative cache TTL для NXDOMAIN/SERVFAIL.
### R3 Наблюдаемость и API
- Добавить API snapshot для resolver health:
- `GET /api/v1/resolver/status` (target),
- `POST /api/v1/resolver/refresh` (target trigger),
- `GET /api/v1/resolver/upstreams` (target metrics).
- Добавить SSE-события:
- `resolver_status_changed`,
- `resolver_upstream_degraded`,
- `resolver_refresh_completed`.
### R4 Multi-client безопасность
- Ввести `domain_owner_map` и conflict-detection до применения policy.
- Гарантировать, что один domain-selector не получает двух владельцев без explicit override.
- Синхронизировать resolver ownership с transport policy revision.
## 6) Критерии готовности B2
- Есть документированная и реализуемая разница ролей `system resolver` vs `sing-box DNS`.
- Resolver имеет прозрачный health/status API и наблюдаемость по upstream.
- При деградации upstream UI получает stale-safe состояние, а не "пустой провал".
- В multi-client режиме домены не уходят в двойное владение без подтверждённого override.
## 7) Ограничения этапа
- В этом этапе не переводим управление маршрутизацией на `SingBox DNS`.
- `DNSTT/Phoenix` DNS-аспекты не расширяются в UI до завершения `SingBox` API/GUI трека.

View File

@@ -0,0 +1,50 @@
# B4 Runtime Dependencies And Preflight
## Важно
- `go.mod` хранит только Go-модули (библиотеки, которые импортируются в коде).
- Внешние сервисы/бинарники (`systemd`, `nft`, `ip`, `sing-box`, `dnstt-client`, `phoenix-client`) не являются Go-модулями и не должны фиксироваться в `go.mod`.
## Текущие Go-зависимости (по `go.mod`)
- `github.com/cenkalti/backoff/v4`
- `github.com/creack/pty`
## Runtime-зависимости ядра (вне go.mod)
### Required (core path)
- `systemctl`
- `nft`
- `ip`
- `curl`
- `/usr/local/bin/adguardvpn-cli-root`
### Required service units (current production path)
- `singbox@.service`
### Recommended / optional
- `nsenter` (предпочтительный exec-mode для netns)
- `wget` (fallback для части egress probe)
- `ps`
- `ipset`
### Optional by enabled transport kind
- `sing-box` (`/usr/local/bin/sing-box` или `/usr/bin/sing-box`)
- `dnstt-client` (`/usr/local/bin/dnstt-client` или `/usr/bin/dnstt-client`)
- `phoenix-client` (`/usr/local/bin/phoenix-client` или `/usr/bin/phoenix-client`)
### Optional service units (зависят от deployment-профиля)
- `adguardvpn-autoconnect.service`
- `smartdns-local.service`
- `selective-vpn2@.service`
- `sing-box.service` (legacy/compat)
- `dnstt-client.service`
- `phoenix-client.service`
## Preflight-check
- Скрипт: `scripts/check_runtime_dependencies.sh`
- Режимы:
- `scripts/check_runtime_dependencies.sh` — проверка required + warning по optional.
- `scripts/check_runtime_dependencies.sh --strict` — fail если есть missing/warning.
## Зачем это перед рефакторингом
- Убираем смешение понятий: Go-зависимости отдельно, runtime/system-зависимости отдельно.
- Перед декомпозицией (`F1.*`) быстро проверяем среду и снижаем ложные регрессии.

View File

@@ -0,0 +1,61 @@
# B5 SingBox Template Migration And Rollback Runbook
## Цель
Операционный runbook для миграции `legacy singbox-<id>.service` к template-модели `singbox@<id>.service` и для аварийного отката при инциденте деплоя.
## Контекст текущей модели
- Целевой runtime: `singbox@.service` + per-instance drop-in `singbox@<id>.service.d/10-selective-vpn.conf`.
- One-shot миграция legacy unit выполняется автоматически в pre-action (`start/restart`) для `kind=singbox`.
- Safety guard: legacy unit обрабатывается только при ownership marker `Environment=SVPN_TRANSPORT_ID=<client_id>`.
## Preflight перед деплоем
1. Проверить runtime-зависимости:
- `scripts/check_runtime_dependencies.sh`
2. Проверить strict-режим (опционально для CI/релиза):
- `scripts/check_runtime_dependencies.sh --strict`
3. Проверить наличие template unit:
- `systemctl list-unit-files 'singbox@.service' --no-legend`
## Управление миграцией
- Отключить авто-миграцию для профиля:
- `config.singbox_legacy_unit_migrate=false`
- Включить dry-run без изменений unit-файлов:
- `config.singbox_legacy_unit_migrate_dry_run=true`
- Поведение dry-run:
- backend пишет trace/stdout о планируемом `legacy -> template` переходе,
- `stop/disable/remove` не выполняются.
## Нормальный migrate-путь
1. Обновить API-бинарь и перезапустить `selective-vpn-api.service`.
2. На `start/restart` конкретного SingBox-клиента backend:
- валидирует ownership legacy unit,
- выполняет `stop + disable + remove` legacy unit,
- выполняет `daemon-reload + reset-failed` для legacy unit,
- запускает template instance `singbox@<id>.service`.
3. Проверить:
- `systemctl status 'singbox@<id>.service'`
- `systemctl list-unit-files 'singbox-*' --no-legend` (legacy не должен появляться как managed unit)
## Аварийный rollback (template -> legacy)
Важно: текущий прод-код нормализует SingBox clients к `config.unit=singbox@.service`, поэтому rollback делается на уровне release rollback (предыдущий API build + восстановление unit/state).
1. Зафиксировать инцидент и остановить изменения policy:
- временно не выполнять `start/restart/switch` через GUI/API.
2. Откатить API до предыдущего релиза (где legacy path поддерживался штатно).
3. Восстановить `transport-clients` state из backup/снапшота релиза (если в инциденте state был изменён).
4. Восстановить legacy unit-файлы `singbox-*.service` из backup среды (если удалены).
5. Выполнить:
- `systemctl daemon-reload`
- `systemctl reset-failed`
6. Поднять нужные legacy unit и проверить egress/health.
## Проверка после rollback
- `systemctl is-active selective-vpn-api.service`
- `systemctl is-active 'singbox-<id>.service'` (для rollback-релиза)
- API smoke:
- `curl -fsS http://127.0.0.1:8080/healthz`
- `curl -fsS http://127.0.0.1:8080/api/v1/transport/clients`
## Примечание
- Legacy `sing-box.service` остаётся только как compat-артефакт окружения.
- Production path для новых профилей: только `singbox@.service`.