docs: drop Phase 11 dissolution scratchpad (shipped)

Design spec for the AWG2 tab dissolution / per-tunnel engine work that
has since landed on the nfqws2-selector branch. Git log covers the
"why".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pull/168/head
h8suga 2 months ago
parent d078250d46
commit 3bf44292f5

@ -1,315 +0,0 @@
# Phase 11 — Растворение AWG2-вкладки
## Архитектурный принцип (важно)
**Tunnels-вкладка** — только про существование и жизненный цикл тоннелей:
создание, редактирование конфига, ключи, peers, deploy на VPS,
up/down/delete, статус (handshake/RX/TX), диагностика (speedtest).
**Никаких priorities, никаких fallback'ов, никакого "куда направить
трафик" — всё это не про Tunnel.**
**Routing-вкладка** — только про правила: какой трафик идёт в какой
тоннель и в каком порядке. Глобальные настройки маршрутизации
(killswitch / SNI / DNS chain / MTU clamp / trace) тоже тут.
Это разделение единственный способ не размазать одну ответственность
по двум вкладкам. Когда добавляется 6-й tunnel-kind, изменения в
Tunnels вкладке ограничены формой для нового kind'а; Routing вкладка
не трогается.
**Допустимое исключение в Tunnels**: per-peer `AllowedIPs` в форме —
это **WireGuard cryptokey-routing primitive** (что peer криптографически
принимает), а не routing-policy. Внешне выглядит как "роутинг", по
семантике это часть конфига тоннеля.
> **Признание**: после Phase 6 я оставил `frontend/src/features/awg2/AWG2.tsx`
> и весь его поддерево как есть, а параллельно поставил две новых вкладки
> «Туннели» / «Маршрутизация». Получилось дублирование: некоторые операции
> доступны и через старую и через новую вкладку, причём настройки в них
> хранятся в разных файлах (`awg.json` vs `tunnels.json` + `routing.json`).
> Это противоречит изначально согласованной протокол-агностичной модели.
>
> Phase 11 разносит **все** функции старой AWG2-вкладки по новым «Туннели»
> и «Маршрутизация» так, чтобы они работали единообразно для AmneziaWG /
> WireGuard / Hysteria, и затем удаляет легаси целиком.
## Карта функций AWG2 → новые вкладки
| Что в AWG2 | Куда переезжает | Замечания |
|---|---|---|
| Список серверов + кнопка «Добавить» | Уже в Tunnels | Готово (TunnelsList + AddTunnelModal) |
| Импорт `.conf` | Tunnels → AddTunnelModal | Сейчас отсутствует — добавить таб «Импорт» |
| Per-server SSH-deploy (генерация конфига на VPS) | Tunnels → TunnelDetail | Только для kinds с Provisioner (AWG/WG); Hysteria без deploy |
| Engine mode (userspace/kernel) | Tunnels → TunnelDetail | Per-AWG-tunnel, не глобально |
| Multitunnel toggle (1..4 ифейсов) | Tunnels → TunnelDetail | Per-AWG-tunnel |
| Configure server (host/port/user/key) | Tunnels → TunnelDetail → раздел «VPS» | Только для kinds с Provisioner |
| Configure tunnel (endpoint/keys/peers/obf) | Tunnels → TunnelDetail (уже в AwgForm) | AwgForm готов для add, нужен edit-mode |
| Per-client peers (gen/QR/export) | Tunnels → TunnelDetail → раздел «Клиенты» | Был PeerShareModal |
| SpeedTest tunnel-vs-direct | Tunnels → TunnelDetail | Был SpeedTestCard |
| QueueTile (tx queue health) | Tunnels → TunnelsList row OR detail | Опционально |
| Refresh status / last_handshake | Tunnels (UNCHANGED status block) | Уже в TunnelsList |
| ServerSummary (host/endpoint/connected) | Tunnels (UNCHANGED) | Уже есть |
| Per-server delete | Tunnels (UNCHANGED) | Уже есть × button |
| Per-server enable/disable | Tunnels → TunnelDetail | Сейчас только delete; нужен toggle |
| Mode (off/zones/full) | Routing → GlobalSettings | Уже в DefaultRule, но без явного UI-выбора режима |
| Zones list (Pi-hole-like) | Routing → RulesList | Готово (RulesList + RuleEditor) |
| MTU (`routing.mtu`) | Routing → GlobalSettings | Был в RoutingPane; не вариант per-rule |
| Killswitch | Routing → GlobalSettings | Глобальная защита от leak |
| SNI routing toggle | Routing → GlobalSettings | Подсетка SNI sniffer'а |
| DNS proxy mode + chain | Routing → GlobalSettings | Pi-hole integration |
| Domain source (resolve/dnsproxy) | Routing → GlobalSettings | Поведение matcher |
| TraceEnabled toggle | Routing → TracePane | Включает per-flow логирование |
| Apply / Commit / 90s-deadman | Routing → header кнопка «Применить» | Готово |
| Per-device routing | Routing → DevicesPane | Был DevicesRoutingPane (mode picker по MAC/IP) |
| Trace stream (DNS+SNI) | Routing → TracePane | Был отдельный таб TracePane |
| «Copy zones from server» | Routing → RulesList action | Был отдельный modal |
| «Insert rule at top» | Routing → RulesList action | Был отдельный кнопка |
## Под-фазы
### 11a — Феатурный inventory (этот документ)
- [x] Карта AWG2 → новые вкладки выше.
- [x] Список backend-endpoint'ов, которые надо мигрировать (см. ниже).
- [x] Список UI-компонентов которые надо перенести / переписать.
### 11b — Backend: per-tunnel endpoints
Каждая операция, которая раньше шла через `/api/awg2/...` с подразумеваемым
«активный сервер», теперь идёт через `/api/tunnels/{id}/...`. Активный
сервер как концепция уходит — у Registry все туннели равноправны, а
«какие сейчас UP» определяется per-tunnel статусом.
| Старое | Новое |
|---|---|
| `POST /api/awg2/servers/{id}/deploy` | `POST /api/tunnels/{id}/deploy` |
| `POST /api/awg2/servers/{id}/select` | (удалить — нет понятия active) |
| `POST /api/awg2/servers/{id}/enabled` | `PATCH /api/tunnels/{id}` `{enabled}` |
| `POST /api/awg2/engine/mode` | `PATCH /api/tunnels/{id}` `{config:{engine_mode}}` |
| `POST /api/awg2/multitunnel` | `PATCH /api/tunnels/{id}` `{config:{multitunnel}}` |
| `POST /api/awg2/peers` | `POST /api/tunnels/{id}/peers` |
| `DELETE /api/awg2/peers/{pid}` | `DELETE /api/tunnels/{id}/peers/{pid}` |
| `GET /api/awg2/peers/{pid}/config` | `GET /api/tunnels/{id}/peers/{pid}/config` |
| `POST /api/awg2/import` | `POST /api/tunnels/import` (kind определяется по content) |
| `POST /api/awg2/install` | `POST /api/tunnels/{id}/install` |
| `POST /api/awg2/config` | `PATCH /api/tunnels/{id}` `{config}` |
| `POST /api/awg2/client/up\|down` | `POST /api/tunnels/{id}/up\|down` (готово) |
| `POST /api/awg2/status/refresh` | `POST /api/tunnels/{id}/status/refresh` |
| `POST /api/awg2/speedtest` | `POST /api/tunnels/{id}/speedtest` (SSE) |
| `GET /api/awg2` (heavy status) | `GET /api/tunnels/{id}` (готово) |
| `GET /api/awg2/live` (counters) | `GET /api/tunnels/{id}/live` |
Реализация: каждое PATCH/POST/DELETE — тонкий handler, который через
`tunnel.Registry.Get(id)` находит тоннель, опционально берёт его
`tunnel.Tunnel.Manager()` (для AWG-семейства, через type assertion на
`awgTunnel.Manager()`), вызывает соответствующую существующую функцию
`awg.Manager` или `routing.Service.awgClient*OS`. Hysteria-туннели для
peer-related endpoint'ов возвращают 405 Method Not Allowed.
### 11c — Backend: routing globals + trace
Глобальные настройки маршрутизации (то, что раньше жило в
`awg.ServerConfig.Routing`) переезжают на уровень routing-сервиса и
персистятся в `routing.json` (а не в каждом сервере).
```go
// routing.json schema additions
type Settings struct {
Killswitch bool `json:"killswitch"`
SNIRouting bool `json:"sni_routing"`
DNSChainEnabled bool `json:"dns_chain_enabled"`
DomainSource string `json:"domain_source"` // "resolve" | "dnsproxy"
MTUClamp int `json:"mtu_clamp"` // 0 = no clamp
TraceEnabled bool `json:"trace_enabled"`
}
```
Endpoint'ы:
- `GET /api/routing/settings` (готово через RulesView частично — расширить)
- `PATCH /api/routing/settings`
- `GET /api/routing/trace` (history, was /api/awg2/trace)
- `GET /api/routing/trace/status`
- `POST /api/routing/trace/enabled`
- `POST /api/routing/trace/clear`
- `GET /api/events/trace` (EventSource — уже существует, но прибит к awg2; переименовать алиас)
- `POST /api/routing/rules/copy` — было `awg2/routing/rules/copy`
- `POST /api/routing/rules/insert-top` — было
Миграция при чтении: `awg.json.servers[active].routing.*``routing.json.settings.*`
выполняется единожды одновременно с миграцией zones → rules в `migrateLegacy()`.
### 11d — Tunnels: TunnelDetail drawer
Per-tunnel detail-view, открывается кликом на строку в TunnelsList.
Содержимое разделено на табы внутри drawer'а.
> Напоминание про границу: ни одна вкладка drawer'а **не содержит**
> priority / fallback / "куда отправлять X" / RouteRule-related UI. Если
> хочешь обсуждать сюда такое — это сигнал что фича не туда.
1. **Обзор**
- Идентификация (ID, kind badge, status dot)
- Live counters (RX/TX, last_handshake, peer endpoint)
- SpeedTest button — открывает встроенный card с NDJSON-progress
- QueueTile (если линукс kmod выдаёт qdisc stats)
- Datapath allocation (read-only): iface name, fwmark, table — это
«как ядро видит этот тоннель», не routing rule.
2. **Конфигурация туннеля** (AWG: AwgForm в edit-mode; Hysteria: HysteriaForm в edit-mode)
- Кнопка «Сохранить» → PATCH /api/tunnels/{id}
- Per-AWG specific: engine mode (kernel/userspace) + multitunnel count + регенерация ключей через keypair endpoint
3. **VPS** (показывается только когда `Tunnel.Provisioner() != nil`)
- SSH credentials (host/port/user/auth_kind/password/key_pem/known_key)
- Deploy button — стримит progress через SSE
- Last deploy result + per-step log
4. **Клиенты** (показывается только когда `Tunnel.Provisioner() != nil`, т.е. серверный туннель)
- Список peers (без router peer)
- Add peer (генерация ключа + автоназначение address)
- Per-peer: edit name, QR code, export .conf/.vpn, delete
### 11e — Routing: GlobalSettings card
Сверху Routing-вкладки, прямо над RulesList:
```
┌─ Глобальные настройки ───────────────────────────────────┐
│ Killswitch [ON ●] Блокировать leak при tunnel down │
│ SNI routing [ON ●] Слушать TLS ClientHello для match│
│ DNS chain [ON ●] Pi-hole перехватывает LAN :53 │
│ Domain source [DNS proxy▾] Источник IP для domain rules│
│ MTU clamp [1380] 0 = без clamp │
│ Trace [OFF ○] Per-flow логирование (производит.)│
└──────────────────────────────────────────────────────────┘
```
Toggle'ы → PATCH /api/routing/settings → routing.json → backend ре-аплаит
mangle chain через event-driven reconcile (Phase 8a).
### 11f — Routing: DevicesPane
Альтернативный вид для тех, кто думает в терминах устройств, не правил.
По сути это generator правил с MAC/IP-source-match.
```
┌─ Устройства (LAN) ───────────────────────────────────────┐
│ Phone (192.168.31.100, aa:bb:..) [через tun-awg ▾] [fb:bypass▾] │
│ MacBook (192.168.31.50) [direct ▾] │
│ srv (192.168.31.243) [через tun-hys ▾] [fb:drop▾]│
└──────────────────────────────────────────────────────────┘
```
Изменение → синтезирует / редактирует rule с `src_ips=[192.168.31.100]`,
`action.tunnel_id=tun-awg-...`, `priority=100+row*10`, имя
`device:192.168.31.100`. На дисплее RulesList эти правила всё равно
видны, просто DevicesPane — короткий UI для повседневного use case.
Сейчас в роутинге уже есть `rule-z0-device192168318` — мигрированные
device-правила. DevicesPane просто даёт удобный view над ними.
### 11g — Routing: TracePane
Перетащить TracePane.tsx (568 строк) внутрь `features/routing/` и:
- Заменить `/api/awg2/trace/*` на `/api/routing/trace/*`
- Заменить `/api/events/trace` ничем (URL не меняется, просто переподписать handler в server.go).
- Заменить «вставить как rule» action на постинг RouteRule через POST /api/routing/rules.
### 11h — Routing: RulesList parity
Что не хватает в текущем RulesList vs legacy RulesTable:
- **Drag-reorder priority**: сейчас priority меняется только в editor. Добавить drag-handle колонку (react-dnd или нативный HTML5 dnd).
- **Copy from server**: «скопировать правила из конфига такого-то туннеля» — потому что когда у тебя 5 AWG-серверов, проще скопировать zones чем перебивать. Modal со списком туннелей → клон правил с переименованием на новый Action.TunnelID.
- **Insert at top**: «вставить новое с priority<всех существующих» — частая операция (правило-исключение поверх catch-all).
- **Pattern type hints**: визуальные badges «regex», «geosite», «list», «cidr» под доменами / IP / etc.
### 11i — Удаление AWG2 nav
```diff
- import AWG2 from "@/features/awg2/AWG2";
- awg2: { label: "AWG2 VPN", Component: AWG2, icon: ... },
- /* удалить из NAV_GROUPS */
```
И удалить весь `frontend/src/features/awg2/` каталог (~ 10 файлов,
~1700 строк TS/X).
### 11j — Удаление legacy backend
```diff
- m.HandleFunc("GET /api/awg2", ...)
- m.HandleFunc("GET /api/awg2/live", ...)
- m.HandleFunc("POST /api/awg2/servers", ...)
- /* 32 endpoint'a в server.go удалить */
```
И удалить из `routing/awgroute.go` всё с префиксом `AWG2*`:
- `AWG2Status` (вместо него TunnelSummary + RulesView)
- `AWG2ServerSummary`
- `AWG2DeployServerResult`
- `AWG2AddServer / AWG2SelectServer / AWG2SetServerEnabled / AWG2Import /
AWG2DeleteServer / AWG2SetConfig / AWG2Deploy / AWG2DeployServer /
AWG2DeployServers / AWG2RoutingConfig / AWG2RoutingApply / ...`
Перенести то что они делали в эквивалент через `tunnels.Registry.Get(id).Manager()`.
`internal/app/awg.go` тоже зачистить — type aliases `AWG2Status =
routing.AWG2Status` и делегаторы удалить.
### 11k — Cut over dual-engine shadow → single engine
Сейчас `routeFor` крутит legacy zone-engine и shadow rule-engine
параллельно, логирует расхождения. Когда AWG2 уйдёт:
- legacy `Zone` struct остаётся в `awg.ServerConfig.Routing.Zones` для
миграции (но не используется в runtime)
- `zonesToRules()` остаётся как один-раз-при-чтении конвертер
- `compareShadow` / `shadowEquivalent` удаляются
- `Service.routeFor` напрямую делегирует в `routeForRules`
- `awgRouteState.routeTable` (старый) удаляется, остаётся только `ruleTable`
## Подсчёт работ
| Под-фаза | LOC backend | LOC frontend | Сложность |
|---|---|---|---|
| 11a inventory | — | — | done |
| 11b per-tunnel API | +800 | — | high (32 endpoints) |
| 11c routing globals | +400 | — | medium |
| 11d TunnelDetail | — | +1500 | very high (4 tabs × kind) |
| 11e GlobalSettings | — | +200 | low |
| 11f DevicesPane | — | +400 | medium |
| 11g TracePane | — | +600 (port + adapt) | medium |
| 11h RulesList parity | +50 | +400 | medium (drag+copy modal) |
| 11i drop AWG2 nav | — | -1700 | trivial |
| 11j drop legacy backend | -1200 | — | high (touching live code path) |
| 11k single engine | -300 | — | medium |
| **итого** | **~ +750 net** | **~ +1400 net** | **multi-day** |
## Порядок исполнения (предложение)
Делаем bottom-up, чтобы на каждом коммите всё работало:
1. **11b + 11c** (backend extensions) — добавляем новые endpoint'ы РЯДОМ
с legacy. Старые UI продолжают работать. Phase 4 dual-shadow ловит,
если мы где-то расходимся.
2. **11d** (TunnelDetail) — самая большая UI-работа. Открывает все
per-tunnel операции в Tunnels-вкладке. После этого AWG2-ServerPane
функционально дублируется. Старая вкладка ОСТАЁТСЯ.
3. **11e + 11f + 11g + 11h** (Routing parity) — закрываем feature gap.
После этого функционально AWG2 не нужна.
4. **11i** (drop AWG2 nav) — убираем вкладку из nav. Файлы остаются на
диске для возможного быстрого отката.
5. **11j** (drop legacy backend) — удаляем `/api/awg2/*` handler'ы
только когда подтвердили что фронт не делает к ним fetch.
6. **11k** (single engine) — удаляем shadow-comparator и legacy
Zone-walker. После этого `Service.routeFor` минимальный.
## Откат / safety
- Текущая ветка `feat/routing-multi-protocol` имеет 22 коммита + Phase 11
будет ещё ~15-20.
- На каждой границе под-фазы коммит, чтобы можно было `git revert <range>`
если что-то сломалось на прод-роутере.
- Шаг 11j (удаление legacy `/api/awg2/*`) — единственный действительно
необратимый; делаем только когда 11d/e/f/g/h полностью верифицированы
на прод-роутере (текущий деплой 1a3d8b3).
- Backup binary на роутере: `/opt/usr/bin/nfqws2-strategy.bak.preroutingrefactor`.
Loading…
Cancel
Save