From 2d2a9e9302e9ae8d1cfe39ba61f5994bb58ec43f Mon Sep 17 00:00:00 2001 From: by-sonic <171230345+by-sonic@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:02:26 +0300 Subject: [PATCH] =?UTF-8?q?fix(bundle):=20=D1=81=D0=BF=D1=80=D1=8F=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20CLI=20=D0=B7=D0=B0=20=D1=84=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D0=B9=20=E2=80=94=20beta.2=20=D0=B8=20beta.3=20=D1=83?= =?UTF-8?q?=D0=BF=D0=B0=D0=BA=D0=BE=D0=B2=D1=8B=D0=B2=D0=B0=D0=BB=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D1=82=D0=BE=D1=82=20=D0=B1=D0=B8=D0=BD=D0=B0?= =?UTF-8?q?=D1=80=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Мой предыдущий фикс (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. --- .github/workflows/ci.yml | 49 ++++++++++++++++++++++++++++++++--- .github/workflows/release.yml | 46 ++++++++++++++++++++++---------- Cargo.lock | 2 +- Cargo.toml | 13 ++++++++-- README.md | 10 +++---- package.json | 2 +- tauri.conf.json | 2 +- 7 files changed, 97 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42e5a6e..69020da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,6 +58,47 @@ jobs: - name: Test run: cargo test --all-targets + bundle: + name: Bundled app must be the GUI binary + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Install frontend dependencies + run: npm ci + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-apple-darwin + + # Явный --target обязателен: без него бандлер выбирал правильный бинарь, а + # с ним — нет, и именно так beta.2 и beta.3 уехали с headless CLI внутри. + - name: Bundle the app + run: npx tauri build --bundles app --target aarch64-apple-darwin + + # Проверяется содержимое, а не имя файла. В beta.3 имя было уже + # правильным, потому что бандлер переименовал CLI, и проверка имени + # ничего не заметила. + - name: The bundled binary must actually be the GUI one + run: | + exe=$(find target -maxdepth 8 -path '*TGLock.app/Contents/MacOS/tglock' | head -1) + test -n "$exe" || { echo "исполняемый файл бандла не найден"; find target -name 'TGLock.app' -maxdepth 6; exit 1; } + ls -la "$exe" + if ! grep -qa 'ipc\.localhost' "$exe"; then + echo "в бандле не GUI: отсутствует признак ipc.localhost"; exit 1 + fi + if grep -qa 'allow-direct' "$exe"; then + echo "в бандле оказался headless CLI: найден признак allow-direct"; exit 1 + fi + echo "OK: в бандле GUI-бинарь" + headless: name: Headless CLI (no WebView, no Node) runs-on: ubuntu-22.04 @@ -73,13 +114,13 @@ jobs: # libwebkit2gtk. It fails the moment anything drags the GUI back into the # headless build, which is the whole point of issues #10 and #17. - name: Lint - run: cargo clippy --no-default-features --lib --bins --all-targets -- -D warnings + run: cargo clippy --no-default-features --features cli --all-targets -- -D warnings - name: Test - run: cargo test --no-default-features --lib --bins + run: cargo test --no-default-features --features cli --lib --bins - name: Build - run: cargo build --release --no-default-features --bin tglock-cli + run: cargo build --release --no-default-features --features cli --bin tglock-cli - name: Start, advertise a proxy link and stop on SIGTERM run: | @@ -123,7 +164,7 @@ jobs: run: cargo check --locked - name: Check the headless dependency graph too - run: cargo check --locked --no-default-features --lib --bins + run: cargo check --locked --no-default-features --features cli --lib --bins frontend: name: Frontend diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cffb971..03bfb33 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,28 +75,48 @@ jobs: prerelease: false args: ${{ matrix.args }} - # Смотрит внутрь собранного бандла. v2.0.0-beta.2 опубликовался с зелёным - # CI, хотя .app и .deb содержали headless CLI вместо приложения: сборка - # была успешной, просто никто не проверял, что внутри. - - name: The bundle must contain the GUI binary + # Проверяет СОДЕРЖИМОЕ упакованного бинаря, а не его имя. + # + # v2.0.0-beta.2 и v2.0.0-beta.3 опубликовались с зелёным CI, хотя в бандле + # лежал headless CLI вместо приложения. В beta.3 имя файла было уже + # правильным — переименованным — поэтому проверка имени прошла. Отличить + # можно только по содержимому: `wry`/`ipc.localhost` есть исключительно в + # GUI, а `allow-direct` — исключительно в CLI. + - name: The bundled binary must actually be the GUI one if: runner.os == 'macOS' shell: bash run: | app=$(find target -maxdepth 6 -name 'TGLock.app' -type d | head -1) test -n "$app" || { echo "TGLock.app не найден"; exit 1; } - exe=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Contents/Info.plist") - echo "CFBundleExecutable = $exe" - test "$exe" = "tglock" || { echo "в бандле не GUI-бинарь"; exit 1; } - test -x "$app/Contents/MacOS/tglock" || { echo "исполняемый файл отсутствует"; exit 1; } + exe="$app/Contents/MacOS/tglock" + test -f "$exe" || { echo "нет $exe:"; ls -la "$app/Contents/MacOS/"; exit 1; } + ls -la "$exe" + if ! grep -qa 'ipc\.localhost' "$exe"; then + echo "в бандле не GUI: отсутствует признак ipc.localhost"; exit 1 + fi + if grep -qa 'allow-direct' "$exe"; then + echo "в бандле оказался headless CLI: найден признак allow-direct"; exit 1 + fi + echo "OK: в бандле GUI-бинарь" - - name: The bundle must contain the GUI binary + - name: The bundled binary must actually be the GUI one if: runner.os == 'Linux' shell: bash run: | deb=$(find target -maxdepth 6 -name '*.deb' | head -1) test -n "$deb" || { echo ".deb не найден"; exit 1; } - dpkg-deb -c "$deb" | grep -E ' \./usr/bin/tglock$' \ - || { echo "в .deb нет /usr/bin/tglock:"; dpkg-deb -c "$deb" | grep '/bin/'; exit 1; } + root=$(mktemp -d) + dpkg-deb -x "$deb" "$root" + exe="$root/usr/bin/tglock" + test -f "$exe" || { echo "нет /usr/bin/tglock:"; dpkg-deb -c "$deb" | grep '/bin/'; exit 1; } + ls -la "$exe" + if ! grep -qa 'ipc\.localhost' "$exe"; then + echo "в .deb не GUI: отсутствует признак ipc.localhost"; exit 1 + fi + if grep -qa 'allow-direct' "$exe"; then + echo "в .deb оказался headless CLI: найден признак allow-direct"; exit 1 + fi + echo "OK: в .deb GUI-бинарь" cli: name: Headless CLI ${{ matrix.platform }} @@ -136,7 +156,7 @@ jobs: IFS=',' read -ra targets <<< "${{ matrix.rust-targets }}" binaries=() for target in "${targets[@]}"; do - cargo build --release --locked --no-default-features \ + cargo build --release --locked --no-default-features --features cli \ --bin tglock-cli --target "$target" binaries+=("target/$target/release/tglock-cli") done @@ -152,7 +172,7 @@ jobs: if: runner.os == 'Windows' shell: bash run: | - cargo build --release --locked --no-default-features \ + cargo build --release --locked --no-default-features --features cli \ --bin tglock-cli --target ${{ matrix.rust-targets }} cp "target/${{ matrix.rust-targets }}/release/tglock-cli.exe" "${{ matrix.asset }}" ./"${{ matrix.asset }}" --version diff --git a/Cargo.lock b/Cargo.lock index 03bf46f..480f923 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3553,7 +3553,7 @@ dependencies = [ [[package]] name = "tglock" -version = "2.0.0-beta.3" +version = "2.0.0-beta.4" dependencies = [ "aes", "cipher", diff --git a/Cargo.toml b/Cargo.toml index 4e3c910..28494e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tglock" -version = "2.0.0-beta.3" +version = "2.0.0-beta.4" edition = "2021" rust-version = "1.88" description = "Telegram unblock via local WebSocket tunnel" @@ -13,6 +13,14 @@ default = ["gui"] # frontend bundle from the build, which is what makes headless servers and # machines without a GPU or monitor able to build and run TGLock at all. gui = ["dep:tauri", "dep:tauri-build", "dep:open"] +# The headless binary. Deliberately NOT in `default`. +# +# When both binaries exist in one build, the Tauri bundler picks the wrong one +# as the application: `tauri build --target ` packaged tglock-cli and +# shipped it as the app in v2.0.0-beta.2 and v2.0.0-beta.3. Keeping the CLI +# behind its own non-default feature means it simply does not exist during an +# application build, so there is nothing to pick wrongly. +cli = ["dep:clap"] [lib] name = "tglock" @@ -26,11 +34,12 @@ required-features = ["gui"] [[bin]] name = "tglock-cli" path = "src/bin/cli.rs" +required-features = ["cli"] [dependencies] tauri = { version = "2", features = [], optional = true } open = { version = "5", optional = true } -clap = { version = "4", features = ["derive"] } +clap = { version = "4", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = [ diff --git a/README.md b/README.md index 155c82a..1211388 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ TGLock — это **локальный прокси** на твоём компь 3. **Собрать самому.** Для CLI это одна команда и никаких зависимостей кроме Rust: ```bash - cargo build --release --locked --no-default-features --bin tglock-cli + cargo build --release --locked --no-default-features --features cli --bin tglock-cli ``` Полная сборка с интерфейсом — [ниже](#-сборка-из-исходников). @@ -220,7 +220,7 @@ journalctl -u tglock -f FROM rust:1.88 AS build WORKDIR /src COPY . . -RUN cargo build --release --locked --no-default-features --bin tglock-cli +RUN cargo build --release --locked --no-default-features --features cli --bin tglock-cli FROM debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \ @@ -435,7 +435,7 @@ npm run tauri build ### Только CLI, без графики ```bash -cargo build --release --locked --no-default-features --bin tglock-cli +cargo build --release --locked --no-default-features --features cli --bin tglock-cli ``` Ни Node.js, ни фронтенда, ни `libwebkit2gtk` для этого не нужно — при выключенной фиче `gui` Tauri и системный WebView в сборку не попадают вообще. Именно так CLI собирается на голом сервере. @@ -445,9 +445,9 @@ cargo build --release --locked --no-default-features --bin tglock-cli ```bash cargo fmt --check cargo clippy --all-targets -- -D warnings -cargo clippy --no-default-features --lib --bins --all-targets -- -D warnings +cargo clippy --no-default-features --features cli --all-targets -- -D warnings cargo test --all-targets -cargo test --no-default-features --lib --bins +cargo test --no-default-features --features cli --lib --bins ``` Тестов 59: разбор `obfuscated2`, каскад маршрутов и его cooldown, протокольные отказы SOCKS5, устойчивость секрета к перезапуску, плюс сквозной тест туннеля против мок-сервера, который реализует сторону Telegram и проверяет, что до неё доходит ровно тот открытый текст, который отправил клиент. Единственный тест с пометкой `#[ignore]` — тот, что требует живой сети. diff --git a/package.json b/package.json index 90946ae..f3412f0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tglock-ui", "private": true, - "version": "2.0.0-beta.3", + "version": "2.0.0-beta.4", "type": "module", "scripts": { "dev": "vite --port 1420", diff --git a/tauri.conf.json b/tauri.conf.json index cb41fec..9ebb014 100644 --- a/tauri.conf.json +++ b/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "TGLock", - "version": "2.0.0-beta.3", + "version": "2.0.0-beta.4", "identifier": "com.bysonic.tglock", "mainBinaryName": "tglock", "build": {