[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-838":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":18,"stars30d":19,"stars90d":16,"forks30d":16,"starsTrendScore":20,"compositeScore":21,"rankGlobal":10,"rankLanguage":10,"license":22,"archived":23,"fork":23,"defaultBranch":24,"hasWiki":25,"hasPages":23,"topics":26,"createdAt":10,"pushedAt":10,"updatedAt":34,"readmeContent":35,"aiSummary":36,"trendingCount":16,"starSnapshotCount":16,"syncStatus":37,"lastSyncTime":38,"discoverSource":39},838,"GooseRelayVPN","Kianmhz\u002FGooseRelayVPN","Kianmhz","SOCKS5 VPN that tunnels raw TCP through Google Apps Script to a VPS exit server, with end-to-end AES-256-GCM and domain-fronted TLS to Google","",null,"Go",1393,196,6,20,0,5,8,419,15,19.88,"MIT License",false,"main",true,[27,28,29,30,31,32,33],"aes-gcm","mitm","proxy","sni","socks5","tunneling","vpn","2026-06-12 02:00:19","# GooseRelayVPN\n\n[![GitHub](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FGitHub-GooseRelayVPN-blue?logo=github)](https:\u002F\u002Fgithub.com\u002Fkianmhz\u002FGooseRelayVPN)\n\n**[🇮🇷 راهنمای فارسی (Persian)](README_FA.md)**\n\nA SOCKS5 VPN that tunnels **raw TCP** through a Google Apps Script web app to your own small VPS exit server. To anything on the network path your client only ever talks TLS to a Google IP with `SNI=www.google.com`. Everything in flight is AES-256-GCM encrypted end-to-end — Google never sees plaintext and never holds the key.\n\n> **How it works in simple terms:** Your browser\u002Fapp talks SOCKS5 to this tool on your computer. The tool wraps every TCP byte in AES-GCM frames and posts them through a Google-facing HTTPS connection to a free Apps Script web app you control. The Apps Script forwards those bytes verbatim to your own VPS, which decrypts and opens the real connection. To the firewall\u002Ffilter it looks like you're just talking to Google.\n\n> ⚠️ **You need a small VPS for the exit server.** Unlike pure-Apps-Script proxies, this project tunnels raw TCP — anything SOCKS5 can carry — so a real `net.Dial` has to happen somewhere. A small $4\u002Fmonth VPS is plenty. In exchange you can tunnel SSH, IMAP, custom protocols, anything — not just HTTP.\n\n## Support This Project\n\nIf you like this project, please consider starring it on GitHub (⭐). It helps the project get discovered.\n\nYou can also support the project financially:\n\n- TRX \u002F USDT TRC20:\n  `TSxg2WAXYnkoR2UiUTzCxbmqNARAt91aqB`\n- BNB \u002F USDT BEP20:\n  `0xe7b48d8fd5fbbb4e3fa9a06723a62a88585139ea`\n- TON:\n  `UQDBzJqzJ5e7uZFPrmarTRSGGbD1UoFK2q5_jWh4D2nnNdUB`\n\n## Important Notes\n\n- Never share `tunnel_key` with anyone. Anyone with this key can use your tunnel\u002FVPS as if they are you.\n- A server with public internet access is required. Your exit server must be reachable from Google Apps Script.\n- Each Google Apps Script deployment ID has a quota of about 20,000 executions per day, and the quota resets around 10:30 AM Iran time (GMT+3:30).\n- You do not need to install a local MITM certificate in this project. The certificate setup in `MasterHttpRelayVPN` is for that project's architecture and is not required here.\n- This project was inspired by the idea in the main repository: https:\u002F\u002Fgithub.com\u002Fmasterking32\u002FMasterHttpRelayVPN\n\n---\n\n## Disclaimer\n\nGooseRelayVPN is provided for educational, testing, and research purposes only.\n\n- **Provided without warranty:** This software is provided \"AS IS\", without express or implied warranty, including merchantability, fitness for a particular purpose, and non-infringement.\n- **Limitation of liability:** The developers and contributors are not responsible for any direct, indirect, incidental, consequential, or other damages resulting from the use of this project.\n- **User responsibility:** Running this project outside controlled test environments may affect networks, accounts, or connected systems. You are solely responsible for installation, configuration, and use.\n- **Legal compliance:** You are responsible for complying with all local, national, and international laws and regulations before using this software.\n- **Google services compliance:** If you use Google Apps Script with this project, you are responsible for complying with Google's Terms of Service, acceptable-use rules, quotas, and platform policies. Misuse may lead to suspension of your Google account or deployment.\n- **License terms:** Use, copying, distribution, and modification are governed by the repository license. Any use outside those terms is prohibited.\n\n---\n\n## How It Works\n\n```\nBrowser\u002FApp\n  -> SOCKS5  (127.0.0.1:1080)\n  -> Zstd-compressed + AES-256-GCM frame batches\n  -> HTTPS to a Google edge IP   (SNI=www.google.com, Host=script.google.com)\n  -> Apps Script doPost()        (dumb forwarder, never sees plaintext)\n  -> Your VPS :8443\u002Ftunnel       (decrypts, demuxes by session_id, dials target)\n  \u003C- Same path in reverse via long-polling\n```\n\nYour application sends TCP bytes through the SOCKS5 listener on your computer. The client groups them into batches of frames, **Zstandard-compresses** each batch (for compressible traffic such as plain HTTP or JSON APIs this reduces the body size by up to 65%, keeping you further from Apps Script's daily quota limits), then seals the whole batch under a single **AES-256-GCM** envelope and POSTs it over a domain-fronted HTTPS connection to your Apps Script web app. The Apps Script is a ~30-line script that forwards the body verbatim to your VPS — it never decrypts and the AES key never touches Google. Your VPS decrypts, dials the real target, and pumps bytes back along the same path. The filter sees only TLS to Google.\n\n---\n\n## Step-by-Step Setup Guide\n\n### Step 1: Get an VPS\n\nYou need a Linux VPS with a public IP. Any provider works.\n\n### Step 2: Get the binaries\n\nYou need two separate programs:\n- **`goose-client`** — runs on **your own computer**. This is what you run every day.\n- **`goose-server`** — runs on **your VPS**. You set it up once and leave it running.\n\n**Option A — Download a pre-built release (recommended):**\n\n1. Go to the [Releases page](https:\u002F\u002Fgithub.com\u002Fkianmhz\u002FGooseRelayVPN\u002Freleases).\n2. Download the right archive for your OS:\n   - Windows: `GooseRelayVPN-client-vX.Y.Z-windows-amd64.zip`\n   - macOS (Intel): `GooseRelayVPN-client-vX.Y.Z-darwin-amd64.tar.gz`\n   - macOS (M1\u002FM2\u002FM3): `GooseRelayVPN-client-vX.Y.Z-darwin-arm64.tar.gz`\n   - Linux: `GooseRelayVPN-client-vX.Y.Z-linux-amd64.tar.gz`\n   - Android \u002F Termux (arm64): `GooseRelayVPN-client-vX.Y.Z-android-arm64.tar.gz`\n3. For the **server**, SSH into your VPS and download the binary for your server OS:\n   - **Linux (most common):**\n     ```bash\n     wget https:\u002F\u002Fgithub.com\u002Fkianmhz\u002FGooseRelayVPN\u002Freleases\u002Flatest\u002Fdownload\u002FGooseRelayVPN-server-vX.Y.Z-linux-amd64.tar.gz\n     tar -xzf GooseRelayVPN-server-vX.Y.Z-linux-amd64.tar.gz\n     ```\n   - **Windows Server:** download `GooseRelayVPN-server-vX.Y.Z-windows-amd64.zip` from the Releases page and extract it to a folder such as `C:\\goose-relay\\`. See Step 8 (Windows) below for service setup.\n\n   (Replace `vX.Y.Z` with the latest version number from the Releases page.)\n\n> 💡 **If the Releases page doesn't open**, you can download directly using these links (replace `vX.Y.Z` with the latest version):\n> - **Client — Windows:** `https:\u002F\u002Fgithub.com\u002FKianmhz\u002FGooseRelayVPN\u002Freleases\u002Fdownload\u002FvX.Y.Z\u002FGooseRelayVPN-client-vX.Y.Z-windows-amd64.zip`\n> - **Client — macOS (Apple Silicon):** `https:\u002F\u002Fgithub.com\u002FKianmhz\u002FGooseRelayVPN\u002Freleases\u002Fdownload\u002FvX.Y.Z\u002FGooseRelayVPN-client-vX.Y.Z-darwin-arm64.tar.gz`\n> - **Client — macOS (Intel):** `https:\u002F\u002Fgithub.com\u002FKianmhz\u002FGooseRelayVPN\u002Freleases\u002Fdownload\u002FvX.Y.Z\u002FGooseRelayVPN-client-vX.Y.Z-darwin-amd64.tar.gz`\n> - **Client — Linux:** `https:\u002F\u002Fgithub.com\u002FKianmhz\u002FGooseRelayVPN\u002Freleases\u002Fdownload\u002FvX.Y.Z\u002FGooseRelayVPN-client-vX.Y.Z-linux-amd64.tar.gz`\n> - **Client — Android\u002FTermux:** `https:\u002F\u002Fgithub.com\u002FKianmhz\u002FGooseRelayVPN\u002Freleases\u002Fdownload\u002FvX.Y.Z\u002FGooseRelayVPN-client-vX.Y.Z-android-arm64.tar.gz`\n> - **Server — Linux:** `https:\u002F\u002Fgithub.com\u002FKianmhz\u002FGooseRelayVPN\u002Freleases\u002Fdownload\u002FvX.Y.Z\u002FGooseRelayVPN-server-vX.Y.Z-linux-amd64.tar.gz`\n\n**Option B — Build from source (Go 1.22+) — not recommended, may be unstable:**\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fkianmhz\u002FGooseRelayVPN.git\ncd GooseRelayVPN\ngo build -o goose-client .\u002Fcmd\u002Fclient\ngo build -o goose-server .\u002Fcmd\u002Fserver\n```\n\n**Option C — Run only the server with Docker (GHCR):**\n\nIf you prefer containers on your VPS, you can run `goose-server` directly from GHCR:\n\n```bash\ndocker pull ghcr.io\u002Fkianmhz\u002Fgooserelayvpn-server:latest\n```\n\n### Step 3: Generate a secret key\n\nRun this once:\n\n```bash\nopenssl rand -hex 32\n```\n\nCopy the 64-character string it prints. You'll use the **same value** in both the client and server configs. Keep it secret — anyone with this key can use your tunnel.\n\n### Step 4: Configure\n\nCopy the example configs:\n\n```bash\ncp client_config.example.json client_config.json\ncp server_config.example.json server_config.json\n```\n\nOpen both files and paste your key into the `tunnel_key` field. Leave `script_keys` empty for now.\n\n`client_config.json`:\n\n```json\n{\n  \"socks_host\":  \"127.0.0.1\",\n  \"socks_port\":  1080,\n  \"google_host\": \"216.239.38.120\",\n  \"sni\":         \"www.google.com\",\n  \"script_keys\": [\"PASTE_DEPLOYMENT_ID\"],\n  \"tunnel_key\":  \"PASTE_OUTPUT_OF_GEN_KEY\"\n}\n```\n\n`server_config.json`:\n\n```json\n{\n  \"server_host\": \"0.0.0.0\",\n  \"server_port\": 8443,\n  \"tunnel_key\":  \"SAME_VALUE_AS_CLIENT\"\n}\n```\n\n### Step 5: Set up the Google Apps Script\n\nThis is the free Google-side piece that hides your traffic.\n\n1. Go to [Google Apps Script](https:\u002F\u002Fscript.google.com\u002F) and sign in.\n2. Click **New project**.\n3. Delete the default code and paste everything from [`apps_script\u002FCode.gs`](apps_script\u002FCode.gs).\n4. Change this line to your VPS IP:\n   ```javascript\n   const VPS_URL = 'http:\u002F\u002FYOUR.VPS.IP:8443\u002Ftunnel';\n   ```\n5. Click **Deploy → New deployment** → set type to **Web app**.\n6. Set **Execute as:** Me and **Who has access:** Anyone.\n7. Click **Deploy**. A dialog appears showing the **Deployment ID**. Copy that value and paste it into `script_keys`.\n8. Paste that ID into `script_keys` in `client_config.json`.\n\n> ⚠️ Every time you edit `Code.gs` you must create a **new deployment** (Deploy → **New deployment**) and update `script_keys`. Just saving the code is not enough.\n\n### Step 6: Open port 8443 on your VPS firewall\n\nThe server needs port 8443 to be reachable from the internet. On your VPS run:\n\n```bash\nsudo ufw allow 8443\u002Ftcp\n```\n\nThen verify it works from your own computer (replace with your real VPS IP):\n\n```bash\ncurl http:\u002F\u002FYOUR.VPS.IP:8443\u002Fhealthz\n```\n\nYou should get JSON like `{ \"ok\": true, \"version\": \"vX.Y.Z\", \"protocol\": 1 }` with HTTP 200. If `curl` times out or refuses, also check your **cloud provider's firewall** (called \"Security Groups\" on AWS\u002FHetzner, \"Firewall Rules\" on DigitalOcean\u002FVultr, etc.) and add an inbound rule for TCP port 8443.\n\n### Step 7: Start the server on your VPS\n\nOn your VPS, run the server binary:\n\n**Linux:**\n```bash\n.\u002Fgoose-server -config server_config.json\n```\n\n**Windows Server:**\n```cmd\n.\\goose-server.exe -config server_config.json\n```\n\nYou should see it print the listening address and the healthz\u002Ftunnel URLs. Leave this terminal open, or set up the systemd\u002FNSSM service (Step 8) to keep it running after reboots.\n\n**Docker (GHCR image):**\n\n> ⚠️ **Important:** The container does **not** auto-generate `server_config.json`. You must create and edit `server_config.json` first (with your own `tunnel_key`), then start the container.\n\n```bash\ndocker run -d \\\n  --name goose-server \\\n  --restart unless-stopped \\\n  -p 8443:8443 \\\n  -v $(pwd)\u002Fserver_config.json:\u002Fapp\u002Fserver_config.json:ro \\\n  ghcr.io\u002Fkianmhz\u002Fgooserelayvpn-server:latest\n```\n\n**Docker Compose (recommended for container setup):**\n\n```bash\ncp server_config.example.json server_config.json\nnano server_config.json\ndocker compose up -d\n```\n\nThe repo includes [`docker-compose.yml`](docker-compose.yml). By default it uses `ghcr.io\u002Fkianmhz\u002Fgooserelayvpn-server:latest`, and you can override it with:\n\n```bash\nGOOSE_SERVER_IMAGE=ghcr.io\u002Fkianmhz\u002Fgooserelayvpn-server:vX.Y.Z docker compose up -d\n```\n\nVerify from your own computer:\n\n```bash\ncurl http:\u002F\u002FYOUR.VPS.IP:8443\u002Fhealthz\n```\n\n### Step 8: Keep the server running after reboot (systemd)\n\nIf you want the exit server to start automatically after a VPS reboot, create a systemd service.\n\nRun on your VPS:\n\n```bash\nsudo nano \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgoose-relay.service\n```\n\nPaste this (adjust the path if your binary is in a different location):\n\n```ini\n[Unit]\nDescription=GooseRelayVPN exit server\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=\u002Froot\nExecStart=\u002Froot\u002Fgoose-server -config \u002Froot\u002Fserver_config.json\nRestart=always\nRestartSec=3\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\n```\n\nThen run:\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl enable goose-relay\nsudo systemctl start goose-relay\nsudo systemctl status goose-relay --no-pager\n```\n\n### Step 8 (Windows): Keep the server running after reboot (NSSM)\n\nIf your VPS runs **Windows Server**, use [NSSM](https:\u002F\u002Fnssm.cc) (Non-Sucking Service Manager) to register `goose-server` as a Windows service instead of systemd. The `goose-server.exe` binary is a plain Go binary — no installer needed.\n\n**1. Open port 8443 in Windows Firewall** (run as Administrator in Command Prompt):\n```cmd\nnetsh advfirewall firewall add rule name=\"GooseRelayVPN\" protocol=TCP dir=in localport=8443 action=allow\n```\nAlso add an inbound TCP\u002F8443 rule in your cloud provider's firewall panel (Security Groups \u002F Firewall Rules).\n\n**2. Download NSSM** from https:\u002F\u002Fnssm.cc\u002Fdownload, extract it, and note the path to `nssm.exe` (e.g. `C:\\nssm\\win64\\nssm.exe`).\n\n**3. Register and start the service** (run as Administrator):\n```cmd\nC:\\nssm\\win64\\nssm.exe install GooseRelayVPN \"C:\\goose-relay\\goose-server.exe\"\nC:\\nssm\\win64\\nssm.exe set GooseRelayVPN AppParameters \"-config C:\\goose-relay\\server_config.json\"\nC:\\nssm\\win64\\nssm.exe set GooseRelayVPN AppDirectory \"C:\\goose-relay\"\nC:\\nssm\\win64\\nssm.exe set GooseRelayVPN Start SERVICE_AUTO_START\nC:\\nssm\\win64\\nssm.exe start GooseRelayVPN\n```\n\n**4. Verify it is running:**\n```cmd\nC:\\nssm\\win64\\nssm.exe status GooseRelayVPN\ncurl http:\u002F\u002FYOUR.VPS.IP:8443\u002Fhealthz\n```\n\nTo stop or uninstall later:\n```cmd\nC:\\nssm\\win64\\nssm.exe stop GooseRelayVPN\nC:\\nssm\\win64\\nssm.exe remove GooseRelayVPN confirm\n```\n\n### Step 9: Run the client on your computer\n\n```bash\n.\u002Fgoose-client -config client_config.json\n```\n\nYou should see output like this:\n\n```\nCLIENT  INFO    GooseRelayVPN client starting\nCLIENT  INFO    SOCKS5 proxy: socks5:\u002F\u002F127.0.0.1:1080\nCLIENT  INFO    pre-flight OK: relay healthy, AES key matches end-to-end\nCLIENT  INFO    ready: local SOCKS5 is listening on 127.0.0.1:1080\n```\n\nThe **pre-flight check** runs automatically at startup and verifies that Apps Script is reachable, the VPS is up, and the AES key matches. If it fails, the message tells you what went wrong.\n\nNow set your browser to use SOCKS5 proxy `127.0.0.1:1080`:\n\n- **Firefox:** Settings → Network Settings → Manual proxy → SOCKS5 host `127.0.0.1` port `1080`. Check **Proxy DNS when using SOCKS v5**.\n- **Chrome\u002FEdge:** Use an extension like FoxyProxy or SwitchyOmega.\n- **System-wide on macOS\u002FLinux:** Set SOCKS5 in network settings.\n\n---\n\n## Android Setup (Termux)\n\nThe Android client runs inside [Termux](https:\u002F\u002Ftermux.dev) — there is no APK. Follow these steps:\n\n**1. Install and set up Termux:**\n```bash\napt update && apt upgrade -y\npkg install wget tar -y\n```\n\n**2. Download and extract the client:**\n```bash\nwget https:\u002F\u002Fgithub.com\u002FKianmhz\u002FGooseRelayVPN\u002Freleases\u002Flatest\u002Fdownload\u002FGooseRelayVPN-client-v1.6.0-android-arm64.tar.gz\ntar -xzvf GooseRelayVPN-client-v1.6.0-android-arm64.tar.gz\ncd GooseRelayVPN-client-v1.6.0-android-arm64\u002F\nchmod +x goose-client\n```\n\n**3. Create your config:**\n```bash\ncp client_config.example.json client_config.json\nnano client_config.json\n```\nFill in your `script_keys` and `tunnel_key`, then save with Ctrl+X.\n\n**4. Run the client:**\n```bash\n.\u002Fgoose-client -config client_config.json\n```\n\nWhen you see `ready: local SOCKS5 is listening on 127.0.0.1:1080` it's working.\n\n**5. Connect your apps:**\n\nUse a SOCKS5-aware app to route traffic through `127.0.0.1:1080`. [NekoBox](https:\u002F\u002Fgithub.com\u002FMatsuriDayo\u002FNekoBoxForAndroid) and [v2rayNG](https:\u002F\u002Fgithub.com\u002F2dust\u002Fv2rayNG) both work well:\n- Add a SOCKS5 proxy pointing to `127.0.0.1:1080`\n- In **per-app settings**, enable the proxy for the apps you want and **exclude Termux** from the VPN so the tunnel itself stays connected\n\n---\n\n## LAN Sharing (Optional)\n\nBy default the client listens on `127.0.0.1:1080` so only your computer can use it. To share with other devices on your local network, set `socks_host` to `0.0.0.0` in `client_config.json` and restart.\n\n> ⚠️ **Security note:** Anyone on your LAN can then proxy through your tunnel and consume your Apps Script quota. Only do this on trusted networks.\n\n---\n\n## Increase capacity with multiple deployments (recommended)\n\nThe **~20,000 calls\u002Fday quota applies per Google account**, not per deployment or project — all deployments under the same account share one quota pool. The client polls about once per second when idle, so a single deployment can sustain steady use, but heavy days hit the cap. Real-time apps like **Telegram or X can drain the quota within a few hours** due to constant polling. To go beyond that, deploy `Code.gs` across **different Google accounts** and put all the Deployment IDs into `script_keys`.\n\n> ⚠️ **Label every deployment with the Google account it lives under.** The client scales its concurrency (4 poll workers per \"bucket\") by **distinct account labels**, not by deployment count — because Apps Script's per-second concurrency cap is also per-account. Two deployments under the same account share one quota and one bucket; two deployments under different accounts give you two buckets.\n\n```json\n{\n  \"script_keys\": [\n    {\"id\": \"FIRST_DEPLOYMENT_ID\",  \"account\": \"acct-a\"},\n    {\"id\": \"SECOND_DEPLOYMENT_ID\", \"account\": \"acct-a\"},\n    {\"id\": \"THIRD_DEPLOYMENT_ID\",  \"account\": \"acct-b\"},\n    {\"id\": \"FOURTH_DEPLOYMENT_ID\", \"account\": \"acct-b\"}\n  ]\n}\n```\n\nThe example above is 4 deployments across 2 accounts → **2 buckets → 8 poll workers, 2 standing long-polls** — twice the parallelism and twice the daily quota of a single account, without overloading either.\n\nIf you leave the labels off (`[\"ID1\", \"ID2\", ...]` plain strings), all deployments collapse into one anonymous bucket — same workers and same idle slots as a single deployment. The client logs a `WARN` at startup so you don't miss it. Use plain strings only if all your deployments really are under one Google account; otherwise label them.\n\nWhat the client does for you automatically:\n\n- **Round-robin** across all configured deployments within active buckets.\n- **Health-aware blacklist** — if one starts failing, the client backs off from it (3 s, 6 s, 12 s, … up to ~48 s) and keeps using the others.\n- **Same-poll failover** — if a poll fails on one deployment, the same payload is retried on another within the same poll cycle, so no traffic is lost during transient quota or 5xx events.\n- **Per-account stats** — the periodic `[stats]` line aggregates request counts per account label so you can see how each Google account's daily quota is being spent.\n\n> 💡 All deployments must use **the same `tunnel_key`** because they all forward to the same VPS, which only has one AES key. You don't need to change anything on the VPS when you add more deployments.\n\n> 💡 You can paste either just the Deployment ID (the part between `\u002Fs\u002F` and `\u002Fexec`) or the full `\u002Fexec` URL — the client extracts the ID either way.\n\n> 💡 **A practical upper bound is 2–3 accounts.** Adding more deployments under accounts you already have just spreads quota and rarely improves throughput; what helps is *another distinct account*.\n\n---\n\n## Configuration\n\n### Client (`client_config.json`)\n\n| Field | Default | What it does |\n|---|---|---|\n| `socks_host` | `127.0.0.1` | Host\u002FIP for the local SOCKS5 listener. Set to `0.0.0.0` for LAN sharing. |\n| `socks_port` | `1080` | Port for the local SOCKS5 listener. |\n| `google_host` | `216.239.38.120` | Google edge IP\u002Fhost to dial (port is fixed to `443`). |\n| `sni` | `www.google.com` | SNI presented during the TLS handshake. Accepts a single string or an array — `[\"www.google.com\", \"mail.google.com\", \"accounts.google.com\"]` — where each SNI host gets its own connection and throttle bucket, which can multiply available bandwidth in regions that rate-limit per domain name. |\n| `script_keys` | — | Array of Apps Script deployments. Each entry can be a bare Deployment ID string or an object `{ \"id\": \"...\", \"account\": \"...\" }` labeling the Google account it's deployed under. **The `account` label is load-bearing**: the client groups deployments by account and runs 4 poll workers per *account bucket* (more if you raise `idle_slots_per_bucket`), matching Apps Script's per-account concurrency cap. Bare strings (or unlabeled objects) all collapse into one anonymous bucket — fine if every deployment is under one Google account, but if they're under multiple accounts, label them or you lose the parallelism. See [Increase capacity with multiple deployments](#increase-capacity-with-multiple-deployments). |\n| `tunnel_key` | — | 64-char hex AES-256 key. Must match the server byte-for-byte. |\n| `socks_user` | *(optional)* | SOCKS5 username (RFC 1929). When set, clients must authenticate or the connection is rejected. Must be paired with `socks_pass` — set both or neither. |\n| `socks_pass` | *(optional)* | SOCKS5 password paired with `socks_user`. |\n| `coalesce_step_ms` | `0` (off) | Adaptive uplink coalescing. Set it to a positive number to make the first kick of a burst of TX operations wait a little for more operations; each new operation resets the timer. This trades a bit of latency for fewer Apps Script calls. A good starting range is 20-40 ms. Set it to `0` to turn coalescing off. The internal safety cap is derived automatically from this value. |\n| `idle_slots_per_bucket` | `1` | Download-throughput tuning. The carrier holds this many concurrent idle long-polls open per account bucket to receive downstream pushes. Default `1` is the safe baseline established by issue #56's fix. Raise to `2` if each Google account has 2+ deployments — this may increase download throughput; leave at `1` if each account has only one deployment (raising it would put 2 simultaneous polls on a single deployment URL, which is more likely to trip Apps Script's per-account concurrency cap). Max `3`; values above are rejected. |\n\n### Server (`server_config.json`)\n\n| Field | Default | What it does |\n|---|---|---|\n| `server_host` | `0.0.0.0` | Host\u002FIP where the exit server binds. |\n| `server_port` | `8443` | Port where the exit server listens. Must be reachable from Google's network. |\n| `tunnel_key` | — | 64-char hex AES-256 key. Must match the client. |\n| `upstream_proxy` | *(optional)* | Route all outbound connections through a local SOCKS5 proxy. Useful when your VPS datacenter IP is blocked by certain sites. Set to `socks5:\u002F\u002F127.0.0.1:40000` to use Cloudflare WARP (DNS is resolved by the proxy, so target sites see the Cloudflare IP instead of your VPS IP). Leave empty or omit to dial directly. |\n| `debug_timing` | `false` | When `true`, logs per-session DNS and TCP dial latency so you can pinpoint where time is going. |\n\n---\n\n## Updating the Apps Script forwarder\n\nIf you change `Code.gs` — for example to point at a new VPS IP — you must create a **new deployment** in the Apps Script editor (Deploy → **New deployment**, not just \"Manage deployments\"). Saving alone does nothing; the live `\u002Fexec` URL serves the published version. After redeploying, update `script_keys` in `client_config.json`.\n\nThe current `Code.gs` also tracks per-deployment invocation counts and exposes them via `doGet`, along with forwarder\u002Fprotocol metadata used by the client's pre-flight check. If you have an older deployment, redeploying once enables the `script=N` field in the client's periodic `[stats]` line and avoids version-mismatch warnings.\n\n---\n\n## Architecture\n\n```\n┌─────────┐   ┌──────────────┐   ┌──────────────┐   ┌─────────────┐   ┌──────────┐\n│ Browser │──►│ goose-client │──►│ Google edge  │──►│ Apps Script │──►│  Your    │──► Internet\n│  \u002F App  │◄──│  (SOCKS5)    │◄──│ TLS, fronted │◄──│  doPost()   │◄──│  VPS     │◄──\n└─────────┘   └──────────────┘   └──────────────┘   └─────────────┘   └──────────┘\n              AES-256-GCM         SNI=www.google     dumb forwarder    decrypt +\n              session multiplex   Host=script.…      no plaintext      net.Dial\n```\n\nKey invariants:\n\n- **Authentication = AES-GCM tag.** No shared password, no certificates. Frames that fail `Open()` are dropped silently.\n- **Apps Script never sees plaintext.** The script is a ~30-line forwarder; the AES key lives only on your machine and the VPS.\n- **DNS travels through the tunnel.** The SOCKS5 server uses a no-op resolver; use `socks5h:\u002F\u002F` so DNS is resolved at the exit, not locally.\n- **Long-poll, full-duplex.** The VPS holds each request open for up to 8s waiting for downstream bytes; the client runs **4 concurrent poll workers per labeled `account` bucket** in `script_keys` (default; scales further with `idle_slots_per_bucket`) — so 1 account = 4 workers, 2 accounts = 8 workers, 3 accounts = 12 workers, regardless of how many deployment IDs each account has. The bucket model exists because Apps Script's per-second concurrency cap is per-account; scaling workers by deployment count instead caused users with multiple IDs under one account to see Apps Script HTML error pages mid-session. Downstream frames are coalesced in a small (~25 ms) window so streaming workloads send fewer, larger HTTP responses.\n- **Health-aware multi-deployment.** When `script_keys` lists more than one deployment, the client picks endpoints in round-robin and exponentially blacklists any that misbehave; one same-poll retry is attempted on a fresh deployment so transient failures don't drop traffic.\n\n### Wire format\n\n- **Frame** (plaintext, inside the sealed batch): `session_id (16) || seq (u64 BE) || flags (u8) || target_len (u8) || target || payload_len (u32 BE) || payload`\n- **Batch seal** (AES-GCM): the entire batch is sealed once — `nonce (12 bytes) || AES-GCM(u16 frame_count || [u32 frame_len || frame_bytes] …)` — one nonce and auth-tag per HTTP body, not per frame.\n- **HTTP body**: `base64(nonce || ciphertext+tag)`, base64 so it survives Apps Script's `ContentService` text round-trip.\n\n---\n\n## Project Files\n\n```\nGooseRelayVPN\u002F\n├── cmd\u002F\n│   ├── client\u002Fmain.go              # Entry point: SOCKS5 listener + carrier loop\n│   └── server\u002Fmain.go              # Entry point: VPS HTTP handler\n├── internal\u002F\n│   ├── frame\u002F                      # Wire format, AES-GCM seal\u002Fopen, batch packer\n│   ├── session\u002F                    # Per-connection state, seq counters, rx\u002Ftx queues\n│   ├── socks\u002F                      # SOCKS5 server + VirtualConn (net.Conn adapter)\n│   ├── carrier\u002F                    # Long-poll loop + domain-fronted HTTPS client\n│   ├── exit\u002F                       # VPS HTTP handler: decrypt, demux, dial upstream\n│   └── config\u002F                     # JSON config loaders\n├── bench\u002F\n│   ├── harness\u002Fmain.go             # E2E benchmark: real binaries, loopback sink\n│   ├── sink\u002Fmain.go                # TCP sink (echo \u002F sized \u002F source \u002F quick modes)\n│   ├── diff\u002Fmain.go                # JSON result comparator with noise-floor logic\n│   ├── baselines\u002F                  # Committed baseline JSON files\n│   └── bench.sh                   # Build + run + compare orchestrator\n├── apps_script\u002F\n│   └── Code.gs                     # ~30-line dumb forwarder\n├── scripts\u002F\n│   └── goose-relay.service         # systemd unit template\n├── client_config.example.json\n└── server_config.example.json\n```\n\n---\n\n---\n\n## Troubleshooting\n\n| Problem | Solution |\n|---|---|\n| `cannot execute binary file: Exec format error` when running `goose-server` or `goose-client` | You downloaded the wrong archive for your OS\u002Farchitecture. The folder name tells you what you got — e.g. `…-darwin-amd64` is a **macOS** binary and won't run on Linux. Re-download the matching archive (Linux VPS → `linux-amd64`; Apple Silicon Mac → `darwin-arm64`; Termux → `android-arm64`). |\n| Pre-flight fails: `cannot reach Apps Script` | Your internet connection can't reach Google. Check `google_host` — try a different IP from the 216.239.x.120 range. |\n| Pre-flight fails: `HTTP 204 — key mismatch` | The `tunnel_key` in `client_config.json` doesn't match the one in `server_config.json` on the VPS. They must be byte-identical. |\n| Pre-flight fails: `Apps Script cannot reach your VPS` | Port 8443 on your VPS is not reachable. Run `sudo ufw allow 8443\u002Ftcp` on the VPS and check your cloud provider's firewall rules. |\n| Log says `relay returned non-batch payload` | Apps Script returned an HTML page instead of an encrypted batch. Three common causes: (1) the deployment in `script_keys` isn't live, or **Who has access** is not set to `Anyone` — re-deploy (Deploy → **New deployment**) and update `script_keys`; (2) the deployment was added to an existing Apps Script project alongside other files — create a **new** project with only `Code.gs` in it, then deploy from there; (3) you have multiple deployments under the same Google account and are hitting that account's per-second concurrency cap — label `script_keys` entries with their `account` so the client throttles per-account (see [Increase capacity with multiple deployments](#increase-capacity-with-multiple-deployments)). |\n| Log says `relay returned HTTP 404 via …` | The Deployment ID in your config doesn't match a live `\u002Fexec`. Re-deploy and update the config. |\n| Log says `relay returned HTTP 500 via …` | Apps Script can't reach `VPS_URL`. Check the server address in `Code.gs`, confirm the VPS is up, and confirm inbound TCP\u002F8443 is open. `curl http:\u002F\u002Fyour.vps.ip:8443\u002Fhealthz` should return 200. |\n| Log says `relay request failed via …: timeout` | Fronted connection to Google is failing. Try a different `google_host` — any 216.239.x.120 served by Google works. |\n| Browser hangs on every request | Make sure your browser extension uses SOCKS5 with **DNS through proxy** enabled (not plain SOCKS5). In Firefox, check **Proxy DNS when using SOCKS v5**. |\n| `[exit] dial X: ... timeout` on the VPS server logs | The target host blocks datacenter IPs, or your VPS has no outbound connectivity for that port. |\n| Cloudflare-protected sites show captchas | Expected. Your VPS's IP is on a datacenter ASN, which Cloudflare's bot scoring often flags. Not a tunnel bug. |\n| YouTube buffers a lot at 1080p | Expected. The tunnel adds ~300-800ms per round trip due to Apps Script dispatch overhead. 480p is comfortable. Deploying multiple `script_keys` (see above) helps with sustained throughput. |\n| One deployment hits quota mid-session | If `script_keys` has more than one entry, the client automatically blacklists the failing one for a few seconds and keeps going on the others. With only one entry, browsing stops until the quota resets (~10:30 AM Iran time \u002F midnight Pacific). |\n| Mismatched AES keys | Symptom: client logs no errors but no traffic flows; VPS logs no `dial ...` lines. Confirm `tunnel_key` is byte-identical in both configs. |\n\n---\n\n## Security Tips\n\n- **Never share `client_config.json` or `server_config.json`** — the AES key is in there and a leaked key means anyone can tunnel through your VPS.\n- **Generate a fresh key with `openssl rand -hex 32`** for every deployment. Don't reuse keys across hosts.\n- **AES-GCM is the only authentication.** There's no password, no rate-limiting, no per-user accounting. Treat the key like a server-admin password.\n- **Apps Script logs every `doPost` invocation** in Google's dashboard (count and duration only — Apps Script never sees plaintext).\n- **Keep `socks_host` on the client at `127.0.0.1`** unless you specifically want LAN sharing.\n- **Each Apps Script deployment is rate-limited to ~20,000 calls\u002Fday** on free Google accounts.\n\n---\n\n## Contributing\n\nPull requests are welcome. For any change that touches the carrier loop, session layer, or poll behavior, please include benchmark results so reviewers can evaluate the performance impact.\n\nThe `bench\u002F` directory contains an end-to-end harness that spins up real `goose-client` and `goose-server` binaries against a loopback TCP sink and measures throughput, TTFB, session rate, and idle CPU.\n\n```bash\n# Build the binaries and run the full benchmark suite\nbash bench\u002Fbench.sh\n```\n\nThe harness compares your working tree against the committed baseline in `bench\u002Fbaselines\u002F` and prints a side-by-side table. Regressions above the noise floor fail the script with exit code 1. Include the output in your PR description.\n\nTo record a new baseline from a specific git ref:\n\n```bash\nbash bench\u002Fbench.sh --update \u003Cref>   # e.g. --update v1.3.0 or --update HEAD\n```\n\n---\n\n## Special Thanks\n\nSpecial thanks to [@abolix](https:\u002F\u002Fgithub.com\u002Fabolix) for making this project possible.\n\n## License\n\nMIT\n","GooseRelayVPN 是一个通过 Google Apps Script 将原始 TCP 流量隧道传输到自建 VPS 服务器的 Socks5 VPN。项目使用 Go 语言编写，支持 AES-256-GCM 加密和域名前置技术，确保数据在传输过程中全程加密且对中间节点透明。适用于需要绕过网络审查或访问受限资源的场景，尤其适合需要传输非 HTTP 协议（如 SSH、IMAP 等）的情况。用户需自行准备一台小型 VPS 作为出口服务器，该方案相比纯基于 Google Apps Script 的代理服务提供了更广泛的协议支持。",2,"2026-06-11 02:39:41","CREATED_QUERY"]