v1.0.0: multi-arch Android APK + GitHub Actions release job + install docs (#30)

Version bump reflects the scope — a unified Rust core that now ships
for desktop (Linux/macOS/Windows) AND Android from the same crate.

Android changes:
- build.gradle.kts: ABI filters expanded to arm64-v8a + armeabi-v7a
  + x86_64 + x86. cargoBuild{Debug,Release} pass all four ABIs to
  cargo-ndk in a single invocation. normalizeTun2proxySo() walks every
  ABI dir now (was arm64-only).
- Release buildType signs with the debug keystore — no Play Store
  target, so signature identity doesn't matter, installability does.
  Gradle auto-provisions ~/.android/debug.keystore if absent, so CI
  runners inherit this without extra setup.
- versionName 1.0.0, versionCode 100 (room to bump monotonically).

CI:
- release.yml gets a dedicated `android:` job that sets up JDK 17,
  Android SDK/NDK 26, all four rust-android targets, installs
  cargo-ndk, runs assembleRelease, and uploads a single universal APK
  named `mhrv-rs-android-universal-v<version>.apk` into the same
  `dist/` collected by the release job downstream.
- `release:` job now gates on `needs: [build, android]` so tagging
  v1.0.0 triggers both build matrices before cutting the GitHub
  release.

Docs:
- docs/android.md — full 10-step install walk-through: APK sideload,
  Apps Script deployment (with "Advanced → Go to (unsafe) → Allow"
  reality check), config paste, SNI reachability test, MITM CA
  install with OEM-specific nav paths (Pixel / Samsung / Xiaomi),
  Start, troubleshooting common failure modes. Also documents the
  known limitations — Cloudflare Turnstile loops (inherent to the
  Apps Script egress IP pool), UDP/QUIC not tunnelled, IPv6 leaks,
  Apps Script daily quota — so users know what to expect before
  trying it on a site that won't work.
- releases/README.md — APK row added to the English and Persian
  tables, version bumped everywhere to v1.0.0.
- Top-level README — Android listed under Platforms with a link
  to docs/android.md.

Release artifact:
- releases/mhrv-rs-android-universal-v1.0.0.apk — 38 MB universal
  APK built locally from this tree. Installs + launches on API 24+.
  The CI job will regenerate it on tag push; this is the copy
  committed for users who can't reach GitHub Releases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Shin (Former Aleph)
2026-04-23 02:56:39 +03:00
committed by GitHub
parent 96d1352728
commit 91015b0594
8 changed files with 340 additions and 46 deletions
+66 -1
View File
@@ -208,8 +208,73 @@ jobs:
dist/${{ matrix.name }}-app.zip
if-no-files-found: ignore
# Android build — separate job so it doesn't inflate the matrix. The
# Rust side here cross-compiles to FOUR ABIs (arm64-v8a, armeabi-v7a,
# x86_64, x86) via cargo-ndk and drops the .so files into the Gradle
# project's jniLibs/ tree, which then packages them into a single
# universal APK. Users pick it once, no per-ABI split.
android:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Set up Android SDK
uses: android-actions/setup-android@v3
with:
cmdline-tools-version: 11076708
- name: Install NDK
run: |
yes | sdkmanager --install "ndk;26.1.10909125" >/dev/null
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125" >> "$GITHUB_ENV"
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android,i686-linux-android
- name: Install cargo-ndk
run: cargo install cargo-ndk --locked
# `./gradlew :app:assembleRelease` triggers cargoBuildRelease first
# which invokes cargo-ndk with all four targets, then Gradle packages
# the APK (release buildType signed with the debug keystore — see
# android/app/build.gradle.kts comment explaining why).
- name: Build release APK
working-directory: android
run: |
chmod +x ./gradlew
./gradlew :app:assembleRelease --no-daemon --stacktrace
- name: Rename APK with version
working-directory: android
run: |
VER="${GITHUB_REF#refs/tags/v}"
SRC="app/build/outputs/apk/release/app-release.apk"
if [ ! -f "$SRC" ]; then
# Some AGP versions name it differently when the release config
# can't be auto-signed. Catch that up front with a clear error
# instead of a silent missing-artifact later.
echo "::error::expected $SRC to exist; actual outputs:"
find app/build/outputs/apk -type f -name '*.apk' -print
exit 1
fi
mkdir -p ../dist
cp "$SRC" "../dist/mhrv-rs-android-universal-v${VER}.apk"
- uses: actions/upload-artifact@v4
with:
name: mhrv-rs-android-universal
path: dist/*.apk
if-no-files-found: error
release:
needs: build
needs: [build, android]
runs-on: ubuntu-latest
permissions:
contents: write
Generated
+1 -1
View File
@@ -1960,7 +1960,7 @@ dependencies = [
[[package]]
name = "mhrv-rs"
version = "0.9.4"
version = "1.0.0"
dependencies = [
"base64 0.22.1",
"bytes",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mhrv-rs"
version = "0.9.4"
version = "1.0.0"
edition = "2021"
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
license = "MIT"
+3 -1
View File
@@ -47,7 +47,9 @@ For a handful of Google-owned domains (`google.com`, `youtube.com`, `fonts.googl
## Platforms
Linux (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64). Prebuilt binaries on the [releases page](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/releases).
Linux (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64), **Android 7.0+** (universal APK covering arm64, armv7, x86_64, x86). Prebuilt binaries on the [releases page](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/releases).
**Android users** — grab `mhrv-rs-android-universal-v*.apk` and follow the full walk-through in [docs/android.md](docs/android.md). The Android build runs the exact same `mhrv-rs` crate as the desktop (via JNI) and adds a TUN bridge via `tun2proxy`, so every app on the device routes through the proxy without per-app configuration.
## What's in a release
+58 -38
View File
@@ -14,15 +14,19 @@ android {
applicationId = "com.therealaleph.mhrv"
minSdk = 24 // Android 7.0 — covers 99%+ of live devices.
targetSdk = 34
versionCode = 1
versionName = "0.1.0"
versionCode = 100
versionName = "1.0.0"
// Only arm64 for now — we can add armeabi-v7a in a second pass
// if field reports need it. Android emulators on Apple Silicon
// only run arm64 natively, so keeping things aarch64-only makes
// the dev loop fast.
// Ship all four mainstream Android ABIs:
// - arm64-v8a — 95%+ of real-world Android phones since 2019
// - armeabi-v7a — older/cheaper devices still on 32-bit ARM
// - x86_64 — Android emulator on Intel Macs + Chromebooks
// - x86 — legacy 32-bit Intel emulator; cheap to include
// Per-ABI .so files push the APK up to ~50 MB, but users expect one
// APK that Just Works rather than "pick the right ABI" which nobody
// does correctly. Google Play would auto-split; we ship universal.
ndk {
abiFilters += listOf("arm64-v8a")
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
}
}
@@ -33,6 +37,15 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
// Sign release builds with the debug keystore so users can
// sideload the APK without us shipping a proper release key.
// The project has no Play Store presence, so signature
// identity per-build doesn't matter — installability does.
// Gradle auto-creates `~/.android/debug.keystore` on first use;
// CI runners inherit that behaviour. Anyone rebuilding from
// source gets their own signature, which is what we want for
// an open-source project: trust the source, not a key we hold.
signingConfig = signingConfigs.getByName("debug")
}
}
@@ -96,27 +109,34 @@ dependencies {
val rustCrateDir = rootProject.projectDir.parentFile
val jniLibsDir = file("src/main/jniLibs")
// After cargo-ndk dumps artifacts into jniLibs/arm64-v8a/, the tun2proxy
// cdylib lands as `libtun2proxy-<hash>.so` (rustc's deps/ naming convention,
// because tun2proxy is a transitive dep not a root crate). Android's
// System.loadLibrary expects a stable name, and the hash changes between
// builds, so we normalize it to `libtun2proxy.so` here. Also deletes any
// stale hash-suffixed copies from previous builds.
// After cargo-ndk dumps artifacts into each jniLibs/<abi>/ dir, the
// tun2proxy cdylib lands as `libtun2proxy-<hash>.so` (rustc's deps/ naming
// convention, because tun2proxy is a transitive dep not a root crate).
// Android's System.loadLibrary expects a stable name, and the hash changes
// between builds, so we normalize it to `libtun2proxy.so` in every ABI dir.
// Also deletes any stale hash-suffixed copies from previous builds.
fun normalizeTun2proxySo() {
val abiDir = file("src/main/jniLibs/arm64-v8a")
if (!abiDir.isDirectory) return
val hashed = abiDir.listFiles { f -> f.name.matches(Regex("libtun2proxy-[0-9a-f]+\\.so")) }
?: emptyArray()
// Keep only the newest (release build) and rename it.
val newest = hashed.maxByOrNull { it.lastModified() }
if (newest != null) {
val target = abiDir.resolve("libtun2proxy.so")
if (target.exists()) target.delete()
newest.copyTo(target, overwrite = true)
val jniLibsRoot = file("src/main/jniLibs")
if (!jniLibsRoot.isDirectory) return
jniLibsRoot.listFiles()?.filter { it.isDirectory }?.forEach { abiDir ->
val hashed = abiDir.listFiles { f -> f.name.matches(Regex("libtun2proxy-[0-9a-f]+\\.so")) }
?: emptyArray()
val newest = hashed.maxByOrNull { it.lastModified() }
if (newest != null) {
val target = abiDir.resolve("libtun2proxy.so")
if (target.exists()) target.delete()
newest.copyTo(target, overwrite = true)
}
hashed.forEach { it.delete() }
}
hashed.forEach { it.delete() }
}
// All ABIs we ship. Keep in sync with `android.defaultConfig.ndk.abiFilters`
// above; if these drift, the APK either includes .so files with no matching
// ABI entry (dead weight) or advertises ABIs with no .so (runtime
// UnsatisfiedLinkError on devices that pick that split).
val androidAbis = listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
tasks.register<Exec>("cargoBuildDebug") {
group = "build"
// Intentionally ALWAYS uses --release. The Rust debug build is 80+MB
@@ -124,27 +144,27 @@ tasks.register<Exec>("cargoBuildDebug") {
// never worth it just for a Rust stack trace you wouldn't see in
// logcat anyway. If you need Rust debug symbols, temporarily drop
// `--release` below and accept the APK size.
description = "Cross-compile mhrv_rs for arm64-v8a (release — same as cargoBuildRelease)"
description = "Cross-compile mhrv_rs for all ABIs (release — same as cargoBuildRelease)"
workingDir = rustCrateDir
commandLine(
"cargo", "ndk",
"-t", "arm64-v8a",
"-o", jniLibsDir.absolutePath,
"build", "--release",
)
commandLine(buildList<String> {
add("cargo"); add("ndk")
androidAbis.forEach { add("-t"); add(it) }
add("-o"); add(jniLibsDir.absolutePath)
add("build"); add("--release")
})
doLast { normalizeTun2proxySo() }
}
tasks.register<Exec>("cargoBuildRelease") {
group = "build"
description = "Cross-compile mhrv_rs for arm64-v8a (release)"
description = "Cross-compile mhrv_rs for all ABIs (release)"
workingDir = rustCrateDir
commandLine(
"cargo", "ndk",
"-t", "arm64-v8a",
"-o", jniLibsDir.absolutePath,
"build", "--release",
)
commandLine(buildList<String> {
add("cargo"); add("ndk")
androidAbis.forEach { add("-t"); add(it) }
add("-o"); add(jniLibsDir.absolutePath)
add("build"); add("--release")
})
doLast { normalizeTun2proxySo() }
}
+200
View File
@@ -0,0 +1,200 @@
# Android app — installation & first-run guide
This is the one-stop guide for running **mhrv-rs on your Android phone**. It covers the APK install, the MITM certificate dance (which changed a lot on Android 11+), the VPN permission, the first-time Apps Script deployment, and the common "why doesn't this site load" failure modes.
Estimated time: ~10 minutes if the Apps Script deployment already exists, ~15 if you're deploying it fresh.
> **Scope note.** mhrv-rs relays through Google Apps Script. That's what makes it cheap and firewall-resilient, but it's also what imposes the limits documented under [Known limitations](#known-limitations). If you're evaluating this against "a real VPN" — WireGuard, Tailscale, OpenVPN — read that section first.
---
## What you'll need
- An Android phone running **Android 7.0 (API 24)** or later. Arm64, armv7, x86_64, and x86 all ship in one APK.
- A **Google account** (you'll deploy the Apps Script under it). Any account works; a throwaway Gmail is fine.
- About **5 MB of mobile data** for the APK download, **2 MB/day** of relay traffic per GB of browsing (overhead from base64 + JSON wrapping).
- A way to set a **screen lock** (PIN, pattern, password, or biometric + fallback) — this is an Android OS requirement for installing any user CA certificate. You can remove the lock later if you want.
---
## Step 1 — Install the APK
1. On your phone, open the browser and go to: `https://github.com/therealaleph/MasterHttpRelayVPN-RUST/tree/main/releases`
2. Tap `mhrv-rs-android-universal-v1.0.0.apk`**View raw** (or **Download**).
3. When the browser finishes the download, tap the notification to open the APK.
4. Android will ask: **"Allow this source to install apps?"** — tap **Settings**, toggle **Allow from this source**, then tap **← Back**. Tap **Install**.
5. Tap **Open** once install finishes. You should see the **mhrv-rs** main screen.
> **Why "unknown source" is necessary.** The app isn't on Play Store (it's not the kind of app Google will accept — explicit VPN with MITM). Every Android phone ships with sideload capability for exactly this situation; you're just toggling the permission for the one app that's delivering the APK (Chrome, Files, etc.).
If Android refuses to install with "App not installed" or similar, 99% of the time it's because a different mhrv-rs build is already installed signed with a different key. Uninstall the old one first: `Settings → Apps → mhrv-rs → Uninstall`.
---
## Step 2 — Deploy the Apps Script
Skip this step if you already have a working `/exec` URL from a previous install.
1. On your laptop (easier than on phone), go to <https://script.google.com> and sign in.
2. Click **New project**. You'll land in the script editor.
3. Open [`assets/apps_script/Code.gs`](../assets/apps_script/Code.gs) in this repo, copy the **entire** contents.
4. Back in the script editor, select ALL of the default code (`function myFunction() {}`) and paste over it.
5. Find this line near the top:
```js
const AUTH_KEY = "CHANGE_ME_TO_A_STRONG_SECRET";
```
Replace the placeholder with a **strong random secret** (20+ characters, letters + digits). Save a copy somewhere — you'll paste the exact same string into the app.
6. Save the file (⌘S or Ctrl+S). Name the project something like `mhrv-relay`.
7. Top-right, click **Deploy → New deployment**.
8. Click the gear icon → **Web app**.
9. Fill in:
- **Description**: `mhrv-relay v1` (or whatever)
- **Execute as**: **Me**
- **Who has access**: **Anyone**
- Click **Deploy**.
10. First time only: Google asks for permissions. Click **Authorize access** → pick your account → on the "Google hasn't verified this app" screen click **Advanced → Go to <project name> (unsafe)** → **Allow**. This is the standard Apps Script deployment flow — the script runs under your account only, Google just hasn't manually audited it.
11. Copy the **Web app URL**. It looks like `https://script.google.com/macros/s/AKfyc.../exec`.
> **What the script actually does.** It receives POST requests from our proxy containing `{ method, url, headers, body_base64 }`, calls `UrlFetchApp.fetch(url, ...)` inside Google's datacenter, and returns `{ status, headers, body_base64 }` back. The "DPI bypass" comes from our proxy connecting to `script.google.com` using a different SNI than the Host header — we hit Google's edge under one name and then funnel traffic to Apps Script under another. Your ISP sees a plain TLS handshake to `www.google.com`.
---
## Step 3 — Fill in the app's config
Back on the phone in the mhrv-rs app:
1. Paste the `/exec` URL (or just the `AKfyc...` ID) into **Deployment URL(s) or script ID(s)**. You can paste multiple — one per line — and the proxy will round-robin between them. Useful later when you hit the 20k/day per-script quota.
2. Paste your **auth_key** — the exact same string you put in `AUTH_KEY` inside `Code.gs`.
3. Leave **google_ip** at the default for now. You'll verify it in step 4.
4. Leave **front_domain** at `www.google.com` — that's the SNI we present on the outbound leg.
---
## Step 4 — Test SNI reachability (strongly recommended)
Before you tap Start, expand the **SNI pool + tester** card and tap **Test all**.
- ✅ **Green check + latency** — your `google_ip` is reachable and accepts the SNI. Proceed.
- ❌ **connect timeout** on every row — your configured `google_ip` is unreachable from your network. Fix by running `nslookup www.google.com` on any working device and pasting the resulting IP into **google_ip**. Then Test all again.
- ❌ **connect timeout** only on some rows — the blocked SNIs are DPI-filtered on your network. Leave them unchecked; the rotation pool only uses the ticked boxes.
- ❌ **dns: …** — your device can't resolve `www.google.com` at all. Switch WiFi or check airplane mode.
Why this matters: if `google_ip` is wrong, the proxy will boot fine but every request will silently time out and you'll chase red herrings.
---
## Step 5 — Install the MITM certificate
This step is annoying but unavoidable: the proxy terminates TLS on your behalf so it can re-encrypt to the Apps Script relay, which means your phone needs to trust a cert we minted locally.
1. In the app, tap **Install MITM certificate**.
2. Read the confirmation dialog — it shows the certificate fingerprint (handy to verify later). Tap **Install**.
3. The app saves `Downloads/mhrv-ca.crt` and deep-links Android into **Settings → Security & privacy** (or similar; wording varies by OEM).
4. If you don't have a screen lock: Android will prompt you to set one. **You have to.** User CAs require a screen lock, period. Set PIN/pattern/password. You can remove it after install if you really want; the cert stays installed.
5. In Settings, navigate: **Encryption & credentials → Install a certificate → "CA certificate"**.
- On Pixel / stock Android: `Security → More security settings → Encryption & credentials → Install a certificate → CA certificate`.
- On Samsung: `Biometrics and security → Other security settings → Install from device storage → CA certificate`.
- On Xiaomi/MIUI: `Passwords & Security → Privacy → Encryption & credentials → Install a certificate → CA certificate`.
- **Do NOT** pick "VPN & app user certificate" or "Wi-Fi certificate" — wrong category, won't work.
6. Android warns you: **"Your network may be monitored by an unknown third party"**. That's us. Tap **Install anyway**.
7. Pick **Downloads** → tap **mhrv-ca.crt**. Give it a friendly name (or accept the default). Tap **OK**.
8. Return to the mhrv-rs app. A snackbar at the bottom will say **Certificate installed ✓** (the app verifies by fingerprint against AndroidCAStore). If it says "not yet installed", go back to step 5 and try again.
> **Why the dance and not an inline KeyChain flow?** Android 11 removed the inline `KeyChain.createInstallIntent` path — tapping Install MITM used to open a category picker directly, but it now opens a dead-end dialog with just a Close button. Google wants CA installs to be intentional, so they funnel you through Settings. We do the grunt work (save the file, deep-link Settings, verify afterwards) but the manual nav is unavoidable on current Android.
---
## Step 6 — Start the proxy
1. Tap **Start**.
2. Android shows the **VPN connection request** dialog: *"mhrv-rs wants to set up a VPN connection..."*. Tap **OK**.
3. A key icon appears in the status bar. That's your VPN indicator.
4. Open Chrome and visit any site. It should load normally — JavaScript, images, everything. Try `https://www.cloudflare.com`, `https://yahoo.com`, `https://discord.com` as stress tests.
5. If you expand **Live logs** in the mhrv-rs app you'll see:
- `SOCKS5 CONNECT -> <hostname>:443` — browser asking the TUN layer to open a flow
- `dispatch <hostname>:443 -> MITM + Apps Script relay (TLS detected)` — routing decision
- `MITM TLS -> <hostname>:443 (sni=<hostname>)` — we minted the leaf cert and the browser accepted it
- `relay GET https://<hostname>/...` — forwarded to Apps Script
- `preflight 204 https://...` — a CORS preflight we answered ourselves (don't worry about these)
---
## Known limitations
Read this before reporting a bug — most "it doesn't work" reports land in one of these buckets.
### Cloudflare Turnstile (the "Verify you are human" checkbox) loops
On Cloudflare-protected sites that challenge every request (not just the first), **you'll solve the challenge, reach the page, then get challenged again on the next click**. This is fundamental to the Apps Script relay model and cannot be fixed in the app:
| Factor | Normal browser | Apps Script relay |
|---|---|---|
| Egress IP | Stable (your ISP) | Rotates across Google's datacenter pool per request |
| User-Agent | Chrome's | Fixed `Google-Apps-Script` (Google overrides it) |
| TLS JA3/JA4 | Chrome's | Google-datacenter's |
CF's `cf_clearance` cookie is bound to the `(IP, UA, JA3)` tuple the challenge was solved against. Different IP next request → re-challenge. No amount of cookie forwarding fixes this.
**Sites that only gate the first page load** (most of CF's Bot Fight Mode customers) work fine after one solve. Sites that challenge every request (cryptocurrency, adult, some forums) won't work through this architecture. Use a different tunnel for those.
### UDP / QUIC (HTTP/3) doesn't go through
Our SOCKS5 listener only handles `CONNECT`, not `UDP ASSOCIATE`. Chrome tries HTTP/3 first and falls back to HTTP/2 over TCP, which works through the proxy. Effect: slightly slower connects on first visit, everything else fine.
### IPv6 leaks
The TUN only routes IPv4 (`0.0.0.0/0`). IPv6 traffic goes out your normal interface, including WebRTC. If you're using mhrv-rs for privacy rather than DPI bypass, disable IPv6 on your WiFi network entirely.
### Apps Script daily quota
Each deployment URL has a daily execution limit (20k/day for consumer Google accounts, higher for Workspace). Heavy streaming or infinite-scroll sites will burn through it. Mitigation: deploy 23 scripts and paste all their `/exec` URLs (one per line) into the app — the proxy round-robins across them.
### The MITM cert has no Play Store signature
We ship the APK signed with the standard Gradle debug keystore. Android installs it fine, but you'll see "app is from an unknown developer" warnings, and `Play Protect` may flag it on some devices. That's accurate — the tradeoff is that the build is reproducible from source without us holding a secret key.
---
## Troubleshooting
### "504 Relay timeout" in Chrome
- Your Apps Script deployment isn't responding. Re-check the `/exec` URL (must end in `/exec`, not `/dev`). Watch Live logs for `Relay timeout` vs `connect:` — the former is the Apps Script leg, the latter is the outbound leg from Google to the origin.
### "Your connection is not private — NET::ERR_CERT_AUTHORITY_INVALID"
- The MITM CA isn't installed, or Chrome doesn't see it. Go back to Step 5. If the snackbar confirmed install but Chrome still complains: clear Chrome's data for the site, or tap Advanced → Proceed anyway for testing, then install the cert properly.
### "NET::ERR_CERT_COMMON_NAME_INVALID" on Cloudflare sites
- You're on a version before v1.0. Upgrade — this was fixed by peeking the TLS ClientHello.
### JavaScript parts of a site don't load
- Pre-v1.0 Apps Script rejected `OPTIONS` CORS preflights, which silently broke `fetch()`. Fixed in v1.0 by short-circuiting preflights at the MITM boundary. If you're on v1.0 and still seeing this: open Live logs, look for `Relay failed` errors, report them.
### The app closes after tapping Stop then Start quickly
- Emulator-specific EGL renderer crash on rapid UI transitions. The VPN service itself survives; only the Compose UI process died. Debounced in v1.0 — buttons disable for 2 seconds after tap. On real hardware this rarely happens.
### Chrome shows a white page with no error
- Very common on emulator with software rendering. Check `adb logcat | grep mhrv_rs` to see if the relay is actually making requests. If yes → Chrome's renderer has issues on the emulator, try a real device. If no → the proxy isn't running; check that the VPN key icon is in the status bar.
### Apps that ignore user CAs
Most non-browser Android apps opt out of trusting user CAs by default (Google's Network Security Config default as of API 24). Banking apps, Netflix, Spotify, most messengers — they'll fail through mhrv-rs with cert errors. The full TUN bridge routes their traffic to us, but their TLS stack refuses our cert. Only Chrome, Firefox, and apps that explicitly opt in will work. This is a general MITM limitation, not an mhrv-rs bug.
---
## Uninstall
1. `Settings → Apps → mhrv-rs → Uninstall`.
2. Optionally remove the MITM CA: `Settings → Security → Encryption & credentials → User credentials → mhrv-rs MITM CA → Remove`.
3. The VPN profile is automatically revoked on uninstall.
---
## راهنمای فارسی
این فایل به انگلیسی نوشته شده تا با باقی مستندات پروژه هماهنگ باشد. اگر راهنمای فارسی می‌خواهید، لطفاً [issue بسازید](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues) تا ترجمه کنیم.
+11 -4
View File
@@ -2,10 +2,11 @@
This folder contains the prebuilt binaries from the latest release, committed directly to the repository for users who cannot reach the GitHub Releases page.
Current version: **v0.9.4**
Current version: **v1.0.0**
| File | Platform | Contents |
|---|---|---|
| `mhrv-rs-android-universal-v1.0.0.apk` | Android 7.0+ (all ABIs) | Universal APK — arm64-v8a, armeabi-v7a, x86_64, x86 in one file |
| `mhrv-rs-linux-amd64.tar.gz` | Linux x86_64 | `mhrv-rs`, `mhrv-rs-ui`, `run.sh` |
| `mhrv-rs-linux-arm64.tar.gz` | Linux aarch64 | `mhrv-rs`, `run.sh` (CLI only) |
| `mhrv-rs-raspbian-armhf.tar.gz` | Raspberry Pi / ARMv7 hardfloat | `mhrv-rs`, `run.sh` (CLI only) |
@@ -42,7 +43,11 @@ cd mhrv-rs-macos-arm64 # or wherever the archive extracted to
Extract `mhrv-rs-windows-amd64.zip`, then double-click `run.bat` inside the extracted folder (accept the UAC prompt so the MITM CA can be installed).
See the [main README](../README.md) for full setup (Apps Script deployment, config, browser proxy settings).
### Android
Copy `mhrv-rs-android-universal-v1.0.0.apk` to your phone, tap it from the Files app, and allow "Install unknown apps" for whichever app is opening the APK (Files, Chrome, etc.). See [the Android guide](../docs/android.md) for the full walk-through of the first-run steps (Apps Script deployment, MITM CA install, VPN permission, SNI tester).
See the [main README](../README.md) for desktop setup (Apps Script deployment, config, browser proxy settings).
---
@@ -50,7 +55,7 @@ See the [main README](../README.md) for full setup (Apps Script deployment, conf
این پوشه شامل فایل‌های آخرین نسخه است و مستقیماً در ریپو قرار گرفته برای کاربرانی که به صفحهٔ GitHub Releases دسترسی ندارند.
نسخهٔ فعلی: **v0.9.4**
نسخهٔ فعلی: **v1.0.0**
### دانلود از طریق ZIP
@@ -68,4 +73,6 @@ cd mhrv-rs-macos-arm64
**ویندوز:** فایل `mhrv-rs-windows-amd64.zip` را extract کنید و داخل پوشه روی `run.bat` دو بار کلیک کنید (UAC را قبول کنید تا گواهی MITM نصب شود).
برای راه‌اندازی کامل (دیپلوی Apps Script، config، تنظیم proxy مرورگر) به [README اصلی](../README.md) مراجعه کنید.
**اندروید:** فایل `mhrv-rs-android-universal-v1.0.0.apk` را روی گوشی کپی کنید، از Files app روی آن tap کنید و اجازهٔ "نصب برنامه‌های ناشناس" را بدهید. راهنمای کامل شروع به کار (دیپلوی Apps Script، نصب CA، اجازهٔ VPN، تستر SNI) در [راهنمای اندروید](../docs/android.md) هست.
برای راه‌اندازی کامل دسکتاپ (دیپلوی Apps Script، config، تنظیم proxy مرورگر) به [README اصلی](../README.md) مراجعه کنید.
Binary file not shown.