Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Test
on:
push:
branches: [ master, main ]
tags: [ '[0-9]*' ]
pull_request:
branches: [ master, main ]

Expand All @@ -21,11 +22,11 @@ jobs:
java-version: '17'
distribution: 'temurin'

- name: Set up Rust
- name: Install Android native build tools
run: |
rustup toolchain install stable --profile minimal
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
cargo install cargo-ndk
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
"cmake;3.22.1" \
"ndk;27.2.12479018"

- name: Cache cargo registry and build
uses: actions/cache@v6
Expand All @@ -36,14 +37,30 @@ jobs:
wgbridge-rs/target
key: cargo-${{ runner.os }}-${{ hashFiles('wgbridge-rs/Cargo.lock') }}

- name: Set up Rust
run: ./scripts/setup_rust_android.sh

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6

- name: Prefetch Gradle dependencies
run: |
./gradlew :app:dependencies
./gradlew :app:prefetchAndroidLintDependencies

- name: Run Rust unit tests
run: cargo test --manifest-path wgbridge-rs/Cargo.toml
run: cargo test --manifest-path wgbridge-rs/Cargo.toml --locked --offline

- name: Run unit tests
run: ./gradlew testGithubDebugUnitTest
run: ./gradlew testFdroidDebugUnitTest --offline

- name: Build F-Droid release APK offline
run: ./gradlew :app:assembleFdroidRelease --offline

- name: Build debug APK
run: ./gradlew assembleGithubDebug
- name: Verify WireGuard libraries in F-Droid APK
run: |
APK=$(find app/build/outputs/apk/fdroid -type f -name '*.apk' -print -quit)
test -n "$APK"
for ABI in armeabi-v7a arm64-v8a x86 x86_64; do
test "$(unzip -Z1 "$APK" "lib/$ABI/libwgbridge.so")" = "lib/$ABI/libwgbridge.so"
done
129 changes: 129 additions & 0 deletions FDROID-UPDATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Updating TrackerControl on F-Droid

TrackerControl's WireGuard engine is compiled from Rust source. New F-Droid
build entries must prepare the pinned Rust toolchain and Cargo dependencies
before F-Droid starts its offline Gradle build.

The examples below use release `2026071201` / `2026.07.12`. Substitute the
actual version code and version name for later releases.

## 1. Tag the release

Only tag a commit that includes the current `rust-toolchain.toml`,
`wgbridge-rs/Cargo.lock`, and `scripts/setup_rust_android.sh` files.

```bash
git tag 2026071201
git push origin 2026071201
```

TrackerControl's F-Droid metadata uses numeric tags as release identifiers.

## 2. Prepare an fdroiddata merge request

Fork [fdroid/fdroiddata](https://gitlab.com/fdroid/fdroiddata), then clone the
fork and create a branch:

```bash
git clone https://gitlab.com/YOUR_USERNAME/fdroiddata.git
cd fdroiddata
git remote add upstream https://gitlab.com/fdroid/fdroiddata.git
git switch -c trackercontrol-2026071201
```

Edit:

```text
metadata/net.kollnig.missioncontrol.fdroid.yml
```

Append this entry under `Builds:`:

```yaml
- versionName: 2026.07.12-fdroid
versionCode: 2026071201
commit: '2026071201'
subdir: app
sudo:
- apt-get update
- apt-get install -y rustup gcc libc-dev
gradle:
- fdroid
prebuild:
- ../scripts/setup_rust_android.sh
ndk: r27c
```

Update the current-version fields at the bottom of the file:

```yaml
CurrentVersion: 2026.07.12-fdroid
CurrentVersionCode: 2026071201
```

Keep the automatic-update configuration unchanged:

```yaml
AutoUpdateMode: Version
UpdateCheckMode: Tags ^[0-9]+$
```

## 3. Validate the metadata

With `fdroidserver` installed and available on `PATH`, run from the
`fdroiddata` checkout:

```bash
fdroid readmeta
fdroid lint net.kollnig.missioncontrol.fdroid
```

For an end-to-end local application check, first provision the pinned Rust
dependencies while networking is available, then build the release offline:

```bash
./scripts/setup_rust_android.sh
./gradlew :app:assembleFdroidRelease --offline
```

The APK should contain `libwgbridge.so` for `armeabi-v7a`, `arm64-v8a`, `x86`,
and `x86_64`.

## 4. Submit the metadata change

From the `fdroiddata` checkout:

```bash
git add metadata/net.kollnig.missioncontrol.fdroid.yml
git commit -m "Update TrackerControl to 2026071201"
git push origin trackercontrol-2026071201
```

Open a merge request from that branch into `fdroid/fdroiddata:master`.

Suggested merge-request description:

```text
TrackerControl now builds its WireGuard engine from Rust source.

The prebuild step installs the repository-pinned Rust toolchain, Android
targets, and cargo-ndk, and fetches Cargo.lock dependencies. The Gradle build
itself runs Cargo with --locked --offline.

Locally verified with:
./gradlew :app:assembleFdroidRelease --offline
```

## If the update bot opens a merge request first

The checkupdates bot normally copies the previous build recipe, which does not
contain the Rust preparation steps. Its source branch belongs to F-Droid's bot
fork and may not be editable by the TrackerControl maintainers.

Create the complete merge request from the TrackerControl maintainer's own
`fdroiddata` fork as described above. Comment on the bot merge request with a
link to the replacement and ask F-Droid's maintainers to close the bot merge
request in favor of it.

F-Droid's general contribution guide is available at
<https://fdroid.gitlab.io/jekyll-fdroid/docs/Submitting_to_F-Droid_Quick_Start_Guide/>.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,10 @@ In combination with F-Droid, this repository uses automated builds and follows a

You need:
- Android Studio (with the Android SDK and build tools)
- Android NDK (any recent version)
- Rust via [rustup](https://rustup.rs) with the Android targets, for the WireGuard engine ([gotatun](https://github.com/mullvad/gotatun), built from source in `wgbridge-rs/`):
- Android NDK 27.2.12479018 (r27c)
- Rust via [rustup](https://rustup.rs), for the WireGuard engine ([gotatun](https://github.com/mullvad/gotatun), built from source in `wgbridge-rs/`). The compiler, Android targets, and `cargo-ndk` version are pinned; install them and pre-fetch locked crates with:
```bash
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
cargo install cargo-ndk
./scripts/setup_rust_android.sh
```

Build from within Android Studio, or use the provided gradle wrapper — see the [Android developer documentation](https://developer.android.com/studio/build/building-cmdline). If you find any problems with these instructions, please file an issue.
Expand Down
36 changes: 25 additions & 11 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ def wgbridgeSrcDir = file("$rootDir/wgbridge-rs")
def wgbridgeOutDir = file("$buildDir/rustJniLibs")
def wgbridgeAbis = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']
def wgbridgeNdkVersion = '27.2.12479018' // keep in sync with defaultConfig.ndkVersion
def wgbridgeCargoNdkVersion = '4.1.2' // keep in sync with scripts/setup_rust_android.sh

// Resolves the lint tool artifacts while networking is available so release
// builds can subsequently run with Gradle's --offline flag (as on F-Droid).
tasks.register('prefetchAndroidLintDependencies') {
description = 'Downloads the Android lint tool artifacts for a later offline build.'
group = 'build setup'
doLast {
configurations.named('androidLintTool').get().resolve()
}
}

// Resolve the NDK directory the way AGP does (local.properties, then env),
// since modern AGP extensions no longer expose android.ndkDirectory.
Expand Down Expand Up @@ -70,7 +81,7 @@ tasks.register('wgbridgeBuild') {
Could not find a `cargo` executable. Install Rust via rustup
(https://rustup.rs), then:
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
cargo install cargo-ndk
cargo install cargo-ndk@${wgbridgeCargoNdkVersion} --locked
and either add ~/.cargo/bin to PATH (restart Android Studio),
or pass -PcargoBin=/path/to/cargo to Gradle.
""".stripIndent())
Expand Down Expand Up @@ -104,20 +115,23 @@ tasks.register('wgbridgeBuild') {
env['CARGO_ENCODED_RUSTFLAGS'] = rustFlags.join('\u001f')
env.remove('RUSTFLAGS')

// cargo-ndk drives the per-ABI cross-compilation and linker setup.
def cargoNdk = "${System.getProperty('user.home')}/.cargo/bin/cargo-ndk"
if (!file(cargoNdk).canExecute() &&
!env['PATH'].split(File.pathSeparator).any { file("$it/cargo-ndk").canExecute() }) {
logger.lifecycle('Installing cargo-ndk ...')
providers.exec {
environment env
commandLine cargoBin, 'install', 'cargo-ndk'
}.result.get().assertNormalExitValue()
// Tool installation must happen before Gradle runs. In particular,
// F-Droid disables network access for the actual build phase.
def cargoNdkAvailable = file("${System.getProperty('user.home')}/.cargo/bin/cargo-ndk").canExecute() ||
env['PATH'].split(File.pathSeparator).any { file("$it/cargo-ndk").canExecute() }
if (!cargoNdkAvailable) {
throw new GradleException("""
Could not find cargo-ndk ${wgbridgeCargoNdkVersion}. Run:
./scripts/setup_rust_android.sh
Gradle deliberately does not install build tools because release
and F-Droid builds must be able to run without network access.
""".stripIndent())
}

def args = [cargoBin, 'ndk']
wgbridgeAbis.each { args += ['-t', it] }
args += ['--platform', '23', '-o', wgbridgeOutDir.absolutePath, 'build', '--release']
args += ['--platform', '23', '-o', wgbridgeOutDir.absolutePath,
'build', '--release', '--locked', '--offline']

providers.exec {
workingDir wgbridgeSrcDir
Expand Down
5 changes: 0 additions & 5 deletions app/src/main/res/values-ru-rRU/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,6 @@
<string name="title_log_whois">Whois %1$s</string>
<string name="title_log_port">Порт %1$d</string>
<string name="title_log_copy">Скопировать</string>
<string-array name="blockingModeNames">
<item>@string/blocking_mode_minimal</item>
<item>Строка/blocking_mode_standard</item>
<item>Строка/blocking_mode_strict</item>
</string-array>
<!-- App Overview -->
<string name="tracked_app_icon">Отключить доступ в интернет для этого приложения</string>
<string name="tracked_app_name">Название приложения</string>
Expand Down
5 changes: 0 additions & 5 deletions app/src/main/res/values-uk-rUA/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,6 @@
<string name="title_log_whois">Whois %1$s</string>
<string name="title_log_port">Порт %1$d</string>
<string name="title_log_copy">Копіювати</string>
<string-array name="blockingModeNames">
<item>@string/режим_блокування_мінімальний</item>
<item>@string/режим_блокування_стандартний</item>
<item>@string/режим_блокування_суворий</item>
</string-array>
<!-- App Overview -->
<string name="tracked_app_icon">Вимкнути доступ до Інтернету для цієї програми</string>
<string name="tracked_app_name">Назва програми</string>
Expand Down
5 changes: 0 additions & 5 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -378,11 +378,6 @@
<string name="title_log_whois">Whois %1$s</string>
<string name="title_log_port">端口 %1$d</string>
<string name="title_log_copy">复制</string>
<string-array name="blockingModeNames">
<item>最小</item>
<item>标准</item>
<item>严格</item>
</string-array>
<!-- App Overview -->
<string name="tracked_app_icon">禁止此应用访问互联网</string>
<string name="tracked_app_name">应用程序名称</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ Your internet traffic is not being sent to a remote VPN server.</string>
<string name="title_log_port">Port %1$d</string>
<string name="title_log_copy">Copy</string>

<string-array name="blockingModeNames">
<string-array name="blockingModeNames" translatable="false">
<item>@string/blocking_mode_minimal</item>
<item>@string/blocking_mode_standard</item>
<item>@string/blocking_mode_strict</item>
Expand Down
9 changes: 9 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[toolchain]
channel = "1.95.0"
profile = "minimal"
targets = [
"aarch64-linux-android",
"armv7-linux-androideabi",
"i686-linux-android",
"x86_64-linux-android",
]
28 changes: 28 additions & 0 deletions scripts/setup_rust_android.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh
set -eu

ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
CARGO_NDK_VERSION=4.1.2

if ! command -v rustup >/dev/null 2>&1; then
echo "rustup is required; install it before running this script." >&2
exit 1
fi

# rust-toolchain.toml pins the compiler and Android targets. This command is
# idempotent and makes the required components available before offline builds.
rustup toolchain install 1.95.0 --profile minimal \
--target aarch64-linux-android \
--target armv7-linux-androideabi \
--target i686-linux-android \
--target x86_64-linux-android

if ! command -v cargo-ndk >/dev/null 2>&1 || \
[ "$(cargo-ndk --version 2>/dev/null | awk '{print $2}')" != "$CARGO_NDK_VERSION" ]; then
cargo install "cargo-ndk@$CARGO_NDK_VERSION" --locked
fi

# Populate Cargo's cache while networking is allowed. wgbridgeBuild passes
# --offline and --locked so the later Gradle/F-Droid build cannot fetch or
# silently change dependencies.
cargo fetch --manifest-path "$ROOT/wgbridge-rs/Cargo.toml" --locked
Loading