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.
This commit is contained in:
therealaleph
2026-05-01 11:52:32 +03:00
parent d65759d8b8
commit 4aac9a793f
12 changed files with 1070 additions and 494 deletions
+26
View File
@@ -0,0 +1,26 @@
<!-- see docs/changelog/v1.1.0.md for the file format: Persian, then `---`, then English. -->
• exit node اختیاری برای دور زدن CF anti-bot روی ChatGPT / Claude / Grok / X (port از upstream [`masterking32/MasterHttpRelayVPN@464a6e1d`](https://github.com/masterking32/MasterHttpRelayVPN/commit/464a6e1d), با hardening): سایت‌های پشت Cloudflare مانند `chatgpt.com`، `claude.ai`، `grok.com`، `x.com`، `openai.com` traffic از Google datacenter IPs (Apps Script's outbound IP space) رو به‌عنوان bot flag می‌کنن + Turnstile / CAPTCHA / 502 challenge برمی‌گردونن. تا v1.9.3 این "Relay error: json: key must be a string at line 2 column 1" یا 502 generic می‌داد + هیچ workaround در apps_script mode نبود. حالا یک endpoint TypeScript کوچک (`assets/exit_node/valtown.ts`) روی val.town / Deno Deploy / fly.io deploy می‌شه + بین Apps Script + destination قرار می‌گیره. مسیر traffic: `client → SNI rewrite → Apps Script (Google IP) → val.town (non-Google IP) → destination`. destination IP val.town رو می‌بینه، نه Google datacenter — heuristic anti-bot CF نمی‌سوزه + صفحه واقعی برمی‌گرده. **leg user-side (Iran ISP → Apps Script) بدون تغییر** — second hop کاملاً درون outbound Apps Script اجرا می‌شه، invisible از شبکه‌ی کاربر. config جدید:
```json
"exit_node": {
"enabled": true,
"relay_url": "https://your-handle-mhrv.web.val.run",
"psk": "<openssl rand -hex 32>",
"mode": "selective",
"hosts": ["chatgpt.com", "claude.ai", "x.com", "grok.com", "openai.com"]
}
```
دو mode: `selective` (default — فقط hosts مشخص از طریق exit node می‌رن) و `full` (همه می‌رن). در صورت failure exit node fallback اتومات به Apps Script direct (سایت‌های CF affected fail می‌گیرن، بقیه کار می‌کنن). hardening over upstream: PSK fail-closed اگر همچنان placeholder باشه (در fresh deploy نمی‌تونه به‌عنوان open relay accidentally سرو بشه)، loop guard (refuse fetch host خود)، 503 explicit برای misconfigured deploys. setup walkthrough در [`assets/exit_node/README.fa.md`](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/blob/main/assets/exit_node/README.fa.md). config مثال در [`config.exit-node.example.json`](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/blob/main/config.exit-node.example.json).
• حذف legacy `telegram` job در `release.yml` — قبلاً وقتی `TELEGRAM_NOTIFY_ENABLED` repo variable روی `true` set بود (در حال حاضر بود)، هر release **دو پست duplicate APK روی main channel** ایجاد می‌کرد: یکی قدیمی (universal APK + changelog) از release.yml و یکی جدید (cross-link به files channel) از telegram-publish-files.yml. فقط cross-link جدید رو می‌خواستیم. legacy job + helper script `.github/scripts/telegram_release_notify.py` حذف شدن. `telegram-publish-files.yml` (per-platform per-file posts با SHA-256 captions) تنها مسیر باقی مونده.
---
• Optional exit node to bypass CF anti-bot on ChatGPT / Claude / Grok / X (ported from upstream [`masterking32/MasterHttpRelayVPN@464a6e1d`](https://github.com/masterking32/MasterHttpRelayVPN/commit/464a6e1d), with hardening): Cloudflare-fronted services like `chatgpt.com`, `claude.ai`, `grok.com`, `x.com`, `openai.com` flag traffic from Google datacenter IPs (Apps Script's outbound IP space) as bots and return Turnstile / CAPTCHA / 502 challenges. Through v1.9.3 this surfaced as "Relay error: json: key must be a string at line 2 column 1" or generic 502 with no apps_script-mode workaround. Now a small TypeScript HTTP endpoint (`assets/exit_node/valtown.ts`) deployed on val.town / Deno Deploy / fly.io sits between Apps Script and the destination. Traffic chain: `client → SNI rewrite → Apps Script (Google IP) → val.town (non-Google IP) → destination`. The destination sees val.town's IP, not Google datacenter — CF's anti-bot heuristic doesn't fire and the real page comes back. **The user-side leg (Iran ISP → Apps Script) is unchanged** — the second hop happens entirely inside Apps Script's outbound, invisible from the user's network, so the DPI evasion property mhrv-rs is built around stays intact. New config:
```json
"exit_node": {
"enabled": true,
"relay_url": "https://your-handle-mhrv.web.val.run",
"psk": "<openssl rand -hex 32>",
"mode": "selective",
"hosts": ["chatgpt.com", "claude.ai", "x.com", "grok.com", "openai.com"]
}
```
Two modes: `selective` (default, only listed hosts route via exit node, recommended) or `full` (everything via exit node, slower). On exit-node failure, mhrv-rs falls back to direct Apps Script automatically — CF-affected sites fail in that case but everything else keeps working, so a down exit node doesn't take you fully offline. Hardening over upstream: PSK fail-closed if still the placeholder (fresh val.town deploy can't accidentally serve as open relay until the user replaces the placeholder), loop guard (refuses to `fetch` its own host), explicit 503 on misconfigured deploys. Setup walkthrough in [`assets/exit_node/README.md`](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/blob/main/assets/exit_node/README.md) (English) and [`README.fa.md`](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/blob/main/assets/exit_node/README.fa.md) (Persian). Complete example config at [`config.exit-node.example.json`](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/blob/main/config.exit-node.example.json).
• Removed the legacy `telegram` job from `release.yml`. Previously, with the `TELEGRAM_NOTIFY_ENABLED` repo variable flipped to `true` (which it had been), every release produced **two duplicate APK posts on the main Telegram channel**: the old `release.yml` job (universal APK + bundled changelog) and the newer `telegram-publish-files.yml` workflow (per-platform per-file posts to the files channel + a single cross-link to the main channel). Only the cross-link was wanted. The legacy job and its helper script `.github/scripts/telegram_release_notify.py` are gone. `telegram-publish-files.yml` is now the only Telegram path. The legacy bundled-on-main pattern is recoverable from `git log` if anyone ever wants it back.