v1.6.0
Highlights
Stability under sustained load
The exit server now properly closes sessions on upstream EOF and keeps partial drains queued so trailing FIN frames always reach the client. Previously, ~270 zombie sessions could accumulate in production logs (active=290 sessions while goroutines=51), eventually starving the server and triggering Apps Script HTML error pages (the root cause of "non-batch payload" reports — issue #56). Closes #31, #44, #62.
Multi-deployment-single-account configs no longer overload Apps Script
Workers and idle long-poll slots now scale by Google account bucket, not endpoint count. Users with multiple deployments under one account previously got 3 × N workers slamming a single account, hitting the per-second concurrency cap and getting HTML error pages back instead of encrypted batches. Workers are now (workersPerEndpoint + idleSlotsPerBucket - 1) × bucketCount. Closes #56, refs #41 #73.
Label your script_keys with the account field — the label is now load-bearing, not just for stats.
New idle_slots_per_bucket knob
Opt up concurrent idle long-polls per account when your accounts can sustain it. Default 1 (safe), max 3. May increase download throughput on accounts with multiple deployments. Closes #14 (downstream tuning).
Adaptive uplink coalescing
Set coalesce_step_ms (default 0, off) to collapse bursts of small TX operations into a single Apps Script call. Trades a bit of latency (20–40 ms typical) for fewer UrlFetchApp invocations on interactive workloads. Closes #14.
Version negotiation + /healthz endpoint
Clients can now query the server's version and protocol on connect; ops can health-check the VPS without holding a tunnel key. Closes #86, #87.
Per-account script stats aggregation
The periodic [stats] line now reports actual UrlFetchApp counts per Google account, so you can see which account is doing the work. Closes #55, #81.
Docker integration
Server now ships with a Dockerfile and docker-compose.yml. Closes #88.
Frame compression
Zstandard (preferred) and DEFLATE compression for batch bodies, with transparent fallback for older peers.
Performance & polish
- HTTP/2 transport tuning: 1 MiB MaxReadFrameSize, ReadIdleTimeout, PingTimeout (#58)
- TCP_NODELAY + TCP_QUICKACK on SOCKS listener for snappier interactive traffic (#60)
- Buffer pooling on hot paths to reduce GC pressure (#63)
- Unpadded raw base64 saves ~1.5% protocol overhead (#64)
- TLS 1.3 floor (#61)
- Dynamic queue-age fairness (#67)
workersPerEndpointbumped 3 → 4
Other changes
carrierworker count now scales withidle_slots_per_bucketso TX pool isn't drained when raising the RX cap- Stats:
goroutines=andclients=added to the always-on[stats]line for leak detection without pprof - systemd:
StartLimit*moved to[Unit]with modern field names - diagnose: lowercase error messages, JSON
doGetpre-flight handling - SOCKS session logs gated behind
debug_timing - README: clarified
script_keysformat, troubleshooting tips for "Exec format" and "non-batch payload", Termuxchmod +xstep - Issue forms added
Full Changelog: v1.5.0...v1.6.0
Upgrade Notes
Redeploy everything together: client, server, and the Apps Script (Code.gs).
The frame wire format remains backward-compatible (raw base64 decoder still accepts padded legacy format; compression is negotiated), but several v1.6.0 features need both ends to be on v1.6.0:
- Version negotiation +
/healthz(server-side) - Compression (both ends must agree)
- Per-account stats aggregation (Code.gs reports invocation counts back to client)
apps_script/Code.gs has substantive changes (70+ lines across 3 commits since v1.5.0). Redeploy the Apps Script to pick up version negotiation, the RELAY_URL rename, and per-account stats. Mixing a v1.5 Apps Script deployment with a v1.6 client is supported but you'll miss the new features.
Spread your script_keys across multiple Google accounts when you can. Workers and idle long-polls now scale by distinct account labels:
| Config | v1.5.0 workers | v1.6.0 workers |
|---|---|---|
| 1 deployment / 1 account | 3 | 4 |
| 3 deployments / 1 account (unlabeled or same label) | 9 | 4 |
| 3 deployments / 3 accounts (each labeled) | 9 | 12 |
| 6 deployments / 3 accounts | 18 | 18 |
| 6 deployments / 6 accounts | 18 | 24 |
If your script_keys are unlabeled, the carrier treats them as one bucket and logs a startup WARN advising you to label them. Add "account": "A", "B", etc. to each entry in script_keys — the label is now load-bearing, not cosmetic.
Single-account users: try idle_slots_per_bucket: 2 (or 3, which is the cap) if your account has 2+ deployments. May increase download throughput at the cost of more simultaneous executions per account. Don't set it above what your account empirically tolerates — going too high re-triggers issue #56 (HTML error pages instead of encrypted batches).