本文面向两类读者:
- 已经有一套 Remote Hosts Code Gateway,只想新增一台电脑或服务器。
- 第一次部署,准备自己搭建 Gateway、接入 ChatGPT,并持续加入多台设备。
这里讨论的是 remote-hosts-code 这一条链路:ChatGPT 通过公网 HTTPS/OAuth/MCP 访问 Gateway,
每台设备运行本机 Agent 并主动向 Gateway 建立出站连接。它和仓库中的 SSH/operator control
plane 是两套独立运行状态,不要把 remote-hosts-code agent、remote-hosts worker-daemon 和
scripts/remote-hosts-systemd-service 混为一谈。
当前仓库发布版本为 0.9.0。0.9.0 的正式 release pipeline 已验证 macOS ARM64 和 Linux x86_64-musl
remote-hosts-code产物。Linux ARM64/aarch64 暂未作为正式二进制产物进入该 release matrix,需要在目标机或可信构建机上从固定源码构建。
最小可用拓扑包含三个角色:
flowchart LR
C[ChatGPT] -->|HTTPS 443 + OAuth + MCP| P[Public hostname / Cloudflare]
P -->|HTTPS origin| R[Reverse proxy / Caddy]
R -->|loopback HTTP| G[remote-hosts-code Gateway]
G <-->|outbound HTTPS poll/results| A1[Device Agent A]
G <-->|outbound HTTPS poll/results| A2[Device Agent B]
A1 --> W1[Workspace / Files / Terminal / PTY]
A2 --> W2[Workspace / Files / Terminal / PTY]
部署顺序不要反:
- 先有 Gateway。 Gateway 保存允许接入的设备身份、OAuth 状态和 MCP 路由。
- 再让 Gateway 对 ChatGPT 公网可达。 外部必须看到一个稳定的 HTTPS origin,例如
https://mcp.example.com。 - 再注册并启动每台设备上的 Agent。 Agent 主动连接 Gateway,因此设备本身通常不需要开放 任何公网入站端口。
- 最后在 ChatGPT 中创建 Developer-mode MCP app 并完成 OAuth。
如果只是在现有 Gateway 上加新机器,从第 6 节开始即可。
对大多数个人/小团队部署,推荐把“公网入口”和“设备执行面”彻底分开:Gateway 是唯一需要被 ChatGPT 从公网访问的核心服务;所有工作站/服务器 Agent 都只主动出站连接 Gateway。
flowchart TB
GPT[ChatGPT / OpenAI MCP client]
EDGE[Public HTTPS Edge\nCloudflare or equivalent]
RP[Reverse Proxy\nCaddy / nginx / Traefik]
GW[remote-hosts-code Gateway\n127.0.0.1:18787]
STATE[(Gateway state)]
A[Device Agent A]
B[Device Agent B]
C[Device Agent C]
GPT -->|HTTPS 443\nOAuth + MCP| EDGE
EDGE -->|HTTPS origin| RP
RP -->|loopback HTTP| GW
GW --> STATE
A -->|outbound HTTPS poll/result| EDGE
B -->|outbound HTTPS poll/result| EDGE
C -->|outbound HTTPS poll/result| EDGE
这套拓扑有几个重要性质:
- ChatGPT 只需要知道一个稳定的公网 HTTPS origin。
- Gateway 本体保持 loopback,不直接暴露公网监听。
- Agent 不需要公网 IP,也不需要给每台工作站开放 SSH/MCP 端口。
- Gateway state 与设备本地 state 各自持久化,设备离线不会导致 Workspace 隐式迁移到另一台机器。
- TLS、WAF、DNS、Tunnel/Origin Rule 都留在公网入口层,不侵入 Agent 协议。
根据 Gateway 所在网络,再从下面三种入口模式选择一个:
| 场景 | 推荐入口 | 典型路径 |
|---|---|---|
| 家庭/NAS/办公室,有可用公网入站或端口映射 | Cloudflare Proxy + Origin Rule | 443 -> Cloudflare -> origin 8443 -> reverse proxy -> 18787 |
| CGNAT、无公网 IPv4、不能开放入站 | Cloudflare Tunnel | 443 -> Cloudflare -> Tunnel -> reverse proxy -> 18787 |
| 公网 VPS/云主机,本身即可稳定对外 | 直接 443 reverse proxy,可选 Cloudflare | 443 -> Caddy/nginx -> 18787 |
8443 不是协议要求,只是当标准 443 已被 NAS/其他服务占用时很实用的 origin 端口选择。真正的产品约束是:ChatGPT 看到稳定 HTTPS origin,Gateway 自身仍只监听受保护的本地地址。
| 组件 | 放在哪里 | 典型监听/连接 | 是否需要公网入站 |
|---|---|---|---|
remote-hosts-code gateway |
NAS/VPS/长期在线 Linux 服务器 | 默认 127.0.0.1:18787 |
否,必须放在反向代理后 |
| Caddy / 其他反向代理 | Gateway 所在服务器或同网段入口 | HTTPS origin,例如 443/8443 | 视公网方案而定 |
| Cloudflare | 公网边缘 | 对 ChatGPT 暴露标准 HTTPS 443 | 是公网入口 |
remote-hosts-code agent |
每台 Mac/Linux/Windows 设备 | 主动出站 HTTPS 到 Gateway | 不需要 |
| ChatGPT | OpenAI 托管侧 | 访问 https://<hostname>/mcp |
需要能访问你的公网域名 |
Gateway 源程序强制绑定 loopback。不要为了“省一层代理”把 Gateway 直接监听到 0.0.0.0。
公网 TLS、Host 归一化和安全响应头应该留在 Caddy/Cloudflare 这一层。
建议满足:
- 24x7 或至少在使用 ChatGPT 时长期在线。
- 能运行 Linux service/systemd,NAS 也可以。
- Gateway 状态目录持久化,不能放临时目录。
- 有一个你控制的域名,例如
example.com。 - 如果使用 Cloudflare Origin Rule:源站需要有公网可达路径,或者路由器能把公网端口转发到源站。
- 如果没有公网 IPv4、位于 CGNAT 后面或不想开放入站端口:改用 Cloudflare Tunnel。
每一台设备都需要:
- 能主动通过 HTTPS 访问 Gateway 公网域名。
- 一个专门用于运行 Agent 的本地用户,或者明确接受 Agent 使用当前用户权限。
- 至少一个授权 root,例如
~/Workspace。 - 私有
agent.json和独立 state directory。 - 不能复制另一台设备的
agent.json。 每台设备必须单独 enroll,获得独立 UUID/token。
当前 ChatGPT Developer mode 支持把远程 MCP Server 添加为开发者 app,支持 streaming HTTP/SSE, 并支持 OAuth。Remote Hosts Code Gateway 使用 streaming HTTP + OAuth。按 OpenAI 当前文档,Developer mode 可在 Web 端用于 Pro、Plus、Business、Enterprise 和 Education 账户;产品资格和菜单位置以后可能变化。 OpenAI 官方文档:
- Developer mode: https://developers.openai.com/api/docs/guides/developer-mode
- Plugin/MCP OAuth: https://developers.openai.com/plugins/build/auth
ChatGPT 产品 UI 会变化,以产品当前显示的回调 URI 和认证配置为准,不要根据旧截图硬编码。
这是自部署最容易误解的一层。
ChatGPT 的 MCP 客户端运行在 OpenAI 侧,因此不能只让你自己的浏览器能访问 Gateway。公网域名必须
能从境外互联网稳定访问,OAuth discovery、登录、token exchange、/mcp、文件下载等路径都要走通。
Remote Hosts 当前生产拓扑使用 Cloudflare 作为公网前门。Cloudflare 有两种完全不同的用法:
适合:
- Gateway 所在网络有公网 IP / DDNS;
- 能把一个 HTTPS origin 端口从公网转到 Caddy;
- 希望外部始终使用标准
https://mcp.example.com,不在 URL 上暴露:8443。
推荐流量:
ChatGPT
-> https://mcp.example.com:443
-> Cloudflare proxy
-> Origin Rule: destination port = 8443
-> home/NAS public endpoint :8443
-> Caddy
-> http://127.0.0.1:18787
-> remote-hosts-code gateway
Cloudflare 官方 Origin Rules 支持覆盖 destination port;Cloudflare 默认代理也支持 HTTPS 8443。 当前官方文档:
- https://developers.cloudflare.com/rules/origin-rules/
- https://developers.cloudflare.com/rules/origin-rules/examples/change-port/
- https://developers.cloudflare.com/fundamentals/reference/network-ports/
Cloudflare 侧建议:
- 创建
mcp.example.com的 A/AAAA/CNAME,开启橙云 Proxy。 - 创建 Origin Rule,匹配
http.host eq "mcp.example.com"。 - Destination Port 改写为
8443。 - SSL/TLS 使用端到端 HTTPS,生产环境优先 Full (strict)。
- 源站 8443 必须最终到达 Caddy 的 TLS listener。可以是路由器/NAT 的
8443 -> 443,也可以是 Caddy 直接监听 8443,取决于你的基础设施;不要假设这两者是同一件事。
Cloudflare Origin Rule 不是穿透工具。如果源站没有公网入站路径、在 CGNAT 后,单纯改端口没有用。
适合:
- 中国大陆家庭宽带、办公室宽带或云环境没有稳定公网 IPv4;
- 位于 CGNAT 后;
- 不想给 Gateway/NAS 开公网入站端口。
cloudflared 从源站主动向 Cloudflare 建立出站连接,公网不需要暴露源站 IP。Cloudflare 官方说明
Tunnel 不需要公网 IP,也不需要开放入站端口:
需要注意:
cloudflared自身必须能稳定访问 Cloudflare;受限防火墙环境需要允许其出站连接,官方当前文档重点 提到 7844/TCP+UDP。- 对本项目,优先让 Tunnel 到达本地反向代理/Caddy,继续保留 Host 归一化、CSP、
Referrer-Policy等行为。 - 如果绕过 Caddy 直接把 Tunnel 指向
127.0.0.1:18787,必须显式复现正确的 HTTP Host 和安全头; Gateway 会校验公网 authority。没有做过这一层验证时不要把“Tunnel Healthy”误认为 OAuth/MCP 已经健康。 - Cloudflare Tunnel 只是网络入口,不替代 Remote Hosts 自己的 OAuth 和设备认证。
优先按约束选,不要按“哪个配置看起来更高级”选:
- 有稳定公网入站,且 443 已被其他站点占用: Cloudflare Proxy + Origin Rule 最自然,公网仍是 443,源站可走 8443。
- 没有公网入站或处于 CGNAT: 直接选择 Tunnel,不要再叠 DDNS + 端口转发。
- Gateway 本来就在公网 VPS: 最简单的是反向代理直接监听 443;是否再加 Cloudflare 取决于 DNS、WAF、隐藏源站和运维偏好。
- 企业网络有自己的公网 LB/WAF: 可以替代 Cloudflare,只要保留标准 HTTPS、正确 Host、OAuth metadata、长连接/streaming 行为和足够的超时。
- “设备能访问 Gateway”与“ChatGPT 能访问 Gateway”是两件事,必须分别测试。
- DDNS 只解决 IP 变化,不解决 CGNAT、运营商封端口、跨境链路质量和 TLS。
- Cloudflare 免费/普通全球网络不等于 Cloudflare 中国大陆专有网络;不要把它描述成“中国加速服务”。
- 如果 origin 在国内,至少从境外网络独立验证
/healthz、OAuth metadata 和/mcp可达性。 - 不要暴露 Gateway 的 loopback 端口,也不要把整个 NAS 管理面板一起代理到同一个公网 hostname。
下面这个案例来自真实部署经验,但已经移除了真实域名、设备身份、账号、路径和实时状态;它是架构案例,不是当前生产状态记录。
背景约束:
- Gateway 放在家庭/NAS 一侧,希望长期在线;
- 同一入口已经承载其他 HTTPS 服务,不希望把 Code Gateway 直接暴露在标准 origin 443;
- ChatGPT 从公网访问时仍希望只看到标准
https://mcp.example.com; - 两台以上工作站可能位于不同网络,它们都应主动出站,不开放各自的公网端口。
采用的结构:
flowchart LR
GPT[ChatGPT]
CF[Cloudflare\npublic :443]
DDNS[Public/DDNS origin\n:8443]
CADDY[Caddy\nTLS + Host normalization]
GW[Gateway\n127.0.0.1:18787]
A[Workstation-A Agent]
B[Workstation-B Agent]
GPT -->|https://mcp.example.com:443| CF
CF -->|Origin Rule\ndestination :8443| DDNS
DDNS --> CADDY
CADDY -->|reverse_proxy| GW
A -->|outbound HTTPS| CF
B -->|outbound HTTPS| CF
关键经验:
- 公网资源 URL 始终保持标准 443。
:8443只存在于 Cloudflare 到 origin 的网络段,不进入 OAuth issuer/resource URL。 - Host 必须统一。 Caddy 转发到 loopback Gateway 时保持/归一化为公网 hostname,否则 Gateway 的 authority 校验会把请求拒绝。
- Gateway 不直接接公网。 即使 origin 8443 可达,真正对外的是 Caddy/TLS 层,Gateway 仍在
127.0.0.1:18787。 - Agent 走与 ChatGPT 相同的公网 origin。 不给 Agent 配置 LAN-only 地址,这样设备换网络时不需要改身份配置。
- 端口映射与 Cloudflare Origin Rule 是两层。 Origin Rule 决定 Cloudflare 连源站哪个端口;路由器/NAT/Caddy 必须真的让该端口可达。
- 如果以后失去公网入站,拓扑可以平滑换成 Tunnel。 Gateway 和 Agent 协议不需要因此改变,只替换公网入口层。
这个案例适合用来理解端口关系,但不应该复制任何维护者实例的域名、DDNS、账号或设备清单。
下面以通用 Linux 为例。任何 NAS/Synology/VPS 的实际安装目录、service UID/GID、存储卷和反向代理路径都属于该实例的 private deployment profile;公共仓库只提供通用模板,不应把某一套生产环境的值当作默认值。
优先使用正式 release 产物;如果需要自行构建,固定到明确 commit/tag 后再构建:
cargo build -p remote-hosts-code --release --lockedLinux x86_64 可以使用 release pipeline 生成的 x86_64-unknown-linux-musl 产物。不要把 macOS
二进制传到 Linux,也不要在 ARM64 Linux 上运行 amd64 artifact。
安装示例:
sudo install -m 0755 remote-hosts-code /usr/local/bin/remote-hosts-code
remote-hosts-code --version发布包有 manifest/SHA-256 时先校验 hash,再安装。
sudo useradd --system --home /var/lib/remote-hosts-code --shell /usr/sbin/nologin remote-hosts-code || true
sudo install -d -o remote-hosts-code -g remote-hosts-code -m 0700 /var/lib/remote-hosts-code
sudo install -d -o root -g root -m 0755 /etc/remote-hosts-code
sudo install -d -o root -g root -m 0700 /root/remote-hosts-setupGateway 运行用户只需要写自己的 state directory,不需要 root。
public-url 必须是 HTTPS origin,不带 trailing slash:
sudo remote-hosts-code init-gateway \
--config /root/remote-hosts-setup/gateway.json \
--public-url https://mcp.example.com \
--state-dir /var/lib/remote-hosts-code \
--owner "your-owner-name" \
--password-file /root/remote-hosts-setup/gateway-login-password.txt这会生成:
gateway.json:包含密码 hash、OAuth/MCP 配置和已注册设备列表。gateway-login-password.txt:Gateway owner 登录密码明文,只用于管理员登录。
不要把这两个文件提交 Git,不要粘贴到聊天,不要放到设备授权 root。
安装运行时配置:
sudo install \
-o remote-hosts-code -g remote-hosts-code -m 0400 \
/root/remote-hosts-setup/gateway.json \
/etc/remote-hosts-code/gateway.jsonOwner password file 不需要给 Gateway service 读取;把它保存在管理员密码管理器或离线安全位置。
示例:
[Unit]
Description=Remote Hosts Code Gateway
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=remote-hosts-code
Group=remote-hosts-code
ExecStart=/usr/local/bin/remote-hosts-code gateway --config /etc/remote-hosts-code/gateway.json
Restart=on-failure
RestartSec=5
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/remote-hosts-code
[Install]
WantedBy=multi-user.target启用后检查:
sudo systemctl daemon-reload
sudo systemctl enable --now remote-hosts-code-gateway.service
sudo systemctl status remote-hosts-code-gateway.serviceGateway 默认应只监听 loopback 127.0.0.1:18787。
如果已经准备好 binary、gateway.json、service user 和 Caddy,可以让仓库模板生成 systemd unit 与 Caddy site,而不是复制某个维护者实例的文件:
sudo python3 scripts/install-code-gateway.py \
--binary /usr/local/bin/remote-hosts-code \
--config /etc/remote-hosts-code/gateway.json \
--state-dir /var/lib/remote-hosts-code \
--public-host mcp.example.com \
--service-user remote-hosts-code \
--service-group remote-hosts-code这个 installer 的域名、service user/group、状态目录、binary/config 路径、Caddy 路径和 Gateway bind 都是部署参数;公共仓库不再为任何具体 NAS/VPS 写死这些值。首次执行默认拒绝覆盖已有不同 unit/site;确认内容完全一致时才使用 --resume。不使用 Caddy 时可传 --skip-caddy。
当前仓库的生产配置思路:
mcp.example.com {
reverse_proxy 127.0.0.1:18787 {
flush_interval -1
header_up Host mcp.example.com
}
}header_up Host 很重要,因为 Gateway 会按 public_url 校验 authority。OAuth 页面需要的 Referrer-Policy、CSP、Cache-Control 等安全响应头由 Gateway 自己生成,反向代理不要再维护一份容易漂移的重复策略。修改 Caddy 前先 validate,验证通过再 reload,不要为了部署 MCP 把其他站点覆盖掉。
本地:
curl -fsS -H 'Host: mcp.example.com' http://127.0.0.1:18787/healthz公网:
curl -fsS https://mcp.example.com/healthz
curl -fsS https://mcp.example.com/.well-known/oauth-protected-resource
curl -fsS https://mcp.example.com/.well-known/oauth-authorization-server公网检查应该从 Gateway 所在局域网之外再做一次。国内部署时最好再从境外网络做一次。
这一节适用于“你的 Gateway 已经在跑,现在加 Mac/Linux/Windows 工作站”。
错误做法:
MacBook agent.json -> 复制到 Linux
正确做法:每台新设备执行一次 enroll,生成新的 device UUID、token、state path、roots 和 scopes。
设备身份可独立撤销,也不会和另一台设备的在线 session 打架。
生产配置通常是 service 用户只读文件,例如 mode 0400。enroll 会原子重写传入的配置文件,
直接以 root 对线上配置执行可能改变 owner/mode。安全流程是:
- 把线上
gateway.json复制到 root-only staging 目录。 - 在 staging copy 上 enroll。
- 验证新的 Gateway config 和新 Agent config。
- 以正确 owner/mode 原子安装回线上路径。
- 重启 Gateway,让它重新加载设备注册表。
- 安全传输该设备自己的
agent.json。
示例:
sudo install -d -m 0700 /root/remote-hosts-enroll
sudo cp /etc/remote-hosts-code/gateway.json /root/remote-hosts-enroll/gateway.json
sudo chmod 0600 /root/remote-hosts-enroll/gateway.json假设新设备名为 Linux-Workstation:
sudo remote-hosts-code enroll \
--gateway-config /root/remote-hosts-enroll/gateway.json \
--agent-config /root/remote-hosts-enroll/linux-workstation-agent.json \
--name Linux-Workstation \
--state-dir /home/YOUR_USER/.local/share/remote-hosts-code/state \
--root /home/YOUR_USER/Workspace \
--allow-write \
--allow-exec \
--shell /bin/bashLinux 上建议显式传 --shell /bin/bash;CLI 默认 shell 是 /bin/zsh,并非所有 Linux 都安装 zsh。
可以提供多个 --root,但只授权确实需要的目录。
- 默认包含
code:read。 --allow-write增加code:write。--allow-exec增加terminal:exec。
最重要的安全边界: --root 限制代码文件工具的工作区范围,但 terminal:exec 启动的是本机 shell,
它拥有运行 Agent 的 OS 用户权限,并不是一个只允许访问 root 的通用沙箱。因此:
- 不要让 Agent 以 root 运行。
- 不要给不可信机器/用户开启
--allow-exec。 - 生产密钥、SSH key、云凭据等不要因为“root 没包含那个目录”就误以为 shell 永远访问不到。
- 最好使用专门的低权限账户运行 Agent。
先保留可回滚备份,然后按服务用户恢复 owner/mode:
sudo cp -p /etc/remote-hosts-code/gateway.json \
/etc/remote-hosts-code/gateway.json.before-new-device
sudo install \
-o remote-hosts-code -g remote-hosts-code -m 0400 \
/root/remote-hosts-enroll/gateway.json \
/etc/remote-hosts-code/gateway.json.new
sudo mv /etc/remote-hosts-code/gateway.json.new \
/etc/remote-hosts-code/gateway.json
sudo systemctl restart remote-hosts-code-gateway.service然后重新检查公网 /healthz。Gateway 当前不会自动热加载 enroll 后的配置,重启是注册生效边界。
x86_64 Linux 使用对应 release artifact:
mkdir -p ~/.local/bin
install -m 0755 remote-hosts-code-linux-amd64 ~/.local/bin/remote-hosts-code
~/.local/bin/remote-hosts-code --version使用正式 release manifest 给出的 SHA-256 校验文件,不要只看文件名。
ARM64 Linux 在 0.9.0 暂无正式预构建 artifact,需要从固定源码 native build,或者扩展 release matrix 后再部署。
把该设备刚生成的 agent.json 通过可信通道传到目标机:
mkdir -p ~/.local/share/remote-hosts-code/state
chmod 700 ~/.local/share/remote-hosts-code
chmod 700 ~/.local/share/remote-hosts-code/state
install -m 0600 /tmp/linux-workstation-agent.json \
~/.local/share/remote-hosts-code/agent.json
rm -f /tmp/linux-workstation-agent.json验证但不启动:
~/.local/bin/remote-hosts-code check \
--agent \
--config ~/.local/share/remote-hosts-code/agent.jsonRUST_LOG=info ~/.local/bin/remote-hosts-code agent \
--config ~/.local/share/remote-hosts-code/agent.json新设备只需要出站 HTTPS 到 Gateway 公网 origin,不需要为 Agent 开放 SSH、MCP 或任意公网监听端口。
0.9.0 的 scripts/remote-hosts-systemd-service 管理的是 Remote Hosts operator API/SSH connector,
不是 remote-hosts-code agent。Code Agent 在 Linux 上当前建议使用单独的 user unit:
[Unit]
Description=Remote Hosts Code Agent
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
ExecStart=%h/.local/bin/remote-hosts-code agent --config %h/.local/share/remote-hosts-code/agent.json
Restart=on-failure
RestartSec=5
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
Environment=RUST_LOG=info
[Install]
WantedBy=default.target保存为:
~/.config/systemd/user/remote-hosts-code-agent.service
启用:
systemctl --user daemon-reload
systemctl --user enable --now remote-hosts-code-agent.service
systemctl --user status remote-hosts-code-agent.service如果需要“用户未登录也持续在线”,由管理员明确决定是否启用 linger:
sudo loginctl enable-linger "$USER"日志:
journalctl --user -u remote-hosts-code-agent.service -fmacOS 使用每用户 LaunchAgent。先把 native binary 和该机器独有的 agent.json 放到私有目录,然后:
python3 scripts/install-code-agent.py \
--binary ~/.local/share/remote-hosts-code/bin/remote-hosts-code \
--config ~/.local/share/remote-hosts-code/agent.json每台 Mac 仍然必须单独 enroll。不要复制另一台 Mac 的身份文件。
0.9.0 开始对 macOS updater/code identity 做稳定化迁移;首次从旧 ad-hoc 身份迁移时,系统可能需要一次 登录用户确认。不要因为弹窗没有出现就反复生成新身份。
后续升级必须保持 release artifact 不变,只对本机安装副本做 host-local code signing。scripts/macos_code_identity.py status 只有在证书信任有效、专用 signing keychain 已进入当前用户 search list、且 identity 没有 CSSMERR_* 时才可视为 ready。需要授权时只使用 scripts/macos_code_identity.py authorize:这张 self-signed 证书使用 user-domain trustRoot + codeSign,不要用 trustAsRoot,也不要为了绕过 codesign: no identity found 而安装 unsigned/ad-hoc 候选。
Windows 仍遵守同一原则:
- 使用 Windows 对应构建产物;
- 为该机器单独 enroll;
agent.json只属于这台机器;- 以普通用户运行;
- 让 Agent 主动连接 Gateway,不开放额外入站端口;
- 使用当前用户 Task Scheduler/服务包装保持常驻。
Windows 平台已有 operator control plane 的安装脚本,但它和 remote-hosts-code agent 不是同一个服务。
如果没有正式 Code Agent installer,宁可显式创建一个只运行
remote-hosts-code agent --config ... 的当前用户任务,也不要错误复用 SSH connector service。
当前 ChatGPT Developer mode 的基本流程:
-
在 ChatGPT Web 打开 Settings -> Security and login -> Developer mode。
-
进入 Plugins/App 管理页,创建一个 Developer-mode remote MCP app。
-
MCP URL 使用:
https://mcp.example.com/mcp -
Authentication 选择 OAuth。
-
Authorization Server / Issuer 使用公网 origin:
https://mcp.example.com -
如果界面显示 callback/redirect URI,确认 Gateway allowlist 中存在完全相同的 URI。
-
完成 owner 登录和授权。
-
刷新 app/tool schema 后,再从新对话测试
devices_list。
Remote Hosts Gateway 已实现 protected-resource metadata、OAuth metadata、Authorization Code + S256
PKCE、DCR 和 issuer identification。OpenAI 当前要求 OAuth MCP Server 正确发布 protected-resource
metadata、OAuth metadata、resource、S256 PKCE,并对 callback issuer 做一致性校验。不要为了“先跑通”
关闭这些检查。
不要把“进程启动了”当作部署成功。至少完成以下层级:
systemctl status remote-hosts-code-gateway.service
curl -fsS https://mcp.example.com/healthzremote-hosts-code check --agent --config <agent.json>
# Linux:
systemctl --user status remote-hosts-code-agent.servicedevices_list能看到新设备。- 新设备状态为 online。
workspace_open能在一个明确授权 root 下成功。- 做一次只读
code_read。 - 允许写时,在临时文件上做一次 version-bound edit 再删除。
- 允许执行时,做一次无副作用终端命令并读取同一个 terminal 的完成状态。
- 有文件传输需求时做小文件 SHA-256 往返。
只有这些完成后,才把设备标记为“可交付使用”。
按顺序检查:
https://mcp.example.com/healthz是否从境外可访问。/.well-known/oauth-protected-resource是否返回正确公网 origin。- OAuth issuer 是否和
public_url逐字符一致,包括 scheme、host、port 和 trailing slash。 - ChatGPT 管理页显示的 redirect URI 是否已精确 allowlist。
- Cloudflare/Caddy 是否把 Host 改坏。
/mcp是否被额外的 SSO/WAF 页面拦截。
这是 Cloudflare -> origin 段的问题,而不是 Device Agent:
- 检查 DNS/DDNS。
- 检查 Origin Rule 的 destination port。
- 检查公网 8443 是否真的到达 Caddy。
- 检查 TLS/SNI/证书和 Cloudflare SSL mode。
- 没有公网入站路径时不要继续修端口,直接评估 Tunnel。
Tunnel Healthy 只代表 cloudflared 到 Cloudflare 的连接正常,不证明本地 origin、Host、OAuth metadata、
Caddy 或 MCP 正常。继续从公网逐个检查 /healthz 和 well-known metadata。
- 在设备上测试能否访问 Gateway 公网 origin。
- 检查本机时间是否明显错误。
- 检查
agent.jsonmode 和 state directory 权限。 - 检查是不是复制了另一台设备的 config。
- 检查 systemd/LaunchAgent 日志。
- 不要通过重新 enroll 同一个名字来掩盖网络错误。
确认该设备 enroll 时是否启用了 --allow-exec,并确认配置的 shell 在目标 OS 上存在。
给团队成员部署时,至少逐项确认:
- Gateway owner password、gateway.json、agent.json、device token 都是秘密。
- 一台设备一个身份,不共享
agent.json。 - Agent 使用普通低权限 OS 用户,不用 root/Administrator 常驻。
- 授权 root 采用最小集合,不要直接给
/或整个 home,除非这是明确的风险决定。 --allow-exec权限远大于 code root;shell 受 OS 用户权限控制,不是通用沙箱。- Gateway 只监听 loopback,公网入口放在 TLS reverse proxy/Cloudflare 后。
- 不把 NAS 管理面、SSH、数据库和 MCP 混在同一个公开 hostname。
- 不使用 OAuth wildcard redirect URI。
- Cloudflare 不能替代 Gateway 自己的 OAuth/device authentication。
- 更新 Gateway 前备份配置和 state,新增设备时保留上一份可回滚 gateway.json。
- 设备离职、丢失或不再可信时,应从 Gateway 注册表撤销它,不要只“把机器关机”。
- 日志和文档中不得记录 device token、owner password、临时文件授权 URL 或其他凭据。
- 准备长期在线 Linux/NAS Gateway。
- 准备域名
mcp.<domain>。 - 安装
remote-hosts-code,执行init-gateway。 - Gateway 仅监听
127.0.0.1:18787。 - 配 Caddy/TLS。
- 有公网入站:Cloudflare Proxy + Origin Rule 443 -> origin 8443。
- 无公网入站/CGNAT:Cloudflare Tunnel。
- 从境外验证
/healthz和 OAuth metadata。 - 每台设备分别
enroll。 - 在设备上安装 Agent,并以普通用户常驻运行。
- ChatGPT Developer mode 创建 remote MCP app,URL 指向
/mcp,OAuth issuer 指向公网 origin。 devices_list -> workspace_open -> read -> optional write/terminal/file完整验收。
- 确认现有 Gateway 健康。
- staging copy
gateway.json。 enroll生成新设备独立agent.json。- 正确 owner/mode 安装更新后的 Gateway config,重启 Gateway。
- 把
agent.json安全传到新设备。 - 安装相同/兼容版本 Agent。
- 建 systemd user service / LaunchAgent / Windows user task。
- 在 ChatGPT 中通过
devices_list验证上线。
0.9.0 已把 Linux operator service systemd 化,但新 Code Agent 首次加入 Gateway 仍需要管理员修改 Gateway config 并重启。后续值得增加:
- 一次性、短时有效 enrollment token;
remote-hosts-code join自助注册;- Linux/Windows Code Agent 一键 installer;
- Linux ARM64 正式 release artifact;
- enrollment 后 Gateway 的安全热加载/设备注册 API。
在这些能力落地前,本文件中的 staging-enroll 流程是推荐的可审计路径。