Telegram Tunnel — справочник для zapret-gui
SkillCommunicationComplete reference for Telegram Tunnel in the zapret-gui project (Keenetic routers on Entware / OpenWrt): local MTProto proxy tg-ws-proxy-go (main engine, package tg-ws-proxy + init.d S99tg-ws-proxy) and backup tg-mtproxy-client. Use for any questions about: config.conf/secret.conf and whi
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Telegram Tunnel — справочник для zapret-gui skill
What this skill tells your AI
The instructions your AI receives, as published by avatardd/zapret-gui in .claude/skills/telegram-tunnel/SKILL.md and read by ahel’s review.
Единый источник истины о том, как работает обход блокировки Telegram в
zapret-gui и что именно можно менять, не сломав рабочую конфигурацию у
пользователей. Читать перед тем, как трогать config.conf, набор CLI-флагов,
режимы выхода на датацентр, ссылку tg://proxy или маршрутизацию DC-подсетей.
Источники истины (в порядке убывания авторитета):
- spatiumstas/tg-ws-proxy-go
— то, что мы реально ставим и запускаем. Ключевые файлы:
на теге
0.9.3— последнем, где проект был роутерным демоном на Go (с v1.0.0 это десктопное приложение на Python, см. §7; файлов ниже там уже нет). Ключевые файлы:src/config.go(полный список флагов — единственный достоверный источник имён),src/constants.go(дефолты, таймауты, IP датацентров),files/common/etc/tg-ws-proxy/config.conf(набор переменных),files/entware/etc/init.d/S99tg-ws-proxy(как переменные превращаются в argv — важнее README). - Flowseal/tg-ws-proxy —
апстрим-апстрим (Windows-версия):
docs/CfProxy.md,docs/CfWorker.md, community-пул доменов.github/cfproxy-domains.txt. - Наш код —
core/tgproxy_manager.py(менеджеры обоих движков, конфиг, ссылка, DC-маршруты),api/tgproxy.py(REST),web/js/pages/tgproxy.js(страница),core/ext_binary_installer.py(BINARIES["tgwsproxy"]),app.py(автозапуск при boot),core/config_manager.py(секцияtgproxy).
⚠️ Главное, что ломается молча: мы не запускаем бинарник сами — мы пишем
config.confи дёргаем чужой init.d. Любое поле, которого нет в списке переменных init.d, не делает ничего, хотя в GUI выглядит как настройка. Так уже было сLOG_LEVEL(§4.3).
1. Что это вообще и зачем
Проблема. У части провайдеров Telegram режется по диапазону IP датацентров, а не только по сигнатуре протокола. Против этого не помогает ни fake-TLS, ни десинхронизация nfqws2: они меняют содержимое потока, но пакет всё равно летит на заблокированный адрес.
Решение tg-ws-proxy-go. На роутере поднимается локальный MTProto-прокси.
Приложение Telegram подключается к нему по обычной ссылке tg://proxy (то есть
считает его прокси-сервером), а наружу соединение уходит не TCP-коннектом на
IP датацентра, а WSS-соединением — через домены Telegram, а при их
недоступности через Cloudflare (обычный CDN-домен или Worker). Для
провайдера это TLS к Cloudflare, а не к диапазону Telegram.
VPS не нужен. Сервер (движок) и клиент (приложение) — в одной домашней сети, трафик между ними не покидает LAN, белый IP не требуется.
Почему не teleproxy. Его Direct-to-DC режим по конструкции коннектится на
настоящий IP датацентра — то есть ровно туда, куда нельзя. Осознанное решение,
зафиксировано в docstring core/tgproxy_manager.py, не «забыли добавить».
Два движка:
| Движок | Что это | Роль |
|---|---|---|
| tgwsproxy | tg-ws-proxy-go, ставится пакетом (opkg/apk), свой init.d, свой PID-файл и лог | Основной |
| mtproto | tg-mtproxy-client, голый Go-бинарник, relay-based, управляем через subprocess.Popen | Резерв на случай отказа всей инфраструктуры Cloudflare (у tgwsproxy это общая точка отказа) |
Одновременно должен работать только один: это две разные ссылки
tg://proxy, приложение использует одну.
2. Цепочка соединения (что происходит на самом деле)
Telegram (телефон/десктоп)
│ tg://proxy?server=<LAN-IP роутера>&port=1443&secret=dd…/ee…
│ обычный MTProto с обфускацией
▼
tg-ws-proxy-go на роутере ← слушает HOST:PORT
│ 1. читает DC ID из обфусцированного init-пакета
│ 2. держит пул WS-соединений (--pool-size на каждый DC)
▼
WSS к инфраструктуре Telegram
│ если прилетает 302 → фоллбэк:
├─► CfProxy: WSS к Cloudflare-домену (свой / community-пул / Worker)
└─► прямой TCP к IP датацентра (--dc-ip-default / --dc-ip)
Данные идут в том же зашифрованном виде — прокси не расшифровывает MTProto, он меняет только транспорт и адрес назначения.
Дефолтные IP датацентров зашиты в src/constants.go:
DC1 149.154.175.50, DC2 149.154.167.51, DC3 149.154.175.100,
DC4 149.154.167.91, DC5 149.154.171.5, DC203 91.105.192.100.
defaultCFProxyDomain = "pclead.co.uk", defaultMaxConns = 256.
Важные таймауты (оттуда же, пригодятся при объяснении «почему отвалилось через
минуту»): wsPoolMaxAge 120s, ioIdleTimeout 5m, dcFailCooldown 60s,
ipFailCooldown 1h, frontingCooldown 30m, dcBlacklistTTL 10m,
cfProxyFailCooldown 1m, обновление списка CF-доменов раз в час.
3. CLI-флаги (сверено с src/config.go)
Go-шный flag понимает и -flag, и --flag; повторный флаг — побеждает
последний (это важно, см. §4.2).
| Флаг | Тип | Дефолт | Смысл |
|---|---|---|---|
-host | string | 127.0.0.1 | адрес прослушивания |
-port | int | 1443 | порт |
-secret | string | — | MTProto secret, 32 hex |
-gen-secret | bool | false | сгенерировать секрет и выйти |
-print-link | bool | false | напечатать tg:// ссылку и выйти |
-v | bool | false | verbose |
-log-file | string | — | путь к логу |
-log-max-mb | float | 5 | ротация лога |
-log-backups | int | 0 | сколько ротаций хранить |
-buf-kb | int | 64 | размер сокет-буфера |
-pool-size | int | 4 | размер WS-пула на каждый DC |
-max-conns | int | 256 | максимум клиентских сессий |
-fake-tls-domain | string | — | включить Fake TLS (ee-секрет) с этим доменом |
-cfproxy-domain | string | pclead.co.uk | свой домен за Cloudflare CDN |
-cfproxy-domains | string | — | пул CF-доменов через запятую |
-cfproxy-worker-domain | string | — | домен(ы) CF-Worker через запятую |
-cfproxy-domains-url | string | — | URL со списком CF-доменов |
-no-cfproxy | bool | false | выключить CF-фоллбэк совсем |
-cfproxy-priority | bool | true | пробовать cfproxy до TCP-фоллбэка |
-no-cfproxy-domain-refresh | bool | false | не обновлять список доменов по URL |
-dc-ip-default | string | 149.154.167.220 | IP для всех неявных DC |
-dc-ip-default-pool | string | — | пул IP через запятую |
-dc-ip | repeat | — | DC:IP, можно повторять |
-dc-ip-pool | repeat | — | DC:IP1,IP2, можно повторять |
-pprof-listen | string | — | адрес pprof |
4. config.conf → argv: что init.d реально делает
4.1. Переменные, которые читает init.d
files/common/etc/tg-ws-proxy/config.conf (дефолты апстрима):
HOST=0.0.0.0
PORT=1443
DC_IP_DEFAULT=149.154.167.220
DC_IP_DEFAULT_POOL=""
FAKE_TLS_DOMAIN=""
CFPROXY_DOMAINS=""
CFPROXY_DOMAINS_URL="https://raw.githubusercontent.com/Flowseal/tg-ws-proxy/main/.github/cfproxy-domains.txt"
CFPROXY_WORKER_DOMAINS=""
EXTRA_ARGS=""
SECRET лежит отдельно в secret.conf (у нас — права 0600).
4.2. Как строится командная строка
set -- --host "$HOST" --port "$PORT" --secret "$SECRET" \
--dc-ip-default "$DC_IP_DEFAULT" --dc-ip-default-pool "$DC_IP_DEFAULT_POOL"
[ -n "$CFPROXY_DOMAINS" ] && set -- "$@" --cfproxy-domains "$CFPROXY_DOMAINS"
[ -n "$CFPROXY_DOMAINS_URL" ] && set -- "$@" --cfproxy-domains-url "$CFPROXY_DOMAINS_URL"
[ -n "$CFPROXY_WORKER_DOMAINS" ] && set -- "$@" --cfproxy-worker-domain "$CFPROXY_WORKER_DOMAINS"
[ -n "$FAKE_TLS_DOMAIN" ] && set -- "$@" --fake-tls-domain "$FAKE_TLS_DOMAIN"
[ -n "$EXTRA_ARGS" ] && set -- "$@" $EXTRA_ARGS
case " $EXTRA_ARGS " in *" -v "*) set -- "$@" --log-file "$LOGFILE" ;; esac
Три следствия, на которые опирается наш код:
EXTRA_ARGSидёт последним → всё, что мы туда кладём, перекрывает собранное выше. Именно поэтому--cfproxy-domain(для которого переменной вconfig.confнет вообще) передаётся только так.config.confшелломsource-ится → значения обязаны быть в кавычках, а управляющие символы отсекаться до записи. У нас:_shell_quote_value()=shlex.quoteна каждое значение + отдельная проверка на< 32 / 127и на$`; & | < >вextra_args.EXTRA_ARGSподставляется без кавычек ($EXTRA_ARGS, намеренно — чтобы шелл разбил на слова) → значение внутри флага не должно содержать пробелов.
4.3. LOG_LEVEL — мёртвая переменная (важно)
LOG_LEVEL не читает никто: ни init.d, ни бинарник (флага такого нет).
Verbose включается флагом -v в EXTRA_ARGS, и обязательно с одним
дефисом: init.d ищет *" -v "*, а --v под шаблон не попадает — бинарник
его поймёт, но --log-file дописан не будет и лога не появится.
Поэтому save_config() сам добавляет -v в EXTRA_ARGS, когда log_level
не в ("", "0", "off", "false"). Ключ LOG_LEVEL мы продолжаем писать только
как учётное поле GUI.
4.4. Наши собственные X_-поля
Шелл молча игнорирует незнакомые переменные, чем мы и пользуемся: X_CF_DOMAIN,
X_CF_WORKER_DOMAIN, X_MODE, X_POOL_SIZE, X_MAX_CONNS, X_BUF_KB,
X_NO_CFPROXY_DOMAIN_REFRESH, X_EXTRA_ARGS — источник истины для GUI,
EXTRA_ARGS — то, что реально уходит бинарнику.
Зачем X_EXTRA_ARGS отдельно: в EXTRA_ARGS лежит смесь пользовательских
флагов и сгенерированных нами (--no-cfproxy, --pool-size=…). Отдавать эту
смесь наружу как extra_args нельзя — GET config → PUT config тем же телом
падал бы на whitelist (Недопустимый extra_args флаг: --no-cfproxy).
get_config() возвращает пользовательскую часть в extra_args, а полную
строку — в extra_args_effective (только для показа).
Whitelist пользовательских флагов (_ALLOWED_USER_EXTRA_FLAGS): --v,
--log-file, --log-max-mb, --log-backups, --pprof-listen,
--no-cfproxy-domain-refresh. Всё остальное — либо генерируем сами из
валидированных полей, либо запрещаем: --secret из extra_args не должен
уметь перетереть секрет.
5. Режимы выхода на датацентр (наш слой)
X_MODE — наша абстракция, у апстрима её нет. Маппинг в флаги (save_config):
| Режим (GUI) | X_MODE | Что добавляется в EXTRA_ARGS |
|---|---|---|
| Прямое подключение | direct | --no-cfproxy |
| Cloudflare community | cfcommunity | ничего (дефолт бинарника: cfproxy включён, приоритетен) |
| Cloudflare custom domain / Worker | cfdomain | --cfproxy-domain=<dom> или --cfproxy-worker-domain=<dom> |
| Hybrid | hybrid | --cfproxy-priority=false (сначала прямое, потом CF) |
| Через WARP-туннель | tunnel | --no-cfproxy + маршрут DC-подсетей в туннель (§8) |
Всегда добавляются --pool-size / --max-conns / --buf-kb из профиля
ресурсов (stealth 1/32/32 · balanced 2/64/64 · low latency 4/128/128).
Инварианты:
cf_domainиcf_worker_domainвзаимоисключающие — ошибка, если заданы оба.cfdomainбез домена — ошибка.- Режимы
cfdomainиtunnelбессмысленно сочетать: при CF-домене исходящее соединение идёт на IP Cloudflare, а маршрут туннеля матчит IP Telegram — просто ни на что не влияет. - Легаси-конфиг без
X_MODE(написан руками или старым GUI):get_config()выводит режим по наличию CF-домена, а не отдаёт «direct» вслепую.
6. Ссылка tg://proxy и секрет
Формат секрета — публичная конвенция MTProxy, не наша выдумка:
dd + <32 hex> → обычный secure-режим
ee + <32 hex> + hex(fake_tls_domain) → fake-TLS (ee), SNI-фронтинг
_build_proxy_link(host, port, secret_hex, fake_tls_domain) собирает
tg://proxy?server=…&port=…&secret=….
Про host в ссылке: если HOST = 0.0.0.0, в ссылку подставляется LAN-адрес
роутера (_lan_ip() — UDP-сокет на 10.255.255.255, ничего не отправляет).
Не определился — пользователь вводит адрес сам; молча подсовывать 127.0.0.1
нельзя, по такой ссылке телефон не подключится.
Fake-TLS домен маскирует ТОЛЬКО входящее соединение клиента к роутеру
(режим ee). Он никак не влияет на TLS-фингерпринт исходящего WSS до
Telegram/Cloudflare — на странице это написано прямым текстом, не убирать.
Ротация секрета (rotate_secret) требует confirm=True: все выданные
ссылки мгновенно перестают работать. Обычное сохранение настроек секрет не
трогает — если secret пустой, берётся существующий из secret.conf.
7. Установка и детект
BINARIES["tgwsproxy"] в core/ext_binary_installer.py:
-
репозиторий
spatiumstas/tg-ws-proxy-go, тег закреплён:release_tag: "0.9.3".⚠️ Апстрим ушёл с роутеров. В v1.0.0 проект переписан с Go на Python и превращён в десктопное GUI-приложение (сборка PyInstaller под Windows/macOS/Linux). Роутерной упаковки там больше нет вообще: в
0.9.3её 52 файла (Makefile,common/ipk/*, init.d), начиная сv1.0.0— ноль, а релизы несут.exeвместоtg-ws-proxy_<ver>_entware_<arch>.ipk. Заодно исчез иsrc/config.go, на который ссылается §3 — он существует только до0.9.3.Раньше здесь стояло
release_tag: ""(«ставить последний релиз») — пока апстрим оставался демоном, это было правильно. После пивота установка уходила в/releases/latest, не находила ни точного имени ассета, ни версионно-независимого суффикса, и падала на 404: движок просто не ставился. Показыватьv1.4.0как «доступно обновление» тоже неверно — это программа для другой платформы.Расфиксировать тег можно только вместе с переездом на другой источник (другой форк или своя сборка). Сторожи: поле
holdу записиtg-ws-proxy-goвdocs/upstream.jsonиTestTgWsProxyPinnedToRouterReleaseвtests/test_binary_installer.py; -
install_kind: "package"— качаем пакет, а не бинарник: Entware.ipk(aarch64, armv7, mips, mipsel) →opkg --force-reinstall install <file>; OpenWrt.apk(aarch64, mips, mipsel) →apk add --allow-untrusted <file>; -
имя ассета версионировано (
tg-ws-proxy_0.9.3-1_entware_aarch64-3.10.ipk), поэтому имя изpackage_assetsгодится только дляpinned_tag. Для более новой версии ассет ищется по версионно-независимому хвосту изpackage_asset_suffixes(_entware_aarch64-3.10.ipk) — без этого «последний релиз» упирался бы в fallback-URL с несуществующим именем; -
целостность:
sha256_map(ключиopkg:<arch>/apk:<arch>) относится кpinned_tag. Приехал он — сверка обязательна и fail-closed (нет хэша под архитектуру — тоже отказ). Версия новее — фиксированного хэша быть не может, используется файл контрольных сумм релиза, если апстрим его публикует; сейчас не публикует, поэтому установка возвращаетsha256_verified=false, GUI показывает это тостом, установщик пишет в лог. Молчать об этом нельзя; -
«уже актуально» для пакетов сравнивается через
_pkg_version_matches_tag(): opkg/apk отдают версию с ревизией сборки (0.9.3-1,0.9.3-r1), тег релиза — без неё, и прямое сравнение никогда не совпадало (пакет качался заново на каждое нажатие); -
пользовательские
config.conf/secret.confпри обновлении не теряются: апстрим объявляет оба файла вconffiles.postinstпри этом сам генерирует SECRET, если он пуст, и делаетinit.d restart; -
маркер установки —
status_file= init.d-скрипт, а неdest: бинарник пакет кладёт куда хочет.
Бампить версию так: скачать ассеты нового релиза, посчитать sha256,
обновить pinned_tag + arch_map/package_assets + sha256_map.
Процедуру стоит проверить, пересчитав хэши предыдущей версии — они должны
совпасть с тем, что уже лежит в манифесте.
Детект в менеджере (_find_tgwsproxy_initd) — по исполняемому init.d:
/opt/etc/init.d/S99tg-ws-proxy (Entware), /etc/init.d/tg-ws-proxy и
/etc/init.d/S99tg-ws-proxy (OpenWrt). Каталог конфига выбирается по тому
же признаку: /opt/… → /opt/etc/tg-ws-proxy, /etc/… →
/etc/tg-ws-proxy; на dev-хосте без пакета — временный каталог, чтобы не
писать в read-only /opt.
Управляем только через init.d (start/stop/status). Не пытаться
демонизировать процесс самим: пакет уже ведёт PID-файл и лог, вторая
демонизация рассинхронизирует PID и сломает рестарты.
start() не верит коду возврата init.d start (тот возвращается сразу после
форка): спит секунду и проверяет фактическое состояние. _status_locked() тоже
не полагается на один сигнал — init.d status плюс независимая TCP-проба
порта.
8. Маршрутизация датацентров Telegram через WARP-туннель
Альтернатива CF-домену: пустить трафик к DC через уже поднятый AWG+WARP или
MASQUE(usque)+WARP. Делается штатным единым слоем (core.unified), а не
новоделом: save_route({... "method": "warp:<iface>" | "awg:<iface>"}) →
applier._apply_tunnel() → CidrRoutingRule.
_DC_ROUTE_ID = "tgproxy-telegram-dc-via-tunnel"— фиксированный id, маршрут ровно один и он пересоздаётся, а не плодится.TELEGRAM_DC_CIDRS— выгрузкаcore.telegram.org/resources/cidr.txt, тот же источник, что уimport/lists/ipset-telegram.txt(тест сверяет их автоматически). IPv6-диапазоны сознательно не берём: на IPv4-только туннелеip -6 ruleне на что вешать, и маршрут отчитался бы ошибкой целиком.list_available_warp_tunnels()отдаёт{kind, iface, label, running}. Имя AWG-конфига ≠ имя интерфейса:awg0-opkgtun0.confживёт наopkgtun0. Братьcfg["iface"], иначеip ruleвешается на несуществующий интерфейс и правило навсегда остаётсяdeferred.- Имя интерфейса приходит с клиента и уходит в argv
ip— валидируется регуляркой. - Снятие маршрута идемпотентно: «маршрута нет» — это уже нужное состояние.
Честное предупреждение в GUI: WARP-туннель и общий VPN — одна и та же инфраструктура. Упадёт WARP — исчезнет и обход Telegram. CF-домен от состояния вашего туннеля не зависит. Не убирать этот текст.
9. Интеграция с nfqws2
nfqws2 поверх tgwsproxy — вторая независимая линия защиты на случай, если провайдер научится фингерпринтить сам WSS-хендшейк к Cloudflare. Не замена CF-фоллбэку.
Практически: домен, через который идёт CF-прокси, должен попасть в hostlist,
который обрабатывает nfqws2. Для явно заданного cf_domain /
cf_worker_domain мы это делаем сами —
_register_cf_domain_for_nfqws() заводит маршрут единого слоя с методом
nfqws2 и фиксированным id _CF_DOMAIN_ROUTE_ID. Убрали домен — маршрут
снимается (_unregister_cf_domain_for_nfqws).
Для community-пула так делать нельзя: домен выбирает сам бинарник во время работы, заранее он неизвестен — обещать обратное было бы дезинформацией.
10. Резервный движок tg-mtproxy-client
Голый Go-бинарник (/opt/usr/bin/tg-mtproxy-client, /opt/sbin/…), собираем
сами — .github/workflows/build-tgproto-binaries.yml (у апстрима в релизах
нет aarch64/armv7). Запуск: --listen <host>:<port> --tunnel-url <relay> --tunnel-secret <hex>.
10.1 Это НЕ MTProto-прокси
Главное заблуждение об этом движке. В отличие от tg-ws-proxy, он не говорит
по MTProto и ссылки tg://proxy у него нет. Это прозрачный форвардер:
на каждом принятом соединении он читает исходный адрес назначения через
SO_ORIGINAL_DST (listener.go, tunnel.go:487), то есть работает только
с трафиком, завёрнутым на его порт правилом iptables/nft REDIRECT, и гонит
сырой TCP на релей по WebSocket.
Следствия:
- отдавать для него
tg://proxy?...бессмысленно — на этом порту никто не ответит по MTProto (раньшеget_connect_info()так и делал, со случайным секретом); - без REDIRECT-правил на CIDR датацентров Telegram движок не получит ни одного соединения.
Правила ставит и снимает сам менеджер — core/tgproxy_redirect.py,
вызывается из start()/stop(). Ничего настраивать руками не нужно.
| Цепочка (iptables) | ZAPRET_TGDC в таблице nat |
| Таблица (nft) | ip tgproxy_redirect, хуки prerouting и output, priority dstnat |
| Хуки | PREROUTING — трафик LAN-клиентов, OUTPUT — самого роутера. Нужны оба |
| Что заворачиваем | TCP на TELEGRAM_DC_CIDRS → REDIRECT --to-ports <порт движка> |
| Семейство | только IPv4 (у апстрима getOriginalDst усекает IPv6-адрес) |
Инварианты этого слоя:
- Свои цепочка/таблица. Снятие точное и идемпотентное, чужие правила не трогаем.
- Применяем ПОСЛЕ успешного старта. Правила на неподнятый порт оборвали бы Telegram совсем.
- Снимаем при любой остановке, даже если процесс умер сам. Правила переживают процесс: оставленные — весь Telegram уходит на порт, который никто не слушает (это хуже, чем «вернулось напрямую»).
- Повторный
applyне копит дубли — цепочка пересоздаётся с нуля. - Частичный набор правил хуже пустого: при ошибке на любом CIDR делаем откат целиком.
⚠️ REDIRECT и «Telegram DC через туннель» (§8) взаимоисключающи. REDIRECT срабатывает в nat раньше, чем принимается решение о маршруте: пакет уйдёт на локальный порт и до туннеля не доедет. Поэтому
route_telegram_dc_via_tunnel()отказывается работать, пока активен REDIRECT, аstart()резервного движка пишет предупреждение в лог, если маршрут в туннель уже настроен.
10.2 --tunnel-secret — ключ релея, а не секрет ссылки
Это HMAC-ключ, которым клиент аутентифицируется на релее
(computeAuthHMAC) и подписывает регистрацию (/register, заголовок
X-Z2K-Auth). Релей обязан знать его заранее, поэтому:
- случайный секрет не работает никогда — ни туннель, ни саморегистрация;
раньше мы генерировали
secrets.token_hex(16), и движок молча не поднимался; - формат — hex произвольной длины (у публичного релея 64 символа). Прежняя проверка «ровно 32 hex» — это формат MTProto-ссылки, и она отвергала настоящий секрет: ввести рабочее значение в GUI было физически нельзя.
10.3 Откуда взят дефолтный секрет (и как обновить, когда протухнет)
Значения по умолчанию лежат в core/tgproxy_manager.py
(MTPROXY_DEFAULT_RELAY, MTPROXY_DEFAULT_TUNNEL_SECRET). Апстрим зашивает
секрет в свои сборки на этапе компиляции
(-X main.defaultTunnelSecret, см. mtproxy-client/Makefile) и не
коммитит его в исходники — поэтому в репозитории его не найти.
Мы собираем бинарник сами и секрет в него не зашиваем: держать его настройкой удобнее (смена = правка конфига, а не пересборка и релиз).
Если туннель перестал подниматься — вероятно, владелец релея сменил ключ. Достать актуальный:
# 1. Взять ЛЮБУЮ опубликованную сборку апстрима — они лежат прямо в репозитории
git clone --depth 1 https://github.com/necronicle/z2k
cd z2k/mtproxy-client/builds
chmod +x tg-mtproxy-client-linux-amd64
# 2. flag.PrintDefaults печатает вшитое значение как default — просто спросить
./tg-mtproxy-client-linux-amd64 -h
# -tunnel-secret string
# Shared secret for tunnel auth (build-injected; ...) (default "63d91c...")
# -tunnel-url string
# Tunnel relay WebSocket URL (default "wss://213.176.74.63.nip.io/ws")
Никакого реверса не нужно: Go сам показывает дефолты флагов. Полученные
значения — в константы core/tgproxy_manager.py.
Про природу ключа: он общий для всех пользователей публичного релея и
раздаётся в каждом опубликованном бинарнике, приватности в нём нет. Но это
инфраструктура автора z2k — если он ключ ротирует, у нас всё встанет молча.
Свой релей и свой ключ задаются в GUI (POST /api/tgproxy/mtproto/config,
поля tgproxy.tunnel_url / tgproxy.tunnel_secret); пустое значение
означает «взять дефолт», поэтому в конфиг оно намеренно НЕ записывается при
запуске — иначе смена дефолта в коде не доехала бы до роутера.
10.4 Прочие инварианты
Каждый — оплаченный урок:
stdout/stderr = DEVNULL. СPIPEбез чтения пайп переполняется и процесс виснет наwrite().- После
kill()обязателенwait()— иначе зомби. - Слушать надо тот адрес, который печатается наружу. Раньше процесс слушал
127.0.0.1, аget_connect_info()отдавал LAN-адрес. relayпроверяется на схему (ws/wss/http/https); берётся из тела запроса, иначе изtgproxy.tunnel_url, иначе — дефолт.
11. Автозапуск
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 133
- Forks
- 10
- Last commit
- Sep 2026
ahel review
S4info
community integration — published by avatardd, not telegram
Automated review, not a security audit. Ruleset v1.
Advanced
- Catalog kind
- skill
- Gateway key
telegram-tunnel- Source
- github.com/avatardd/zapret-gui