diff --git a/.devtools.conf b/.devtools.conf index 32ff606..f481c2e 100644 --- a/.devtools.conf +++ b/.devtools.conf @@ -4,3 +4,8 @@ APP_NAME=ezvpn MAIN_ACTIVITY=dev.flexaccess.ezvpn/.MainActivity CORE_REPO=ezvpn CORE_GH_REPO=flexaccessdev/ezvpn + +# The app version (ezvpn.versionName/versionCode) moves independently of the +# core; a bump only rewrites the release pin, and the build derives the core +# version shown in the UI from ezvpn.releaseTag. +BUMP_VERSION_MODE=core-key diff --git a/CLAUDE.md b/CLAUDE.md index f0b1344..02a37fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ - make changes on the sibling project ../ezvpn (the Rust core this app loads) when needed; its design notes for this app are in ../ezvpn/docs/Android-App.md. Run its `cargo ndk -t arm64-v8a --platform 29 clippy --lib -- -D warnings` after Rust changes touching the Android build. - always test on the development emulator over adb (`10.22.35.66:5555`, an arm64 Android VM; `adb connect 10.22.35.66`); a VpnService cannot be exercised meaningfully on the JVM. `scripts/run-device.sh` targets it by default (`ADB_SERIAL` overrides), builds the local core, installs, launches, and tails logcat. When several devices are attached, always pass `-s ` to adb / set `ANDROID_SERIAL` for Gradle install tasks. `scrcpy -s 10.22.35.66:5555` mirrors/controls the emulator screen. - the Android Studio emulator is relay-only by default (its Wi-Fi sits behind netsim's user-mode NAT, and `-vmnet-bridged` alone only re-backs the cellular `eth0`); for LAN addresses / direct iroh paths start it with `-feature -WiFiPacketStream -vmnet-bridged ` (sudo, Apple silicon) — README "Emulator networking". Verified with emulator 37.1.11 on a stock Play Store image, no root needed. -- the Rust artifact (`libezvpn.so` per ABI, zipped as `libezvpn-android.zip`) is delivered by download + sha256 pin in `gradle.properties` (`app/build.gradle.kts` `fetchEzvpnJniLibs`). Bump with `scripts/bump-jnilibs.sh ` after the ezvpn release workflow publishes the asset. For FFI dev against a local build run `../ezvpn/build-android.sh` then set `EZVPN_LOCAL_JNILIBS=1` for every gradle invocation — only the exact value `1` opts in. +- the Rust artifact (`libezvpn.so` per ABI, zipped as `libezvpn-android.zip`) is delivered by download + sha256 pin in `gradle.properties` (`app/build.gradle.kts` `fetchEzvpnJniLibs`). Bump with `scripts/bump-jnilibs.sh ` after the ezvpn release workflow publishes the asset; it only rewrites the pin — the app's own `ezvpn.versionName`/`ezvpn.versionCode` are independent of the core release and are bumped by hand (the UI shows both, the core one via `BuildConfig.EZVPN_CORE_VERSION`). For FFI dev against a local build run `../ezvpn/build-android.sh` then set `EZVPN_LOCAL_JNILIBS=1` for every gradle invocation — only the exact value `1` opts in. - `EzvpnNative` must stay at `dev.flexaccess.ezvpn.EzvpnNative`: the JNI symbol names in ../ezvpn/src/ffi_android.rs encode that class. `EzvpnNative.init(context)` must run once before anything else (Application.onCreate): it registers the JVM/context that iroh's Android DNS/interface discovery needs, or the first connect aborts the process. - pure logic (CIDR math, the route plan with its underlay bypass — `excludeRoute` on API 33+, subtraction below, profile model/validation, split-DNS rules, JSON shapes) lives in `tunnelcore` (no Android deps) so it is unit-testable with `./gradlew :tunnelcore:test`. Put new pure helpers there, not in the service. - no foreground-service notification: the system binds the VpnService while its interface is established, which keeps the process alive (WireGuard does the same). Don't add one. diff --git a/README.md b/README.md index e90c261..57f3b85 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,10 @@ scripts/build-release-apk.sh --unsigned # no signing (not installable as is) Google Play only accepts an App Bundle (`.aab`), on every track including internal testing; `--bundle` builds one signed with the same keystore (Play App Signing uses it as the upload key). Every upload needs a higher -`ezvpn.versionCode` in `gradle.properties`. +`ezvpn.versionCode` in `gradle.properties`. The app's `ezvpn.versionName` / +`ezvpn.versionCode` are its own and independent of the pinned `ezvpn` release: +`scripts/bump-jnilibs.sh` only re-pins the core, whose version the app shows +next to its own. The release key is a keystore outside the repo (default `~/.config/ezvpn-android/release.jks`, override with `EZVPN_KEYSTORE`; diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b9e373a..4530076 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -90,8 +90,13 @@ android { applicationId = "dev.flexaccess.ezvpn" minSdk = 29 targetSdk = 37 + // The app's own version (gradle.properties), independent of the core pin. versionCode = providers.gradleProperty("ezvpn.versionCode").get().toInt() versionName = providers.gradleProperty("ezvpn.versionName").get() + // The pinned libezvpn release, shown next to the app version in the UI. + // A local FFI build (EZVPN_LOCAL_JNILIBS) still reports the pinned + // number: the local .so carries none. + buildConfigField("String", "EZVPN_CORE_VERSION", "\"${ezvpnReleaseTag.removePrefix("v")}\"") // 64-bit only (Google Play's 64-bit requirement; 32-bit devices are // not supported): arm64-v8a for phones plus x86_64 for VMs/emulators. diff --git a/app/src/main/kotlin/dev/flexaccess/ezvpn/ui/TunnelListScreen.kt b/app/src/main/kotlin/dev/flexaccess/ezvpn/ui/TunnelListScreen.kt index 122a3f5..d9cd00c 100644 --- a/app/src/main/kotlin/dev/flexaccess/ezvpn/ui/TunnelListScreen.kt +++ b/app/src/main/kotlin/dev/flexaccess/ezvpn/ui/TunnelListScreen.kt @@ -63,7 +63,7 @@ fun TunnelListScreen( }, bottomBar = { Text( - stringResource(R.string.tunnel_list_version, BuildConfig.VERSION_NAME), + stringResource(R.string.tunnel_list_version, BuildConfig.VERSION_NAME, BuildConfig.EZVPN_CORE_VERSION), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5c129ea..6286008 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,7 +5,7 @@ Auth keys Add profile - ezvpn %1$s + ezvpn %1$s · core %2$s No profiles Tap + to add a VPN profile. Waiting… diff --git a/gradle.properties b/gradle.properties index 0a91e41..d8b590b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,12 +8,15 @@ android.nonTransitiveRClass=true # The Rust core (libezvpn.so, built from the sibling ../ezvpn repo by its # build-android.sh and released as libezvpn-android.zip). By default the app # downloads this pinned release zip and verifies its sha256 (reproducible). -# Bump both together with scripts/bump-jnilibs.sh , which also sets the -# app versionName below. For local FFI dev set EZVPN_LOCAL_JNILIBS=1 to use -# ../ezvpn/dist/android/jniLibs instead (see README). +# Bump both together with scripts/bump-jnilibs.sh . The tag (minus "v") +# is also what the UI shows as the core version (BuildConfig.EZVPN_CORE_VERSION). +# For local FFI dev set EZVPN_LOCAL_JNILIBS=1 to use ../ezvpn/dist/android/jniLibs +# instead (see README); the UI still shows the pinned number then. ezvpn.releaseTag=v0.0.44 ezvpn.releaseSha256=3f636dc29de73f992d7b94f117b1b1eed49d838dfd2cded9986f84764a116a78 -# App version; versionName follows the pinned ezvpn release (numeric part). +# The app's own version, independent of the pinned ezvpn release: a core bump +# does not touch these. Bump by hand when the app changes; every Play upload +# needs a higher versionCode. ezvpn.versionName=0.0.44 ezvpn.versionCode=5