Commit Graph

293 Commits

Author SHA1 Message Date
therealaleph 5ea77aedb9 docs(readme): rename video caption to "راهنمای تصویری راه اندازی به زبان فارسی"
Symmetric with the text-guide caption right below it ("راهنمای جامع متنی…") — تصویری/متنی parallel makes the video-vs-text distinction obvious at a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:45:50 +03:00
therealaleph 81a0e44f12 docs(readme): single-line layout for Kian Irani's guide credit
Two links on one line: "راهنمای جامع متنی راه اندازی به زبان فارسی" → his guide, "Kian Irani" → his GitHub. Plain "با تشکر از" between them. Cleaner than the previous two-line + sub-text version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:45:00 +03:00
therealaleph 880d304bf3 docs(readme): link to @KIAN-IRANi's comprehensive Persian setup guide under the video
Adds a centered link below the video pointing to https://kian-irani.github.io/mhrv-setup-full-tunell/, with credit to @KIAN-IRANi (https://github.com/KIAN-IRANi). Both links open in a new tab. Persian-speaking users now have three escalating depths of guide right at the top of the README: the 5-min Quick Start, the YouTube walk-through, and Kian Irani's full long-form guide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:43:37 +03:00
therealaleph e91189c4f5 docs(readme): open the Persian-guide video in a new tab
Adds target="_blank" + rel="noopener noreferrer" so the click doesn't navigate the README away from the repo. The `rel` value is the conventional safety pair: `noopener` blocks the new tab from accessing `window.opener`, `noreferrer` strips the Referer header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:41:46 +03:00
therealaleph e85e42bd98 docs(readme): embed Persian setup-guide YouTube video below the language-switcher links
YouTube iframe embeds get sanitized by GitHub's README renderer, so we use the standard "thumbnail-image-linking-to-youtube.com" pattern instead — visually identical (Play overlay + auto-loaded by YouTube's CDN), survives the sanitizer, and one click opens the video on YouTube.

Caption: "راهنمای راه اندازی به فارسی:".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:41:12 +03:00
therealaleph 50bb3d0eaf ci(telegram): include Persian changelog in announcement + main-channel cross-link
Up through v1.9.7 the Telegram posts said only "📦 mhrv-rs vX.Y.Z منتشر شد" + a hashtag and a link to the files channel — subscribers had to click through to GitHub to see what actually changed. Now the announcement post in the files channel and the cross-link post in the main channel both inline the Persian half of `docs/changelog/v{version}.md`.

How it works:
- New `load_changelog(repo_root, version)` reads `docs/changelog/v{version}.md`, strips the leading `<!-- ... -->` editor comment, splits on the lone `---` line that separates Persian from English. Returns (None, None) if the file doesn't exist (lets out-of-band re-publishes for old tags whose changelog file was never landed work without crashing).
- New `md_to_tg_html(md, max_len)` does a minimal markdown → Telegram-flavoured-HTML conversion: `**bold**`, `[text](url)`, `` `code` `` are translated; nested patterns (e.g. `[`code`](url)`, `**[`code`](url)**`) work via a placeholder/unwind pass that loops until stable. Truncates at the 4096-char sendMessage limit, snapping to a newline boundary so a span isn't cut in half, with a "see full notes on GitHub" tail.
- Falls back gracefully if the changelog file is missing — uses the old skeleton message.

Verified locally on docs/changelog/v1.9.7.md: 3039 chars after conversion, well under the 4096 limit, all bold / code / link spans render correctly including nested ones (`[`assets/apps_script/Code.gs`](url)` becomes `<a href="url"><code>assets/apps_script/Code.gs</code></a>`).

This change takes effect on the next release (v1.9.8+); v1.9.7 is already published with the old skeleton.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:23:01 +03:00
github-actions[bot] b75a816334 chore(releases): refresh prebuilt binaries for v1.9.7
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-01 16:33:35 +00:00
therealaleph 680502759a feat: v1.9.7 — friendly LAN-share toggle + supersede unshipped v1.9.6
The desktop UI now has a single "Share with other devices on my Wi-Fi / network" checkbox in place of the cryptic `listen_host: 0.0.0.0` text field. When enabled:
- Bind auto-flips to 0.0.0.0
- LAN IP is detected via the standard UDP-connect trick (no actual traffic) and shown alongside the proxy ports for handing to the guest device
- Tooltip explains macOS Firewall prompt behavior
- A pre-existing custom bind IP in config.json is preserved with a "Custom bind: ..." badge so the next Save can't clobber it

New `src/lan_utils.rs` module with detect_lan_ip / is_share_on_lan / is_loopback_only helpers (3 unit tests).

Also rolls in the v1.9.6 changes (release was cancelled before binaries shipped):
- Code.gs / CodeFull.gs: removed duplicate doGet, switched HtmlService -> ContentService, stripped X-Forwarded-* family in SKIP_HEADERS, added SAFE_REPLAY_METHODS fallback when fetchAll throws as a whole.
- Rust client: parse_relay_json now unwraps goog.script.init iframe wrappers (defense-in-depth for legacy deployments or redirect-induced GET-on-doGet).
- README rewritten as a short bilingual landing page; advanced reference moved to docs/guide.md + docs/guide.fa.md. Persian guide's `[x]` task list replaced with a table because GitHub's RTL renderer mangles checkbox positions inside `<div dir="rtl">`.

Tests: 6 new regression tests (3 goog.script.init unwrap + 3 lan_utils). 179 lib + 33 tunnel-node tests all passing. Bind on 0.0.0.0 smoke-tested via lsof.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.9.7
2026-05-01 19:15:52 +03:00
therealaleph cbb08468bc fix: v1.9.6 — Code.gs/CodeFull.gs hardening, goog.script.init unwrap, README rewrite
Server-side (Apps Script) fixes — users replace their Code.gs with assets/apps_script/Code.gs (or CodeFull.gs for full mode) and Manage deployments → ✏️ → New version → Deploy:
- Removed duplicate doGet in Code.gs (HtmlService one was overriding ContentService one due to JS hoisting → every GET to /exec returned a goog.script.init iframe instead of the placeholder HTML)
- CodeFull.gs doGet switched from HtmlService to ContentService (same reason)
- SKIP_HEADERS now strips X-Forwarded-* / Forwarded / Via family — second line of defense to v1.2.9's client-side stripping (#104), in case a misconfigured upstream proxy adds these
- _doBatch fallback when UrlFetchApp.fetchAll() throws as a whole — per-item fetch on safe methods so one bad URL no longer poisons the entire batch (port from masterking32@3094288)

Client-side (Rust) defense-in-depth:
- parse_relay_json now unwraps goog.script.init("...userHtml...") if any deployment returns the iframe-wrapped form (legacy Code.gs, or a redirect that GETs doGet). New extract_apps_script_user_html + decode_js_string_escapes helpers. Tested against a real deployment's doGet response.

Docs:
- README rewritten as short bilingual landing page (English + Persian RTL) targeting normal users; advanced reference moved to docs/guide.md + docs/guide.fa.md.

Tests: 3 new regression tests. 176 lib + 33 tunnel-node tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:00:32 +03:00
github-actions[bot] d336bd39e5 chore(releases): refresh prebuilt binaries for v1.9.5
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-01 12:56:08 +00:00
therealaleph 541b37ad7d fix: v1.9.5 — exit-node tolerates TLS close without close_notify (#585)
Issue #585 from @gregtheph: v1.9.4's exit-node feature failed for every
ChatGPT/Claude/Grok request with `io: peer closed connection without
sending TLS close_notify` and fell back to direct Apps Script (which
can't reach those sites either, producing the no-json error chain).

Root cause: rustls is strict about TLS shutdown — when the peer (val.town's
host) closes the underlying TCP without first sending a TLS close_notify
alert, rustls surfaces this as `io::ErrorKind::UnexpectedEof`. Our
read_http_response propagated this as a hard error, even when the body
was already complete per Content-Length.

Fix: treat UnexpectedEof the same as `n == 0` (graceful EOF). If
Content-Length is satisfied, return the response; if mid-body truncation,
still error as BadResponse. Same handling added to the chunked reader
and the no-framing reader.

4 new regression tests:
- read_http_response_tolerates_unexpected_eof_with_content_length
- read_http_response_tolerates_unexpected_eof_no_framing
- parse_exit_node_response_unwraps_valtown_envelope
- parse_exit_node_response_surfaces_explicit_error

173 lib tests + 33 tunnel-node tests + both release builds passing.
v1.9.5
2026-05-01 15:38:47 +03:00
github-actions[bot] 7268baf098 chore(releases): refresh prebuilt binaries for v1.9.4
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-01 09:10:33 +00:00
therealaleph 4aac9a793f feat: v1.9.4 — exit node for ChatGPT/Claude/Grok + drop duplicate Telegram post
Two changes addressing user-reported issues today:

1. Exit-node feature ported from upstream masterking32@464a6e1d, with
   hardening. Cloudflare-protected sites (chatgpt.com, claude.ai,
   grok.com, x.com, openai.com) flag Google datacenter IPs as bots and
   return Turnstile / CAPTCHA / 502 challenges. Apps Script's UrlFetchApp
   exits from those IPs, so v1.9.3 surfaces these as "Relay error: json:
   key must be a string..." with no apps_script-mode workaround.

   Now a small TypeScript HTTP endpoint (assets/exit_node/valtown.ts)
   deployed on val.town / Deno Deploy sits between Apps Script and the
   destination. Chain: client → Apps Script (Google IP) → val.town
   (non-Google IP) → destination. Destination sees val.town's IP, no
   CF challenge.

   Config:
     "exit_node": {
       "enabled": true,
       "relay_url": "https://...web.val.run",
       "psk": "<openssl rand -hex 32>",
       "mode": "selective",
       "hosts": ["chatgpt.com", "claude.ai", "x.com", "grok.com", "openai.com"]
     }

   Hardening over upstream: PSK fail-closed if still placeholder (fresh
   deploy can't be open relay), loop guard (refuses fetch of own host),
   explicit 503 on misconfigured. Fallback to direct Apps Script on exit
   node failure (CF-affected sites fail, others keep working). Setup
   docs in English + Persian at assets/exit_node/README*.md. Example
   config at config.exit-node.example.json.

2. Removed the legacy `telegram` job from release.yml. With
   TELEGRAM_NOTIFY_ENABLED repo var set to true, every release was
   producing two duplicate APK posts on the main Telegram channel: the
   old bundled-APK-on-main job AND the newer per-file files-channel
   posts (telegram-publish-files.yml). Only the per-file flow is wanted.
   Legacy job and its helper telegram_release_notify.py are gone.
   Recoverable from git log if anyone needs the bundled pattern back.

169 mhrv-rs lib tests + 33 tunnel-node tests + UI build clean.
v1.9.4
2026-05-01 11:52:32 +03:00
github-actions[bot] d65759d8b8 chore(releases): refresh prebuilt binaries for v1.9.3
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-30 17:17:30 +00:00
therealaleph 55c1256fe1 feat(android): v1.9.3 — youtube_via_relay toggle in Android UI + CI retry fix
- PR #535 by @yyoyoian-pixel: Switch toggle for youtube_via_relay in
  Android Advanced settings, matching the desktop UI checkbox. Closes
  the parity gap that forced Android users to hand-edit config.json.
  Closes #520.

- ci(telegram-publish): --clobber on gh release download so retries
  survive partial downloads (caused the v1.9.2 telegram publish to
  fail; manually re-triggered).
v1.9.3
2026-04-30 19:58:23 +03:00
Shin (Former Aleph) 1929f8e14b feat(android): add youtube_via_relay toggle to Advanced settings (#535)
Parity fix: the desktop UI already has a youtube_via_relay checkbox in src/bin/ui.rs, but the Android UI was missing it — users had to hand-edit config.json on Android. By @yyoyoian-pixel.

Adds youtubeViaRelay field to MhrvConfig with JSON serialization, deserialization, and config-sharing encode. New Switch toggle in Advanced settings section matching the desktop UI checkbox. EN + FA string resources for label and helper text.

Closes #520 (vampire137 Android youtube_via_relay request).

Local verification: cargo test --lib 169/169 passing, both release builds clean. Pure Kotlin/Android change; no Rust impact.
2026-04-30 19:57:13 +03:00
therealaleph c98ae73c92 ci(telegram-publish): use --clobber to survive partial-download retries
The v1.9.2 telegram publish failed because attempt 1 hit a transient
HTTP 500 from GitHub's release-asset CDN mid-download, leaving partial
files in assets/. Attempts 2 and 3 then errored on "already exists"
because gh release download refuses to overwrite without --clobber.

With --clobber, retries can complete cleanly even when an earlier
attempt left files behind. No change to the success path.
2026-04-30 19:55:46 +03:00
yyoyoian-pixel 576dfa69e4 feat(android): add youtube_via_relay toggle to Advanced settings
Expose the `youtube_via_relay` config flag in the Android UI, matching
the desktop checkbox. Adds the field to MhrvConfig with serialization
round-trip (toJson / loadFromJson / encode), a Switch in the Advanced
section, and EN + FA string resources.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 16:28:53 +02:00
github-actions[bot] d162a47e3c chore(releases): refresh prebuilt binaries for v1.9.2
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-30 14:21:32 +00:00
therealaleph 8d0004c5f0 chore: v1.9.2 — Apps Script + Cloudflare Worker alternative backend
Pure docs + GAS/Worker addition shipped via PR #533 (#380 / #393 audit
task). No Rust client changes. Bumping to v1.9.2 so users get the new
deploy assets in the next release tarball + Telegram channel binaries
include the updated docs.
v1.9.2
2026-04-30 17:02:28 +03:00
Shin (Former Aleph) 1f7a1b33c5 feat(cfw): add Apps Script + Cloudflare Worker alternative backend (#533)
Adds opt-in alternative backend for mode: "apps_script". Deploy Code.cfw.gs (new GAS variant) + worker.js (Cloudflare Worker), and Apps Script becomes a thin auth+forward layer that pushes the outbound fetch to CF's edge. By @dazzling-no-more.

Closes the audit task on the v1.9.x roadmap (#380, #393).

Pure docs + GAS/Worker addition; mhrv-rs Rust client unchanged. Same JSON envelope on the wire, same mode/script_id/auth_key — only difference is what the deployed Apps Script does after authentication.

Hardened over upstream denuitt1/mhr-cfw: per-request AUTH_KEY check, fail-closed on placeholder secret, x-relay-hop loop guard + self-host fetch block, SKIP_HEADERS parity with Code.gs, batch handler with Promise.all + soft cap MAX_BATCH_SIZE = 40 paired with WORKER_BATCH_CHUNK on GAS side.

Honest limitations called out in docs:
- Not compatible with mode: "full" (raw-TCP/UDP tunnel ops not ported)
- YouTube long-form gets worse (30s CF Worker wall vs Apps Script 6min — SABR cliff arrives sooner)
- Cloudflare anti-bot unaffected (Worker IP often stricter than Google IP)
- No day-one UrlFetchApp quota relief (batch path unreachable from current single-shape client)

English + Persian docs (assets/cloudflare/README.md + README.fa.md) covering setup, three-matching-AUTH_KEY security model, trade-off table, full-mode incompatibility section.

Local verification: cargo test --lib 169/169 passing, both release builds clean.
2026-04-30 17:00:43 +03:00
dazzling-no-more 9013eb9ef7 feat(cfw): add Apps Script + Cloudflare Worker alternative backend 2026-04-30 16:41:19 +04:00
github-actions[bot] 777a28a16b chore(releases): refresh prebuilt binaries for v1.9.1
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-30 05:52:36 +00:00
therealaleph cd6ff8d381 feat: v1.9.1 — operator quality-of-life: tunable auto-blacklist, configurable batch timeout, MHRV_AUTH_KEY hint, run.bat CLI fallback
Four small fixes that address recurring user-issue patterns:

- src/config.rs / src/domain_fronter.rs: auto_blacklist_strikes,
  auto_blacklist_window_secs, auto_blacklist_cooldown_secs config fields
  (#391, #444). Previously 3 strikes / 30s window / 120s cooldown were
  hard-coded. Single-deployment users on flaky networks hit this too
  aggressively; multi-deployment users want tighter fail-fast. Defaults
  preserve historical behavior. Power-user file edit only — no UI
  control yet. Clamps to [1, 86400] for durations.

- src/config.rs / src/domain_fronter.rs / src/tunnel_client.rs:
  request_timeout_secs config field (#430, masterking32 PR #25).
  Replaces hard-coded BATCH_TIMEOUT 30s. DomainFronter::batch_timeout()
  exposes the value, fire_batch reads it. Clamped to [5s, 300s].

- tunnel-node/src/main.rs: detect MHRV_AUTH_KEY env var being set
  while TUNNEL_AUTH_KEY is unset, and emit a specific warning pointing
  at the right env var name. Catches the recurring #391/#444 docker
  run typo that made users chase phantom AUTH_KEY-mismatch decoys.

- assets/launchers/run.bat: when both UI renderers (glow + wgpu)
  fail on older Windows / RDP / VM-without-GPU, fall back to launching
  mhrv-rs.exe (CLI) instead of just printing "open an issue".
  Addresses #417 / #426 / #487. CLI has the same proxy functionality
  on 127.0.0.1:8085 (HTTP) / :8086 (SOCKS5).

169 mhrv-rs lib tests + 33 tunnel-node tests still passing. UI build
clean. ConfigWire round-trips the new fields with skip-default-on-write
so unchanged configs stay clean.
v1.9.1
2026-04-30 08:33:53 +03:00
therealaleph 58fb141cf7 docs(fronting-groups): add redd.it to the fastly group's domains list
Reddit serves images from redd.it (their CDN-style image host) which is
also on Fastly. Without this entry, the example config matches reddit.com
but image loads still fall back to direct, which is unreliable from Iran
ISPs. Suggested by @Shjpr9 in #502.
2026-04-30 06:57:34 +03:00
github-actions[bot] 7083d6eca4 chore(releases): refresh prebuilt binaries for v1.9.0
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-29 16:50:21 +00:00
therealaleph 48a0e469c0 feat: v1.9.0 — multi-edge fronting + edge DNS cache + DoH default flip + hotspot sharing
Three substantive PRs landed for this release plus an Iran-safe DoH default:

- #488 by @dazzling-no-more (with credit to @patterniha): fronting_groups
  config field generalizes the Google-edge SNI-rewrite trick to any
  multi-tenant CDN edge (Vercel, Fastly, etc.). Renames `mode = "google_only"`
  → `mode = "direct"` with a deprecated alias keeping existing configs working.
  This is the v1.9.0 headline — new top-level config field + public mode-string
  rename are minor-bump territory. xmux moves to v1.10.0.

- #494 by @dazzling-no-more: edge-cache DNS at Apps Script (CodeFull.gs)
  using CacheService. udp_open / port=53 ops served from cache or DoH
  fallback chain (Cloudflare → Google → Quad9). Cache hits drop typical
  first-hop DNS latency from 600-1200ms to 200-400ms. Default-on, opt-out
  via ENABLE_EDGE_DNS_CACHE; every failure mode falls through to existing
  tunnel-node forward path (zero regression).

- #483 by @yyoyoian-pixel: default listen_host from 127.0.0.1 to 0.0.0.0
  so an Android phone running the tunnel + an iPhone/laptop on the same
  hotspot can use it as proxy. Old configs with explicit 127.0.0.1 are
  honored (not overwritten).

Plus: default `tunnel_doh: true` (flipped from false in v1.8.x) per #468
— Iran ISPs filter direct connections to dns.google, chrome.cloudflare-dns.com,
and other pinned DoH hosts. The bypass-on default silently broke DNS for
the dominant Iranian userbase. The safe default keeps DoH inside the
tunnel; non-Iran users can opt back into the bypass for the latency win.
Backwards-compatible — any config with explicit tunnel_doh keeps its setting.

169 mhrv-rs lib tests + 33 tunnel-node tests + 11 edge-DNS JS tests all
passing. Clean release + UI builds.
v1.9.0
2026-04-29 19:32:37 +03:00
Shin (Former Aleph) 79cca105de feat: multi-edge fronting_groups + rename google_only to direct (#488)
Generalizes the Google-edge SNI-rewrite trick to any multi-tenant CDN edge (Vercel, Fastly, …). By @dazzling-no-more, with credit to @patterniha for the original technique (MITM-DomainFronting).

New `fronting_groups: [{name, ip, sni, domains}]` config field — matched hosts get MITM-decrypted at the local CA and re-encrypted upstream against `ip` with `sni` as the TLS SNI. Works alongside the built-in Google fronting and `passthrough_hosts`.

Rename: `mode = "google_only"` → `mode = "direct"`. Old name kept as deprecated alias on parse — no existing config / saved settings break. UI dropdown updated, on-disk file migrates on next Save.

Review fixes folded in: SNI validated via rustls at config-load gate, Vec<Arc<>> refcount instead of clone-on-match, byte-level dot-anchored matcher (no per-match format!()), startup warnings for inert combos.

Working example at config.fronting-groups.example.json. Full doc at docs/fronting-groups.md including precedence rules + the cross-tenant Host-header leak warning.

Test plan: cargo build --release clean, cargo test --lib 169/169 passing (+8 new: dispatch matching, config validation, alias back-compat).

Per author's recommendation, this lands as the v1.9.0 headline — new top-level config field + public mode-string rename are minor-bump territory. xmux moves to v1.10.0.
2026-04-29 19:27:17 +03:00
Shin (Former Aleph) aad900ee06 feat(codefull.gs): edge-cache DNS to skip tunnel-node round-trip (#494)
Edge DNS caching at the Apps Script layer using CacheService. By @dazzling-no-more.

Intercepts `udp_open`/port=53 ops in `_doTunnelBatch` and serves them from CacheService (cache hit) or DoH (cache miss). Cache hits drop typical first-hop DNS latency from ~600-1200ms to ~200-400ms.

- DoH fallback chain: Cloudflare → Google → Quad9 over RFC 8484 GET
- Per-qtype cache key keeps A and AAAA from colliding
- Min RR TTL clamped to [30s, 6h]; NXDOMAIN/SERVFAIL get 45s negative cache; NODATA-with-SOA honors SOA TTL per RFC 2308 §5
- Splice helper preserves op-index ordering across mixed TCP+DNS batches
- Default-on, opt-out via `ENABLE_EDGE_DNS_CACHE`; every failure mode falls through to existing tunnel-node forward path (zero regression)
- Privacy-aware: CacheService is volatile + has no on-disk artifact (vs Sheets which would persist a Drive-listed log of every domain users resolve)

11 pure-JS tests covering parsers, txid non-mutation, TTL high-bit clamp, NXDOMAIN-with-SOA TTL extraction, malformed/truncated input rejection, splice correctness for mixed batches. All 160 Rust lib tests still passing.
2026-04-29 19:25:21 +03:00
Shin (Former Aleph) d9593060fb feat: listen on all interfaces, hotspot sharing for iOS/laptop (#483)
Closes #481. Default `listen_host` from 127.0.0.1 to 0.0.0.0 so Android-phone-as-tunnel + iPhone/laptop on same hotspot can use it as proxy. Old configs with explicit `127.0.0.1` honored (not overwritten). By @yyoyoian-pixel.

Local verification: cargo test --lib 160/160 passing, both release builds clean.
2026-04-29 19:24:07 +03:00
dazzling-no-more 72ed6d893a feat(codefull.gs): edge-cache DNS to skip tunnel-node round-trip 2026-04-29 19:10:19 +04:00
dazzling-no-more f32d343260 docs(fronting-groups): add netlify (CloudFront) example 2026-04-29 17:28:49 +04:00
dazzling-no-more 8ed8e85687 feat: multi-edge fronting_groups + rename google_only to direct 2026-04-29 17:12:15 +04:00
yyoyoian-pixel ca171f702a feat: listen on all interfaces by default, add hotspot sharing docs
Change default `listen_host` from `127.0.0.1` to `0.0.0.0` so the
proxy is reachable from other devices on the same network. This
enables hotspot sharing: run the app on Android with hotspot enabled,
and an iPhone/iPad/laptop on the same WiFi can use the proxy by
pointing at the Android's hotspot IP (192.168.43.1:8080 for HTTP,
:1081 for SOCKS5).

On iOS, apps like Shadowrocket or Potatso can create a local VPN
that routes all device traffic through the SOCKS5 proxy — giving
full tunnel coverage without installing the app on the iOS device.

Added a "Sharing via hotspot" section to README with setup steps
for iOS, macOS, and Windows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 14:17:31 +02:00
github-actions[bot] 23911ae93a chore(releases): refresh prebuilt binaries for v1.8.5
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-29 03:45:13 +00:00
therealaleph 75401acf0c fix: v1.8.5 — tunnel-node caps TCP drain at 16 MiB to stay under Apps Script body ceiling
@bankbunk reported (#460) that on a 1 Gbps VPS, raw MP4 streams in Full
mode died with `batch JSON parse error: EOF while parsing a string at
line 1 column 52428685` minutes into playback. Root cause: drain_now
took the entire per-session read buffer in one shot. On high-bandwidth
VPS the reader task fills the buffer with tens of MiB between polls;
the resulting batch response (raw + base64 1.33× + JSON envelope)
exceeded Apps Script's ~50 MiB hard cap; Apps Script truncated mid-base64;
the client's serde_json parse hit EOF and the stream tore.

Fix: drain_now now returns at most TCP_DRAIN_MAX_BYTES (16 MiB) per call
and leaves the tail in the buffer for the next poll. EOF is held back
until the buffer is fully drained so partial drains don't tear the
session prematurely. Three regression tests cover the cap, the under-cap
pass-through, and the EOF-holdback case (33 tunnel-node tests passing).

@bankbunk's wondershaper rate-limit workaround (40 Mbps cap on the VPS
interface) is no longer necessary — high-bandwidth VPS users can run at
line rate again.
v1.8.5
2026-04-29 06:28:17 +03:00
therealaleph 8273325e4c docs(maintainer): add maintainer knowledge base for contributors and automated agents 2026-04-29 04:53:52 +03:00
therealaleph 61196ea6e6 Revert "docs(maintainer): add skill knowledge base for cloud-scheduled DOPR agents"
This reverts commit 9f821cc7580c8fe012346c451c86891b8b185485.
2026-04-29 04:45:25 +03:00
therealaleph 69d9317d35 docs(maintainer): add skill knowledge base for cloud-scheduled DOPR agents
Mirror of ~/.claude/skills/mhrv-rs-maintainer/ — SKILL.md plus eight reference
files plus assets. Cloud-scheduled agents clone the repo fresh on each fire
and have no access to the maintainer's local home directory; embedding the
skill in docs/maintainer/ lets them read the same canonical context as the
local maintainer and produce replies indistinguishable from a local DOPR
session.

The local copy at ~/.claude/skills/mhrv-rs-maintainer/ remains the source of
truth; this directory mirrors it.
2026-04-29 04:44:29 +03:00
github-actions[bot] 4e47d244bf chore(releases): refresh prebuilt binaries for v1.8.4
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-28 22:54:57 +00:00
therealaleph 434ad19b9d chore: v1.8.4 — adaptive batch coalescing + tunnel-node long-poll fixes
Two PRs from @yyoyoian-pixel field-tested in Iran:

- #448: adaptive batch coalescing replaces fixed 8ms window. P75 RTT
  6.2s → 3.0s in Iran network testing. Configurable via
  coalesce_step_ms / coalesce_max_ms.

- #446: tunnel-node long-poll 5s → 15s for Telegram/Google Push
  persistent connection stability. Adaptive 40ms-step / 500ms-max
  straggler settle replaces fixed 30ms.

Includes desktop UI build fix: round-trip the new coalesce_* fields
through to_config(). Desktop UI sliders queued for v1.8.x batch.
v1.8.4
2026-04-29 01:37:14 +03:00
Shin (Former Aleph) 7c89772e6e feat(client): adaptive batch coalescing with configurable UI (#448)
Adaptive batch coalescing from @yyoyoian-pixel based on field testing in Iran.

Replace fixed 8ms batch coalesce with adaptive 40ms-step / 1000ms-max scheme. Apps Script adds ~1.5s overhead per HTTP call — packing more ops into each batch means fewer total calls. Field testing showed P75 RTT 6.2s → 3.0s, fast (<3s) batches 61% → 74-85%.

Both values configurable via config.json (coalesce_step_ms, coalesce_max_ms) and Android UI Advanced sliders (10-500ms / 100-2000ms).

Note: desktop UI's to_config() needs follow-up to round-trip the new fields. Filing immediately as a separate commit so v1.8.4 can ship both PRs together.
2026-04-29 01:34:13 +03:00
Shin (Former Aleph) 87ec804eba fix(tunnel-node): raise long-poll to 15s, adaptive straggler settle (#446)
Tunnel-node stability fix from @yyoyoian-pixel based on field testing in Iran.

- LONGPOLL_DEADLINE 5s → 15s: persistent connections (Telegram XMPP :5222, Google Push :5228) stay alive instead of forcing re-handshakes every 5s
- Replace fixed 30ms straggler settle with adaptive 40ms-step / 500ms-max — packs more session responses into each batch, breaks early when all ready

Local verification: tunnel-node 30/30 tests pass, main crate 160/160 tests pass, both build clean.
2026-04-29 01:32:26 +03:00
yyoyoian-pixel 0ca6f77dec feat(client): adaptive batch coalescing, configurable via Android UI
Replace the fixed 8ms batch coalesce window with an adaptive scheme:
after each new op arrives, wait another 40ms (step) for more ops.
The timer resets on every arrival, up to 1000ms (max) from the first
op. Both values are configurable via config JSON (coalesce_step_ms,
coalesce_max_ms) and two new sliders in the Android Advanced section.

Why this helps: Apps Script adds ~1.5s overhead per HTTP call. The
previous 8ms window barely caught any concurrent ops — most batches
carried just 1 op. With 40ms/1000ms, batches average 2-3 ops,
reducing total Apps Script calls for the same workload.

Tested on device in Iran:
  Before: P75=6.2s, 61% fast (<3s), ~1 op/batch
  After:  P75=3.0s, 74-85% fast, ~2-3 ops/batch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 22:02:17 +02:00
yyoyoian-pixel ca76fe91d0 fix(tunnel-node): raise long-poll to 15s, adaptive straggler settle up to 500ms
Apps like Telegram maintain persistent XMPP connections (:5222) and
Google Push uses :5228 — both rely on long-lived sessions with
periodic heartbeats. At the previous 5s long-poll deadline, the
tunnel-node returned empty responses frequently enough that Telegram
interpreted it as connection instability and rotated sessions. Each
reconnect costs a full TLS handshake (~4s through Apps Script),
causing visible video/voice interruptions and buffering.

Raising the long-poll deadline to 15s keeps these persistent
connections alive: the tunnel-node holds the response open until
server data actually arrives (push notification, chat message, media
chunk) rather than returning empty every 5s. Tested on censored
networks in Iran where users reported smoother Telegram video
playback and fewer session resets.

The straggler settle is now adaptive (40ms steps, 500ms max): after
the first session in a batch gets data, keep checking every 40ms
whether neighboring sessions also have data. Break early when all
sessions are ready — no fixed 500ms wait when data is already there.
On high-latency relays where each Apps Script call costs ~1.5s
overhead, packing more session responses into one batch saves quota
and reduces total round-trips.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 21:44:14 +02:00
github-actions[bot] 36e58be555 chore(releases): refresh prebuilt binaries for v1.8.3
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-28 19:03:40 +00:00
therealaleph 7e8e467d3d chore: v1.8.3 — sheet cache + DoH bypass + H1 keepalive + 431 + clearer errors
Three substantive PRs from contributors landed for this release:

- #443 by @euvel: optional spreadsheet-backed response cache in Code.gs.
  Implements all 5 review suggestions from the design discussion (#400):
  TTL-aware caching, 35 KB body-size gate, header rewriting on hit,
  circular buffer for O(1) writes, Vary-aware compound keys.

- #439 by @dazzling-no-more: bypass Apps Script tunnel for known DoH
  endpoints on TCP/443. Cloudflare/Google/Quad9/AdGuard/NextDNS/OpenDNS/
  CleanBrowsing/dns.sb/dns0.eu/AliDNS/doh.pub/Mullvad. Saves the ~2s
  UrlFetchApp roundtrip per name without losing privacy (DoH is
  already encrypted). Default on; users can opt out via tunnel_doh: true
  or extend the list via bypass_doh_hosts.

- #438 by @dazzling-no-more: H1 container keepalive + 431 oversized-
  headers + clearer port-collision message. Cherry-picks from upstream
  Python (Apr 23-26 window). Keepalive prevents Apps Script V8 cold
  starts (visible as YouTube stalls after pause); 431 replaces silent
  socket drops on >64 KB headers (which caused browser retry loops).
v1.8.3
2026-04-28 21:44:10 +03:00
Shin (Former Aleph) 84ea21c0bd feat(code.gs): optional spreadsheet-backed response cache with TTL (#443)
Adds optional Sheets-backed response cache to Code.gs. By @euvel.

All five review suggestions from #400 addressed:
- TTL-aware caching (Cache-Control max-age, no-cache, no-store, private)
- 35 KB body-size gate (under Sheets cell limit)
- Header rewriting on cache hit (Date, Age, Cache-Control, X-Cache, X-Cached-At)
- Circular buffer for O(1) writes (no appendRow/deleteRows reindexing)
- Vary-aware compound cache keys (Accept-Encoding, Accept-Language)

Opt-in via single constant. Zero overhead when disabled. No breaking changes.
2026-04-28 21:40:58 +03:00
Shin (Former Aleph) 554e51f3b6 feat: bypass Apps Script tunnel for DoH endpoints on TCP/443 (#439)
Routes browser DoH lookups (Cloudflare, Google, Quad9, AdGuard, NextDNS, OpenDNS, dns.sb, dns0.eu, AliDNS, doh.pub, Mullvad) around the Apps Script tunnel via plain TCP. By @dazzling-no-more.

DNS-over-HTTPS is already encrypted; tunneling it adds 2s UrlFetchApp roundtrip per name without privacy benefit. New `bypass_doh_hosts` config (default true) lets users opt out. Gated to TCP/443 — private DoH on :8443 should use `passthrough_hosts`.

Local verification: cargo build clean, cargo test --lib 160/160 passing (+6 new matches_doh_host tests).
2026-04-28 21:40:05 +03:00
Shin (Former Aleph) 92951e7e6f feat: H1 container keepalive + 431 oversized-headers (#438)
Cherry-picks of stability/hardening fixes from upstream Python (masterking32/MasterHttpRelayVPN), Apr 23-26 window. By @dazzling-no-more.

- 240s H1 container keepalive prevents Apps Script V8 cold-start stalls
- 64 KB header-block cap returns explicit HTTP/1.1 431 instead of socket drop  
- Clearer port-collision error message

Local verification: cargo build clean, cargo test --lib 154/154 passing.
2026-04-28 21:39:07 +03:00