背景 将 Mac mini 配置为 OpenClaw 节点,通过 Tailscale 连接到远程 Gateway,实现:
开机自动连接 Gateway
Gateway 直接控制节点执行命令
Codex CLI 远程使用
最终架构:
1 2 3 4 5 6 7 8 9 ┌─────────────────┐ ┌─────────────────┐ │ Mac mini │ │ Gateway 服务器 │ │ (节点) │ │ (云端网关) │ ├─────────────────┤ ├─────────────────┤ │ Tailscale │◄──── Tailscale ───►│ Tailscale │ │ Trojan 客户端 │ │ Trojan 服务端 │ │ OpenClaw Node │◄──── WebSocket ───►│ socat 转发 │ │ Codex CLI │ │ OpenClaw Gateway│ └─────────────────┘ └─────────────────┘
踩坑清单
#
坑点
问题
解决方案
1
Tailscale 未安装 GUI
tailscale up 报错
用 brew services start tailscale
2
OpenClaw Node 路径错误
找不到模块
用 openclaw node run 而非直接调用
3
WebSocket 协议不匹配
需要审批
添加 OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1
4
Gateway 绑定 loopback
外部无法连接
用 socat 转发端口
5
节点首次连接需审批
连接失败
openclaw devices approve
6
Codex OAuth 地区限制
403 Forbidden
本地浏览器完成授权,绕过代理
7
OpenAI 封锁代理 IP
SOCKS5 也被拒
本地网络直接授权
8
nodes run –raw 需审批
shell 包装器限制
不用 --raw,直接传命令
9
–cwd 路径格式
审批失败
使用完整绝对路径
10
LaunchAgent 权限
Node.js 找不到
设置 EnvironmentVariables PATH
完整配置步骤 第一阶段:服务器端配置 1. Gateway 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 apt install socat socat TCP-LISTEN:18790,reuseaddr,fork TCP:127.0.0.1:18789 &cat > /etc/systemd/system/openclaw-socat.service << 'EOF' [Unit] Description=OpenClaw Gateway socat forwarder After=network.target [Service] Type=simple ExecStart=/usr/bin/socat TCP-LISTEN:18790,reuseaddr,fork TCP:127.0.0.1:18789 Restart=always [Install] WantedBy=multi-user.target EOF systemctl enable openclaw-socat systemctl start openclaw-socat
2. Trojan 服务端配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apt install trojancat > /usr/src/trojan/server.conf << 'EOF' { "run_type" : "server" , "local_addr" : "0.0.0.0" , "local_port" : 443, "remote_addr" : "127.0.0.1" , "remote_port" : 80, "password" : ["YOUR_PASSWORD" ], "ssl" : { "cert" : "/path/to/fullchain.cer" , "key" : "/path/to/private.key" } } EOF
第二阶段:Mac mini 节点配置 1. Tailscale 安装与配置 1 2 3 4 5 6 7 8 9 10 11 12 13 brew install tailscalesudo brew services start tailscale tailscale up tailscale status
⚠️ 坑点 1: 如果没有安装 Tailscale GUI 应用,tailscale up 会报错 “failed to connect to local Tailscale service”。解决方法是用 brew services start tailscale 启动守护进程。
2. Trojan 客户端配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 mkdir -p ~/trojancd ~/trojan wget https://github.com/trojan-gfw/trojan/releases/download/v1.16.0/trojan-1.16.0-macos-x64.tar.xz tar xf trojan-1.16.0-macos-x64.tar.xzcat > ~/trojan/trojan/client.json << 'EOF' { "run_type" : "client" , "local_addr" : "127.0.0.1" , "local_port" : 1080, "remote_addr" : "YOUR_SERVER_IP" , "remote_port" : 443, "password" : ["YOUR_PASSWORD" ] } EOFcd ~/trojan/trojan ./trojan -c client.json & curl -x socks5://127.0.0.1:1080 https://ifconfig.me
3. HTTP 代理脚本 Codex OAuth 需要 HTTP 代理,但 Trojan 只提供 SOCKS5。创建转换脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 cat > /tmp/http_proxy.py << 'EOF' "" "Simple HTTP proxy that forwards to SOCKS5" "" import socket import struct import threading import sys SOCKS5_HOST = "127.0.0.1" SOCKS5_PORT = 1080 HTTP_PORT = 8118 def socks5_connect(target_host, target_port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(30) sock.connect((SOCKS5_HOST, SOCKS5_PORT)) sock.send(b"\x05\x01\x00" ) response = sock.recv(2) if response != b"\x05\x00" : raise Exception("SOCKS5 auth failed" ) request = b"\x05\x01\x00\x01" request += socket.inet_aton(socket.gethostbyname(target_host)) request += struct.pack(">H" , target_port) sock.send(request) response = sock.recv(10) if response[1] != 0: raise Exception(f"SOCKS5 connect failed: {response[1]}" ) return sock def handle_client(client_sock): try: request = client_sock.recv(8192) if not request: return lines = request.decode("utf-8" , errors="ignore" ).split ("\r\n" ) if not lines: return first_line = lines[0].split () if len(first_line) < 3: return method = first_line[0] if method == "CONNECT" : host_port = first_line[1].split (":" ) host = host_port[0] port = int(host_port[1]) if len(host_port) > 1 else 443 remote_sock = socks5_connect(host, port) client_sock.send(b"HTTP/1.1 200 Connection Established\r\n\r\n" ) while True: data = client_sock.recv(8192) if not data: break remote_sock.send(data) response = remote_sock.recv(8192) if response: client_sock.send(response) remote_sock.close() except Exception as e: print (f"Error: {e}" , file=sys.stderr) finally: client_sock.close() def main(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("0.0 .0.0 ", HTTP_PORT)) server.listen(100) print (f"HTTP proxy running on port {HTTP_PORT}" ) sys.stdout.flush() while True: client_sock, addr = server.accept() thread = threading.Thread(target=handle_client, args=(client_sock,)) thread.daemon = True thread.start()if __name__ == "__main__" : main() EOFchmod +x /tmp/http_proxy.py python3 /tmp/http_proxy.py &
4. OpenClaw Node 安装 1 2 3 4 5 6 npm install -g openclaw openclaw --version
5. LaunchAgent 配置(开机自启) Trojan LaunchAgent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" > <plist version ="1.0" > <dict > <key > Label</key > <string > com.trojan.client</string > <key > ProgramArguments</key > <array > <string > /Users/mars/trojan/trojan/trojan</string > <string > -c</string > <string > /Users/mars/trojan/trojan/client.json</string > </array > <key > RunAtLoad</key > <true /> <key > KeepAlive</key > <true /> <key > StandardOutPath</key > <string > /tmp/trojan.log</string > <key > StandardErrorPath</key > <string > /tmp/trojan.log</string > </dict > </plist >
OpenClaw Node LaunchAgent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" > <plist version ="1.0" > <dict > <key > Label</key > <string > com.openclaw.node</string > <key > ProgramArguments</key > <array > <string > /usr/local/bin/node</string > <string > /usr/local/lib/node_modules/openclaw/dist/index.js</string > <string > node</string > <string > run</string > </array > <key > EnvironmentVariables</key > <dict > <key > PATH</key > <string > /usr/local/bin:/usr/bin:/bin</string > <key > OPENCLAW_GATEWAY_URL</key > <string > ws://GATEWAY_TAILSCALE_IP:18790</string > <key > OPENCLAW_GATEWAY_TOKEN</key > <string > YOUR_GATEWAY_TOKEN</string > <key > OPENCLAW_ALLOW_INSECURE_PRIVATE_WS</key > <string > 1</string > </dict > <key > RunAtLoad</key > <true /> <key > KeepAlive</key > <true /> <key > StandardOutPath</key > <string > /tmp/openclaw-node.log</string > <key > StandardErrorPath</key > <string > /tmp/openclaw-node.log</string > </dict > </plist >
⚠️ 坑点 3: OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1 必须设置,否则 WebSocket 连接需要审批。
⚠️ 坑点 10: 必须设置 PATH 环境变量,否则 Node.js 找不到系统命令。
1 2 3 4 5 6 7 mv com.trojan.client.plist ~/Library/LaunchAgents/mv com.openclaw.node.plist ~/Library/LaunchAgents/ launchctl load ~/Library/LaunchAgents/com.trojan.client.plist launchctl load ~/Library/LaunchAgents/com.openclaw.node.plist
6. 节点配对 1 2 3 4 5 openclaw devices pending openclaw devices approve <node-id>
第三阶段:Codex CLI 配置 1. 安装 Codex 1 2 3 4 5 npm install -g @openai/codex codex --version
2. OAuth 认证 ⚠️ 坑点 6 & 7: 如果代理 IP 被 OpenAI 封锁,OAuth 会失败。
解决方案:在本地网络完成授权(不需要代理)
会输出类似:
1 2 3 Starting local login server on http: Open this URL to authenticate: https:
在 Mac mini 的浏览器中打开这个 URL (不需要代理),完成授权后会自动重定向到 localhost:1455,Codex 会自动完成认证。
第四阶段:Gateway 控制节点 1. 验证节点连接 1 2 3 4 5 openclaw nodes status
2. 执行命令 ⚠️ 坑点 8 & 9:
1 2 3 4 5 6 7 8 9 10 11 12 13 openclaw nodes run --node Mac-mini --raw "hostname" openclaw nodes run --node Mac-mini -- hostname openclaw nodes run --node Mac-mini --cwd /Users/mars/hewu/01_code -- ls -la openclaw nodes run --node Mac-mini --env PATH=/usr/local/bin:$PATH -- codex --version
完整配置清单 服务器端
组件
状态
说明
Tailscale
✅
连接 Mac mini
socat
✅
转发 18790 → 18789
Trojan 服务端
✅
提供代理
OpenClaw Gateway
✅
核心服务
Mac mini 节点
组件
状态
说明
Tailscale
✅
brew services 自启
Trojan 客户端
✅
LaunchAgent 自启
HTTP 代理
✅
Python 脚本
OpenClaw Node
✅
LaunchAgent 自启
Codex CLI
✅
OAuth 认证完成
常见问题排查 1. 节点无法连接 1 2 3 4 5 6 7 8 tailscale status lsof -i :18790tail -50 /tmp/openclaw-node.log
2. Codex 认证失败 1 2 3 4 5 curl -x socks5://127.0.0.1:1080 https://ifconfig.me
3. 命令执行失败
参考资料
OpenClaw 官方文档:https://docs.openclaw.ai
Tailscale 文档:https://tailscale.com/kb
Trojan 文档:https://trojan-gfw.github.io/trojan/
总结 关键配置点:
Gateway 绑定 loopback,需要 socat 转发
OpenClaw Node 需要设置 OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1
LaunchAgent 必须设置 PATH 环境变量
Codex OAuth 最好在本地网络完成
openclaw nodes run 不要用 --raw
Mac mini 重启后会自动:
连接 Tailscale
启动 Trojan 代理
连接 OpenClaw Gateway
可以直接通过 Gateway 控制节点
配置时间:2026-03-15 OpenClaw 版本:2026.3.7 Node.js 版本:v22.22.1