A macOS wrapper around your Xray (VLESS + Reality) client that can force all of your Mac's traffic through it, or just set it as the system proxy — managed with simple commands, with automatic rollback if anything goes wrong
sudo ./install.shThis downloads xray + tun2socks and symlinks xraywrap onto your PATH
(/usr/local/bin/xraywrap). From then on, run it as a plain command from
anywhere:
xraywrap setup 'vless://uuid@host:port?...&security=reality&...#label'
# writes etc/config.env from a share link (the kind
# x-ui/3x-ui etc. generate for a client) - quote it!
xraywrap start # tun mode: whole machine, on by default
xraywrap start socks # lighter alternative: system SOCKS proxy only
xraywrap status
xraywrap stop
xraywrap doctor
xraywrap panic # hard reset if something's stuck
sudo xraywrap enable # start tun mode automatically at boot (launchd)
sudo xraywrap disable # stop doing that
(start/stop/restart/enable/disable/panic/uninstall need sudo
since they touch routes / system network settings — xraywrap re-checks
this itself and tells you if you forgot.)
Config lives in etc/config.env (gitignored — it holds your VLESS UUID and
Reality keys; install.sh creates it from etc/config.env.example on first
run if it's missing) — edit it, then sudo xraywrap restart.
Didn't run the installer? Everything above also works as
sudo /Users/budd/dev/xraywrap/bin/xraywrap <command>.
xraywrap setup '<vless-uri>' parses a vless:// share link and writes
etc/config.env for you — no root, just a file write. It pulls out the
UUID, host, port, flow, and the sni/fp/pbk/sid Reality params, and
refuses to proceed if the link isn't security=reality + type=tcp (the
only combination the generated xray-client.json template supports —
plain TLS, ws, grpc, etc. links will get a clear error instead of a
silently-broken config). Re-running it preserves your existing
SOCKS_PORT/TUN_DNS_SERVERS/NETWORK_SERVICE/STARTUP_GRACE_SECONDS
settings and backs up the previous config.env (timestamped, alongside
it) before overwriting.
Quote the URI — it contains &, which your shell will otherwise treat
as "run this in the background" and truncate the string.
xrayruns unprivileged, listening as a SOCKS5 server on127.0.0.1only — same VLESS+Reality client config validated earlier.tun2socksopens a virtualutunNinterface and feeds everything it receives on that interface to the local SOCKS5 proxy.- We assign the tun interface
198.18.0.1and add routes for the bulk of IPv4 space (the exact partition tun2socks' own maintainers document for macOS) pointing at it. Your existing LAN routes (e.g.192.168.1.0/24) are more specific and keep winning on longest-prefix-match, so local devices/printers/etc. stay reachable normally. - DNS (optional,
TUN_DNS_SERVERSin config) gets pointed at public resolvers so lookups are tunneled too, not leaked to your ISP.
Xray's own outbound connection to your VPS (v2ray.budd.codes:8443) is a
normal process socket subject to the same routing table as everything else.
Once the wide tun routes are in place, that connection would get recaptured
and sent back into the tunnel it's trying to establish — infinite loop.
Before installing the wide routes, xraywrap resolves your server's IP and adds an explicit host route for it via your original gateway/interface. That's more specific than any of the wide tun routes, so Xray's own traffic to the VPS always goes out directly, never through the tunnel.
xraywrap doctor checks this route is actually present and flags it as a
loop risk if it's missing.
startprints exactly what it's about to change and asks for confirmation (skip with--yes).- Right after coming up, it runs a live connectivity check. If that fails, it automatically rolls back every change it made (routes, DNS, processes) before exiting — you should never be left stranded.
- The state file (
var/run/state.env) records exactly what was changed, sostopreverses precisely that, in reverse order. panicignores the state file and force-restores networking anyway: kills known processes, removes the known route partition, resets DNS and the system SOCKS proxy on every network service. Use it ifstopwon't work or you're not sure what state things are in.- Nothing here uses
pf/firewall rules or touches your default route directly — only additive, individually-reversible route entries.
tun |
socks |
|
|---|---|---|
| Forces | every process, all protocols | only proxy-aware apps |
| Needs root | yes (routes, utun, DNS) | yes (networksetup) |
| Loop risk | mitigated via bypass route | none (no routing changes) |
| Good for | "force the whole PC" | quick/low-risk testing |
sudo xraywrap enable installs a LaunchDaemon
(/Library/LaunchDaemons/com.xraywrap.tunnel.plist) that runs
xraywrap start tun --yes once at boot, as root, with KeepAlive off (it's
a one-shot trigger — xray/tun2socks daemonize themselves; launchd isn't
supervising them long-term). It's opt-in: install.sh never enables it for
you. sudo xraywrap disable removes the job (the running tunnel itself, if
any, is untouched — xraywrap stop that separately). Logs from the boot run
land in var/log/launchd.log.
- macOS (uses
utun+ BSDroute; won't work on Linux/Windows as-is). - No Homebrew dependency —
installfetches both binaries straight from their GitHub releases for your CPU architecture.
bin/xraywrap the CLI
etc/config.env your server + behavior config
vendor/ downloaded xray + tun2socks binaries
var/run/ pidfiles + state.env (what's currently applied)
var/log/ xray.log, tun2socks.log