6 Commits

Author SHA1 Message Date
by-sonic 96eb62b097 Merge branch 'main' of https://github.com/by-sonic/tglock 2026-07-30 16:26:35 +03:00
Никита Sonic 4ae755c6ae fix(ci): одна проверка бандла на Python вместо трёх копий grep (#34)
* fix(bundle): спрятать CLI за фичей — beta.2 и beta.3 упаковывали не тот бинарь

Мой предыдущий фикс (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.

* fix(ci): одна проверка бандла на Python вместо трёх копий grep

Проверка содержимого бандла, добавленная в #33, работала на Linux и давала
ложное «в бандле не GUI» на macOS. Причина в BSD grep: в UTF-8-локали он молча
не находит строки в бинарных данных там, где GNU grep находит. Из-за этого
задача macOS в релизе beta.4 упала уже после загрузки артефактов, а вместе с
ней снова пропустилась задача с CLI-бинарями.

Сами артефакты beta.4 при этом корректны — проверено скачиванием: в бандле
tglock на 20.3 МБ, universal/fat, признаки GUI на месте, признаков CLI нет.
Не хватает только tglock-cli-*.

Проверка вынесена в scripts/verify_bundle_binary.py и вызывается из всех трёх
мест: задачи CI bundle, проверки macOS и проверки Linux в релизе. Один скрипт
вместо трёх копий шелл-кода исключает и платформенные различия grep, и
расхождение копий между собой.

Скрипт проверен на реальных исторических артефактах:
  beta.4 (исправный)  → код 0
  beta.3 (сломанный)  → код 1, найдены allow-direct и secret-file
  beta.2 (сломанный)  → код 1, то же

---------

Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
2026-07-30 16:26:32 +03:00
by-sonic 00d24a8647 fix(ci): одна проверка бандла на Python вместо трёх копий grep
Проверка содержимого бандла, добавленная в #33, работала на Linux и давала
ложное «в бандле не GUI» на macOS. Причина в BSD grep: в UTF-8-локали он молча
не находит строки в бинарных данных там, где GNU grep находит. Из-за этого
задача macOS в релизе beta.4 упала уже после загрузки артефактов, а вместе с
ней снова пропустилась задача с CLI-бинарями.

Сами артефакты beta.4 при этом корректны — проверено скачиванием: в бандле
tglock на 20.3 МБ, universal/fat, признаки GUI на месте, признаков CLI нет.
Не хватает только tglock-cli-*.

Проверка вынесена в scripts/verify_bundle_binary.py и вызывается из всех трёх
мест: задачи CI bundle, проверки macOS и проверки Linux в релизе. Один скрипт
вместо трёх копий шелл-кода исключает и платформенные различия grep, и
расхождение копий между собой.

Скрипт проверен на реальных исторических артефактах:
  beta.4 (исправный)  → код 0
  beta.3 (сломанный)  → код 1, найдены allow-direct и secret-file
  beta.2 (сломанный)  → код 1, то же
2026-07-30 16:18:42 +03:00
by-sonic ef81d91ad1 Merge branch 'main' of https://github.com/by-sonic/tglock 2026-07-30 16:09:00 +03:00
Никита Sonic ed121d0968 fix(bundle): спрятать CLI за фичей — beta.2 и beta.3 упаковывали не тот бинарь (#33)
Мой предыдущий фикс (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>
2026-07-30 16:08:56 +03:00
by-sonic 2d2a9e9302 fix(bundle): спрятать CLI за фичей — beta.2 и beta.3 упаковывали не тот бинарь
Мой предыдущий фикс (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.
2026-07-30 16:02:26 +03:00
8 changed files with 136 additions and 27 deletions
+38 -4
View File
@@ -58,6 +58,40 @@ jobs:
- name: Test - name: Test
run: cargo test --all-targets 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 -maxdepth 6 -name 'TGLock.app'; exit 1; }
python3 scripts/verify_bundle_binary.py "$exe"
headless: headless:
name: Headless CLI (no WebView, no Node) name: Headless CLI (no WebView, no Node)
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -73,13 +107,13 @@ jobs:
# libwebkit2gtk. It fails the moment anything drags the GUI back into the # libwebkit2gtk. It fails the moment anything drags the GUI back into the
# headless build, which is the whole point of issues #10 and #17. # headless build, which is the whole point of issues #10 and #17.
- name: Lint - 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 - name: Test
run: cargo test --no-default-features --lib --bins run: cargo test --no-default-features --features cli --lib --bins
- name: Build - 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 - name: Start, advertise a proxy link and stop on SIGTERM
run: | run: |
@@ -123,7 +157,7 @@ jobs:
run: cargo check --locked run: cargo check --locked
- name: Check the headless dependency graph too - 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: frontend:
name: Frontend name: Frontend
+19 -13
View File
@@ -75,28 +75,34 @@ jobs:
prerelease: false prerelease: false
args: ${{ matrix.args }} args: ${{ matrix.args }}
# Смотрит внутрь собранного бандла. v2.0.0-beta.2 опубликовался с зелёным # Проверяет СОДЕРЖИМОЕ упакованного бинаря, а не его имя.
# CI, хотя .app и .deb содержали headless CLI вместо приложения: сборка #
# была успешной, просто никто не проверял, что внутри. # v2.0.0-beta.2 и v2.0.0-beta.3 опубликовались с зелёным CI, хотя в бандле
- name: The bundle must contain the GUI binary # лежал 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' if: runner.os == 'macOS'
shell: bash shell: bash
run: | run: |
app=$(find target -maxdepth 6 -name 'TGLock.app' -type d | head -1) app=$(find target -maxdepth 6 -name 'TGLock.app' -type d | head -1)
test -n "$app" || { echo "TGLock.app не найден"; exit 1; } test -n "$app" || { echo "TGLock.app не найден"; exit 1; }
exe=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Contents/Info.plist") exe="$app/Contents/MacOS/tglock"
echo "CFBundleExecutable = $exe" test -f "$exe" || { echo "нет $exe:"; ls -la "$app/Contents/MacOS/"; exit 1; }
test "$exe" = "tglock" || { echo "в бандле не GUI-бинарь"; exit 1; } python3 scripts/verify_bundle_binary.py "$exe"
test -x "$app/Contents/MacOS/tglock" || { echo "исполняемый файл отсутствует"; exit 1; }
- name: The bundle must contain the GUI binary - name: The bundled binary must actually be the GUI one
if: runner.os == 'Linux' if: runner.os == 'Linux'
shell: bash shell: bash
run: | run: |
deb=$(find target -maxdepth 6 -name '*.deb' | head -1) deb=$(find target -maxdepth 6 -name '*.deb' | head -1)
test -n "$deb" || { echo ".deb не найден"; exit 1; } test -n "$deb" || { echo ".deb не найден"; exit 1; }
dpkg-deb -c "$deb" | grep -E ' \./usr/bin/tglock$' \ root=$(mktemp -d)
|| { echo "в .deb нет /usr/bin/tglock:"; dpkg-deb -c "$deb" | grep '/bin/'; exit 1; } 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; }
python3 scripts/verify_bundle_binary.py "$exe"
cli: cli:
name: Headless CLI ${{ matrix.platform }} name: Headless CLI ${{ matrix.platform }}
@@ -136,7 +142,7 @@ jobs:
IFS=',' read -ra targets <<< "${{ matrix.rust-targets }}" IFS=',' read -ra targets <<< "${{ matrix.rust-targets }}"
binaries=() binaries=()
for target in "${targets[@]}"; do 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" --bin tglock-cli --target "$target"
binaries+=("target/$target/release/tglock-cli") binaries+=("target/$target/release/tglock-cli")
done done
@@ -152,7 +158,7 @@ jobs:
if: runner.os == 'Windows' if: runner.os == 'Windows'
shell: bash shell: bash
run: | run: |
cargo build --release --locked --no-default-features \ cargo build --release --locked --no-default-features --features cli \
--bin tglock-cli --target ${{ matrix.rust-targets }} --bin tglock-cli --target ${{ matrix.rust-targets }}
cp "target/${{ matrix.rust-targets }}/release/tglock-cli.exe" "${{ matrix.asset }}" cp "target/${{ matrix.rust-targets }}/release/tglock-cli.exe" "${{ matrix.asset }}"
./"${{ matrix.asset }}" --version ./"${{ matrix.asset }}" --version
Generated
+1 -1
View File
@@ -3553,7 +3553,7 @@ dependencies = [
[[package]] [[package]]
name = "tglock" name = "tglock"
version = "2.0.0-beta.3" version = "2.0.0-beta.5"
dependencies = [ dependencies = [
"aes", "aes",
"cipher", "cipher",
+11 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tglock" name = "tglock"
version = "2.0.0-beta.3" version = "2.0.0-beta.5"
edition = "2021" edition = "2021"
rust-version = "1.88" rust-version = "1.88"
description = "Telegram unblock via local WebSocket tunnel" 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 # 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. # machines without a GPU or monitor able to build and run TGLock at all.
gui = ["dep:tauri", "dep:tauri-build", "dep:open"] 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 <triple>` 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] [lib]
name = "tglock" name = "tglock"
@@ -26,11 +34,12 @@ required-features = ["gui"]
[[bin]] [[bin]]
name = "tglock-cli" name = "tglock-cli"
path = "src/bin/cli.rs" path = "src/bin/cli.rs"
required-features = ["cli"]
[dependencies] [dependencies]
tauri = { version = "2", features = [], optional = true } tauri = { version = "2", features = [], optional = true }
open = { version = "5", 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 = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
tokio = { version = "1", features = [ tokio = { version = "1", features = [
+5 -5
View File
@@ -122,7 +122,7 @@ TGLock — это **локальный прокси** на твоём компь
3. **Собрать самому.** Для CLI это одна команда и никаких зависимостей кроме Rust: 3. **Собрать самому.** Для CLI это одна команда и никаких зависимостей кроме Rust:
```bash ```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 FROM rust:1.88 AS build
WORKDIR /src WORKDIR /src
COPY . . 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 FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
@@ -435,7 +435,7 @@ npm run tauri build
### Только CLI, без графики ### Только CLI, без графики
```bash ```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 собирается на голом сервере. Ни Node.js, ни фронтенда, ни `libwebkit2gtk` для этого не нужно — при выключенной фиче `gui` Tauri и системный WebView в сборку не попадают вообще. Именно так CLI собирается на голом сервере.
@@ -445,9 +445,9 @@ cargo build --release --locked --no-default-features --bin tglock-cli
```bash ```bash
cargo fmt --check cargo fmt --check
cargo clippy --all-targets -- -D warnings 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 --all-targets
cargo test --no-default-features --lib --bins cargo test --no-default-features --features cli --lib --bins
``` ```
Тестов 59: разбор `obfuscated2`, каскад маршрутов и его cooldown, протокольные отказы SOCKS5, устойчивость секрета к перезапуску, плюс сквозной тест туннеля против мок-сервера, который реализует сторону Telegram и проверяет, что до неё доходит ровно тот открытый текст, который отправил клиент. Единственный тест с пометкой `#[ignore]` — тот, что требует живой сети. Тестов 59: разбор `obfuscated2`, каскад маршрутов и его cooldown, протокольные отказы SOCKS5, устойчивость секрета к перезапуску, плюс сквозной тест туннеля против мок-сервера, который реализует сторону Telegram и проверяет, что до неё доходит ровно тот открытый текст, который отправил клиент. Единственный тест с пометкой `#[ignore]` — тот, что требует живой сети.
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "tglock-ui", "name": "tglock-ui",
"private": true, "private": true,
"version": "2.0.0-beta.3", "version": "2.0.0-beta.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --port 1420", "dev": "vite --port 1420",
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""Проверяет, что в бандл приложения попал GUI-бинарь, а не headless CLI.
Зачем это существует. В v2.0.0-beta.2 и v2.0.0-beta.3 бандлер Tauri упаковал
`tglock-cli` как исполняемый файл приложения, и оно не запускалось ни на одной
платформе. В beta.3 имя файла было уже правильным — бандлер переименовал CLI —
поэтому проверка имени или `CFBundleExecutable` ничего не заметила. Отличить
можно только по содержимому.
Почему на Python, а не grep. BSD grep на macOS в UTF-8-локали молча не находит
строки в бинарных данных там, где GNU grep находит: проверка проходила на
Linux и давала ложное «не GUI» на macOS. Один скрипт для всех платформ и для
всех трёх мест, где проверка вызывается, исключает подобные расхождения.
Использование:
python3 scripts/verify_bundle_binary.py <путь-к-бинарю>
"""
import sys
# Присутствуют только в GUI: строка CSP из tauri.conf.json и имя движка WebView.
GUI_MARKERS = (b"ipc.localhost", b"wry")
# Присутствуют только в CLI: имена флагов clap.
CLI_MARKERS = (b"allow-direct", b"secret-file")
def main(argv: list[str]) -> int:
if len(argv) != 2:
print(f"использование: {argv[0]} <путь-к-бинарю>", file=sys.stderr)
return 2
path = argv[1]
try:
with open(path, "rb") as handle:
data = handle.read()
except OSError as error:
print(f"не удалось прочитать {path}: {error}", file=sys.stderr)
return 1
found_gui = [marker.decode() for marker in GUI_MARKERS if marker in data]
found_cli = [marker.decode() for marker in CLI_MARKERS if marker in data]
print(f"файл: {path}")
print(f"размер: {len(data) / 1048576:.1f} МБ")
print(f"GUI: {found_gui or 'признаков нет'}")
print(f"CLI: {found_cli or 'признаков нет'}")
if found_cli:
print("ОШИБКА: в бандле headless CLI вместо приложения", file=sys.stderr)
return 1
if not found_gui:
print("ОШИБКА: это не GUI-бинарь, признаков GUI нет", file=sys.stderr)
return 1
print("OK: в бандле GUI-бинарь")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "TGLock", "productName": "TGLock",
"version": "2.0.0-beta.3", "version": "2.0.0-beta.5",
"identifier": "com.bysonic.tglock", "identifier": "com.bysonic.tglock",
"mainBinaryName": "tglock", "mainBinaryName": "tglock",
"build": { "build": {