Мой предыдущий фикс (mainBinaryName в #31) фиксом не был. Он изменил только
имя выходного файла: бандлер по-прежнему брал headless CLI и просто
переименовывал его в tglock. Проверено по содержимому опубликованных
артефактов, а не по имени:
версия файл в MacOS/ размер признаки CLI признаки GUI
beta.1 tglock 17.8 МБ нет ipc.localhost, wry×3206
beta.2 tglock-cli 4.3 МБ tglock-cli×6 нет
beta.3 tglock 4.3 МБ tglock-cli×6 нет
То есть beta.3 тоже не запускается, и моя же проверка CFBundleExecutable это
пропустила, потому что сверяла имя.
Настоящая причина найдена воспроизведением локально. Ломается только при явном
--target: без него бандлер выбирает GUI, с ним — CLI. Локальная сборка, на
которой я объявил фикс подтверждённым, шла без --target, а обе сборки в CI — с
ним. Обобщение было неправомерным.
Исправление убирает саму возможность выбора: CLI спрятан за фичей cli, которой
нет в default. При сборке приложения второго бинаря просто не существует.
Проверено против воспроизведённой поломки:
до : tglock.exe 1.7 МБ, признаки CLI, GUI нет
после: tglock.exe 9.0 МБ, признаки GUI, CLI нет, tglock-cli.exe не собран
Проверки переписаны на содержимое:
- release.yml распаковывает .app и .deb и ищет ipc.localhost (есть только в
GUI) и allow-direct (есть только в CLI). Поймала бы и beta.2, и beta.3;
- новая задача CI bundle собирает бандл с явным --target aarch64-apple-darwin
и проверяет его так же. Ловит до публикации, а не после;
- заодно исправлен шаблон grep для .deb: dpkg-deb -c выводит путь без ./, из-за
чего проверка ложно падала на исправной сборке.
Команда сборки CLI теперь требует --features cli; обновлены README, ci.yml и
release.yml.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
Мой предыдущий фикс (mainBinaryName в #31) фиксом не был. Он изменил только
имя выходного файла: бандлер по-прежнему брал headless CLI и просто
переименовывал его в tglock. Проверено по содержимому опубликованных
артефактов, а не по имени:
версия файл в MacOS/ размер признаки CLI признаки GUI
beta.1 tglock 17.8 МБ нет ipc.localhost, wry×3206
beta.2 tglock-cli 4.3 МБ tglock-cli×6 нет
beta.3 tglock 4.3 МБ tglock-cli×6 нет
То есть beta.3 тоже не запускается, и моя же проверка CFBundleExecutable это
пропустила, потому что сверяла имя.
Настоящая причина найдена воспроизведением локально. Ломается только при явном
--target: без него бандлер выбирает GUI, с ним — CLI. Локальная сборка, на
которой я объявил фикс подтверждённым, шла без --target, а обе сборки в CI — с
ним. Обобщение было неправомерным.
Исправление убирает саму возможность выбора: CLI спрятан за фичей cli, которой
нет в default. При сборке приложения второго бинаря просто не существует.
Проверено против воспроизведённой поломки:
до : tglock.exe 1.7 МБ, признаки CLI, GUI нет
после: tglock.exe 9.0 МБ, признаки GUI, CLI нет, tglock-cli.exe не собран
Проверки переписаны на содержимое:
- release.yml распаковывает .app и .deb и ищет ipc.localhost (есть только в
GUI) и allow-direct (есть только в CLI). Поймала бы и beta.2, и beta.3;
- новая задача CI bundle собирает бандл с явным --target aarch64-apple-darwin
и проверяет его так же. Ловит до публикации, а не после;
- заодно исправлен шаблон grep для .deb: dpkg-deb -c выводит путь без ./, из-за
чего проверка ложно падала на исправной сборке.
Команда сборки CLI теперь требует --features cli; обновлены README, ci.yml и
release.yml.
Симптом: на macOS приложение не запускается совсем — иконка подпрыгивает в
доке и гаснет, хотя разрешение выдано.
Причина. В #25 в крейте появился второй бинарь tglock-cli, а mainBinaryName в
tauri.conf.json задан не был. Бандлер Tauri выбрал главным исполняемым файлом
приложения headless CLI. Запуск .app стартовал консольный прокси, окна не
создавалось.
Проверено на опубликованных артефактах:
CFBundleExecutable размер MacOS/*
beta.1 tglock 18.6 МБ ← GUI, правильно
beta.2 tglock-cli 4.5 МБ ← консольный прокси
Задеты все платформы, не только macOS. В .deb лежит usr/bin/tglock-cli, а в
TGLock.desktop прописан Exec=tglock-cli. Установщик Windows упал с 2.0 МБ до
0.6 МБ — тот же признак. То есть beta.2 не запускается нигде.
Почему это уехало с зелёным CI: сборка релиза была успешной, потому что никто
не проверял, что внутри бандла. Тесты гоняют ядро, а не собранное приложение.
Исправление: mainBinaryName = "tglock" в tauri.conf.json. Проверено сборкой на
этой же ветке — Tauri берёт tglock.exe, установщик вернулся к нормальному
размеру.
Чтобы это не повторилось, добавлены две проверки:
- в CI, на каждый PR: mainBinaryName обязан совпадать с именем [[bin]], у
которого required-features = ["gui"]. Проверено в обе стороны — на верном
конфиге проходит, на подставленном tglock-cli падает;
- в release.yml, после сборки: на macOS читается CFBundleExecutable из
Info.plist, на Linux проверяется наличие /usr/bin/tglock в .deb. Смотрит
внутрь настоящего артефакта, а не в конфиг.
README: пример команды с хешем больше не прибит к конкретной версии, ссылка на
запуск сборки заменена на общее указание, и добавлена строка для тех, кто уже
скачал beta.2 и не смог её открыть.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
Претензия про VirusTotal всплывала в обсуждениях и не была нигде объяснена.
Отмахнуться «это ложное срабатывание» нельзя: движок реагирует на реальное
поведение программы. Поэтому в README добавлен раздел, который объясняет
механизм и даёт способы проверить, не доверяя автору на слово.
Что написано:
- что увидит пользователь: SmartScreen на Windows, детекты у части движков на
VirusTotal;
- почему: неподписанный файл проверяется эвристиками строже, а поведение —
открыть локальный порт, объявить себя прокси и прописаться в настройки
Telegram — совпадает с профилем прокси-троянов. Программа делает именно это,
только по просьбе пользователя, и автоматически отличить одно от другого
движок не может;
- что подписи не будет: сертификат это ежегодный платёж, проект бесплатный.
Формулировка прямая, без «скоро подпишем»;
- три проверяемых пути: сверка sha256 с digest, который GitHub публикует на
странице релиза (с командами под три ОС), открытый лог сборки в Actions с
указанием конкретного run и коммита, сборка из исходников одной командой;
- если этого недостаточно — не запускать, и это названо нормальным решением, а
не паранойей, со ссылкой на альтернативу.
Конкретные числа детектов не приводятся: они меняются от сборки к сборке и со
временем, обещать «4 из 59» значит закладывать в документацию то, что устареет.
Соответствующие пункты обновлены в ARCHITECTURE_V2.md (Current limitations) и
в статусе ISSUE_AUDIT.md — там это было записано как открытый вопрос,
требующий покупки сертификата, теперь как принятое решение.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
Резервный маршрут через Worker был в коде с 2.0, но воспользоваться им никто
не мог: в ARCHITECTURE_V2.md описан только контракт эндпоинта — это
спецификация для того, кто будет писать воркер, а не руководство. Ни скрипта,
ни шагов в репозитории не было. Поэтому люди, у которых легли все обычные
маршруты, писали «не работает» вместо того, чтобы включить запасной выход.
Добавлено:
- worker/tglock-worker.js — готовый скрипт. Проверяет путь и upgrade,
подтверждает подпротокол binary (без этого клиент рвёт рукопожатие),
соединяется только с семью адресами Telegram, которые запрашивает TGLock,
и поддерживает необязательный TGLOCK_TOKEN. Без списка адресов воркер стал
бы открытым TCP-прокси для любого, кто узнает его адрес.
- docs/CLOUDFLARE_WORKER.md — когда это нужно и когда нет (таблица
«что видно в приложении → нужен ли Worker»), установка через веб-интерфейс,
проверка живости, подключение в GUI и через --worker, ограничение доступа,
контракт для своих реализаций.
- Ссылки из README: в FAQ про блокировку web.telegram.org и в блок docs.
Контракт закреплён тестами, чтобы документация не разошлась с кодом:
- worker_path вынесен в функцию, из неё же строятся боевые маршруты;
- documented_worker_contract_matches_the_requested_path сверяет формат пути;
- worker_allowlist_covers_every_address_a_route_can_ask_for падает, если в
маршрутах появится адрес, которого нет в скрипте воркера;
- connects_through_the_documented_worker_contract поднимает сервер, ведущий
себя ровно по документации, и проверяет что туннель работает в обе стороны
и что запрошен именно документированный URI.
Чего тесты не проверяют: развёрнутый воркер в самом Cloudflare. Это указано и
в самой инструкции.
Отдельно: снят флаг prerelease в release.yml. До правки /releases/latest
отдавал v2.0.0-beta.1, то есть кнопка «Скачать» в README вела на сборку без
CLI и без фикса рендера. Существующий релиз v2.0.0-beta.2 помечен как latest
вручную.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
Интерфейс построен на системном WebView, а тот без 3D-ускорения окно не
создаёт. Отсюда весь класс жалоб: не стартует в виртуалке, не стартует с
дефолтным драйвером Microsoft, не стартует без монитора. Диагноз в #17 дал
@de4me: в VirtualBox приложение запускается только после включения галочки
«Включить ускорение 3D».
Перед стартом Tauri TGLock теперь сам запрашивает программный рендер:
- Windows: WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS с --disable-gpu
и --disable-gpu-compositing;
- Linux: WEBKIT_DISABLE_COMPOSITING_MODE и WEBKIT_DISABLE_DMABUF_RENDERER;
- macOS: не требуется, WebKit сам уходит в программный рендер.
Для панели со статусом программный рендер не стоит ничего заметного, поэтому
он выбран значением по умолчанию, а не аварийным режимом. Уже заданные
оператором переменные не перезаписываются, TGLOCK_FORCE_GPU=1 отключает
механизм целиком.
Логика вынесена в чистую функцию software_rendering_vars и покрыта четырьмя
тестами: значение по умолчанию, отключение через TGLOCK_FORCE_GPU, уважение
чужой переменной и правильный набор ключей на каждой платформе.
Версия поднята до 2.0.0-beta.2 в Cargo.toml, package.json и tauri.conf.json:
нужен тег, чтобы в релиз попали и tglock-cli, и этот фикс.
README получил отдельный вопрос в FAQ про «окно не появляется»,
ARCHITECTURE_V2.md — обновлённый пункт в Current limitations.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
Аудит репозитория после #25 и #26. Расхождения между тем, что написано, и тем,
что есть:
- README обещал `tglock-cli-*` в таблице загрузок, но в релизе
v2.0.0-beta.1 такого артефакта нет: задача `cli` в release.yml сработает
только на следующем теге. Заменено на честную формулировку с командой
сборки из main.
- ui/main.ts держал начальным значением маршрута строку «Автоматический
маршрут», которую бэкенд больше не отдаёт: при коде 0 возвращается
«Маршрут ещё не выбран». Иначе до первого опроса статуса интерфейс
показывал название несуществующего маршрута.
- ARCHITECTURE_V2.md не упоминал ни разделения на библиотеку и два бинаря, ни
фичи `gui`, ни правила различения протоколов, ни политики прямого релея, ни
того, что GUI не запускается без WebView. Добавлены разделы, а последнее
внесено в Current limitations вместе с отсутствием подписи бинарей.
- ISSUE_AUDIT.md описывал состояние на 29 июля. Сам аудит оставлен как
фиксация на дату, сверху добавлен статус на 30 июля: что закрыто, что
осталось открытым и почему, и что исправлено вне списка issues.
- HABR.md — черновик статьи, а не документация, но был указан в README как
«подробный технический разбор». В нём «два файла, 350 строк, четыре
платформы», тогда как сейчас 2872 строки Rust, семь файлов и три платформы
плюс headless. Добавлена шапка с поправкой, ссылка в README переписана так,
чтобы читателя не отправляли к устаревшим числам за документацией.
Проверено: fmt, clippy в обоих вариантах сборки, 47 + 12 тестов, npm run build,
все ссылки на файлы в .md существуют.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
fix(proxy): считать туннель только после успешного рукопожатия
README обещал то, чего код не делает. Проверено по исходникам, исправлено:
- «Голосовые/видеозвонки рвутся» стояло в списке «кому подойдёт», то есть
подразумевалось, что TGLock их лечит. Не лечит: звонки по UDP, проксируется
только TCP. То же обещание было в FAQ. Появился явный раздел «чего TGLock
не делает» — звонки, всё кроме Telegram, Android и iOS.
- «IP отобразится прямо в интерфейсе TGLock» в описании LAN-режима. Такого
поля в интерфейсе нет: StatusSnapshot отдаёт только порт. Заменено на то,
что есть — готовую tg://-ссылку и команды для поиска адреса руками.
- «Кода ~350 строк» — в действительности 2872 строки Rust (из них ~1140
тесты) и ~380 строк TypeScript.
- «DC ID — i32 в [60..64]» — на самом деле i16 в [60..62], отрицательное
значение означает медиа-соединение.
- Транспорт описывался как единственный маршрут через web.telegram.org. В 2.0
это каскад: закреплённые IP, дублёры kwsN-1, системный DNS и опциональный
Cloudflare Worker, с cooldown на упавших. Указано, что системный DNS и hosts
не изменяются, а SNI и Host остаются настоящими.
- FAQ про macOS ссылался на файл tglock-macos-arm64, которого в релизах нет.
- «Собирает бинарники для всех 4 платформ» — их три, плюс CLI.
- Пустая колонка «Размер» в таблице загрузок заполнена реальными размерами.
- FAQ про использование как обычного SOCKS5 не отражал, что на сетевом адресе
не-Telegram запросы отклоняются.
- FAQ про блокировку web.telegram.org обещал спасение, которого нет; теперь
там сказано и про запас маршрутов, и про предел подхода.
Реклама RoseVPN сведена в один блок сверху: удалены секция внизу, три вставки
в FAQ и ссылка в подвале.
Добавлен раздел «Как помочь» с тем, что прислать в баг-репорте, и списком
известных ограничений, по которым issue открывать не нужно.
Правка кода, без которой один из абзацев README был бы неправдой: счётчик
Stats::ws увеличивался до WebSocket-рукопожатия, поэтому пока прокси перебирал
маршруты по несколько секунд каждый, интерфейс уже показывал «Telegram на
связи». Теперь счёт ведёт RAII-guard после успешного connect, и состояния
«порт открыт», «идёт перебор» и «туннель есть» различимы. Тест
a_tunnel_counts_only_after_the_handshake_succeeds держит это: молчащий
listener, рукопожатие в полёте, ws и last_route обязаны остаться нулями.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
Реализует направление PR #15 поверх архитектуры 2.0. Сам PR смерджить нельзя:
он патчит src/bypass.rs, src/network.rs и src/ws_proxy.rs, которых больше нет,
и правит системный DNS — в 2.0 это не нужно, потому что адреса Telegram зашиты
в маршрутах, а SNI остаётся настоящим. Взято разделение GUI/CLI и произвольный
bind-адрес, отброшены DNS-менеджмент и проверка root: CLI не требует прав.
Closes#10, #17 — GUI не создаёт окно без 3D-ускорения, на машине без монитора
и в виртуалке. Причина в WebView под Tauri, поэтому лечится не программным
рендером, а бинарём, в котором WebView нет вовсе: при выключенной фиче gui
Tauri и фронтенд в сборку не попадают. Отдельная задача CI собирает и гоняет
CLI на голом ubuntu без Node.js и без libwebkit2gtk.
Структура:
- src/lib.rs — ядро (mtproto, proxy, transport, config), без Tauri
- src/main.rs — GUI, required-features = ["gui"]
- src/bin/cli.rs — headless-бинарь на clap
- build.rs вызывает tauri_build только при включённой фиче gui
Исправлено по пути:
- Определение протокола: SOCKS5 и MTProto различались по первому байту, но
is_reserved_init не исключает 0x05, поэтому примерно одно соединение из 256
уезжало в SOCKS5-ветку и умирало. Теперь неоднозначный первый байт решается
по полному 64-байтному init и секрету.
- Ярлык маршрута в UI: код 2 подписывался как «Cloudflare Worker», хотя это
запасной Telegram IP, а системный DNS и настоящий Worker оба показывались
как «Автоматический маршрут». Метки переехали в transport::route_label,
общий для обоих интерфейсов.
- Секрет прокси: под DynamicUser и ProtectHome домашней папки нет, secret_path
возвращает None и секрет генерировался заново при каждом старте, ломая всем
настроенным клиентам tg://-ссылку. Добавлен --secret-file.
- README обещал Rust 1.75+, тогда как Cargo.toml требует 1.88 и CI это
проверяет. Это и есть первопричина #3.
Политика доступа: прямые не-Telegram соединения разрешены только на loopback,
на любом сетевом адресе нужен явный --allow-direct. Правило из ISSUE_AUDIT о
том, что LAN не должен становиться открытым SOCKS5, теперь выражено в типе
ListenConfig и покрыто тестами.
Тесты: 46 в библиотеке + 12 в CLI. Появился сквозной тест туннеля против
мок-релея, который реализует сторону Telegram по obfuscated2 — проверяется
не внутренняя консистентность, а что реле получает ровно тот открытый текст,
который отправил клиент, и обратно. Плюс расписание backoff, фолбэк при всех
маршрутах в cooldown, валидация Worker-доменов, отказы SOCKS5, устойчивость
секрета к перезапуску и корректная остановка по SIGTERM.
Документация: секция CLI в README с юнитом systemd и Dockerfile.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>