diff --git a/README.md b/README.md
index 972f251..d840141 100644
--- a/README.md
+++ b/README.md
@@ -1,401 +1,24 @@
-# VK TURN Proxy
+
-VK TURN Proxy - клиент и сервер для прокидывания локального UDP/TCP-трафика через TURN-реле, получаемые из ссылки на VK Calls. Типичный сценарий - поднять небольшой `server` на VPS рядом с WireGuard или Xray, а на клиентском устройстве запустить `client`, который слушает локальный адрес вроде `127.0.0.1:9000`.
+

-> [!CAUTION]
-> Проект предназначен для обучения, исследований и администрирования собственных стендов. Используйте его только там, где у вас есть право запускать такой трафик и менять сетевую конфигурацию.
+
+
+
-## Содержание
+## О проекте
-- [Как это работает](#как-это-работает)
-- [Возможности](#возможности)
-- [Что нужно](#что-нужно)
-- [Быстрый старт: WireGuard](#быстрый-старт-wireguard)
- - [Запуск сервера на VPS](#1-запустите-сервер-на-vps)
- - [Настройка WireGuard](#2-настройте-wireguard-на-клиенте)
- - [Запуск клиента](#3-запустите-клиент)
-- [Android через Termux](#android-через-termux)
-- [iOS через iSH](#ios-через-ish)
-- [systemd-сервис](#сервер-как-systemd-сервис)
-- [Docker](#docker)
-- [VLESS / Xray](#vless--xray)
-- [WRAP-режим](#wrap-режим)
-- [Яндекс Телемост](#яндекс-телемост)
-- [Флаги клиента](#флаги-клиента)
-- [Флаги сервера](#флаги-сервера)
-- [Captcha](#captcha)
-- [Сборка из исходников](#сборка-из-исходников)
-- [Решение проблем](#решение-проблем)
-- [Похожие проекты](#похожие-проекты)
-- [Лицензия](#лицензия)
+**btp** - туннель UDP/TCP через TURN-реле VK Calls. Клиент берёт временные TURN-учётки из ссылки на звонок и гонит ваш
+трафик (WireGuard, Xray/VLESS) до сервера на VPS поверх DTLS.
-## Как Это Работает
+## Быстрый старт
-Схема для WireGuard:
-```text
-WireGuard client -> 127.0.0.1:9000 -> VK TURN Proxy client
- -> VK TURN relay -> VK TURN Proxy server на VPS
- -> 127.0.0.1:<порт WireGuard> -> WireGuard server
-```
-Клиент берет временные TURN-учетные данные из ссылки VK Calls, открывает одно или несколько соединений к TURN-реле и отправляет через них трафик к вашему `server`. Между `client` и `server` используется DTLS. Для WireGuard сервер пересылает данные в UDP backend, для VLESS/Xray - в TCP backend через KCP и smux.
+
-## Возможности
+---
-- VK Calls как основной источник TURN-учетных данных.
-- TCP или UDP подключение клиента к TURN-реле.
-- Несколько параллельных TURN-потоков через `-n`.
-- WireGuard/Hysteria-подобный UDP backend.
-- VLESS/Xray TCP backend через `-vless`.
-- Bonding для VLESS через `-vless-bond`.
-- Дополнительная WRAP-обфускация DTLS-пакетов через `-wrap`.
-- Автоматическое и ручное прохождение VK captcha.
-- Docker-образ для серверной части.
+Telegram канал: [Free Turn](https://t.me/+5BdkU4q_CGQyNTdi)
-## Что Нужно
-
-- VPS с публичным IP.
-- На VPS уже должен слушать backend:
- - WireGuard: обычно `127.0.0.1:51820/udp`;
- - Xray/VLESS: обычно `127.0.0.1:443/tcp`.
-- Ссылка на активный VK Calls вида `https://vk.com/call/join/...`.
-- На клиенте: WireGuard, Xray или другой локальный клиент, который будет ходить в `127.0.0.1:9000`.
-
-Ссылку VK Calls лучше создать самостоятельно. Не завершайте звонок для всех, если хотите использовать эту ссылку дальше.
-
-## Быстрый Старт: WireGuard
-
-### 1. Запустите Сервер На VPS
-
-Скачайте бинарник для Linux amd64:
-
-```bash
-curl -L -o server https://github.com/samosvalishe/btp/releases/latest/download/server-linux-amd64
-chmod +x server
-```
-
-Запустите `server`, указав локальный адрес WireGuard:
-
-```bash
-./server -listen 0.0.0.0:56000 -connect 127.0.0.1:51820
-```
-
-Порт `56000/udp` должен быть доступен снаружи. Если WireGuard слушает другой порт, замените `51820`.
-
-### 2. Настройте WireGuard На Клиенте
-
-В клиентском конфиге WireGuard замените endpoint сервера на локальный адрес VK TURN Proxy:
-
-```ini
-Endpoint = 127.0.0.1:9000
-MTU = 1280
-```
-
-На Android добавьте Termux или приложение-клиент в исключения WireGuard. На Windows, Linux и macOS перед включением WireGuard нужно добавить маршрут до TURN-реле, иначе клиент может попытаться подключаться к TURN уже через сам VPN.
-
-### 3. Запустите Клиент
-
-Linux:
-
-```bash
-curl -L -o client https://github.com/samosvalishe/btp/releases/latest/download/client-linux-amd64
-chmod +x client
-./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" | ./routes.sh
-```
-
-Windows PowerShell от администратора:
-
-```powershell
-Invoke-WebRequest -Uri https://github.com/samosvalishe/btp/releases/latest/download/client-windows-amd64.exe -OutFile client.exe
-.\client.exe -listen 127.0.0.1:9000 -peer :56000 -vk-link "" | .\routes.ps1
-```
-
-macOS:
-
-```bash
-curl -L -o client https://github.com/samosvalishe/btp/releases/latest/download/client-darwin-arm64
-chmod +x client
-./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" | ./routes-macos.sh
-```
-
-После появления соединения включите WireGuard.
-
-Если вы скачали только бинарник, но не клонировали репозиторий, возьмите нужный route-скрипт из этого репозитория: `routes.sh`, `routes.ps1` или `routes-macos.sh`.
-
-## Android Через Termux
-
-1. Установите Termux из F-Droid.
-2. В WireGuard укажите `Endpoint = 127.0.0.1:9000` и `MTU = 1280`.
-3. Добавьте Termux в исключения WireGuard.
-4. Запустите в Termux:
-
-```bash
-termux-wake-lock
-curl -L -o client https://github.com/samosvalishe/btp/releases/latest/download/client-android-arm64
-chmod +x client
-./client -listen 127.0.0.1:9000 -peer :56000 -vk-link ""
-```
-
-Чтобы снять wake lock:
-
-```bash
-termux-wake-unlock
-```
-
-## iOS Через iSH
-
-Это запасной вариант, если нет нативного клиента.
-
-```bash
-apk update
-apk add curl
-curl -L -o client https://github.com/samosvalishe/btp/releases/latest/download/client-linux-386
-chmod +x client
-GOMAXPROCS=1 GODEBUG=asyncpreemptoff=1 ./client -listen 127.0.0.1:9000 -peer :56000 -vk-link ""
-```
-
-Чтобы iSH дольше жил в фоне, можно в начале сессии выполнить:
-
-```bash
-cat /dev/location > /dev/null &
-```
-
-## Сервер Как systemd-Сервис
-
-Пример `/etc/systemd/system/btp.service`:
-
-```ini
-[Unit]
-Description=VK TURN Proxy server
-After=network.target
-
-[Service]
-Type=simple
-ExecStart=/opt/btp/server -listen 0.0.0.0:56000 -connect 127.0.0.1:51820
-Restart=always
-RestartSec=5
-User=nobody
-Group=nogroup
-
-[Install]
-WantedBy=multi-user.target
-```
-
-Применить:
-
-```bash
-sudo systemctl daemon-reload
-sudo systemctl enable --now btp.service
-sudo systemctl status btp.service
-```
-
-## Docker
-
-Образ публикуется в GitHub Container Registry:
-
-```bash
-docker pull ghcr.io/samosvalishe/btp:latest
-```
-
-Если backend слушает на хосте, удобнее использовать host network:
-
-```bash
-docker run --rm --network host \
- -e CONNECT_ADDR=127.0.0.1:51820 \
- ghcr.io/samosvalishe/btp:latest
-```
-
-Bridge mode:
-
-```bash
-docker run --rm -p 56000:56000/udp \
- -e CONNECT_ADDR=:51820 \
- ghcr.io/samosvalishe/btp:latest
-```
-
-Переменные окружения:
-
-| Переменная | По умолчанию | Описание |
-| --- | --- | --- |
-| `CONNECT_ADDR` | обязательна | backend, куда сервер пересылает трафик |
-| `LISTEN_ADDR` | `0.0.0.0:56000` | адрес прослушивания сервера |
-| `VLESS_MODE` | `false` | включает `-vless` |
-| `VLESS_BOND` | `false` | включает `-vless-bond` |
-| `WRAP_MODE` | `false` | включает `-wrap` |
-| `WRAP_KEY` | пусто | ключ для `-wrap-key` |
-| `VK_TURN_KCP_PROFILE` | `balanced` | профиль KCP (`fast`, `balanced`, `slow`) |
-| `VK_TURN_KCP_MTU` | `1200` | переопределить MTU для KCP |
-
-Сборка образа вручную:
-
-```bash
-docker build -t btp .
-```
-
-## VLESS / Xray
-
-В режиме `-vless` VK TURN Proxy прокидывает TCP-соединения. На VPS `server` подключается к локальному TCP backend, например к Xray inbound на `127.0.0.1:443`. На клиенте `client` слушает локальный TCP адрес, на который должен смотреть ваш Xray/v2rayN/sing-box клиент.
-
-Сервер:
-
-```bash
-./server -listen 0.0.0.0:56000 -connect 127.0.0.1:443 -vless
-```
-
-Клиент:
-
-```bash
-./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" -vless
-```
-
-С bonding:
-
-```bash
-./server -listen 0.0.0.0:56000 -connect 127.0.0.1:443 -vless
-./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" -vless -vless-bond -n 4
-```
-
-## WRAP-Режим
-
-`-wrap` маскирует TURN-payload под SRTP: добавляет RTP/opus-заголовок и шифрует тело ChaCha20-Poly1305 AEAD. Это не защита (DTLS уже шифрует внутренний канал), а обфускация под голосовой трафик, чтобы content-filter VK не дропал пакеты. Ключ должен совпадать на клиенте и сервере.
-
-Сгенерировать ключ:
-
-```bash
-./server -gen-wrap-key
-```
-
-Запуск:
-
-```bash
-./server -listen 0.0.0.0:56000 -connect 127.0.0.1:51820 -wrap -wrap-key <64-hex-key>
-./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" -wrap -wrap-key <64-hex-key>
-```
-
-> **Breaking change (V2-0):** удалены клиентский `-no-dtls` и **серверный** `-vless-bond`.
-> Клиентский `-vless-bond` сохранён — это и есть переключатель bonding.
-> `-no-dtls` помечался DO NOT USE и в проде не работал (VK дропает пакеты без DTLS-handshake).
-> Серверный `-vless-bond` больше не нужен: сервер автоопределяет bond по magic-префиксу в каждом стриме.
-
-## Настройка KCP (VLESS)
-
-В режиме `-vless` для передачи данных поверх DTLS используется KCP. Его можно настроить через переменные окружения (работает и для клиента, и для сервера):
-
-| Переменная | Профили / Значения | Описание |
-| --- | --- | --- |
-| `VK_TURN_KCP_PROFILE` | `fast`, `balanced`, `slow` | Предустановленные режимы работы KCP. |
-| `VK_TURN_KCP_MTU` | например, `1200` | Максимальный размер пакета. |
-
-**Профили:**
-- `fast` (или `legacy`): Минимальные задержки, активная переотправка, MTU 1280.
-- `balanced` (или `cc`): Оптимальный баланс для большинства сетей, MTU 1200.
-- `slow` (или `conservative`): Для очень нестабильных каналов, MTU 1150.
-
-Для более тонкой настройки доступны переменные: `VK_TURN_KCP_NODELAY`, `VK_TURN_KCP_INTERVAL`, `VK_TURN_KCP_RESEND`, `VK_TURN_KCP_NC`, `VK_TURN_KCP_SNDWND`, `VK_TURN_KCP_RCVWND`, `VK_TURN_KCP_ACK_NODELAY`.
-
-**FEC (Reed-Solomon):** `VK_TURN_KCP_FEC=data:parity` (например, `10:3`) — упреждающая коррекция ошибок. На каждые `data` пакетов добавляется `parity` избыточных, потери до `parity` из `data+parity` восстанавливаются без ретрансмита. Overhead по полосе: `parity/data` (для `10:3` — 30%). Должно совпадать на клиенте и сервере. По умолчанию выключено (`0:0`). Включай только при случайных потерях; при шейпе по полосе FEC ухудшит goodput.
-
-
-## Флаги Клиента
-
-| Флаг | По умолчанию | Описание |
-| --- | --- | --- |
-| `-listen` | `127.0.0.1:9000` | локальный адрес `ip:port`, куда подключается WireGuard или Xray клиент |
-| `-peer` | обязательный | адрес сервера VK TURN Proxy на VPS, `host:port` (например `:56000`) |
-| `-vk-link` | обязательный | ссылка VK Calls вида `https://vk.com/call/join/...` |
-| `-n` | `10` | количество параллельных TURN-потоков (соединений к TURN-реле) |
-| `-udp` | `false` | подключаться к TURN-реле по UDP (по умолчанию TCP/TLS) |
-| `-turn` | из ссылки | переопределить IP TURN-сервера |
-| `-port` | из ссылки | переопределить порт TURN-сервера |
-| `-vless` | `false` | режим TCP-форвардера (VLESS/Xray) вместо UDP-релея для WireGuard |
-| `-vless-bond` | `false` | распределять одно VLESS TCP-соединение по всем активным smux-сессиям (только с `-vless`) |
-| `-wrap` | `false` | маскировать TURN-payload под SRTP (см. [WRAP-режим](#wrap-режим)); ключ должен совпадать с сервером |
-| `-wrap-key` | пусто | общий ключ для `-wrap`, 32 байта в hex (64 символа) |
-| `-gen-wrap-key` | `false` | напечатать новый ключ для `-wrap-key` и выйти |
-| `-manual-captcha` | `false` | пропустить авто-решение captcha и сразу открыть ручной режим в локальном браузере |
-| `-streams-per-cred` | `10` | сколько TURN-потоков делят один кеш VK-учёток |
-| `-dns` | `auto` | режим DNS-резолвинга: `udp` \| `doh` \| `auto` (UDP/53, sticky-fallback на DoH) |
-| `-dns-servers` | пусто | список UDP/53 DNS-серверов через запятую вместо встроенных, формат `ip[:port][,ip[:port]...]` |
-| `-debug` | `false` | включить подробные debug-логи |
-
-## Флаги Сервера
-
-| Флаг | По умолчанию | Описание |
-| --- | --- | --- |
-| `-listen` | `0.0.0.0:56000` | локальный адрес прослушивания `ip:port` |
-| `-connect` | обязательный | адрес локального бэкенда `host:port` (WireGuard `127.0.0.1:51820` или Xray `127.0.0.1:443`) |
-| `-vless` | `false` | режим TCP-форвардера (VLESS/Xray) вместо UDP-релея; bond определяется автоматически по magic-префиксу в стриме |
-| `-wrap` | `false` | маскировать TURN-payload под SRTP (см. [WRAP-режим](#wrap-режим)); ключ должен совпадать с клиентом |
-| `-wrap-key` | пусто | общий ключ для `-wrap`, 32 байта в hex (64 символа) |
-| `-gen-wrap-key` | `false` | напечатать новый ключ для `-wrap-key` и выйти |
-| `-debug` | `false` | включить подробные debug-логи |
-
-## Captcha
-
-Для VK Calls клиент умеет автоматически проходить captcha. Если автоматика не сработала, включается ручной сценарий через локальный браузер. Можно сразу запросить ручной режим:
-
-```bash
-./client -manual-captcha -listen 127.0.0.1:9000 -peer :56000 -vk-link ""
-```
-
-Профиль браузера сохраняется в `vk_profile.json` рядом с бинарником и может помочь последующим запросам выглядеть последовательнее.
-
-## Сборка Из Исходников
-
-Нужен Go 1.25.x.
-
-```bash
-go build -o client ./cmd/client
-go build -o server ./cmd/server
-go test ./...
-```
-
-Кросс-сборка примера для Linux amd64:
-
-```bash
-GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o server-linux-amd64 ./cmd/server
-GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o client-linux-amd64 ./cmd/client
-```
-
-Android arm64 клиент как `.so` для упаковки в приложение (PowerShell):
-
-```powershell
-$env:GOOS="android"; $env:GOARCH="arm64"; $env:CGO_ENABLED="0"; go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn.so .\cmd\client
-```
-
-То же из bash:
-
-```bash
-GOOS=android GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn.so ./cmd/client
-```
-
-Linux arm64 клиент как `.so` (для упаковки в Android-приложение в обход NDK; ABI совпадает):
-
-```bash
-GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn.so ./cmd/client
-```
-
-Linux amd64 клиент/сервер как `.so`:
-
-```bash
-GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn-client.so ./cmd/client
-GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn-server.so ./cmd/server
-```
-
-Все `.so` выше — обычные ELF-бинари под `.so`-именем (`CGO_ENABLED=0`, не настоящий shared library). Если нужен настоящий `c-shared` — `CGO_ENABLED=1` + NDK toolchain (см. `.github/workflows/release.yml`).
-
-## Решение Проблем
-
-- Сначала запускайте VK TURN Proxy client, потом включайте WireGuard.
-- Если WireGuard забирает весь трафик, добавьте маршрут до IP TURN-реле через `routes.sh`, `routes.ps1` или `routes-macos.sh`.
-- Если TCP до TURN не работает, попробуйте `-udp`.
-- Если соединение нестабильное, попробуйте уменьшить `-n`, например `-n 1`.
-- Если VK просит captcha слишком часто, попробуйте `-manual-captcha`, затем повторите обычный запуск.
-- Если клиент зависает на получении TURN-данных, проверьте, что ссылка VK Calls живая и не была завершена для всех.
-- Если сервер запущен в Docker bridge mode, `CONNECT_ADDR=127.0.0.1:51820` укажет внутрь контейнера, а не на хост. Используйте host network или IP хоста.
-- Если включен `-wrap`, убедитесь, что и клиент, и сервер используют одинаковый `-wrap-key`.
-
-## Лицензия
-
-GPL-3.0. См. [LICENSE](LICENSE).
+
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..9ce45ed
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,10 @@
+# Документация
+
+- [Быстрый старт (WireGuard)](quickstart.md)
+- [Режимы: UDP / VLESS / WRAP / KCP](modes.md)
+- [Флаги клиента и сервера](flags.md)
+- [Развёртывание: systemd, Docker](deploy.md)
+- [Мобильные: Android (Termux), iOS (iSH)](mobile.md)
+- [Сборка из исходников](build.md)
+- [Решение проблем](troubleshooting.md)
+- [Глоссарий](GLOSSARY.md)
diff --git a/docs/build.md b/docs/build.md
new file mode 100644
index 0000000..7ca9fb8
--- /dev/null
+++ b/docs/build.md
@@ -0,0 +1,46 @@
+# Сборка
+
+Нужен Go 1.25.x.
+
+```bash
+go build -o client ./cmd/client
+go build -o server ./cmd/server
+go test ./...
+```
+
+## Кросс-Сборка
+
+**Linux amd64:**
+
+```bash
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o server-linux-amd64 ./cmd/server
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o client-linux-amd64 ./cmd/client
+```
+
+**Android arm64 как `.so`** (упаковка в приложение, PowerShell):
+
+```powershell
+$env:GOOS="android"; $env:GOARCH="arm64"; $env:CGO_ENABLED="0"
+go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn.so .\cmd\client
+```
+
+Bash:
+
+```bash
+GOOS=android GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn.so ./cmd/client
+```
+
+**Linux arm64 как `.so`** (в обход NDK, ABI совпадает):
+
+```bash
+GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn.so ./cmd/client
+```
+
+**Linux amd64 как `.so`:**
+
+```bash
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn-client.so ./cmd/client
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w -checklinkname=0" -trimpath -o libvkturn-server.so ./cmd/server
+```
+
+> Все `.so` выше — обычные ELF под `.so`-именем (`CGO_ENABLED=0`, не настоящий shared library). Настоящий `c-shared` — `CGO_ENABLED=1` + NDK toolchain (см. `.github/workflows/release.yml`).
diff --git a/docs/deploy.md b/docs/deploy.md
new file mode 100644
index 0000000..a566c3d
--- /dev/null
+++ b/docs/deploy.md
@@ -0,0 +1,74 @@
+# Развёртывание
+
+## systemd
+
+`/etc/systemd/system/btp.service`:
+
+```ini
+[Unit]
+Description=VK TURN Proxy server
+After=network.target
+
+[Service]
+Type=simple
+ExecStart=/opt/btp/server -listen 0.0.0.0:56000 -connect 127.0.0.1:51820
+Restart=always
+RestartSec=5
+User=nobody
+Group=nogroup
+
+[Install]
+WantedBy=multi-user.target
+```
+
+```bash
+sudo systemctl daemon-reload
+sudo systemctl enable --now btp.service
+sudo systemctl status btp.service
+```
+
+## Docker
+
+Образ:
+
+```bash
+docker pull ghcr.io/samosvalishe/btp:latest
+```
+
+**Host network** (backend на хосте):
+
+```bash
+docker run --rm --network host \
+ -e CONNECT_ADDR=127.0.0.1:51820 \
+ ghcr.io/samosvalishe/btp:latest
+```
+
+**Bridge mode:**
+
+```bash
+docker run --rm -p 56000:56000/udp \
+ -e CONNECT_ADDR=:51820 \
+ ghcr.io/samosvalishe/btp:latest
+```
+
+> В bridge `CONNECT_ADDR=127.0.0.1:...` указывает внутрь контейнера. Используйте host network или IP хоста.
+
+**Переменные:**
+
+| Переменная | По умолчанию | Описание |
+| --- | --- | --- |
+| `CONNECT_ADDR` | **обязательна** | backend сервера |
+| `LISTEN_ADDR` | `0.0.0.0:56000` | адрес прослушивания |
+| `VLESS_MODE` | `false` | включает `-vless` |
+| `WRAP_MODE` | `false` | включает `-wrap` |
+| `WRAP_KEY` | пусто | значение `-wrap-key` |
+| `VK_TURN_KCP_PROFILE` | `balanced` | профиль KCP |
+| `VK_TURN_KCP_MTU` | `1200` | MTU для KCP |
+
+> `VLESS_BOND` в `docker-entrypoint.sh` ещё передаёт `-vless-bond` серверу, но серверный флаг удалён в V2-0 (bond автоопределяется по magic-префиксу). Не устанавливайте `VLESS_BOND=true` — сервер не запустится.
+
+Сборка образа:
+
+```bash
+docker build -t btp .
+```
diff --git a/docs/flags.md b/docs/flags.md
new file mode 100644
index 0000000..2cdbb3c
--- /dev/null
+++ b/docs/flags.md
@@ -0,0 +1,39 @@
+# Флаги
+
+## Клиент
+
+| Флаг | По умолчанию | Описание |
+| --- | --- | --- |
+| `-listen` | `127.0.0.1:9000` | локальный адрес `ip:port`, куда подключается WireGuard или Xray клиент |
+| `-peer` | **обязательный** | адрес сервера на VPS, `host:port` |
+| `-vk-link` | **обязательный** | ссылка VK Calls `https://vk.com/call/join/...` |
+| `-n` | `10` | параллельных TURN-потоков |
+| `-udp` | `false` | TURN по UDP вместо TCP/TLS |
+| `-turn` | из ссылки | переопределить IP TURN-сервера |
+| `-port` | из ссылки | переопределить порт TURN-сервера |
+| `-vless` | `false` | TCP-форвардер (VLESS/Xray) вместо UDP-релея |
+| `-vless-bond` | `false` | распределять TCP-соединение по smux-сессиям (только с `-vless`) |
+| `-wrap` | `false` | SRTP-мимикрия payload |
+| `-wrap-key` | пусто | общий ключ, 32 байта hex (64 символа) |
+| `-gen-wrap-key` | `false` | напечатать новый ключ и выйти |
+| `-manual-captcha` | `false` | сразу ручной режим captcha |
+| `-streams-per-cred` | `10` | потоков на один кеш VK-учёток |
+| `-dns` | `auto` | `udp` \| `doh` \| `auto` |
+| `-dns-servers` | пусто | свои UDP/53 резолверы, `ip[:port][,ip[:port]...]` |
+| `-debug` | `false` | debug-логи |
+
+## Сервер
+
+| Флаг | По умолчанию | Описание |
+| --- | --- | --- |
+| `-listen` | `0.0.0.0:56000` | адрес прослушивания `ip:port` |
+| `-connect` | **обязательный** | локальный backend `host:port` (WG `127.0.0.1:51820` / Xray `127.0.0.1:443`) |
+| `-vless` | `false` | TCP-форвардер; bond автоопределяется |
+| `-wrap` | `false` | SRTP-мимикрия payload |
+| `-wrap-key` | пусто | общий ключ, 32 байта hex |
+| `-gen-wrap-key` | `false` | напечатать новый ключ и выйти |
+| `-debug` | `false` | debug-логи |
+
+## Переменные Окружения (KCP)
+
+См. [modes.md → KCP](modes.md#kcp-только-в--vless).
diff --git a/docs/mobile.md b/docs/mobile.md
new file mode 100644
index 0000000..5c2d0f3
--- /dev/null
+++ b/docs/mobile.md
@@ -0,0 +1,35 @@
+# Мобильные
+
+## Android (Termux)
+
+1. Termux из F-Droid.
+2. WireGuard: `Endpoint = 127.0.0.1:9000`, `MTU = 1280`.
+3. Termux в исключения WireGuard.
+4. В Termux:
+
+```bash
+termux-wake-lock
+curl -L -o client https://github.com/samosvalishe/btp/releases/latest/download/client-android-arm64
+chmod +x client
+./client -listen 127.0.0.1:9000 -peer :56000 -vk-link ""
+```
+
+Снять wake lock: `termux-wake-unlock`.
+
+## iOS (iSH)
+
+Запасной вариант без нативного клиента.
+
+```bash
+apk update
+apk add curl
+curl -L -o client https://github.com/samosvalishe/btp/releases/latest/download/client-linux-386
+chmod +x client
+GOMAXPROCS=1 GODEBUG=asyncpreemptoff=1 ./client -listen 127.0.0.1:9000 -peer :56000 -vk-link ""
+```
+
+Дольше в фоне:
+
+```bash
+cat /dev/location > /dev/null &
+```
diff --git a/docs/modes.md b/docs/modes.md
new file mode 100644
index 0000000..8ec0330
--- /dev/null
+++ b/docs/modes.md
@@ -0,0 +1,81 @@
+# Режимы
+
+## UDP-Релей (WireGuard / Hysteria)
+
+Дефолт. Клиент слушает локальный UDP, сервер форвардит UDP в backend (WG `127.0.0.1:51820`).
+
+## VLESS / Xray (TCP)
+
+Флаг `-vless` на обеих сторонах. Сервер коннектит локальный TCP backend (Xray inbound `127.0.0.1:443`). Клиент слушает локальный TCP, на него смотрит Xray/v2rayN/sing-box.
+
+Поверх DTLS — KCP + smux.
+
+```bash
+./server -listen 0.0.0.0:56000 -connect 127.0.0.1:443 -vless
+./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" -vless
+```
+
+**Bonding** — распределение одного TCP-соединения по всем активным smux-сессиям, флаг только клиентский:
+
+```bash
+./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" -vless -vless-bond -n 4
+```
+
+> **Breaking (V2-0):** серверный `-vless-bond` удалён, сервер автоопределяет bond по magic-префиксу в стриме. Клиентский `-no-dtls` тоже удалён (VK дропает не-DTLS).
+
+## WRAP (SRTP-Мимикрия)
+
+`-wrap` маскирует TURN-payload под SRTP: RTP/opus-заголовок + ChaCha20-Poly1305 AEAD на теле. Не защита (DTLS уже шифрует), а обфускация под голос — иначе VK content-filter дропает. Ключ должен совпадать.
+
+Сгенерировать ключ:
+
+```bash
+./server -gen-wrap-key
+```
+
+Запуск:
+
+```bash
+./server ... -wrap -wrap-key <64-hex>
+./client ... -wrap -wrap-key <64-hex>
+```
+
+## KCP (Только В `-vless`)
+
+Переменные окружения (клиент и сервер):
+
+| Переменная | Значения | Описание |
+| --- | --- | --- |
+| `VK_TURN_KCP_PROFILE` | `fast` \| `balanced` \| `slow` | Предустановка. |
+| `VK_TURN_KCP_MTU` | напр. `1200` | Размер пакета. |
+| `VK_TURN_KCP_FEC` | `data:parity`, напр. `10:3` | Reed-Solomon FEC. |
+
+**Профили:**
+
+- `fast` (`legacy`) — минимальные задержки, активная переотправка, MTU 1280.
+- `balanced` (`cc`) — баланс для большинства сетей, MTU 1200.
+- `slow` (`conservative`) — нестабильные каналы, MTU 1150.
+
+Тонкая настройка: `VK_TURN_KCP_NODELAY`, `_INTERVAL`, `_RESEND`, `_NC`, `_SNDWND`, `_RCVWND`, `_ACK_NODELAY`.
+
+**FEC:** на каждые `data` пакетов — `parity` избыточных. Потери до `parity` из `data+parity` восстанавливаются без ретрансмита. Overhead полосы: `parity/data` (для `10:3` — 30%). Включайте при случайных потерях; при шейпе по полосе FEC ухудшит goodput. По умолчанию `0:0`.
+
+## Captcha
+
+Клиент сам проходит VK captcha. Если автоматика не справилась — открывается локальный браузер. Принудительно ручной:
+
+```bash
+./client -manual-captcha ...
+```
+
+Профиль браузера: `vk_profile.json` рядом с бинарником.
+
+## DNS
+
+Флаг `-dns`:
+
+- `udp` — только UDP/53;
+- `doh` — только DNS-over-HTTPS;
+- `auto` (дефолт) — сначала UDP/53, sticky-fallback на DoH при полном отказе.
+
+Свои резолверы: `-dns-servers ip[:port][,ip[:port]...]`.
diff --git a/docs/quickstart.md b/docs/quickstart.md
new file mode 100644
index 0000000..58a5998
--- /dev/null
+++ b/docs/quickstart.md
@@ -0,0 +1,55 @@
+# Быстрый Старт (WireGuard)
+
+## Что Нужно
+
+- VPS с публичным IP, WireGuard слушает `127.0.0.1:51820/udp`.
+- Активная ссылка VK Calls: `https://vk.com/call/join/...` — создайте сами, не завершайте звонок для всех.
+- WireGuard на клиенте: `Endpoint = 127.0.0.1:9000`, `MTU = 1280`.
+
+## 1. Сервер На VPS
+
+```bash
+curl -L -o server https://github.com/samosvalishe/btp/releases/latest/download/server-linux-amd64
+chmod +x server
+./server -listen 0.0.0.0:56000 -connect 127.0.0.1:51820
+```
+
+Порт `56000/udp` открыт снаружи.
+
+## 2. Маршруты (Split Tunneling)
+
+WireGuard забирает весь трафик, включая трафик к TURN-реле — туннель замыкается сам на себя. Скрипт маршрутов добавляет исключение: трафик к TURN идёт напрямую через шлюз ISP.
+
+Флаг `-debug` включает debug-логи, из которых скрипт читает IP TURN-сервера. `2>&1` перенаправляет их в пайп.
+
+> **Android:** маршруты не нужны — добавьте Termux в исключения WireGuard.
+
+## 3. Клиент
+
+**Linux:**
+
+```bash
+curl -L -o client https://github.com/samosvalishe/btp/releases/latest/download/client-linux-amd64
+chmod +x client
+./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" -debug 2>&1 | ./scripts/routes.sh
+```
+
+**Windows (PowerShell от администратора):**
+
+```powershell
+Invoke-WebRequest -Uri https://github.com/samosvalishe/btp/releases/latest/download/client-windows-amd64.exe -OutFile client.exe
+.\client.exe -listen 127.0.0.1:9000 -peer :56000 -vk-link "" -debug 2>&1 | .\scripts\routes.ps1
+```
+
+**macOS:**
+
+```bash
+curl -L -o client https://github.com/samosvalishe/btp/releases/latest/download/client-darwin-arm64
+chmod +x client
+./client -listen 127.0.0.1:9000 -peer :56000 -vk-link "" -debug 2>&1 | ./scripts/routes-macos.sh
+```
+
+Когда скрипт напечатает `Ensuring route to ...` — включайте WireGuard.
+
+> Скачали только бинарник без репо? Возьмите скрипты отдельно:
+> [`scripts/routes.sh`](../scripts/routes.sh) · [`scripts/routes.ps1`](../scripts/routes.ps1) · [`scripts/routes-macos.sh`](../scripts/routes-macos.sh)
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
new file mode 100644
index 0000000..d4784b7
--- /dev/null
+++ b/docs/troubleshooting.md
@@ -0,0 +1,12 @@
+# Решение Проблем
+
+- Сначала запускайте client, потом включайте WireGuard.
+- WireGuard забирает весь трафик → пайп с `-debug 2>&1` и скриптом маршрутов обязателен: `scripts/routes.sh` / `scripts/routes.ps1` / `scripts/routes-macos.sh`.
+- Маршруты не добавляются → убедитесь, что в команде есть `-debug 2>&1 |` перед скриптом.
+- TCP до TURN не работает → попробуйте `-udp`.
+- Соединение нестабильное → уменьшите `-n` (например, `-n 1`).
+- VK часто требует captcha → `-manual-captcha`, затем обычный запуск.
+- Клиент висит на получении TURN-данных → ссылка VK Calls умерла / звонок завершён для всех.
+- Docker bridge mode → `CONNECT_ADDR=127.0.0.1:...` указывает внутрь контейнера. Используйте `--network host` или IP хоста.
+- `-wrap` включён → ключ должен совпадать на клиенте и сервере.
+- `VLESS_BOND=true` в docker → не ставьте, серверный флаг удалён в V2-0.
diff --git a/routes.sh b/routes.sh
deleted file mode 100644
index d660e9c..0000000
--- a/routes.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-
-gateway="$(ip -o -4 route show to default | awk '/via/ {print $3}' | head -1)"
-if [[ -z "${gateway}" ]]; then
- echo "Could not determine default gateway" >&2
- exit 1
-fi
-
-ip_cmd=(ip)
-if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
- ip_cmd=(sudo ip)
-fi
-
-while IFS= read -r remote; do
- remote="${remote%$'\r'}"
- [[ -z "$remote" ]] && continue
-
- if [[ ! "$remote" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
- echo "Skipping unexpected input: $remote" >&2
- continue
- fi
-
- echo "Ensuring route to $remote via $gateway"
- "${ip_cmd[@]}" route replace "$remote" via "$gateway"
-done
diff --git a/routes-macos.sh b/scripts/routes-macos.sh
similarity index 89%
rename from routes-macos.sh
rename to scripts/routes-macos.sh
index 8a09471..31e5301 100644
--- a/routes-macos.sh
+++ b/scripts/routes-macos.sh
@@ -1,4 +1,5 @@
#!/bin/zsh
+# Usage: ./client -debug ... 2>&1 | ./scripts/routes-macos.sh
set -u
default_info="$(route -n get default 2>/dev/null || true)"
@@ -18,11 +19,9 @@ fi
while IFS= read -r line; do
line="${line//$'\r'/}"
- # Try to extract:
- # - plain IPv4
- # - IPv4/CIDR
- # - relayed-address=IPv4:port -> use only IPv4
remote="$(printf '%s\n' "$line" | sed -nE '
+ s/.*TURN server IP: (([0-9]{1,3}\.){3}[0-9]{1,3}).*/\1/p
+ t done
s/.*relayed-address=(([0-9]{1,3}\.){3}[0-9]{1,3}):[0-9]+.*/\1/p
t done
s/^(([0-9]{1,3}\.){3}[0-9]{1,3}\/[0-9]{1,2})$/\1/p
diff --git a/routes.ps1 b/scripts/routes.ps1
similarity index 50%
rename from routes.ps1
rename to scripts/routes.ps1
index 0e34b6c..0b2012b 100644
--- a/routes.ps1
+++ b/scripts/routes.ps1
@@ -1,42 +1,51 @@
-# Получаем default gateway (IPv4)
+# Usage: .\client.exe -debug ... 2>&1 | .\scripts\routes.ps1
+# Requires Administrator privileges.
+
$gateway = Get-NetRoute `
-DestinationPrefix "0.0.0.0/0" `
| Sort-Object RouteMetric `
| Select-Object -First 1 -ExpandProperty NextHop
if (-not $gateway) {
- Write-Error "Не удалось определить default gateway"
+ Write-Error "Could not determine default gateway"
exit 1
}
Write-Host "Default gateway: $gateway"
-# Читаем адреса из stdin
$input | ForEach-Object {
- $addr = $_.Trim()
- if ($addr -eq "") { return }
+ $line = $_.Trim()
+ if ($line -eq "") { return }
- if ($addr -notmatch '^\d{1,3}(\.\d{1,3}){3}$') {
- Write-Warning "Пропускаем неожиданный ввод: $addr"
- return
+ $prefix = $null
+
+ if ($line -match 'TURN server IP: ((\d{1,3}\.){3}\d{1,3})') {
+ $prefix = "$($Matches[1])/32"
+ } elseif ($line -match 'relayed-address=((\d{1,3}\.){3}\d{1,3}):\d+') {
+ $prefix = "$($Matches[1])/32"
+ } elseif ($line -match '^((\d{1,3}\.){3}\d{1,3}/\d{1,2})$') {
+ $prefix = $Matches[1]
+ } elseif ($line -match '^((\d{1,3}\.){3}\d{1,3})$') {
+ $prefix = "$line/32"
}
- $prefix = "$addr/32"
+ if (-not $prefix) { return }
+
$existingRoutes = @(Get-NetRoute `
-DestinationPrefix $prefix `
-PolicyStore ActiveStore `
-ErrorAction SilentlyContinue)
if ($existingRoutes | Where-Object { $_.NextHop -eq $gateway }) {
- Write-Host "Маршрут к $addr через $gateway уже существует"
+ Write-Host "Route to $prefix via $gateway already exists"
return
}
if ($existingRoutes.Count -gt 0) {
- Write-Host "Обновляем маршрут к $addr через $gateway"
+ Write-Host "Updating route to $prefix via $gateway"
$existingRoutes | Remove-NetRoute -Confirm:$false -ErrorAction Stop
} else {
- Write-Host "Добавляем маршрут к $addr через $gateway"
+ Write-Host "Ensuring route to $prefix via $gateway"
}
New-NetRoute `
@@ -45,4 +54,3 @@ $input | ForEach-Object {
-PolicyStore ActiveStore `
-ErrorAction Stop | Out-Null
}
-
diff --git a/scripts/routes.sh b/scripts/routes.sh
new file mode 100644
index 0000000..a4979bb
--- /dev/null
+++ b/scripts/routes.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+# Usage: ./client -debug ... 2>&1 | ./scripts/routes.sh
+set -euo pipefail
+
+gateway="$(ip -o -4 route show to default | awk '/via/ {print $3}' | head -1)"
+if [[ -z "${gateway}" ]]; then
+ echo "Could not determine default gateway" >&2
+ exit 1
+fi
+
+ip_cmd=(ip)
+if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
+ ip_cmd=(sudo ip)
+fi
+
+while IFS= read -r line; do
+ line="${line%$'\r'}"
+
+ remote=""
+ if [[ "$line" =~ TURN\ server\ IP:\ (([0-9]{1,3}\.){3}[0-9]{1,3}) ]]; then
+ remote="${BASH_REMATCH[1]}"
+ elif [[ "$line" =~ relayed-address=(([0-9]{1,3}\.){3}[0-9]{1,3}):[0-9]+ ]]; then
+ remote="${BASH_REMATCH[1]}"
+ elif [[ "$line" =~ ^(([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2})$ ]]; then
+ remote="$line"
+ elif [[ "$line" =~ ^(([0-9]{1,3}\.){3}[0-9]{1,3})$ ]]; then
+ remote="$line"
+ fi
+
+ [[ -z "$remote" ]] && continue
+
+ echo "Ensuring route to $remote via $gateway"
+ "${ip_cmd[@]}" route replace "$remote" via "$gateway"
+done