pr0xteus

module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: MIT

README

pr0xteus

CI version license coverage Docker Pulls

Your Docker services need to leave through a VPN, but you do not want to hand them a provider account, turn the host into a VPN client, or accidentally run an open proxy. pr0xteus is the small private control plane between those things: give it WireGuard files you are allowed to use, and trusted containers get short-lived SOCKS5 exits from the pools you approve.

Contents

What it does

pr0xteus starts a short-lived Docker cell when a trusted caller needs a SOCKS5 exit. Each cell owns one WireGuard configuration, waits for a handshake, runs microSocks, and is reaped once it is idle or unhealthy. Pools and country routing are operator-owned local files; callers cannot name configs, images, Docker arguments, or host paths.

It is not an open proxy. The control API needs a bearer token, binds to host loopback in the supplied Compose stack, and reaches Docker only through a restricted socket proxy. Caller input picks a configured country or pool — not an image, a host path, or a provider config.

Quick start

You need Linux, Docker, and a WireGuard .conf file from a VPN provider or private network you are allowed to use. Docker already includes Compose, so there is no separate Compose install dance.

Install it
curl -fsSL https://raw.githubusercontent.com/psyb0t/pr0xteus/main/install.sh | sudo bash

That makes ~/.pr0xteus/, puts the local docker-compose.yml and starter config there, generates a bearer token in owner-only .env, and installs the pr0xteus command. No source checkout required. It pins to the latest tagged release — never :latest on your box — and the controller derives its matching cell image from that tag, so both move together only when you upgrade.

Right after installing, edit ~/.pr0xteus/.env if you want to change the loopback ports, tune logging, or turn on the optional tailnet API (below) — everything is a plain key you edit, not a CLI flag to remember.

Want to track main instead of a release? Add --rolling to force the moving :latest image for a single run — on the installer (… | sudo bash -s -- --rolling) or on any pr0xteus start / pr0xteus upgrade.

Give it one WireGuard exit

Copy your provider or private-network file into the config directory:

cp /wherever/you/keep/your-vpn.conf ~/.pr0xteus/secrets/wireguard/us.conf

Then edit these two small files so us means your file without .conf and US is the country you want callers to request:

# ~/.pr0xteus/secrets/pools.yaml
pools:
  us:
    region: north-america
    purpose: private-service-egress
    configs: [us]
    exit_countries:
      us: US
# ~/.pr0xteus/config/egress-routing.yaml
country_to_pool:
  US: us
default_pool: us

Start it:

pr0xteus start

The controller stays private on http://127.0.0.1:8000; metrics and health stay on http://127.0.0.1:9091. Useful commands are deliberately boring:

pr0xteus status
pr0xteus logs --follow
pr0xteus stop
pr0xteus upgrade     # re-pin to the newest release, pull it, drop the old image
pr0xteus uninstall   # stop the stack, remove the command, ask before deleting data

upgrade re-pins ~/.pr0xteus/.env to the latest release and removes the previous image so dangling layers don't pile up; uninstall only deletes your ~/.pr0xteus data and volumes if you say yes at the prompt.

The complete example shows an allocation, a private SOCKS5 consumer, and an actual egress proof.

Tailscale

Want the same private API from another machine without opening a host port? Give the installed stack its own Tailscale identity. Add these lines to ~/.pr0xteus/.env:

PR0XTEUS_TAILSCALE_ENABLED=true
TS_AUTHKEY=your-tailscale-auth-key
TS_HOSTNAME=pr0xteus

Then run:

pr0xteus start

That starts an optional sidecar in its own network namespace, waits for it to join your tailnet, and configures Tailscale Serve to proxy http://pr0xteus/v1/... to the private controller. It exposes no host port, does not touch a host Tailscale client, and still requires the bearer token. The sidecar's tailnet state lives in ~/.pr0xteus/tailscale/state, so it keeps the same identity across restarts. For Headscale, add its login argument to TS_EXTRA_ARGS.

Run it yourself

The wrapper is the normal operator path. If you want every Docker command in front of you, initialize the same local stack directly:

mkdir -p ~/.pr0xteus
docker run --rm --user "$(id -u):$(id -g)" \
  -v "$HOME/.pr0xteus:/config" \
  psyb0t/pr0xteus:latest config init \
  --config-dir /config \
  --host-config-dir "$HOME/.pr0xteus" \
  --controller-image psyb0t/pr0xteus:latest

Add your WireGuard file and edit the generated pool and routing files exactly as in Quick start. Validate and start it with Docker itself:

docker run --rm --user "$(id -u):$(id -g)" \
  -v "$HOME/.pr0xteus:/config:ro" \
  psyb0t/pr0xteus:latest config check --config-dir /config

docker compose --project-directory "$HOME/.pr0xteus" \
  --env-file "$HOME/.pr0xteus/.env" \
  -f "$HOME/.pr0xteus/docker-compose.yml" \
  up --detach --pull always

That local docker-compose.yml is generated by the image and is yours to inspect or run directly. The wrapper is only a small guardrail around these same commands. Deployment details include the direct Tailscale command too.

Complete example

The operator walkthrough starts with the installer, maps a real WireGuard file into a logical country pool, starts the private stack, then proves a transient consumer actually exits through its returned SOCKS5 URL. It also covers replacing a bad allocation and why a health check is not a live-tunnel check.

How it is wired

trusted client ── private HTTP API ── pr0xteus ── socket proxy ── Docker
                                      │
                                      └── private SOCKS5 cell ── WireGuard peer
  • Controller — validates requests, selects an approved pool config, starts and monitors cells, and exposes Prometheus metrics.
  • Socket proxy — has the raw Docker socket; the controller gets only the required container API surface.
  • Cell — gets NET_ADMIN, SETUID, SETGID, /dev/net/tun, and one read-only config file; it starts as root for network setup and then runs the SOCKS5 daemon as UID 1500.

The fuller story is in docs/architecture.md, with implementation detail in internal/README.md and cell/README.md.

Configuration

All sensitive or provider-specific material lives under ~/.pr0xteus/, stays out of Git, and stays out of Docker build contexts:

  • secrets/wireguard/*.conf — real WireGuard files.
  • secrets/pools.yaml — named pools and their approved config basenames.
  • config/egress-routing.yaml — country-to-pool policy.
  • .env — the private bearer token, absolute host configuration path, and image selection.

The installer writes .env; it is not something you need to create by hand. A published controller already names its matching cell: latest uses cell-latest, while vX.Y.Z uses cell-vX.Y.Z. An operator can override PR0XTEUS_CELL_IMAGE, but that override must be pinned by digest. Local development uses the locally built psyb0t/pr0xteus:cell-dev with the explicit unpinned escape hatch in the generated .env.

To stay on a particular release, change PR0XTEUS_CONTROLLER_IMAGE=psyb0t/pr0xteus:vX.Y.Z in ~/.pr0xteus/.env, run pr0xteus setup, then run pr0xteus start.

Pool filenames need not be provider-specific. For a file that does not follow the legacy <country>-<location>.conf convention, add its country explicitly:

pools:
  primary:
    configs: [my-provider-node]
    exit_countries:
      my-provider-node: US

API

The API is versioned and JSON-only:

POST /v1/proxies  # request exactly one configured country or pool
GET  /v1/pools    # authenticated pool/tunnel operator view
GET  /healthz     # separate metrics listener, keep it private
GET  /metrics     # Prometheus, separate metrics listener

POST /v1/proxies returns a private socks5:// URL only after the cell has completed its WireGuard handshake wait and opened its SOCKS5 listener. The exact request, response, and failure contract live in docs/api.md.

Agent integrations

This repo ships a documentation skill for agents that need to drive a trusted pr0xteus controller. It knows the private control API, the real setup sequence, and the sharp edge around private Docker networking. It does not pretend pr0xteus is an MCP server, because it is not one.

Claude Code
claude plugin marketplace add psyb0t/agents
claude plugin install pr0xteus@psyb0t

Claude Code asks for the private controller URL and bearer token when the plugin is enabled; the token is stored as sensitive user configuration.

Codex
codex plugin marketplace add psyb0t/agents
codex plugin add pr0xteus@psyb0t

Inside this repository, use $pr0xteus. After marketplace installation, use $pr0xteus:pr0xteus.

OpenClaw

The same documentation skill is published to ClawHub on tagged releases:

openclaw skills install @psyb0t/pr0xteus

There is intentionally no OpenClaw MCP bridge: pr0xteus exposes a private HTTP API, not an MCP endpoint. The detailed setup reference is here.

Development

Everything supported goes through Make. Go tooling, formatting, linting, and tests run inside Dockerfile.dev, not through a host Go installation.

Source checkout and Make are for development only; an operator uses the image and the installer quick start above.

make help          # every supported operation
make format        # gofumpt + shfmt
make lint          # Go, shell, and format checks
make test          # unit tests plus a real Testcontainers WireGuard/SOCKS5 stack
make test-real     # opt-in real Surfshark allocation and public-IP egress proof
make test-coverage # runs all test packages and requires 90% production coverage
make audit         # govulncheck
make audit-compose # Compose safety checks
make build         # controller image
make build-cell    # WireGuard + microSocks image

make test-integration uses Testcontainers to build and start the production controller, a real WireGuard peer, the production cell image, and a sibling SOCKS5 client on an isolated Docker network. It allocates a proxy through the real API and proves that SOCKS5 traffic traverses the WireGuard tunnel to a private test HTTP server. It needs no provider account, real WireGuard bundle, host port, or persistent container; Testcontainers tears down only the exact resources it created.

make test-coverage runs go test -tags=integration -race ./..., so it executes unit tests and every Testcontainers package, including tests/*_test.go. Its 90% gate measures pr0xteus production code only (internal/pkg/services/pr0xteus and pkg/client): Go does execute test source, but does not treat _test.go files as coverable production code. The fixture exports native coverage from its test-only controller image into ignored .cover/ storage and merges it with the test profile, so the reported number includes the real controller process instead of only test binaries. Normal integration runs keep using Dockerfile, the production controller image.

make test-real is deliberately separate from make test and CI. It loads the ignored local Surfshark bundle at secrets/wg/surfshark-wireguard/ with the matching secrets/wg/pools.yaml and config/egress-routing.yaml, starts its own Testcontainers controller and consumer, requests a real egress proxy, then verifies that the consumer's public IPv4 address changes when traffic uses the returned SOCKS5 URL. Set PR0XTEUS_REAL_TEST_COUNTRY=US before running make test-real to select the routing input. It creates a fresh test token and a unique controller scope; it neither reads the production token nor touches a running stack.

The public Go client surface is under pkg/client; it is useful when another Go service should request and retry egress proxies without re-implementing the HTTP contract.

Security shape

  • The raw Docker socket is only mounted into docker-socket-proxy, never the controller or cells.
  • The controller is non-root, read-only, capability-empty, resource-capped, log-capped, and exposes only loopback ports.
  • Optional tailnet access is a separate, capability-minimized Tailscale sidecar. It is the only service with /dev/net/tun, NET_ADMIN, and NET_RAW; it has its own tailnet identity and exposes only the authenticated controller API through Tailscale Serve.
  • A cell has the specific WireGuard exception: NET_ADMIN and /dev/net/tun, plus SETUID/SETGID solely for its one-way final drop to the non-root microSocks account. It begins with default-drop firewall policy, allows the WireGuard peer, and does not start microSocks until a handshake arrives.
  • Real configuration and tokens are neither tracked nor included in either image build context.
  • The controller accepts a strict, size-bounded JSON body and stores only a SHA-256 digest of the bearer token after startup.
  • Every cell carries a controller scope label, so shutdown and orphan recovery only ever act on cells from that controller scope.

These are meaningful boundaries, not magic. Anyone able to modify local pool policy, read the bearer token, or control Docker on the host is already inside the operator trust boundary.

Project layout

cmd/        — process entry point
internal/   — API, pool manager, Docker spawner, reaper, metrics
pkg/client/ — public Go client for the private control API
tests/      — Testcontainers-backed controller, WireGuard, cell, and SOCKS5 tests
cell/       — WireGuard + microSocks worker image and entrypoint
docs/       — architecture, deployment, and API references
scripts/    — Makefile-backed dependency and image helpers

More docs

License and notices

The project source is MIT licensed. See THIRD_PARTY_NOTICES.md and vendor/ for dependency licenses and attribution, including the development-only GPL-3.0 linter.

Directories

Path Synopsis
internal
app
pkg/services/pr0xteus
Package pr0xteus implements logical pools of cell-backed WireGuard tunnels with on-demand spawn, idle reap, and an authenticated HTTP control API.
Package pr0xteus implements logical pools of cell-backed WireGuard tunnels with on-demand spawn, idle reap, and an authenticated HTTP control API.
pkg
client
Package client is the HTTP client library consumers use to send outbound requests through pr0xteus.
Package client is the HTTP client library consumers use to send outbound requests through pr0xteus.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL