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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ This part is unchanged from the original project. Follow @masterking32's guide o

#### Can't reach `script.google.com` from your network?

If your ISP is already blocking Google Apps Script (or all of Google), you need Step 1's browser connection to succeed *before* you have a relay to use. `mhrv-rs` ships a small bootstrap mode for exactly this: `google_only`.
If your ISP is already blocking Google Apps Script (or all of Google), you need Step 1's browser connection to succeed *before* you have a relay to use. `mhrv-rs` ships a `direct` mode for exactly this — SNI-rewrite tunnel only, no Apps Script relay required. (Was named `google_only` before v1.9 — the old name is still accepted in config files.)

1. Build / download the binary as in Step 2 below.
2. Copy [`config.google-only.example.json`](config.google-only.example.json) to `config.json` — no `script_id`, no `auth_key` required.
2. Copy [`config.direct.example.json`](config.direct.example.json) to `config.json` — no `script_id`, no `auth_key` required.
3. Run `mhrv-rs serve` and set your browser's HTTP proxy to `127.0.0.1:8085`.
4. In `google_only` mode the proxy only relays `*.google.com`, `*.youtube.com`, and the other Google-edge hosts via the same SNI-rewrite tunnel the full client uses. Other traffic goes direct — no Apps Script relay exists yet.
4. In `direct` mode the proxy only routes `*.google.com`, `*.youtube.com`, and the other Google-edge hosts (plus any [`fronting_groups`](docs/fronting-groups.md) you've configured) via the SNI-rewrite tunnel. Other traffic goes raw — no Apps Script relay exists yet.
5. Do Step 1 in your browser (the connection to `script.google.com` will be SNI-fronted). Deploy Code.gs, copy the Deployment ID.
6. In the desktop UI or the Android app (or by editing `config.json`) switch the mode back to `apps_script`, paste the Deployment ID and your auth key, and restart.

Expand Down Expand Up @@ -481,15 +481,15 @@ Donations cover hosting, self-hosted CI runner costs, and continued maintenance.

#### به `script.google.com` هم دسترسی ندارید؟

اگر `ISP` شما از قبل `Apps Script` (یا کل گوگل) را مسدود کرده، برای مرحلهٔ ۱ باید مرورگرتان **اول** به `script.google.com` برسد — قبل از اینکه رله‌ای داشته باشید. `mhrv-rs` یک حالت بوت‌استرپ کوچک دقیقاً برای همین دارد: `google_only`.
اگر `ISP` شما از قبل `Apps Script` (یا کل گوگل) را مسدود کرده، برای مرحلهٔ ۱ باید مرورگرتان **اول** به `script.google.com` برسد — قبل از اینکه رله‌ای داشته باشید. `mhrv-rs` یک حالت `direct` دقیقاً برای همین دارد — فقط تونل بازنویسی `SNI`، بدون نیاز به رلهٔ `Apps Script`. (قبل از v1.9 این حالت `google_only` نام داشت — نام قدیمی همچنان در فایل کانفیگ پذیرفته می‌شود.)

۱. برنامه را طبق مرحلهٔ ۲ پایین دانلود کنید

۲. فایل [`config.google-only.example.json`](config.google-only.example.json) را در کنار فایل اجرایی به نام `config.json` کپی کنید — نه `script_id` لازم دارد و نه `auth_key`
۲. فایل [`config.direct.example.json`](config.direct.example.json) را در کنار فایل اجرایی به نام `config.json` کپی کنید — نه `script_id` لازم دارد و نه `auth_key`

۳. برنامه را اجرا کنید و `HTTP proxy` مرورگرتان را روی `127.0.0.1:8085` تنظیم کنید

۴. در حالت `google_only`، پروکسی فقط `*.google.com`، `*.youtube.com` و بقیهٔ میزبان‌های لبهٔ گوگل را از طریق همان تونل بازنویسی `SNI` رد می‌کند. بقیهٔ ترافیک مستقیم می‌رود — هنوز رله‌ای در کار نیست
۴. در حالت `direct`، پروکسی فقط `*.google.com`، `*.youtube.com` و بقیهٔ میزبان‌های لبهٔ گوگل (به علاوهٔ هر [`fronting_groups`](docs/fronting-groups.md) که تنظیم کرده باشید) را از طریق تونل بازنویسی `SNI` رد می‌کند. بقیهٔ ترافیک مستقیم می‌رود — هنوز رله‌ای در کار نیست

۵. حالا مرحلهٔ ۱ را در مرورگر انجام دهید (اتصال به `script.google.com` با `SNI` فرونت می‌شود). `Code.gs` را مستقر کنید و `Deployment ID` را کپی کنید

Expand Down
4 changes: 2 additions & 2 deletions SF_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ A free way to bypass internet censorship by routing your traffic through your ow
**1. Set up the relay in your Google account (one-time).**
Go to <https://script.google.com>, sign in, click **New project**. Delete the sample code, paste in the [Code.gs file from this repo](assets/apps_script/Code.gs), change `AUTH_KEY = "..."` to a password only you know. Click **Deploy → New deployment → Web app**, set "Execute as: Me", "Who has access: Anyone". Copy the long ID from the URL — that's your **Deployment ID**.

> Can't reach `script.google.com` because it's blocked? Run mhrv-rs first in `google_only` mode (use [`config.google-only.example.json`](config.google-only.example.json)). It only relays Google sites and lets you reach the Apps Script editor through the bypass tunnel. Do step 1 in your browser, then switch back to normal mode.
> Can't reach `script.google.com` because it's blocked? Run mhrv-rs first in `direct` mode (use [`config.direct.example.json`](config.direct.example.json)). It only relays Google sites (plus any [fronting_groups](docs/fronting-groups.md) you've configured) and lets you reach the Apps Script editor through the bypass tunnel. Do step 1 in your browser, then switch back to normal mode. (`direct` was named `google_only` before v1.9 — the old name still works.)

**2. Install and run mhrv-rs.**
Download the package for your system from [Releases](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/releases/latest) and unzip it.
Expand Down Expand Up @@ -94,7 +94,7 @@ This project is free and run by volunteers. If it helped you and you can spare a
**۱. ساخت ریله در حساب گوگل (فقط یک بار).**
به <https://script.google.com> بروید، وارد حساب گوگل شوید و روی **New project** بزنید. کد پیش‌فرض را پاک کنید و محتوای [فایل Code.gs](assets/apps_script/Code.gs) همین مخزن را در آن جای‌گذاری کنید. خط `AUTH_KEY = "..."` را به یک رمز دلخواه که فقط خودتان می‌دانید تغییر دهید. سپس **Deploy → New deployment → Web app** را بزنید، گزینهٔ "Execute as: Me" و "Who has access: Anyone" را انتخاب کنید. آی‌دی طولانی توی URL را کپی کنید — این **Deployment ID** شماست.

> اگر `script.google.com` خودش بسته است، اول mhrv-rs را در حالت `google_only` اجرا کنید (از [`config.google-only.example.json`](config.google-only.example.json) استفاده کنید). این حالت فقط سایت‌های گوگل را تونل می‌کند تا بتوانید به ویرایشگر Apps Script برسید. مرحلهٔ ۱ را در مرورگر انجام دهید و بعد به حالت معمولی برگردید.
> اگر `script.google.com` خودش بسته است، اول mhrv-rs را در حالت `direct` اجرا کنید (از [`config.direct.example.json`](config.direct.example.json) استفاده کنید). این حالت فقط سایت‌های گوگل (به علاوهٔ هر [fronting_groups](docs/fronting-groups.md) که تنظیم کرده باشید) را تونل می‌کند تا بتوانید به ویرایشگر Apps Script برسید. مرحلهٔ ۱ را در مرورگر انجام دهید و بعد به حالت معمولی برگردید. (نام قبلی این حالت `google_only` بود — همچنان پذیرفته می‌شود.)

**۲. نصب و اجرای mhrv-rs.**
بستهٔ مخصوص سیستم خودتان را از [بخش Releases](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/releases/latest) دانلود کنید و از حالت فشرده در بیاورید.
Expand Down
25 changes: 16 additions & 9 deletions android/app/src/main/java/com/therealaleph/mhrv/ConfigStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,18 @@ enum class UiLang { AUTO, FA, EN }
*
* - [APPS_SCRIPT] (default) — full DPI bypass through the user's deployed
* Apps Script relay. Requires a Deployment ID + Auth key.
* - [GOOGLE_ONLY] — bootstrap mode. Only the SNI-rewrite tunnel to the
* Google edge is active, so the user can reach `script.google.com` to
* deploy Code.gs in the first place. No Deployment ID / Auth key needed.
* Non-Google traffic goes direct (no relay).
* - [DIRECT] — no Apps Script relay. Only the SNI-rewrite tunnel is
* active: Google edge by default, plus any user-configured
* `fronting_groups` (Vercel, Fastly, …). Useful as a bootstrap to
* reach `script.google.com` and deploy Code.gs, or as a standalone
* mode for users who only need fronting-group targets. No Deployment
* ID / Auth key needed. Non-matching traffic goes raw (no relay).
* Was named `GOOGLE_ONLY` before fronting_groups was added — the
* string `"google_only"` is still accepted on parse for back-compat.
* - [FULL] — full tunnel mode. ALL traffic is tunneled end-to-end through
* Apps Script + a remote tunnel node. No certificate installation needed.
*/
enum class Mode { APPS_SCRIPT, GOOGLE_ONLY, FULL }
enum class Mode { APPS_SCRIPT, DIRECT, FULL }

data class MhrvConfig(
val mode: Mode = Mode.APPS_SCRIPT,
Expand Down Expand Up @@ -177,14 +181,14 @@ data class MhrvConfig(
// "missing field `mode`" and startProxy silently returns 0.
put("mode", when (mode) {
Mode.APPS_SCRIPT -> "apps_script"
Mode.GOOGLE_ONLY -> "google_only"
Mode.DIRECT -> "direct"
Mode.FULL -> "full"
})
put("listen_host", listenHost)
put("listen_port", listenPort)
socks5Port?.let { put("socks5_port", it) }

// In google_only mode these are unused by the Rust side, but we
// In direct mode these are unused by the Rust side, but we
// still persist whatever the user typed so flipping back to
// apps_script mode doesn't wipe their settings.
put("script_ids", JSONArray().apply { ids.forEach { put(it) } })
Expand Down Expand Up @@ -286,7 +290,7 @@ object ConfigStore {
// Always include essential fields.
obj.put("mode", when (cfg.mode) {
Mode.APPS_SCRIPT -> "apps_script"
Mode.GOOGLE_ONLY -> "google_only"
Mode.DIRECT -> "direct"
Mode.FULL -> "full"
})
val ids = cfg.appsScriptUrls.mapNotNull { url ->
Expand Down Expand Up @@ -391,7 +395,10 @@ object ConfigStore {

return MhrvConfig(
mode = when (obj.optString("mode", "apps_script")) {
"google_only" -> Mode.GOOGLE_ONLY
"direct" -> Mode.DIRECT
// Deprecated alias kept forever for back-compat with
// configs written before the rename.
"google_only" -> Mode.DIRECT
"full" -> Mode.FULL
else -> Mode.APPS_SCRIPT
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ class MhrvVpnService : VpnService() {
startForeground(NOTIF_ID, buildNotif(cfg.listenPort, notifSocks5Port))

// Deployment ID + auth key are required for apps_script and full
// modes — both talk to Apps Script. Only google_only (bootstrap)
// runs without them. Closes #73 regression where google_only
// users hit this branch and crashed on startForeground timeout.
val needsCreds = cfg.mode != Mode.GOOGLE_ONLY
// modes — both talk to Apps Script. Only `direct` mode runs
// without them. Closes #73 regression where direct-mode users
// hit this branch and crashed on startForeground timeout.
val needsCreds = cfg.mode != Mode.DIRECT
if (needsCreds && (!cfg.hasDeploymentId || cfg.authKey.isBlank())) {
Log.e(TAG, "Config is incomplete — deployment ID + auth key required for ${cfg.mode}")
try { stopForeground(STOP_FOREGROUND_REMOVE) } catch (_: Throwable) {}
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/java/com/therealaleph/mhrv/Native.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ object Native {
* Live traffic/usage counters for a running proxy handle. Returns a
* JSON blob with the StatsSnapshot fields — or an empty string if the
* handle is unknown or the proxy isn't using the Apps Script relay
* (google_only / full-only modes).
* (direct / full-only modes).
*
* Schema (all integer fields unless noted):
* relay_calls, relay_failures, coalesced, bytes_relayed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ private fun ImportConfirmDialog(
val preview = ids.take(3).joinToString("\n") { " ${it.take(20)}…" }
val modeLabel = when (cfg.mode) {
com.therealaleph.mhrv.Mode.APPS_SCRIPT -> "apps_script"
com.therealaleph.mhrv.Mode.GOOGLE_ONLY -> "google_only"
com.therealaleph.mhrv.Mode.DIRECT -> "direct"
com.therealaleph.mhrv.Mode.FULL -> "full"
}

Expand Down
18 changes: 9 additions & 9 deletions android/app/src/main/java/com/therealaleph/mhrv/ui/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ fun HomeScreen(
}
},
enabled = (isVpnRunning ||
cfg.mode == Mode.GOOGLE_ONLY ||
cfg.mode == Mode.DIRECT ||
(cfg.hasDeploymentId && cfg.authKey.isNotBlank())) && !transitioning,
colors = ButtonDefaults.buttonColors(
containerColor = if (isVpnRunning) ErrRed else OkGreen,
Expand Down Expand Up @@ -837,7 +837,7 @@ private fun DeploymentIdsField(
}

// =========================================================================
// Mode dropdown: apps_script (default) vs google_only (bootstrap).
// Mode dropdown: apps_script (default), direct (no relay), or full.
// =========================================================================

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -847,11 +847,11 @@ private fun ModeDropdown(
onChange: (Mode) -> Unit,
) {
val labelApps = "Apps Script (MITM)"
val labelGoogle = "Google-only (bootstrap)"
val labelDirect = "Direct (no relay)"
val labelFull = "Full tunnel (no cert)"
val currentLabel = when (mode) {
Mode.APPS_SCRIPT -> labelApps
Mode.GOOGLE_ONLY -> labelGoogle
Mode.DIRECT -> labelDirect
Mode.FULL -> labelFull
}
var expanded by remember { mutableStateOf(false) }
Expand All @@ -878,8 +878,8 @@ private fun ModeDropdown(
onClick = { onChange(Mode.APPS_SCRIPT); expanded = false },
)
DropdownMenuItem(
text = { Text(labelGoogle) },
onClick = { onChange(Mode.GOOGLE_ONLY); expanded = false },
text = { Text(labelDirect) },
onClick = { onChange(Mode.DIRECT); expanded = false },
)
DropdownMenuItem(
text = { Text(labelFull) },
Expand All @@ -891,8 +891,8 @@ private fun ModeDropdown(
val help = when (mode) {
Mode.APPS_SCRIPT ->
"Full DPI bypass through your deployed Apps Script relay."
Mode.GOOGLE_ONLY ->
"Bootstrap: reach *.google.com directly so you can open script.google.com and deploy Code.gs. Non-Google traffic goes direct."
Mode.DIRECT ->
"SNI-rewrite tunnel only — no relay. Reach *.google.com (and any configured fronting_groups) directly. Useful as a bootstrap to open script.google.com and deploy Code.gs."
Mode.FULL ->
"All traffic tunneled end-to-end through Apps Script + remote tunnel node. No certificate needed."
}
Expand Down Expand Up @@ -1430,7 +1430,7 @@ private fun CollapsibleSection(
* this device relayed.
*
* Hidden when the handle is 0 (proxy not running) or the JSON comes back
* empty (google_only / full-only configs don't run a DomainFronter and so
* empty (direct / full-only configs don't run a DomainFronter and so
* have nothing to report).
*/
@Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"mode": "google_only",
"mode": "direct",
"google_ip": "216.239.38.120",
"front_domain": "www.google.com",
"listen_host": "127.0.0.1",
Expand Down
51 changes: 51 additions & 0 deletions config.fronting-groups.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"mode": "direct",
"google_ip": "216.239.38.120",
"front_domain": "www.google.com",
"listen_host": "127.0.0.1",
"listen_port": 8085,
"socks5_port": 8086,
"log_level": "info",
"verify_ssl": true,
"fronting_groups": [
{
"name": "vercel",
"ip": "76.76.21.21",
"sni": "react.dev",
"domains": [
"vercel.com",
"vercel.app",
"vercel.dev",
"vercel.live",
"vercel.sh",
"nextjs.org",
"now.sh",
"cursor.com",
"ai-sdk.dev"
]
},
{
"name": "fastly",
"ip": "151.101.1.140",
"sni": "www.python.org",
"domains": [
"reddit.com",
"redditstatic.com",
"redditmedia.com",
"githubassets.com",
"githubusercontent.com",
"pypi.org",
"fastly.com"
]
},
{
"name": "netlify",
"ip": "35.157.26.135",
"sni": "letsencrypt.org",
"domains": [
"netlify.app",
"netlify.com"
]
}
]
}
Loading
Loading