Сборка
TailSocks использует высокоавтоматизированный и модульный конвейер сборки, исключающий технический долг, связанный с поддержкой громоздкого форка Tailscale.
Конвейер динамических патчей
Заголовок раздела «Конвейер динамических патчей»Вместо постоянного разрешения конфликтов слияния (merge conflicts) скрипт сборки применяет патч-конвейер:
- Загрузка чистого ядра. Скачивается облегчённый архив стабильного исходного кода Tailscale (версия закреплена в
appctr/TAILSCALE_VERSION, сейчасv1.104.1; архив сверяется сappctr/TAILSCALE_SHA256). - Атомарное внедрение патчей. Применяется серия модульных атомарных
.patchфайлов (из папкиappctr/patches/) в алфавитном порядке для адаптации исходников под мобильные функции: поддержка SOCKS5 прокси, нативная файловая система для Taildrop, генерация сертификатов LocalAPI и мониторинг сети Android. - Агрессивное оптимизирование. С помощью большого набора тегов сборки Go (
ts_omit_systray,ts_omit_kube,ts_omit_aws,ts_omit_bird,ts_omit_driveи др.) из кода вырезаются неиспользуемые компоненты Linux, серверных систем и корпоративных модулей. - Результат. Один
libtailscale.soна каждую ABI — демон со встроенным CLI (ts_include_cli), быстро компилирующийся и минимально расходующий ресурсы в песочнице Android.
Требования к окружению
Заголовок раздела «Требования к окружению»- Операционная система: Linux или macOS.
- Go: конкретная версия не требуется — сборка использует
GOTOOLCHAIN=autoи сама скачивает нужный тулчейн Go. - Android SDK: compileSdk 37, targetSdk 35, minSdk 24.
- Android NDK: 27.x (переменная
ANDROID_NDK_HOME). - gomobile:
go install golang.org/x/mobile/cmd/gomobile@latest && gomobile init. - Gradle: 8.x и новее, через обёртку
./gradlew. - Keystore для релиза: собственный
.jksи переменныеKEYSTORE_FILE,KEYSTORE_PASSWORD,KEY_ALIAS,KEY_PASSWORD(см. ниже).
Шаги сборки
Заголовок раздела «Шаги сборки»Клонирование репозитория
Заголовок раздела «Клонирование репозитория»git clone --recurse-submodules https://github.com/bropines/tailsocks.gitcd tailsocksКомпиляция нативного ядра Go
Заголовок раздела «Компиляция нативного ядра Go»cd appctrbash build.shcd ..Скрипт сборки автоматически скачает нужную версию Tailscale, применит патчи и скомпилирует бинарник PIE (libtailscale.so) под четыре архитектуры: arm64-v8a, armeabi-v7a, x86, x86_64.
Он собран с апстримовским тегом ts_include_cli и потому заодно является CLI: работает как tailscale, когда его argv[0] — tailscale (ссылка приложения в каталоге данных) или когда задан TS_BE_CLI=1 (обёртка Root-режима).
Сборка Android APK
Заголовок раздела «Сборка Android APK»Debug APK — без keystore, ставится рядом с релизом благодаря суффиксу .dev:
./gradlew app:assembleDebugRelease APK — нужен собственный keystore.
Задайте все четыре переменные, иначе задача packageRelease завершится с понятной ошибкой:
KEYSTORE_FILE="$PWD/tailsocks.jks" KEYSTORE_PASSWORD=... \KEY_ALIAS=... KEY_PASSWORD=... ./gradlew app:assembleReleaseЕсли keystore ещё нет, создайте его: keytool -genkeypair -v -keystore tailsocks.jks -alias tailsocks -keyalg RSA -keysize 4096 -validity 10000.
Особенности релизной сборки
Заголовок раздела «Особенности релизной сборки»- R8-минификация и сжатие ресурсов включены (
isMinifyEnabled = true,isShrinkResources = true). В проекте намеренно нет JSON через рефлексию: модели сериализуютсяkotlinx.serialization(core/AppJson.kt), ресурсы никогда не ищутся по динамическому имени, а сервис AppFunctions создаёт сгенерированные KSP классы$Aggregated…_Implнапрямую, а не через рефлексию. Поэтому урезанная сборка не зависит от keep-правил для этих путей. - Проверка JNI (
verifyReleaseNativeMethods). TUN-библиотека регистрирует Java-методы по имени внутриJNI_OnLoad. Если R8 удалит хотя бы одинexternal fun,System.loadLibraryупадёт в рантайме (однажды так и было — каждая остановка роняла приложение).- Задача запускается автоматически после
minifyReleaseWithR8и передassembleRelease/bundleRelease. Она собирает всеexternal funизapp/src/main/java, проверяет, что каждый попал под keep-правило вseeds.txtR8 и что ни один native-член не значится вusage.txt, иначе сборка падает. - Отдельно:
./gradlew :app:verifyReleaseNativeMethodsпосле релизной сборки. - При ошибке правьте
app/proguard-rules.pro— обычный-keepдляnative <methods>(не-keepclasseswithmembernames).
- Задача запускается автоматически после
- Лицензии сторонних компонентов (
app/src/main/assets/licenses.json). Экран «Лицензии открытого ПО» читает закоммиченный список.python3 scripts/licenses/generate.pyсобирает его из того, что реально попадает в сборку: списков Go-модулей, записанных вlibgojni.soиlibtailscale.so, артефактовreleaseRuntimeClasspath(черезscripts/licenses/shipped-artifacts.init.gradle.kts) и C-библиотек ndk-build. Тексты лицензий берутся из кэша Go-модулей, POM-файлов и самих артефактов.- Запускайте после
appctr/build.shи Gradle-сборки при каждой смене зависимостей. CI делает--checkи предупреждает, если список устарел. - Тексты, которых нет ни в одном артефакте, лежат в
scripts/licenses/texts/.
- Запускайте после
- Версионирование.
versionNameиversionCodeберутся изversion.properties(4.5.2/4050200; отладочная сборка добавляет-dev). Хэш коммита —BuildConfig.GIT_HASH. Сборщик, который сам обновляет приложение, добавляет-PselfUpdate=false. Код должен следовать из имени — релиза или беты (5.1.0-beta.1/5009910), — иначе Gradle откажется конфигурировать сборку. Правило, два канала и порядок выпуска релиза описаны в разделе Выпуск релизов.
Установка и запуск через ADB
Заголовок раздела «Установка и запуск через ADB»Включены ABI-сплиты, поэтому каждая сборка даёт universal APK и по одному на архитектуру (arm64-v8a, armeabi-v7a, x86, x86_64) в app/build/outputs/apk/<debug|release>/.
# Debug-сборкаadb install -r app/build/outputs/apk/debug/app-universal-debug.apk
# Release-сборкаadb install -r app/build/outputs/apk/release/app-universal-release.apk
# Запуск основного экранаadb shell am start -n io.github.bropines.tailscaled/io.github.bropines.tailscaled.ui.MainActivity