Give every new connection a German Tor horizon

July 16, 2026 · View on GitHub

Véspero mark

Véspero

Una luz. Todos los rumbos.
One light. Every direction.

A self-hosted, pluggable egress controller behind one quiet, stable proxy endpoint.

Rust 2024 Tokio Axum React 19 TypeScript 5 OpenAPI 3.1 Linux Windows CI

English · Español · 简体中文

The connection remains. Only the horizon moves.

Véspero sits between the proxy you already trust and the network beyond it. Your client keeps one HTTP/SOCKS5 address; the web console or API decides where new TCP connections leave—through the host, a VPNGate tunnel, or a country-constrained Tor circuit.

It is an egress control plane, not another client protocol. It does not claim anonymity, and it does not reimplement Tor or OpenVPN.

Véspero control deck showing Tor selected as the active egress
One relay. Three kinds of horizon. The active provider and observed exit IP remain visible.

One address, many horizons

What stays stillWhat can move
Client configurationExit provider
127.0.0.1:19080 relayDirect, VPNGate, or Tor
HTTP/SOCKS5 contractCountry, node, and routing policy
Established connectionsNew connections only
Automation surfaceThe route behind it

A switch is deliberately calm: existing connections finish on the socket they already own; connections accepted after the switch use the new route. If a selected provider cannot become ready, Véspero fails closed instead of silently leaking new traffic through Direct.

What is already real

  • One stable HTTP and SOCKS5 TCP relay with optional user/password authentication and a connection limit.
  • Runtime switching between Direct, VPNGate, and a managed client-only Tor process.
  • A polished web console for provider identity, country selection, exit-IP checks, route health, filters, policy, and event history.
  • A typed OpenAPI 3.1 control surface with embedded Swagger UI for people, scripts, and agents.
  • VPNGate catalog refresh, parallel probes, favorites, country/IP-type policy, blacklist TTL, watchdog failover, and tunnel-bound DNS.
  • Debian packages, systemd integration, Windows bundles, and compatibility with existing VGL_* deployments.

The route in one glance

flowchart LR
    Client["Unchanged client"] --> Front["sing-box · Mihomo · Hysteria 2<br/>browser · agent"]
    Front -->|"HTTP / SOCKS5"| Relay["Véspero relay<br/>127.0.0.1:19080"]

    Console["Web console"] --> Control["Egress controller"]
    API["OpenAPI 3.1"] --> Control
    Control -. "atomic route selection" .-> Relay

    Relay --> Choice{"Route for a<br/>new connection"}
    Choice --> Direct["Direct<br/>host network"]
    Choice --> VPN["VPNGate<br/>bound OpenVPN interface"]
    Choice --> Tor["Tor<br/>local SOCKS upstream"]

    classDef core fill:#082f36,stroke:#45d5c1,color:#f6fffd,stroke-width:2px;
    classDef control fill:#e9faf6,stroke:#159b8c,color:#082f36;
    classDef provider fill:#fffaf0,stroke:#ff8e72,color:#34251e;
    class Relay core;
    class Console,API,Control control;
    class Direct,VPN,Tor provider;

The data path and control path stay separate. Front ends such as Hysteria 2, sing-box, or Mihomo remain responsible for client-facing transport; Véspero owns the egress choice behind their local upstream.

Providers

ProviderIdentity in the UIHow it is selectedRuntime path
DirectLOCALOne clickHost network
VPNGateCOMMUNITYNode, country, favorites, or automatic policyManaged OpenVPN process and interface-bound sockets
TorNETWORK · TCPAutomatic or ISO country codeManaged client-only Tor process and SOCKS5 chaining

All providers converge on a small internal model—Direct, Interface, or SOCKS5 upstream. That is the extension seam for the next provider; clients and the stable relay do not have to learn anything new.

VPNGate route catalog with filters, routing policy, and event log
Live route inventory, visible provider provenance, deliberate policy, and a history of every transition.

Quick start

Linux is the complete target because isolated VPNGate routing depends on SO_BINDTODEVICE and policy routing. Direct and Tor also work on Windows.

Build prerequisites: Rust, Node.js 22+, OpenVPN, and Tor.

git clone https://github.com/tianrking/Vespero.git
cd Vespero
sudo bash scripts/install.sh

The installer builds the console and controller, installs vespero.service, and generates a control password in the root-only /etc/default/vespero file.

Keep the control plane local and open it through SSH:

ssh -L 18081:127.0.0.1:18081 root@YOUR_VPS
SurfaceDefault
Web consolehttp://127.0.0.1:18081
Swagger UIhttp://127.0.0.1:18081/docs
OpenAPI documenthttp://127.0.0.1:18081/openapi.json
Stable relaysocks5://127.0.0.1:19080 or http://127.0.0.1:19080
Servicevespero.service
Configuration/etc/default/vespero

Never expose an unauthenticated relay to the Internet. For a public control surface, keep Véspero on loopback and terminate HTTPS with the reverse proxy you already operate.

Put Véspero behind your ingress

Your users keep connecting to the transport they already have. Only its server-side upstream points to Véspero.

Hysteria 2 server
outbounds:
  - name: vespero
    type: socks5
    socks5:
      addr: 127.0.0.1:19080

# Véspero's relay is TCP-only today. Hysteria still uses QUIC/UDP as transport.
disableUDP: true
sing-box outbound
{
  "type": "socks",
  "tag": "vespero",
  "server": "127.0.0.1",
  "server_port": 19080
}
Mihomo proxy
proxies:
  - name: vespero
    type: socks5
    server: 127.0.0.1
    port: 19080

Control it from the web—or from code

Protected endpoints receive the control password as Authorization: Bearer <password>. VESPERO_TOKEN remains the internal environment-variable name for compatibility; the person using the console simply sees a password.

PASSWORD="$(sudo sed -n 's/^VESPERO_TOKEN=//p' /etc/default/vespero)"

# Discover installed providers and their capabilities
curl -H "Authorization: Bearer $PASSWORD" \
  http://127.0.0.1:18081/api/providers

# Give every new connection a German Tor horizon
curl -X POST \
  -H "Authorization: Bearer $PASSWORD" \
  -H "Content-Type: application/json" \
  -d '{"provider":"tor","country":"DE"}' \
  http://127.0.0.1:18081/api/egress/select

The embedded Swagger UI can authorize, inspect schemas, and execute every control endpoint without installing another tool.

Véspero OpenAPI 3.1 Swagger console
The same control plane is legible to a person, a script, or an agent.

Configuration

The important defaults are intentionally private:

VariableDefaultPurpose
VESPERO_CONTROL127.0.0.1:18081Web and API listener
VESPERO_RELAY127.0.0.1:19080Stable HTTP/SOCKS5 listener
VESPERO_TOKENGenerated on installControl password; compatibility name
VESPERO_TUNvespero0VPNGate OpenVPN interface
VESPERO_TOR_CMDtorTor executable
VESPERO_TOR_SOCKS127.0.0.1:19050Managed Tor SOCKS port
VESPERO_RELAY_USER / VESPERO_RELAY_PASSEmptyOptional relay authentication

See packaging/default.env for the complete set. VESPERO_* values take precedence; legacy VGL_* names remain readable during migration.

Honest boundaries

  • The relay currently carries TCP. UDP-capable ingress protocols must disable proxied UDP or handle it outside Véspero.
  • Tor country selection is a circuit constraint, not a permanent or dedicated IP, and some countries may have no usable exits.
  • VPNGate is a live volunteer network. Availability, ownership, performance, and trust change over time.
  • Véspero exposes provider identity precisely because an exit is a route—not a promise of anonymity.
  • Unsafe application behavior remains unsafe regardless of which provider is selected.

Build confidence, not just binaries

python3 scripts/check-readmes.py
cargo fmt -- --check
cargo clippy --all-targets --locked -- -D warnings
cargo test --locked

cd web
npm ci
npm run build

CI repeats the work across Ubuntu and Windows, builds Debian and Windows packages, installs them in clean target environments, verifies authentication and the embedded OpenAPI console, and publishes artifacts only after the matrix is green.

The Linux live-provider test can also exercise Direct, Tor, country-constrained Tor, VPNGate, and restoration through the real relay:

sudo VESPERO_E2E_FULL=1 ./scripts/e2e-linux.sh

An installed Linux gateway has a stricter, disruptive release acceptance test. It verifies live preemption, cancellation, process/TUN cleanup, both relay protocols, and—when configured—the complete Hysteria2 path. It always restores Direct on exit:

sudo VESPERO_REQUIRE_HYSTERIA=on ./scripts/live-ubuntu-check.sh

Architecture details and provider extension rules live in docs/ARCHITECTURE.md; the next deliberate steps live in docs/ROADMAP.md.


Véspero does not ask the client to move.
It changes the sky beyond the connection.