todo/tproxy/tproxy-in-bot.md
loop-uh e37bda6197
Some checks failed
Published content check / validate (push) Failing after 6s
tproxy: подписи к изображениям, единый термин, починенная ссылка
Мелкие хвосты после перепроверки раздела:

- у всех пяти изображений раздела появился альтернативный текст — он нужен
  и для доступности, и для поиска;
- режим доставки (carrier_mode) везде называется одинаково: варианты «способ
  доставки» и «режим транспорта» из заметки про бота убраны;
- ссылка [[ZaStoGram]] в обзоре раздела MTProxy вела на заметку, которой в
  хранилище нет, и отдавала 404 на сайте: заменена на внешнюю ссылку на
  исходники клиента в Forgejo.
2026-08-22 00:44:40 +03:00

186 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
date: 2026-08-22
tags:
- tproxy
- telegram
- web-прокси
- боты
- mtproxy
- выдача-доступа
aliases:
- Как выдавать WEB-прокси из Telegram-бота
- t.me/webproxy ссылка формат
- WEB-прокси в своём боте
- Раздача tproxy пользователям
- Отзыв доступа WEB-прокси
- Сколько профилей в profiles.json
- Можно ли автоматизировать выдачу WEB-прокси
link: https://github.com/telegramdesktop/tproxy-server
---
# 🤖 Выдача WEB-прокси Telegram из своего бота
![[tproxy-bot-header.png|Обложка: выдача WEB-прокси из Telegram-бота — две строки на пользователя]]
> [!info] О чём заметка
> WEB-прокси — новый, четвёртый тип прокси в Telegram, где трафик едет внутри обычных HTTPS-запросов к вашему сайту. Что это такое и зачем нужно — в обзорной заметке [[tproxy/tproxy|WEB-прокси Telegram: трафик внутри обычного сайта]]; как поднять сервер — в [[tproxy/tproxy-server-setup|Установка tproxy-server]]. Здесь разбирается только один вопрос: как раздавать такой доступ пользователям из любого Telegram-бота, что для этого нужно хранить и почему автоматическая выдача упирается в неожиданное ограничение.
> [!warning] Насколько это проверено
> Разбор сделан по исходному коду серверной части `tproxy-server` (репозиторий `telegramdesktop/tproxy-server`, состояние на 22 августа 2026) и по коду Telegram Desktop. Автор прямо помечает проект как доказательство работоспособности (`proof-of-concept`) — экспериментальную реализацию, а не готовый продукт. Поля конфигурации и формат ссылки могут поменяться без сохранения совместимости. Практических историй массовой выдачи WEB-прокси через ботов на 22 августа 2026 года нет: клиентская поддержка ещё не дошла до стабильных сборок Telegram (подробности в [[tproxy/tproxy#Можно ли этим пользоваться прямо сейчас|обзорной заметке]]).
## TL;DR
1. Пользователю нужно передать **всего два значения** — имя хоста и 32-символьный шестнадцатеричный секрет. Это на порядок проще, чем выдача VLESS или WireGuard, где генерируются ключи и целые конфигурационные файлы.
2. Ссылка для передачи выглядит как `https://t.me/webproxy?server=proxy.example.com&secret=000102…`, есть и вариант `tg://webproxy` с теми же параметрами.
3. Секреты живут в файле `profiles.json` на сервере. Их количество ограничено параметром `max_profiles` — по умолчанию **32**.
4. Главная проблема автоматизации: **сервер не умеет перечитывать конфигурацию на лету**. Добавить профиль можно только перезапуском процесса, а перезапуск рвёт активные сессии всех остальных пользователей.
5. Отзыв доступа у одного человека возможен только сменой секрета его профиля. Поэтому «один профиль на пользователя» упирается в потолок из пункта 3, а «один общий секрет на всех» лишает возможности отключить конкретного человека.
6. Практичный вывод на август 2026: бот может раздавать заранее нарезанный пул профилей, но не выдавать доступ по-настоящему динамически.
## Что вообще нужно выдать пользователю
Сначала — чем WEB-прокси отличается от привычных схем выдачи. Когда бот выдаёт доступ к VLESS, он создаёт на сервере отдельную запись пользователя со своим идентификатором `UUID` — длинным уникальным номером, который сервер сверяет при каждом подключении. Когда бот выдаёт WireGuard, он генерирует пару ключей и собирает целый конфигурационный файл с адресами и маршрутами. В обоих случаях на сервере появляется физическая сущность, привязанная к конкретному человеку.
С WEB-прокси всё устроено иначе, и это устройство унаследовано от MTProxy. Пользователю передаются ровно два поля:
```text
Hostname: proxy.example.com
Secret: 000102030405060708090a0b0c0d0e0f
```
Имя хоста пишется без `https://`, без порта, без слеша и без параметров: тип прокси WEB жёстко подразумевает HTTPS и порт 443. Секрет — те же 32 шестнадцатеричных символа (16 байт), что и у обычного MTProxy; общепринята запись в нижнем регистре, её же требует установщик сервера. Никаких пользовательских ключей, сертификатов и профилей на стороне клиента не создаётся.
Проще говоря: доступ к WEB-прокси — это не персональная учётная запись, а знание пароля. Кто знает пару «домен + секрет», тот подключается; сервер не различает, один это человек или тысяча.
Из этой пары клиент сам, локально, вычисляет так называемый **bridge-capability** — производный «пропуск», который потом уходит в сетевой запрос вместо самого секрета. Считается он как `HMAC-SHA256`, где ключом служат байты секрета, а сообщением — строка `tdesktop-web-proxy-bridge-v1\n` вместе с именем хоста. HMAC — это способ получить из ключа и сообщения короткий отпечаток, который невозможно подделать, не зная ключа.
Практическое следствие этой формулы важнее самой криптографии: **пропуск привязан к домену**. Один и тот же секрет на двух разных доменах даёт два разных пропуска. Поэтому бот не может выдать пользователю «секрет вообще» — он всегда выдаёт пару. При переезде на другой домен ранее выданные ссылки ломаются просто потому, что в них записан старый домен. Сам секрет при этом не обесценивается: если тот же секрет прописан в профиле нового домена, достаточно перевыдать ссылку с новым адресом. Обратное тоже верно — один секрет на двух доменах технически допустим, но связывает обе площадки одним ключом.
## Формат ссылки и как её отдавать в чате
Готовая ссылка, которую бот отправляет пользователю сообщением:
```text
https://t.me/webproxy?server=proxy.example.com&secret=000102030405060708090a0b0c0d0e0f
```
Клиенты также принимают эквивалентную запись через внутреннюю схему приложения: `tg://webproxy?server=…&secret=…`. Это тот же самый набор параметров, просто ссылка открывается напрямую установленным клиентом, минуя веб-страницу.
> [!warning] Публичный t.me пока не знает этого маршрута
> В документации проекта прямо сказано: публичный фронтенд `t.me` маршрут `webproxy` пока не регистрирует. То есть ссылка вида `https://t.me/webproxy?...` на 22 августа 2026 года не обязательно откроется как прокси — при тестировании её нужно скармливать клиенту напрямую. Для бота это значит, что нельзя рассчитывать на привычный сценарий «пользователь нажал на ссылку, всё настроилось само»: пока разумнее отдавать и ссылку, и два поля отдельным текстом, чтобы человек мог ввести их руками.
Отдельная тонкость касается доменов с национальными буквами. Если ваш домен интернационализированный (например, кириллический), пользователю нужно публиковать его в форме `xn--…` — это ASCII-запись такого имени, называемая A-label. Причина техническая: разные версии библиотеки, на которой построен клиент, по-разному приводят некоторые символы к каноническому виду, из-за чего один и тот же домен на Windows и на Linux даёт **разные пропуски** и подключение молча не состоится — подробности в [[tproxy/tproxy-protocol|разборе протокола]]. Бот, который отдаёт домен в ASCII-форме, эту проблему обходит целиком.
## Где на сервере живут секреты
Все выданные секреты перечислены в одном файле; путь к нему задаётся обязательным полем настроек. В эталонной установке оператор правит `/etc/tproxy-server/profiles.json`, а служба читает его копию, которую systemd подкладывает во временный каталог при каждом старте. Структура файла проста:
```json
{
"profiles": [
{
"name": "alpha",
"secret": "0123456789abcdef0123456789abcdef",
"backend": "127.0.0.1:2398",
"carrier_mode": "https"
},
{
"name": "beta",
"secret": "fedcba9876543210fedcba9876543210",
"backend": "127.0.0.1:2399",
"carrier_mode": "websocket",
"limits": {
"max_sessions": 32,
"max_streams": 512,
"max_streams_per_session": 32
}
}
]
}
```
Каждая запись здесь называется **профилем** — это именованный набор «секрет плюс правила», под которым подключается группа пользователей. От этих полей зависит вся модель выдачи.
**`name`** — идентификатор от 1 до 64 символов, уникальный внутри файла. Он не виден пользователю и нужен серверу для учёта: по имени профиля считаются его сессии, подключения к бэкенду и ограничения скорости создания новых соединений.
**`secret`** — тот самый секрет, который получает пользователь. Принимается в шестнадцатеричном виде (32 или 34 символа) либо в кодировке base64url. Раскодироваться он обязан ровно в 16 или 17 байт, причём 17-байтовый вариант должен начинаться с байта `dd` — это унаследованный от MTProxy признак режима случайного дополнения. Два профиля не могут иметь секреты, дающие одинаковый пропуск: такой файл сервер не примет.
**`backend`** — адрес, куда реле отправляет извлечённый из кадров трафик этого профиля, то есть адрес работающего рядом MTProxy. Обязательно локальный числовой адрес вида `127.0.0.1:2398`; имена вроде `localhost` и внешние адреса отвергаются. Это осознанное ограничение архитектуры: реле физически не может отправить трафик наружу, куда попросит клиент, поэтому даже взломанное реле не превращается в открытый прокси для всего интернета.
**`carrier_mode`** — режим доставки, один из четырёх (`https`, `https-lanes`, `websocket`, `websocket-lanes`); по умолчанию `https`. Подробнее эти режимы разобраны в [[tproxy/tproxy-protocol|заметке про устройство протокола]]. Здесь важно другое: режим выбирается **на сервере, через профиль**, и клиент про него ничего не знает. Значит, бот может выдавать разным группам пользователей разные секреты и тем самым переключать им режим доставки — например, отдавать `websocket` тем, у кого сеть его пропускает, и консервативный `https` остальным. Со стороны пользователя это по-прежнему просто «другой секрет».
**`limits`** — необязательные квоты профиля: сколько одновременных сессий и потоков ему разрешено, с какой скоростью он может создавать новые, сколько данных копить. Пропущенное значение наследуется из общих настроек сервера (а два поля — предел потоков на сессию и число одновременных подключений к бэкенду — берут меньшее из общей настройки и собственного предела потоков профиля). Заданное значение может только **понизить** общий потолок, но не поднять; ноль означает «наследовать», поэтому обнулить лимит в профиле нельзя. Для бота это единственный встроенный механизм справедливого дележа: если один секрет раздали слишком широко, его квота не даст ему съесть весь сервер.
У файла не должно остаться ни одного бита прав для группы и для остальных — на практике это `0400`, но и `0600` проверку проходит. Сервер это проверяет и отказывается стартовать, если файл доступен группе или всем остальным. Исключение сделано для механизма systemd, который подкладывает секреты сервису во временный каталог; тогда допускаются права только на чтение.
## Главное ограничение: перезапуск ради каждого нового пользователя
**Сервер читает конфигурацию ровно один раз — при запуске.** Обработчика сигнала на перечитывание файлов в коде нет: процесс подписан только на сигналы завершения работы. Значит, добавление нового профиля в `profiles.json` само по себе не делает ничего — сервер продолжает работать со старым списком секретов, пока его не перезапустят.
А перезапуск, в свою очередь, не бесплатный. В документации это сказано прямо: рестарт реле **аннулирует все активные сессии**, и существующие соединения намеренно не восстанавливаются. Клиенты после этого пересоздают транспорт и подключаются заново, то есть у каждого активного пользователя в этот момент происходит обрыв связи на несколько секунд.
Из этих двух фактов получается неприятная арифметика. Классическая модель бота «пользователь оплатил — бот тут же выдал ему персональный доступ» здесь означает: каждая новая покупка перезапускает сервис и роняет сессии всех остальных. При десятке выдач в час прокси начинает моргать почти непрерывно. Проще говоря: выдача доступа перестаёт быть операцией над одним пользователем и становится операцией над всем сервером.
Отсюда следуют три работающие стратегии, и выбирать приходится осознанно.
Прежде чем их перечислять, важная деталь, которую легко упустить: **секрет должен знать не только реле, но и MTProxy за ним**. Реле передаёт байты рукопожатия прозрачно и в них не заглядывает, поэтому каждый выданный секрет обязан быть перечислен в аргументах запуска MTProxy. Профиль, добавленный только в файл профилей, даст странную картину: настройки вроде бы верные, а подключение не проходит. Хорошая новость в том, что перезапуск MTProxy, в отличие от перезапуска реле, активные сессии не рвёт — потоки просто переподключаются через живую сессию.
**Пул заранее нарезанных профилей.** Оператор один раз создаёт N профилей со случайными секретами (по умолчанию их не может быть больше 32 — это параметр `max_profiles`), прописывает те же секреты бэкенду MTProxy, запускает сервер и дальше просто раздаёт неиспользованные секреты из этого пула. Бот хранит соответствие «профиль → пользователь» в своей базе, а сервер о пользователях ничего не знает. Перезапуски не нужны вовсе, пока пул не исчерпан. Плата: потолок числа выданных секретов на один домен — по умолчанию 32, поднять его можно, но это правка файла настроек и, значит, тот же перезапуск, — и необходимость планировать пул заранее.
**Пакетное применение.** Бот копит заявки и применяет их окном — например, раз в сутки ночью, одним перезапуском. Пользователь получает доступ не мгновенно, а к утру. Для платного сервиса это обычно неприемлемо, для закрытого круга — вполне.
**Один общий секрет.** Все пользователи получают один и тот же секрет, как это давно делается с обычным MTProxy. Выдача становится мгновенной и бесплатной, перезапусков нет вообще. Плата — невозможность отключить одного человека.
## Отзыв доступа: то, чего в этой модели нет
Отзыв — обратная сторона «доступ это просто знание пароля». Раз сервер не различает пользователей внутри профиля, отобрать доступ у одного человека, не тронув остальных, невозможно. Единственный способ сделать секрет недействительным — изменить его в `profiles.json` и перезапустить реле. После этого доступ теряют **все**, кому этот секрет был выдан.
Для сравнения: в VLESS отзыв — это удаление конкретной записи пользователя на узле, и соседи ничего не замечают. Именно поэтому подписочные сервисы обычно строятся на VLESS, а не на MTProxy: без индивидуального отзыва невозможно закрыть доступ по окончании оплаченного периода.
Если индивидуальный отзыв нужен, остаётся схема «один профиль на пользователя»: тогда смена секрета в его профиле бьёт только по нему. Но эта схема сразу упирается во всё те же два ограничения — потолок числа профилей и перезапуск сервиса ради изменения. Плюс появляется третье, но здесь важно не преувеличить. Если всем профилям достаточно общей политики и маршрутизации, им хватит одного процесса MTProxy, которому просто перечислены все секреты. Отдельный процесс на своём порту (и своя строка в правилах файрвола) нужен только тем профилям, которым требуется собственная маршрутизация или раздельная статистика на стороне MTProxy: сам MTProxy ведёт учёт на уровне процесса, а не отдельного секрета.
> [!important] Честный вывод про биллинг
> На август 2026 WEB-прокси не годится для сервиса с оплатой по подписке и автоматическим отключением после окончания периода. Механизма, который делает это дёшево, в архитектуре нет: ни персональных учётных записей, ни горячего применения изменений, ни отзыва без ущерба соседям. Он хорошо подходит для другого — раздать рабочий доступ ограниченному кругу людей там, где остальные способы не проходят по сети.
## Что бот должен хранить у себя
Раз сервер не ведёт учёт пользователей, всё состояние ложится на бота. Минимальный набор полей на одну выдачу:
- имя хоста, с которого выдан доступ (оно же часть пропуска, поэтому обязательно сохранять вместе с секретом, а не подставлять «текущий домен» при показе);
- имя профиля на сервере — чтобы знать, какую запись править при отзыве;
- сам секрет в открытом виде: из работающего процесса реле его уже не достать (после старта в памяти остаётся только производный пропуск), но на сервере он продолжает лежать открытым текстом в файле профилей и в переменных окружения MTProxy. Хранить его у бота нужно не потому, что он безвозвратно теряется, а чтобы не ходить за ним на сервер и не зависеть от его переустановки;
- идентификатор пользователя Telegram и дата выдачи;
- режим доставки, если разным группам выдаются разные (иначе потом не понять, почему у части людей другое поведение).
Отдельно стоит подумать про учёт трафика. Реле публикует метрики на локальном служебном порту, но это счётчики классов событий по процессу, а не разбивка по людям — и это сделано намеренно, чтобы не хранить лишнего. Если нужен учёт хотя бы по группам, единственный доступный способ — дать каждой группе свой профиль со своим MTProxy и снимать статистику с этого процесса. Индивидуального учёта по пользователям в этой схеме нет, и в архитектуре первой версии места под него не заложено.
## Сколько ботов и сколько доменов
Ещё одно ограничение, которое стоит заложить в архитектуру сразу: **один процесс реле обслуживает ровно одно имя хоста** — в настройках ровно одно поле для публичного домена (подробнее в [[tproxy/tproxy-server-setup|заметке про установку]]).
Поэтому масштабирование выглядит так: каждому новому серверу — свой домен, своя DNS-запись, свой сертификат и свой набор профилей. Бот, раздающий доступ на несколько серверов, обязан хранить домен вместе с каждой выдачей и не пытаться «переназначить» пользователя на другой сервер простой заменой адреса — секрет там другой, и пропуск не совпадёт.
Обратная сторона: домены получаются независимыми. Если один из них попадёт под блокировку, остальные продолжат работать, а бот сможет перевыдать пострадавшим пользователям пару с другого сервера. Для схемы с одним общим секретом это, пожалуй, главный аргумент держать хотя бы два домена.
## Пошаговый план внедрения
- [ ] Поднять сервер по инструкции из [[tproxy/tproxy-server-setup|заметки про установку]] и убедиться, что обычный сайт на домене открывается, а служебные порты снаружи закрыты.
- [ ] Решить модель выдачи: пул профилей, пакетное применение или один общий секрет. От этого зависит вся остальная логика бота.
- [ ] Если выбран пул — сгенерировать секреты заранее (`openssl rand -hex 16` на каждый), вписать их в `profiles.json` одним разом и запустить сервер; после этого перезапуски не понадобятся.
- [ ] Завести в базе бота таблицу выдач с полями из раздела выше, обязательно включая домен и имя профиля.
- [ ] Научить бота отдавать пользователю и готовую ссылку, и два поля отдельным текстом — на случай, если ссылка `t.me/webproxy` в его клиенте не сработает.
- [ ] Заранее написать процедуру отзыва (смена секрета плюс перезапуск) и честно посчитать, скольких пользователей она затронет.
- [ ] Проверить, что в журналах бота и сервера не оседают ни секреты, ни ссылки с пропуском: адрес bridge-запроса содержит производный пропуск, и его попадание в логи равносильно утечке доступа.
## 📚 См. также
- [[tproxy/tproxy|WEB-прокси Telegram: трафик внутри обычного сайта]] — обзор нового типа прокси и ответ на вопрос, можно ли им пользоваться уже сейчас.
- [[tproxy/tproxy-server-setup|Установка tproxy-server: свой WEB-прокси на своём домене]] — развёртывание сервера от DNS-записи до проверки.
- [[tproxy/tproxy-protocol|Как устроен WEB-прокси изнутри]] — кадры, пропуск, режимы транспорта и лимиты.
- [[mtproxy/mtproxy|MTProxy и Telegram-транспорты — раздел]] — обычный MTProxy, от которого WEB-прокси унаследовал модель секретов.
- 🔗 [telegramdesktop/tproxy-server](https://github.com/telegramdesktop/tproxy-server) — исходники серверной части и вся официальная документация проекта.
---
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: [исходник этой заметки](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/tproxy/tproxy-in-bot.md) · [скачать весь репозиторий одним zip-архивом](https://git.zapret.moe/zapretdiscordyoutube/todo/archive/main.zip).