Один бинарник ultra-relay работает в одной из двух ролей из JSON-спецификации (bridge или exit): внешний узел и внутренний узел с согласованным TLS и HTTP-слоем между ними. Управление пользователями — PostgreSQL-база и HTTP API только на loopback. Маршрутизацию и транспорт обеспечивает встроенное ядро (Xray-core).
Дополнительно — ultra-bot: Telegram-бот с веб-интерфейсом (Mini App) для управления пользователями и мониторинга прямо из мессенджера.
| Пакет / каталог | Назначение |
|---|---|
internal/mimic |
Шаблоны HTTP для внутреннего транспорта (apijson, steamlike; plusgaming — псевдоним apijson). |
internal/auth |
Управление пользователями: PostgreSQL-бэкенд с кешом, опциональный fallback на JSON-файл. |
internal/config |
Загрузка и валидация spec, сборка конфигурации для встроенного ядра. |
internal/proxy |
Запуск ядра внутри процесса ultra-relay. |
internal/adminapi |
HTTP API только на admin_listen: CRUD пользователей и exit-нод, health/failover, экспорт клиента, статистика. |
internal/bot |
Telegram-бот (long polling) + Mini App HTTP-сервер с HMAC-валидацией initData. |
internal/db |
PostgreSQL: пользователи, exit-ноды (failover), трафик, Telegram-состояние, администраторы. |
internal/exits |
Выбор active exit по priority и health, probe worker для failover. |
internal/stats |
Сбор статистики трафика через gRPC API встроенного ядра. |
internal/install, internal/loglevel, internal/realitykey |
Установка по SSH, управление уровнями логов, ключевой материал. |
cmd/ultra-relay, cmd/ultra-install, cmd/ultra-bot |
Точки входа бинарников. |
Клиенты: Happ, подписки и домашний Linux-прокси.
Для разработки: инструкции Codex, карта архитектуры, проверки и особенности разработки, архитектурные решения.
make build # ./ultra-relay
make build-install # ./ultra-install
make build-bot # ./ultra-bot
make build-linux-amd64 # кросс-компиляция для Linux x86-64
make build-bot-linux-amd64
make test
make format
make lintТребуется Go из go.mod.
С машины с Go, ssh, scp и ключом к обоим хостам:
make installСкрипт собирает ultra-relay-linux-amd64 для Linux-серверов и ultra-install для текущей ОС. На macOS не запускайте ultra-install-linux-amd64 локально — будет Exec format error.
Цель для внешнего TLS handshake на bridge (-reality-dest): задайте -reality-dest host:port при установке, при необходимости — -reality-sni. В install.config это переменные REALITY_DEST / REALITY_SNI.
Неинтерактивно: скопируйте install.config.sample → install.config (файл в .gitignore), заполните и запустите make install или ULTRA_INSTALL_CONFIG=/path/to/conf make install.
Вручную:
make build-linux-amd64 build-install
./ultra-install -bridge FRONT -exit BACK -identity ~/.ssh/key \
-reality-dest 'HOST:443' -reality-sni 'HOST'Флаги: ./ultra-install -h (включая -public-host, -preset, -routing-mode, -exit-only, -tunnel-uuid, -warp, -disable-doh, -db-host и другие).
При make install можно сразу поставить две exit VPS (primary + backup): в install.config укажите EXIT и EXIT2 (см. install.config.sample). Установщик развернёт доступные ноды, сгенерирует отдельный tunnel_uuid для каждой и положит на bridge exit_nodes.bootstrap.json — при первом старте строки импортируются в PostgreSQL. Exit, недоступные по SSH, попадают в bootstrap с enabled: false (не маршрутизируются, пока не установлены и не включены через закрытый relay API). Backup обычно с EXIT2_PRIORITY=200, primary — 100. Недоступные exit не блокируют установку, если хотя бы одна exit задеployed; иначе ultra-install завершится с ошибкой.
| Где хранится | Что |
|---|---|
PostgreSQL exit_nodes |
Список exit: address, port, tunnel_uuid, priority (меньше = primary), enabled |
exit_nodes.bootstrap.json на bridge |
Bootstrap при первом старте (импорт в БД; fallback — spec.exit) |
Exit spec.json на каждом VPS |
Свой exit.tunnel_uuid, общие mimic_preset, splithttp_*, transport |
Failover: bridge каждые ~30 с проверяет TCP-доступность enabled exit и выбирает active с минимальным priority среди reachable. При смене active — перезагрузка Xray на bridge (краткий разрыв сессий). Клиенты по-прежнему подключаются только к bridge; выбор exit прозрачен.
- Обновить bridge до версии с поддержкой multi-exit (
make installили замена бинарника + restart). - Mini App → Настройки → Exit-ноды → «Добавить exit» (имя, адрес dial с bridge, порт, priority).
- Скопировать
tunnel_uuidи команду из блока Deploy в ответе API. - С локальной машины (Go, ssh, scp):
Общие параметры туннеля (
make build-linux-amd64 build-install ./ultra-install -exit-only -bridge BRIDGE_IP -exit NEW_EXIT_IP \ -identity ~/.ssh/key -tunnel-uuid 'UUID-ИЗ-API'
splithttp_path,mimic_preset,TRANSPORT,TUNNEL_PORT) читаются с bridgespec.json. Флаг-dry-runпечатает exit spec без SSH. - Старую exit отключить или удалить в Mini App. Backup обычно с
priority=200, primary —100.
Admin API (loopback, Bearer token): GET/POST /v1/exits, PATCH/DELETE /v1/exits/{id}, расширенный GET /v1/health (exits[], active_exit_id). POST /v1/exits возвращает deploy.install_example.
Telegram-алерты: exit_down / exit_up (по active exit), exit_failover при переключении active.
ultra-bot запускается на bridge-узле и предоставляет веб-интерфейс администратора в Telegram.
Минимальная конфигурация:
- Создать бота: @BotFather →
/newbot→ скопировать токен. - Обязательно: скопировать
.env.sample→.envи вставитьTELEGRAM_BOT_TOKEN(без этогоmake installзавершится ошибкой приBOT_ENABLE=y). - Получить домен — см. раздел «Домен для Mini App» ниже.
- В
install.configраскомментировать и заполнить:BOT_ENABLE=y BOT_DOMAIN=bot.example.com # FQDN HTTPS-входа; по умолчанию bridge BOT_PORT=8444 - Запустить
make install. В конце будет выведена команда/start <токен>— отправить её боту для регистрации первого администратора.
Остальные секреты (ULTRA_RELAY_ADMIN_TOKEN, DB DSN) берутся автоматически из /etc/ultra-relay/environment и spec.json — вручную задавать не нужно.
Long polling и алерты к api.telegram.org с bridge идут через локальный SOCKS5 (127.0.0.1:10809) в Xray и далее на active exit (с тем же failover, что и пользовательский трафик).
Mini App открывается по кнопке от бота и предоставляет:
- Личный кабинет «Мой VPN»: подписка Happ, инструкции и предпочтительная локация.
- Администратору — переключение в свой личный кабинет, список участников, отключение, включение и сброс доступа, групповую регистрацию и именные приглашения.
- «Сервис → Серверы»: управление Vultr с подтверждением цены, состоянием операций, понятными ошибками и журналом этапов; состояние копий БД.
- Ранее выданные ручные конфиги, статистику и диагностику — в отдельном прежнем интерфейсе.
Ручное добавление серверов из Mini App удалено. Установка с локального компьютера
через make install и закрытый relay API сохраняется. Подробнее:
регистрация, Vultr, копии БД.
Telegram Mini App требует публичного HTTPS-адреса. Сертификат получается автоматически через Let's Encrypt — нужен только домен с DNS A-записью.
Варианты получения домена:
| Вариант | Стоимость | Как |
|---|---|---|
| Платный домен (reg.ru, namecheap и др.) | ~$10–15/год | Зарегистрировать любое доменное имя |
| Бесплатный поддомен afraid.org | Бесплатно | Выбрать поддомен, добавить A-запись на IP HTTPS-входа |
| Поддомен существующего домена | Бесплатно | Добавить A-запись в уже имеющийся домен |
После получения домена:
-
Добавить A-запись в DNS на IP HTTPS-входа (по умолчанию bridge):
bot.example.com. A <IP bridge-сервера>При отдельном входе на exit задайте
BOT_INGRESS_MODE(sshдля существующего nginx,vultrдля управляемого VPS),BOT_INGRESS_IPиBOT_PUBLIC_URL; настройка и защита узла описаны в инструкции HTTPS-входа. Бот остаётся на bridge.Проверить:
make verify-miniapp— публичный DNS/TLS и backend bridge проверяются отдельно. -
Убедиться, что порты открыты на bridge:
- 80/tcp — для HTTP-01 ACME challenge (нужен только при выдаче/обновлении сертификата)
- 8444/tcp — Mini App HTTPS (или другой
BOT_PORT)
-
Прописать домен в
install.configи запуститьmake install. -
В @BotFather: Bot Settings → Menu Button (или Web App URL) =
https://bot.example.com:8444/— точно как вBOT_DOMAINиBOT_PORT. -
На мобильных сетях РФ надёжнее
BOT_PORT=443(reverse proxy на bridge); по умолчанию 8444.
Оркестратор использует ssh -o BatchMode=yes: без принятого ключа команды завершатся ошибкой.
Таймаут подключения SSH: ULTRA_SSH_CONNECT_TIMEOUT (секунды, по умолчанию 10) — для ultra-install, make install и make relay-logs. Недоступные exit пропускаются с WARNING; bridge обязателен.
По умолчанию — StrictHostKeyChecking=accept-new: при первом подключении ключ записывается в known_hosts. Для строгой модели доверия задайте ULTRA_INSTALL_SSH_STRICT_HOST_KEY=yes — хосты должны быть в known_hosts заранее.
Сгенерировать ключ:
ssh-keygen -t ed25519 -f ~/.ssh/ultra_relay_ed25519 -C "ultra-relay-deploy"При passphrase: eval "$(ssh-agent -s)" и ssh-add ~/.ssh/ultra_relay_ed25519.
Скопировать публичный ключ на серверы:
ssh-copy-id -i ~/.ssh/ultra_relay_ed25519.pub root@BRIDGE_IP
ssh-copy-id -i ~/.ssh/ultra_relay_ed25519.pub root@EXIT_IPОпционально ~/.ssh/config:
Host ultra-front
HostName BRIDGE_IP
User root
IdentityFile ~/.ssh/ultra_relay_ed25519
Host ultra-back
HostName EXIT_IP
User root
IdentityFile ~/.ssh/ultra_relay_ed25519| Симптом | Проверка |
|---|---|
Permission denied (publickey) |
Ключ в authorized_keys, пользователь, путь -i. |
Host key verification failed |
Первый вход: StrictHostKeyChecking=accept-new; при смене ключа хоста — правка known_hosts. |
Конфигурация задаётся JSON-файлом (-spec). Поле schema_version — сейчас 1. Поле tunnel_tls_provision описывает источник TLS-сертификата на exit для внутреннего канала — см. deploy/TLS.md.
Два независимых сегмента:
- Клиент → bridge: публичный inbound; блок
realityзадаёт параметры TLS для внешних клиентов. - Bridge → exit: межузловой канал;
tunnel_transport: splithttp(XHTTP stream-up H2, по умолчанию) или устаревшийgrpc. Параметры HTTP:mimic_preset,splithttp_host,splithttp_path. На bridge блокexitв spec — legacy/bootstrap; рабочий список upstream — таблицаexit_nodesв PostgreSQL (см. несколько exit-нод). На каждой exit VPS свойexit.tunnel_uuid. - Клиент → bridge (REALITY): по умолчанию
vless_flow: xtls-rprx-vision(Xray 26). После смены flow пользователи должны переимпортировать подписку из Mini App. Отключение:DISABLE_VLESS_FLOW=yвinstall.config. Внутренний туннель bridge→exit по-прежнему без flow (ограничение Xray; предупреждения в логах exit допустимы).
Маршрутизация на bridge (при split_routing: true, domainStrategy: IPIfNonMatch):
routing_mode: blocklist(по умолчанию) — трафик изgeosite_exit_tags/geoip_exit_tags/domain_exitна exit, остальное прямо.routing_mode: ru_direct— русские домены (.ru,.su,.рф, VK, Яндекс и т.д.) напрямую, остальное на exit. Опциональноgeosite_block_tags→ blackhole.
Параметры протокола:
anti_censor.warp_proxy: true— на exit использовать Cloudflare WARP в режиме прокси; destination-сайты видят Cloudflare IP вместо IP датацентра.anti_censor.disable_doh: false(по умолчанию) — DNS over HTTPS; bridge использует Yandex DoH для.ru-доменов и Cloudflare для остального.- Генератор использует
xPaddingBytes(стандарт 100–1000), сохраняя совместимость старых профилей. Фрагментация через freedom/dialerProxyвключается только явнымanti_censor.fragment.packets. Ограничения и проверка — в документации обхода цензуры. /clientэкспортирует обратно-совместимый основной профильfast_tcp_reality(старый VLESS+REALITY+TCP+Vision URI) и настроенные резервные профили для Xray-compatible клиентов. Чтобы резервный XHTTP-профиль был доступен извне, задайтеPUBLIC_XHTTP_PORT/anti_censor.public_xhttp_port;make installвнесёт это в spec, аultra-relaybest-effort откроет локальный firewall на bridge. Старыйvless_portпри этом не меняется.anti_censor.profile:fast,balanced(дефолт для новых fallback-настроек),stealth. Значение сохраняется для совместимости spec; само по себе не включает фрагментацию и не меняет стандартный padding или legacy TCP URI.
SOCKS5 на bridge: два режима — (1) общий inbound в spec (socks5.enabled, по умолчанию 127.0.0.1); (2) per-user kind=socks5 в Admin API / Mini App — отдельный порт из диапазона 10810–10899, логин = UUID, пароль в карточке пользователя (socks5://… в UI). Оба используют тот же routing, что VLESS. Per-user порты слушают 0.0.0.0; ultra-relay best-effort открывает их в локальном firewall. На мобильных сетях нестандартные порты (108xx, 8444) могут быть менее надёжны — для Telegram in-app proxy или Mini App обычно лучше :443.
Тонкая настройка: опциональный объект xray_wire в spec задаёт теги, шифрование, sniffing и другие параметры; пустые поля не переопределяют встроенные значения (см. internal/config/xray_wire_spec.go).
Обновление geo-файлов: scripts/update-geo-assets.sh <geo_assets_dir>.
Admin API на bridge слушает только loopback (admin_listen в spec, по умолчанию 127.0.0.1:8443). Доступ с локальной машины — через SSH port forwarding:
ssh -L 8443:127.0.0.1:8443 user@BRIDGE_IPВеб-интерфейс: http://127.0.0.1:8443/admin/ — вставить ULTRA_RELAY_ADMIN_TOKEN (выводится при make install, хранится в /etc/ultra-relay/environment).
Локально в dev: http://127.0.0.1:18443/admin/ (из examples/spec.bridge.dev.json).
curl:
curl -H "Authorization: Bearer …" http://127.0.0.1:8443/v1/users
curl -H "Authorization: Bearer …" http://127.0.0.1:8443/v1/exits
curl -H "Authorization: Bearer …" http://127.0.0.1:8443/v1/health
curl -H "Authorization: Bearer …" http://127.0.0.1:8443/v1/users/UUID/client
curl -H "Authorization: Bearer …" http://127.0.0.1:8443/v1/traffic/monthlyОтвет /client содержит vless_uri и full_xray_config_base64 для запуска Xray-клиента.
- TLS-материалы для exit (не коммитить):
cd examples openssl req -x509 -newkey rsa:2048 -keyout test-key.pem -out test-cert.pem -days 3650 -nodes \ -subj "/CN=splithttp.invalid" -addext "subjectAltName=DNS:splithttp.invalid"
- Пустой
users.json([]) и токенULTRA_RELAY_ADMIN_TOKEN. - Терминал A — exit:
./ultra-relay -spec examples/spec.exit.dev.json - Терминал B — bridge:
export ULTRA_RELAY_ADMIN_TOKEN="$(openssl rand -hex 16)" ./ultra-relay -spec examples/spec.bridge.dev.json -admin-token "$ULTRA_RELAY_ADMIN_TOKEN"
- Для
ultra-botв dev-режиме:# В .env: TELEGRAM_BOT_TOKEN=... и ULTRA_RELAY_ADMIN_TOKEN=... ./ultra-bot -spec examples/spec.bridge.dev.json -dev -port 8080
VERIFY_IP_URL=https://YOUR_HOST/your-probe-path make verify-relay
# или с явными хостами:
VERIFY_IP_URL=https://api.ipify.org make verify-relay BRIDGE=… EXIT=… IDENTITY=…
make benchmark-relay # read-only speed: client→bridge→exit→WARP, /v1/health, exit direct vs WARP
make verify-miniapp # публичный HTTPS-вход и backend (BOT_DOMAIN, BOT_PUBLIC_URL, BOT_INGRESS_IP)Скрипты: scripts/verify-relay.sh -h, scripts/benchmark-relay.sh -h, scripts/verify-miniapp.sh. benchmark-relay не меняет конфигурацию: прямой замер с exit нужен только для отделения проблем WARP от проблем туннеля. Для сравнения реальных сайтов задайте BENCH_DOWNLOAD_URLS='https://…file1,https://…file2' — скрипт сравнит локальный системный путь и экспортированный Xray SOCKS. Быстрая проверка TLS-кандидатов: scripts/probe-tls-sni-candidates.sh.
Любое изменение пользователей или exit-нод (через API или Mini App), а также failover на другую exit приводит к пересборке конфигурации и перезапуску ядра на bridge — активные сессии могут прерваться. При частых правках имеет смысл батчить изменения. Режим blocklist с большим geosite_exit_tags увеличивает стоимость матчинга; при необходимости сузьте список тегов.
make relay-logs
# или явно: make relay-logs BRIDGE=… EXIT=… EXIT2=…
# make relay-logs SINCE_RESTART=1 # журнал с последнего restart ultra-relayСкрипт читает EXIT и EXIT2 из install.config. Если exit-нода недоступна по SSH, выводится WARNING и сбор продолжается (код выхода 1 только при недоступном bridge).
Уровень логов: ULTRA_RELAY_LOG_LEVEL (в /etc/ultra-relay/environment) или флаг -log-level.
- На каждой exit VPS должны совпадать
mimic_preset,splithttp_host,splithttp_pathи transport с bridge (при-exit-onlyони подтягиваются с bridge spec). - У каждой exit свой
tunnel_uuid; на bridge UUID хранятся вexit_nodes. - Нельзя отключить или удалить последнюю enabled exit.
- Failover v1 — primary/backup, не балансировка нагрузки между exit.
- Смена
splithttp_pathможет разорвать существующие сессии между узлами. - Для Telegram Mini App требуется FQDN с DNS A-записью на bridge и открытый порт 80 (ACME HTTP-01 challenge).
- Поведение зависит от среды; валидируйте spec и TLS на своих площадках.
См. LICENSE.