The release step created releases with no body. Read the tag's annotation into RELEASE_NOTES.md and pass it via body_path so each release carries purpose-written notes. Fetch full history so the annotated tag object is available to the build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
weircon-random-proxy
A small HTTP fetch service that forwards client requests through one of N upstream WireGuard tunnels and returns the response. Designed to run as an unprivileged LXC on Proxmox, with each tunnel isolated in its own network namespace.
Use it whenever you need an HTTP-facing API that returns results from rotating egress IPs — testing geo-restricted endpoints, distributing outbound traffic across providers, hiding a backend's real source IP, building rate-limit-resilient clients, and so on. It is intentionally generic and doesn't care what consumer calls it.
Architecture
Internet
│
▼
┌──────────────────────────────────┐
│ Reverse proxy (e.g. NPM, nginx) │
│ • TLS termination │
│ • Validates X-WEIRCON-RANDOM-IP │
│ (API key) header │
│ • Strips header before forward │
└──────────────┬───────────────────┘
│ http://<lxc-ip>:8080
▼
┌──────────────────────────────────────────────────────┐
│ LXC: weircon-random-proxy (unprivileged + TUN) │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ fetch-service (Go, net/http + ReverseProxy) │ │
│ │ • Reads target URL from: │ │
│ │ - query: ?url=https://example.com │ │
│ │ - header: X-WEIRCON-RANDOM-IP-REDIRECT │ │
│ │ • Reads tunnel selection from: │ │
│ │ - header: X-WEIRCON-PROXY-ID (0..N-1) │ │
│ │ - default: random │ │
│ │ • SOCKS5 to 10.99.0.{10+id}:1080 │ │
│ │ • Streams response chunked │ │
│ │ • Optional /ui — built-in API tester │ │
│ └─────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌──────┴──────┐ br-weircon (10.99.0.1/24)│
│ │ bridge │ │
│ └──┬───┬───┬──┘ │
│ veth ────┘ │ └──── veth │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ netns: │ │ netns: │ │ netns: │ …× N │
│ │ proxy0 │ │ proxy1 │ │ proxyN-1│ │
│ │ veth: │ │ veth: │ │ veth: │ │
│ │10.99.0.10│ │10.99.0.11│ │10.99.0.X│ │
│ │ + wg0 │ │ + wg0 │ │ + wg0 │ │
│ │ + micro │ │ + micro │ │ + micro │ │
│ │ socks │ │ socks │ │ socks │ │
│ │ :1080 │ │ :1080 │ │ :1080 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ ▼ ▼ ▼ │
│ WireGuard egress (any provider) │
└──────────────────────────────────────────────────────┘
Tunnel count is configurable. Default deploy uses 10 tunnels; the WireGuard configs themselves can come from any provider that gives you a standard [Interface] / [Peer] config (ProtonVPN, Mullvad, AzireVPN, self-hosted, …). Drop the configs into /etc/weircon-random-proxy/wg/proxy<N>.conf and the rest is identical.
Client API
Fetch a URL through a random tunnel:
curl -H "X-WEIRCON-RANDOM-IP: $API_KEY" \
-H "X-WEIRCON-RANDOM-IP-REDIRECT: https://api.ipify.org" \
https://random-proxy.example.com/
Or with the URL as a query parameter:
curl -H "X-WEIRCON-RANDOM-IP: $API_KEY" \
"https://random-proxy.example.com/?url=https://api.ipify.org"
Pin a specific tunnel:
curl -H "X-WEIRCON-RANDOM-IP: $API_KEY" \
-H "X-WEIRCON-PROXY-ID: 3" \
"https://random-proxy.example.com/?url=https://api.ipify.org"
Parameters
| Source | Name | Required | Description |
|---|---|---|---|
| Reverse-proxy hdr | X-WEIRCON-RANDOM-IP |
yes* | API key. Validated and stripped by the reverse proxy in front. |
| Header or query | X-WEIRCON-RANDOM-IP-REDIRECT / ?url= |
yes | Target URL. Header takes precedence over the query parameter. |
| Header | X-WEIRCON-PROXY-ID |
no | 0..N-1. Omitted: pick at random. |
| Header | X-WEIRCON-FORWARD-METHOD |
no | HTTP method sent to the target. Default: same as incoming. |
| Body | passthrough | no | Forwarded as-is on POST/PUT/PATCH. |
*Only when a reverse proxy is in front. Direct LAN access doesn't require it.
Response
Status, headers, and body of the upstream response are returned directly. The service adds X-WEIRCON-EGRESS-PROXY: <id> so the caller can see which tunnel was used.
Built-in API tester (/ui)
The fetch service ships with an embedded HTML page at /ui that documents the API and lets you exercise every endpoint interactively:
- Enter API key (stored in browser localStorage).
- "Try it out" form for
/with all parameters as inputs. - Live cURL equivalent.
- Tunnel health panel — sends a probe to each tunnel and shows egress IP + latency. A healthy stack returns N distinct IPs.
The UI is enabled by default. Set WEIRCON_UI_ENABLED=false in fetch.env to disable it (the rest of the service is unaffected). The UI is also gated by your reverse proxy's auth check by default — see npm/advanced.conf for an optional exception that allows the UI page itself to load without an API key.
Components
| Path | Purpose |
|---|---|
service/main.go |
Go fetch service (static binary, runs in the LXC's main netns). |
service/ui.html |
Embedded API tester page served at /ui. |
service/Makefile |
make build → ~6MB static binary; make install to /usr/local/bin. |
lxc/setup-host.md |
Proxmox host: pct create, TUN passthrough, push instructions. |
lxc/setup-container.sh |
Runs inside the LXC: apt deps, microsocks, helper scripts, systemd units. |
lxc/netns-up.sh |
Brings one tunnel namespace up (proxy<N> with wg0 + veth + bridge). |
lxc/netns-down.sh |
Tears it back down (invoked by systemd ExecStopPost). |
lxc/systemd/weircon-proxies.target |
Grouping target for all tunnels. |
lxc/systemd/weircon-proxy@.service |
Templated unit. Enable with weircon-proxy@{0..N-1}.service. |
lxc/systemd/weircon-fetch.service |
The fetch service itself. Hardened (DynamicUser, no capabilities). |
lxc/fetch.env.example |
Default env. Copy to /etc/weircon-random-proxy/fetch.env. |
npm/advanced.conf |
Reverse-proxy snippet: header check, strip, forward. |
wg-configs/ |
Local placeholder. Actual .conf files live on the LXC, not in git. |
Service env vars
| Var | Default | Description |
|---|---|---|
WEIRCON_LISTEN |
:8080 |
host:port to bind. |
WEIRCON_PROXY_ADDRS |
(computed) | Comma-separated host:port list of upstream SOCKS5 endpoints. |
WEIRCON_PROXY_HOST |
127.0.0.1 |
Fallback host when WEIRCON_PROXY_ADDRS is unset. |
WEIRCON_PROXY_BASE_PORT |
25400 |
First port in the fallback range. |
WEIRCON_PROXY_COUNT |
10 |
Number of fallback endpoints. |
WEIRCON_REQUEST_TIMEOUT_SEC |
30 |
Per-upstream-fetch ceiling. |
WEIRCON_UI_ENABLED |
true |
Toggle the embedded /ui page. |
If WEIRCON_PROXY_ADDRS is set it wins; otherwise the service computes WEIRCON_PROXY_HOST:WEIRCON_PROXY_BASE_PORT + i for i in [0, WEIRCON_PROXY_COUNT).
Service internals
net/http/httputil.ReverseProxystreams response bodies chunked, without buffering.- One
*http.Transportper tunnel — connection pools stay separate per egress. golang.org/x/net/proxy.SOCKS5ContextDialer on each Transport.- Static
CGO_ENABLED=0binary, ~6 MB, no runtime dependencies in the LXC.
Build:
cd service && make build # → ./weircon-random-proxy
Deploy
Dev machine:
cd service && make build # → ./weircon-random-proxy
Proxmox host (see lxc/setup-host.md for full detail):
pct createwith--unprivileged 1 --features nesting=1,keyctl=1(Debian 12).- Add TUN passthrough lines to
/etc/pve/lxc/<ctid>.conf. pct pushbinary, scripts, units, and your WireGuard configs into the container.pct enter <ctid> && bash /root/setup-container.sh.systemctl enable --now weircon-proxies.target weircon-proxy@{0..9}.service weircon-fetch.service.- Point your reverse proxy at the LXC on port 8080; paste
npm/advanced.confinto its advanced config with your API key. - Open
https://random-proxy.example.com/uito verify.
Releases
Tagged builds (v*) trigger .gitea/workflows/release.yml which produces a tarball with the binary + LXC scripts + reverse-proxy config bundled for one-shot deployment.