Skip to content

feat: bring your own proxy (http, https, socks5), no account needed - #21

Merged
cvl merged 13 commits into
mainfrom
feat/byo-upstream
Oct 21, 2026
Merged

cvl merged 13 commits into
mainfrom
feat/byo-upstream

Conversation

@cvl

@cvl cvl commented Oct 21, 2026

Copy link
Copy Markdown
Contributor
  • docs: keep the provider compatibility table to provider, scheme, endpoint, result and behaviour
  • fix(upstream): keep SHELLROUTE_PROXY out of the child environment; plain-language probe failures
  • docs: own-proxy examples use the your-proxy-name placeholder and say it is the user's alias; README covers every use case
  • docs: Evomi https and socks5 rows and the SOCKS5 general-failure hint (completes the previous commit's docs)
  • feat(upstream): password hint on SOCKS5 'general failure' after a credentials exchange; Evomi https and socks5 results
  • docs: add the Webshare free-plan result to the provider compatibility table
  • docs: start the own-proxy provider compatibility table with the Evomi datacenter result
  • feat(upstream): show the observed exit country instead of a typed label
  • test: run the Docker proxy layer from scripts/run-tests.sh; parse the colored summary
  • feat: bring-your-own-proxy docs, status for own sessions, Docker upstream test layer
  • feat(tui): /connect routes the interactive shell through an own upstream
  • feat(cli): bring your own proxy with upstream add/list/remove/test, run , --proxy, proxy --upstream
  • feat(proxy): upstream abstraction with hosted, HTTP(S) and SOCKS5 implementations

cvl added 13 commits October 14, 2026 20:33
…lementations

The local proxy now talks to an Upstream interface instead of the gateway
fields. Three implementations: the hosted gateway (unchanged semantics for
X-Exit-IP, 402, 407, X-Route-Dead), a user's own HTTP or HTTPS proxy with
optional basic auth, and a hand-written SOCKS5 client with username/password
auth and remote name resolution. Plain HTTP over SOCKS5 is rewritten to
origin form.

A user's own proxy never reports route death, so a credentials error marks
the route dead at once and three consecutive connection failures mark it
dead; a success resets the count. The hosted gateway keeps its explicit
signalling and no counter.

ParseUpstreamURL builds an upstream from http, https, socks5 or socks5h URLs
with a required port; RedactURL and Describe never show the password.
Probe and Check open one tunnel (one retry, 10 s each) and read the exit IP
through the upstream, classifying failures as resolve, refused, timeout,
credentials or proxy status.

session.StartOwn starts the local proxy over an own upstream with no API
call and no billing; Stop returns duration and bytes only. proxytest holds
in-process HTTP, HTTPS and SOCKS5 fakes for tests.

Signed-off-by: contra <vytautas@civilis.lt>
…un <name>, --proxy, proxy --upstream

Saved upstreams live in config.toml as [[upstream]] tables (name, url,
country label, note); names are 3 to 32 characters of [a-z0-9_-] so they
never collide with a country code. The URL password is stored in the 0600
file and never printed: list, status and the session file show user:***.

shellroute run resolves its target in this order: --proxy <url>, --country,
a two-letter country before --, a saved upstream name before --, then
SHELLROUTE_PROXY from the environment, then default_country. Own routes need
no account: no login check, no API call, no version check, and they work
with the API unreachable. The start of an own route checks the tunnel once
with one retry (10 s each) and prints 'via work · exit IP … · N ms'; a
failure names the cause (resolve, refused, timeout, credentials, proxy
status). --no-check skips the probe. The end-of-run summary shows duration
and bytes only.

shellroute proxy --upstream <name|url> runs the persistent local proxy over
an own upstream; proxy stop and the singleton guard show the upstream name.
The interactive shell starts without login when saved upstreams exist.

Signed-off-by: contra <vytautas@civilis.lt>
…n upstream

The control port accepts /connect?upstream=<name>, starts the session with
session.StartOwn and exports SHELLROUTE_UPSTREAM next to the usual
variables; switching back to a hosted route clears it. Route checks for an
own session use the local route state only, so a dead or unreachable
Shellroute API never blocks the shell. Rotation is the provider's job:
/rotate says so and the reconnect-on-death path reports the loss instead of
rotating. /balance, /countries, /cities, /iptype and /sticky answer 'not
applicable' while an own route is active. The prompt shows 'work (DE) · ip';
status shows the upstream and its redacted URL; disconnect prints duration
and bytes only. The shell starts without a login when upstreams are saved.

Signed-off-by: contra <vytautas@civilis.lt>
…ream test layer

README gets a 'Use your own proxy' section; docs/bring-your-own-proxy.md
covers URL formats, how run picks the route, what stays the same, what is
different, failure behavior and troubleshooting by error message;
docs/compatibility.md says the matrix applies to both route kinds; the
changelog lists 0.2.0 as unreleased.

shellroute status lists live own-proxy sessions from the session files and
no longer needs a login when only those exist.

A SOCKS5 'not allowed by ruleset' after a credentials exchange gets the hint
'(check the username and password)': 3proxy answers a wrong password that way.

scripts/test-upstreams.sh runs the layer-2 checks C-01 to C-10 and C-14
against 3proxy (HTTP with auth, SOCKS5 with and without auth), gost (SOCKS5
with auth; HTTPS with auth on Linux), squid (open and basic auth) and an
ssh -D tunnel, all in Docker on one network with a marker container only
the proxies can resolve. CI gets an 'upstreams' job for it.

Signed-off-by: contra <vytautas@civilis.lt>
… colored summary

run-tests.sh gets an 'Own proxies in Docker' stage that runs
scripts/test-upstreams.sh when a Docker daemon is up and reports SKIP
otherwise. The byte check (C-07) now strips terminal color codes before
reading the 'Data used' line; on a terminal the reset code after the label
contains a digit and broke the pattern, so the check failed although the
download and its checksum were fine. Failure lines use the 'FAIL:' shape the
gate's summary looks for.

Signed-off-by: contra <vytautas@civilis.lt>
upstream add loses --country. A typed country was shown as 'work (DE)' as
if Shellroute knew where the proxy exits; it does not. The start-up probe
now asks the exit IP service for JSON (ipinfo.io/json, then ipify) and
records the country it reports for the observed exit IP. upstream test,
run, status, the shell prompt and the session file show it as
'DE (observed)', or nothing when the service does not report one.
The session file is rewritten after the probe so status and stop see the
observed values.

Signed-off-by: contra <vytautas@civilis.lt>
… datacenter result

Signed-off-by: contra <vytautas@civilis.lt>
… table

Signed-off-by: contra <vytautas@civilis.lt>
…dentials exchange; Evomi https and socks5 results

Evomi's SOCKS5 gateway answers a wrong password with reply 1 (general
failure) at the CONNECT step after accepting the RFC 1929 exchange, as
3proxy does with reply 2. Both now carry the '(check the username and
password)' hint when credentials were sent; without credentials the text is
unchanged. The compatibility table gains the Evomi https (public certificate,
no extra setup) and socks5 rows.

Signed-off-by: contra <vytautas@civilis.lt>
… (completes the previous commit's docs)

Signed-off-by: contra <vytautas@civilis.lt>
…it is the user's alias; README covers every use case

Signed-off-by: contra <vytautas@civilis.lt>
…ain-language probe failures

The command run through an own proxy inherited SHELLROUTE_PROXY, so a
password passed that way reached the child process, contrary to the
documented promise. buildProxyEnv now drops the variable.

A probe that opened the tunnel but got no usable answer from the exit IP
service showed the raw Go error (URL, "context deadline exceeded"). Probe
now returns a ProbeError whose text is one of three plain sentences, and
Cause knows it; the run, test and interactive paths all use that text.
A local start failure (port, session file) no longer prints the raw error.

StartOwn moves to session/own.go to keep session.go under 500 lines.

Signed-off-by: contra <vytautas@civilis.lt>
…oint, result and behaviour

Signed-off-by: contra <vytautas@civilis.lt>
@cvl
cvl requested a review from a team as a code owner October 21, 2026 12:31
@cvl
cvl merged commit 0e8b581 into main Oct 21, 2026
4 checks passed
@cvl
cvl deleted the feat/byo-upstream branch October 22, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant