🛰️ Claude Code / Codex skill:搭 VLESS-XHTTP-REALITY 中转 + WireGuard 落地多出口代理链(playbook+脚本,非一键),出口 IP 为落地机 · A CC/Codex skill that builds a VLESS-XHTTP-REALITY relay backhauled over WireGuard to landing VPS (SNAT multi-exit), so the exit IP is the landing's
SaferSkills independently audited reality-wireguard-relay (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Build the topology:
Client ──VLESS/XHTTP/REALITY──▶ Relay (中转, reachable entry)
│
└──WireGuard──▶ Landing (落地) ──NAT──▶ Internet
exit IP = landing's public IP(s)The relay terminates REALITY and forwards proxied traffic into a WireGuard tunnel. The landing does the real egress. Keep the landing as a pure kernel WireGuard forward + NAT with no userspace proxy — that is the lowest-overhead design and should be the default. Only run xray/socks on the landing if the user has a concrete reason.
This skill assumes SSH root access to both ends. It drives the boxes over SSH and is non-interactive. Work one phase at a time and verify each phase before moving on — silent misconfig here looks like "线路不通" later and is expensive to chase.
Four decisions shape everything. Resolve them up front; don't guess:
GFW-blocked while its IPv6 still works (or vice-versa). The client link must point at the reachable family, and the client must have that same IP family. Confirm this — it is the #1 real-world cause of "connects on the server but not for me".
the landing has no IPv6, the tunnel is IPv4-only → the relay's outbound must force IPv4 (see pitfalls), or v6-bound proxy traffic leaks straight out the relay.
landing → per-source SNAT + one relay line per exit IP. Several distinct landings under one entry (e.g. socks5 exit + WG-landing-A + WG-landing-B, user-selectable) → one inbound, several UUIDs, route by `user` — not a port/IP per line, which tends to be unreachable on the client's path. See architecture.md "Fanning one entry".
out several IPs that all land on one box. Building N WireGuard tunnels to one box is pointless and conflicts on ports. Verify machine identity first (Phase 1) — this check has saved entire rebuilds.
references/architecture.md has the full decision tree, the single-exit vs multi-exit variants, and why each piece is shaped the way it is. Read it before building.
Track these as todos and do them in order.
Push your key to every box so the rest is non-interactive. If only a password is known, use sshpass -p '<pw>' ssh-copy-id -o StrictHostKeyChecking=accept-new -i <pubkey> <host>. Clear any stale host key first (ssh-keygen -R <ip>) — a reused/ reinstalled IP triggers REMOTE HOST IDENTIFICATION CHANGED and blocks login.
On each claimed landing, collect identity + reachability:
cat /etc/machine-id ; curl -s4 ifconfig.co ; curl -s6 ifconfig.co ; ip -br addr showmachine-id + same egress IP across "several servers" ⇒ **one box, multipleIPs**. Switch to the multi-exit-on-one-box variant (single tunnel + per-source SNAT).
(test with a real bind(), see Phase 5 / scripts/verify_egress.py) before promising distinct exit IPs.
Install on both ends (apt-get install -y wireguard wireguard-tools iptables), generate a keypair on each, exchange public keys. Use the config templates in assets/templates/ (landing-wg0.conf, relay-wg0.conf). Pick a tunnel subnet (default 10.66.66.0/24, landing .1, relay .2, extra relay source IPs .3+). The templates pin `MTU = 1280` + an MSS clamp on both ends — keep it. The path carrying the WG UDP often can't pass a 1420-byte inner packet even when both eth0s look like 1500, which silently black-holes large TLS handshakes later (pitfall #9). 1280 is the safe floor; don't "optimize" it back up without proving the inner PMTU. systemctl enable --now wg-quick@wg0. Verify: wg show shows a recent handshake and the relay can ping <landing tunnel IP>.
net.ipv4.ip_forward=1 in /etc/sysctl.d/.MASQUERADE for the tunnel subnet out the WAN iface.SNAT --to-source <public IP> rule **per tunnel sourceIP** (-s 10.66.66.3 → IP_a, -s 10.66.66.4 → IP_b, …). Put the rules in the wg0.conf PostUp/PostDown so they track the interface.
The relay must send only proxied traffic through WireGuard, never its own SSH/system traffic. Use Table = off on the relay's wg0 and a dedicated routing table: ip route add default dev wg0 table 51820 plus a rule selecting proxied traffic (ip rule add from <tunnel-subnet> table 51820, and/or an fwmark rule). See the relay template and references/architecture.md. Verify: the relay's own default egress is still its own public IP (so SSH stays up), while traffic bound to a tunnel source IP exits via the landing.
Install xray (bash -c "$(curl -sL https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install). Generate xray uuid, xray x25519 (REALITY keypair), openssl rand -hex 8 (shortId), openssl rand -hex 4 (path) — one set per line. Build the config with scripts/gen_xray_relay_config.py (one inbound per exit, each freedom outbound with domainStrategy: ForceIPv4 + sendThrough its tunnel source IP, routing inboundTag→ outboundTag). xray run -test -config ... then restart. Verify: all ports listen on :: (dual-stack).
Run in this order; each isolates a layer:
scripts/verify_egress.py on the relay): real-bind() fromeach tunnel source IP → expect the matching landing exit IP. If wrong, also check iptables -t nat -L POSTROUTING -nv counters on the landing.
::1:<port>with each line's REALITY params → exit IP must match.
relay entry, then run a real client → exit IP. Proves clients can actually reach it.
passes even through an MTU-black-holed tunnel. Also fetch a big-cert site (google/github/youtube) end to end — if those hang while api.ipify.org works, it's pitfall #9. Never sign off a WG line on api.ipify.org alone.
references/verification.md has copy-paste test snippets (Gates A–F).
scripts/gen_client_links.py emits the vless://… links and QR PNGs into all-links.txt. The QR avoids copy-paste corruption (IPv6 brackets, #, & get mangled in chat apps), which matters when handing a config to a different device. Remind the user the client needs the same IP family as the relay entry.
When the user wants something to hand off rather than raw text — a printable sheet, a page to open on a phone, a spreadsheet inventory — run scripts/export_links.py over the all-links.txt from Phase 7:
python3 scripts/export_links.py --input client-links/all-links.txt --out ./exportsIt reads only the vless:// lines, so the same command also works on any link blob (e.g. a file pasted from elsewhere). Output (default = all four formats): exports/qr/<name>.png (one QR each), exports/lines.html (self-contained gallery, QR inlined), exports/lines.pdf (printable, one line per row), exports/lines.xlsx (table + embedded QR). Limit with --format pdf,xlsx. Needs segno reportlab openpyxl pillow (pip install --user …); each format degrades independently if its lib is missing. See the script's --help for details.
These are real failures seen in the field; each wasted significant time. Skim the file before building, and revisit it the moment a verification gate fails:
relay's own IPv6 instead of going through the v4 tunnel.
restart runs the new PostDown, never removing the old PostUp rule) → everything exits the primary IP, SNAT counters stuck at 0.
used the primary IP in testing. Verify per-source egress with a real bind() (python socket, which is what xray sendThrough does), never with curl --interface.
0.0.0.0/0 with wg-quick's default routing on the relay hijacks thehost default route** and kills your SSH. Always Table = off + your own table.
wg show shows 0 transfer. Open it in the cloud panel.
requests pass. Pin MTU = 1280 + MSS clamp on both wg0 ends (baked into the templates; pitfall #9). Verify with Gate F, not api.ipify.org.
scripts/verify_egress.py — run on the relay; real-bind() per-source egress test.scripts/gen_xray_relay_config.py — generate the multi-line REALITY xray config.scripts/gen_client_links.py — emit vless:// links + QR PNGs (needs segno).scripts/export_links.py — turn any vless:// blob into PDF / HTML / PNG / Excelexports (needs segno reportlab openpyxl pillow).
Run each with --help. They take a small JSON "lines" spec (see references/lines-spec.md).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.