Telegram Tunnel — справочник для zapret-gui

SkillCommunication

Complete 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.

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-подсетей.

Источники истины (в порядке убывания авторитета):

  1. 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).
  2. Flowseal/tg-ws-proxy — апстрим-апстрим (Windows-версия): docs/CfProxy.md, docs/CfWorker.md, community-пул доменов .github/cfproxy-domains.txt.
  3. Наш код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, не «забыли добавить».

Два движка:

ДвижокЧто этоРоль
tgwsproxytg-ws-proxy-go, ставится пакетом (opkg/apk), свой init.d, свой PID-файл и логОсновной
mtprototg-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).

ФлагТипДефолтСмысл
-hoststring127.0.0.1адрес прослушивания
-portint1443порт
-secretstringMTProto secret, 32 hex
-gen-secretboolfalseсгенерировать секрет и выйти
-print-linkboolfalseнапечатать tg:// ссылку и выйти
-vboolfalseverbose
-log-filestringпуть к логу
-log-max-mbfloat5ротация лога
-log-backupsint0сколько ротаций хранить
-buf-kbint64размер сокет-буфера
-pool-sizeint4размер WS-пула на каждый DC
-max-connsint256максимум клиентских сессий
-fake-tls-domainstringвключить Fake TLS (ee-секрет) с этим доменом
-cfproxy-domainstringpclead.co.ukсвой домен за Cloudflare CDN
-cfproxy-domainsstringпул CF-доменов через запятую
-cfproxy-worker-domainstringдомен(ы) CF-Worker через запятую
-cfproxy-domains-urlstringURL со списком CF-доменов
-no-cfproxyboolfalseвыключить CF-фоллбэк совсем
-cfproxy-prioritybooltrueпробовать cfproxy до TCP-фоллбэка
-no-cfproxy-domain-refreshboolfalseне обновлять список доменов по URL
-dc-ip-defaultstring149.154.167.220IP для всех неявных DC
-dc-ip-default-poolstringпул IP через запятую
-dc-iprepeatDC:IP, можно повторять
-dc-ip-poolrepeatDC:IP1,IP2, можно повторять
-pprof-listenstringадрес 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 configPUT 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 communitycfcommunityничего (дефолт бинарника: cfproxy включён, приоритетен)
Cloudflare custom domain / Workercfdomain--cfproxy-domain=<dom> или --cfproxy-worker-domain=<dom>
Hybridhybrid--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_CIDRSREDIRECT --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