1. 先选择部署模式
| 模式 | 适用场景 | 用户怎样接入 | 建议 |
|---|---|---|---|
| 账户中心 | 一台服务器给自己多台电脑、家人、朋友或小团队使用。 | 管理员网页生成一次性 CMRA1.…接入密钥;用户只填用户名和密钥。 | 默认推荐。能查看设备、公钥指纹、在线状态和审计,并可逐台撤销。 |
| 单用户手动租户 | 只有一位技术用户,或账户中心暂不可用时。 | 手动生成 Ed25519 密钥、开通 Linux 账号、写 SSH config,再在客户端填三个高级字段。 | 步骤更少但容易配错,不适合让小白自行完成。 |
两种模式的数据面原理相同:每台电脑一把私钥、一个无 Shell SSH 身份、一个域名和一个 Unix Socket。区别在于账户中心把登记、主机密钥固定、配置生成、审计和撤销自动化。
2. 网络拓扑和通信方向
关键点是电脑主动连接云服务器。家庭宽带通常位于 NAT/运营商 CGNAT 后,外部不能主动连接电脑;反向 SSH 把这个问题变成电脑到云端的普通出站连接。因此:
- 家用路由器不用配置端口映射,也不用申请公网 IPv4。
- 云服务器需要公网入站 22、80、443。
- 电脑所在网络需要允许出站 TCP 22;若公司/校园网封锁 22,隧道无法建立。
- 本机/云端都不应把 8787 暴露到公网,账户中心的 8790 也只监听云端回环地址。
3. 准备服务器、域名和变量
3.1 推荐服务器条件
- Ubuntu 24.04 LTS 或 Debian 12,具有 systemd、OpenSSH 和 sudo。
- 一枚稳定公网 IPv4;使用 IPv6 时还要确认端到端路由和 IPv6 防火墙。
- 至少 1 核 CPU、1 GB 内存和 10 GB 磁盘可作为小规模起点;实际容量取决于日志、用户数和系统组件。
- 能从用户电脑访问 TCP 22,并能从手机访问 TCP 443。
- 系统时间和时区同步正常。时间严重错误会导致 TLS 和一次性密钥判断异常。
3.2 先确定示例变量
后文使用以下示例。把 example.com替换成自己的域名,不要把尖括号占位符原样运行。
| 变量 | 示例 | 用途 |
|---|---|---|
| 服务器公网 IP | 203.0.113.10 | DNS A 记录目标;文档保留地址仅作示例。 |
| 基础域名 | relay.example.com | 设备子域名的后缀,也是示例 SSH 主机。 |
| 账户中心 | accounts.relay.example.com | 管理员网页登录和客户端激活接口。 |
| 设备域名 | r0123….relay.example.com | 账户中心自动为每台设备生成。 |
| 管理员名 | admin | 每台服务器只创建一个管理员。 |
3.3 需要完整维护者发布包
服务器部署不能只上传普通用户的 EXE/APK。解压后的目录至少要有:
relay-control/server.js
relay-control/lib/
relay-control/public/index.html
scripts/install_relay_control_plane.sh
scripts/relay_control_apply_job.sh
scripts/provision_shared_relay_tenant.sh
scripts/revoke_shared_relay_tenant.sh安装命令必须从这个发布包根目录运行,因为脚本会按相对位置复制账户中心和开通/撤销脚本。
4. 配置云安全组和系统防火墙
云厂商“安全组”和服务器里的 UFW/nftables 是两道独立防火墙。任意一道拒绝,连接都会失败。
4.1 云安全组入站规则
| 协议/端口 | 来源 | 原因 |
|---|---|---|
| TCP 22 | 优先限制为管理员和所有电脑端的公网 IP;无法固定时才允许全网 | 电脑建立反向 SSH 隧道,管理员维护服务器。 |
| TCP 80 | 0.0.0.0/0;启用 IPv6 时另加 ::/0 | HTTP 跳转和部分 ACME 验证。 |
| TCP 443 | 0.0.0.0/0;启用 IPv6 时另加 ::/0 | 手机 HTTPS/WebSocket 和账户中心。 |
4.2 服务器 UFW
如果服务器使用 UFW,保持当前 SSH 会话不要关闭,先允许 SSH,再启用。Debian 未安装时先安装;已经由 firewalld/nftables 管理的服务器应在现有防火墙中添加等价规则,不要叠加一套互相冲突的规则。
sudo apt install -y ufw
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose若 SSH 使用自定义端口,先允许真实端口并同步修改云安全组;当前客户端默认示例使用 22。远程用户来自动态地址时,22 可能只能全网开放,此时必须坚持公钥认证、受限账号、禁用密码和及时更新 OpenSSH。
4.3 出站规则
多数云安全组默认允许全部出站,可保持默认。若采用严格白名单,至少允许 DNS 到配置的解析器、NTP 时间同步,以及 TCP 80/443 用于系统更新和证书续期。SSH 的响应包由有状态防火墙自动允许。
5. 配置并验证 DNS
5.1 账户中心模式需要三类记录
relay.example.com A 203.0.113.10
accounts.relay.example.com A 203.0.113.10
*.relay.example.com A 203.0.113.10relay.example.com是 SSH 主机,必须有单独记录;通配符不会覆盖它自身。accounts.relay.example.com是控制面。*.relay.example.com让以后自动生成的设备子域名无需逐个添加 DNS。- 只有服务器真正配置了公网 IPv6 时才添加 AAAA。错误 AAAA 会让部分手机优先走不可达 IPv6。
若使用 Cloudflare 等代理,首次部署建议全部设为“仅 DNS”。尤其 SSH 主机不能直接经过普通 HTTP CDN 代理。直连验收通过后再评估 CDN/WebSocket、源站证书和真实客户端 IP 配置。
5.2 在电脑和服务器分别验证
Windows PowerShell:
Resolve-DnsName relay.example.com
Resolve-DnsName accounts.relay.example.com
Resolve-DnsName test-device.relay.example.com云服务器:
getent ahosts relay.example.com
getent ahosts accounts.relay.example.com
getent ahosts test-device.relay.example.com三个名称最终都应解析到预期公网 IP。DNS 控制台已保存但外部仍是旧值时,等待 TTL/递归缓存刷新,不要用修改本机 hosts 的方式代替正式验收。
6. 申请和续期 TLS 证书
账户中心和所有设备域名都通过 HTTPS。Nginx 模式需要一张覆盖 *.relay.example.com的通配符证书;通配符证书必须使用 DNS-01 验证。
6.1 临时验收:手工 DNS-01
sudo apt update
sudo apt install -y certbot
sudo certbot certonly --manual --preferred-challenges dns -d '*.relay.example.com'Certbot 会要求在 DNS 中创建一个 _acme-challenge.relay.example.com TXT 记录。必须等外部解析能读到正确 TXT 后再继续。典型证书路径为:
/etc/letsencrypt/live/relay.example.com/fullchain.pem
/etc/letsencrypt/live/relay.example.com/privkey.pem6.2 正式环境:DNS 插件自动续期
- 在 Certbot 官方安装向导选择操作系统、Web 服务器和 DNS 提供商。
- 给 DNS API 凭据最小权限,最好只允许修改目标域名的 ACME TXT 记录。
- 凭据文件只允许 root 读取,不放入发布包、代码仓库或客户端。
- 签发后执行
sudo certbot renew --dry-run验证自动续期。 - 确认续期后 Nginx 会 reload,或配置 deploy hook 执行
systemctl reload nginx。
通配符 *.relay.example.com覆盖 accounts.relay.example.com和单层设备域名,但不覆盖 relay.example.com本身。示例基础域名只承载 SSH,不需要 Web TLS;如果也要在该名称提供 HTTPS,应把基础域名本身加入证书。
7. 安装服务器基础软件
在云服务器的管理员 Shell 执行:
sudo apt update
sudo apt install -y openssh-server nginx certbot curl openssl sudo nodejs
sudo systemctl enable --now ssh nginx
node --version
sudo sshd -t
sudo nginx -t账户中心要求 Node.js 18 或更高,建议 Node.js 22 LTS。Ubuntu 24.04/Debian 12 的系统包通常满足最低要求;如果 node --version低于 18,应按照 Node.js 官方下载说明升级后再继续,安装脚本不会偷偷替换系统 Node。
还要确认服务器存在 Ed25519 主机公钥:
sudo test -r /etc/ssh/ssh_host_ed25519_key.pub
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub账户中心会把这个公钥提供给 Windows 客户端做主机固定校验。服务器重装或更换 host key 后,客户端应报警;不要为了省事关闭 StrictHostKeyChecking。
8. 部署多人账户中心
把完整维护者包上传、解压并进入根目录后,先确认文件:
test -f relay-control/server.js
test -f scripts/install_relay_control_plane.sh
test -f scripts/relay_control_apply_job.sh
test -f scripts/provision_shared_relay_tenant.sh
test -f scripts/revoke_shared_relay_tenant.sh然后运行:
sudo bash scripts/install_relay_control_plane.sh --account-domain accounts.relay.example.com --base-domain relay.example.com --ssh-host relay.example.com --tls-certificate /etc/letsencrypt/live/relay.example.com/fullchain.pem --tls-certificate-key /etc/letsencrypt/live/relay.example.com/privkey.pem --admin-username admin首次安装会在终端中隐藏输入并要求两次输入至少 12 位的管理员密码。不要把密码写进命令行参数。安装器会在变更服务前检查域名格式、证书覆盖范围、Node 版本、SSH host key、Nginx/OpenSSH 配置和 sudoers。
8.1 安装后产生的关键对象
| 对象 | 位置/监听 | 安全作用 |
|---|---|---|
| 服务用户 | codex-relay-control,无登录 Shell | Web 服务不以 root 运行。 |
| 程序 | /opt/codex-mobile-relay-control | root 拥有,服务只读执行。 |
| 状态 | /var/lib/codex-mobile-relay-control/state.json,权限 0600 | 保存账户、摘要、设备和审计,不保存用户电脑私钥。 |
| 控制服务 | 127.0.0.1:8790 | 不暴露公网,只允许本机反向代理。 |
| 受限 root helper | /usr/local/libexec/codex-mobile-relay-apply | 只接受严格校验的开通/撤销任务,Web 进程不能执行任意 root 命令。 |
| Nginx 账户站点 | 80/443 | TLS、HSTS、大小限制、每来源限流,并覆盖伪造的转发 IP 头。 |
安装完成后打开 https://accounts.relay.example.com。每台服务器只允许一个由服务器所有者在本机创建的管理员,网页不能生成第二个管理员。
9. 开通用户和建立隧道
- 管理员登录账户中心
填写授权说明、可选用户名和 1–720 小时有效期,生成一次性接入密钥。完整
CMRA1.…只显示一次。 - 只发用户名和接入密钥
不要发送服务器 root 密码、管理员 SSH 私钥、证书私钥或其他设备的任何密钥。
- 用户在 Windows 客户端一键接入
进入 “公网访问”,填写用户名和完整接入密钥。客户端从密钥解析账户中心地址。
- 客户端生成设备身份
在当前 Ubuntu 的
~/.ssh生成独立 Ed25519 密钥,只上传.pub公钥;随后固定服务器 Ed25519 主机公钥并写入独立 SSH 别名。 - 账户中心开通受限租户
每台设备获得独立
cmrelay-…账号、子域名和/run/codex-mobile-relay/…/codex-mobile.sock。 - 客户端启动反向隧道
电脑连接云端 TCP 22,把远程 Unix Socket 映射回 Ubuntu 的
127.0.0.1:8787,并在网络断开后自动重试。 - 手机连接并由电脑允许
使用新公网连接链接;第一台公网手机仍需在电脑端 “设备管理”确认。
一次性密钥只用于登记,服务端只保存授权码摘要,激活后不能再次使用。设备日常登录令牌由 Windows safeStorage/DPAPI 加密保存;这不能替代 Windows 账号和系统本身的安全。
10. 单用户手动模式
只有一位技术用户时,可以不部署账户中心,但仍应使用开通脚本建立同样的 SSH/Socket 隔离。
10.1 用户在自己的 Ubuntu 生成密钥
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/codex-mobile-relay-alice -C codex-mobile-relay-alice
cat ~/.ssh/codex-mobile-relay-alice.pub只把最后输出的公钥交给管理员。没有 .pub后缀的文件是私钥,绝不能上传服务器、发到聊天或放进发布包。
10.2 管理员开通租户和 HTTPS
把公钥保存为服务器上的 /tmp/alice.pub,然后:
sudo bash scripts/provision_shared_relay_tenant.sh --tenant alice --domain alice.relay.example.com --public-key-file /tmp/alice.pub
sudo certbot --nginx -d alice.relay.example.com
rm -f /tmp/alice.pub如果已经有覆盖该域名的通配符证书,可以在开通命令中同时传入 --tls-certificate和--tls-certificate-key,省去单域名 Certbot 步骤。
10.3 用户写入 SSH 别名
Host codex-relay-alice
HostName alice.relay.example.com
User cmrelay-alice
IdentityFile ~/.ssh/codex-mobile-relay-alice
IdentitiesOnly yes这段内容写入用户 Ubuntu 的 ~/.ssh/config。第一次信任前,管理员应通过独立渠道提供:
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub第一次连接会询问是否信任主机 key。用户核对指纹后才输入 yes;OpenSSH 会把它写入 known_hosts,以后变更就会报警。ssh-keyscan只能获取网络当前返回的 key,不能独立证明它属于正确服务器。
10.4 在 Windows 客户端填写三个高级字段
公网 HTTPS URL:https://alice.relay.example.com
SSH 目标:codex-relay-alice
远程 Unix Socket:/run/codex-mobile-relay/alice/codex-mobile.sock保存后点击 “重启隧道”。开通脚本会把账号限制为公钥认证、无 Shell、无命令、无 SFTP、无 PTY、无 Agent/X11 转发;只允许远程方向 Unix Socket 转发,并用 PermitListen none禁止任意 TCP 监听。
11. 已有 Docker Caddy 的服务器
如果 80/443 已被 Docker 中的 Caddy 占用,不要再启动 Nginx,也不要让两个服务争抢端口。给 Caddy 容器加入只读挂载:
volumes:
- /etc/codex-mobile-relay/caddy-sites:/etc/caddy/relay-sites:ro
- /run/codex-mobile-relay:/run/codex-mobile-relay:ro主 Caddyfile 加入:
import /etc/caddy/relay-sites/*.caddy先运行 docker compose config并在容器中验证 Caddy 配置。确定 Caddy 网络在宿主机的网关地址后:
sudo apt install -y socat
sudo bash scripts/install_relay_control_plane_caddy.sh --account-domain accounts.relay.example.com --base-domain relay.example.com --ssh-host relay.example.com --caddy-container relay-caddy --caddy-gateway 172.18.0.1 --admin-username admin172.18.0.1只是示例,必须替换成真实 Docker 网络网关。该模式仍让账户服务只监听 127.0.0.1:8790,另用只绑定 Docker 私网网关的 socat 桥接;Caddy 为精确域名自动签发/续期证书。
12. 逐层验收
不要只看“网页能打开”。按下面顺序执行,任何一层失败就先修这一层。
12.1 配置语法和服务
sudo sshd -t
sudo nginx -t
sudo systemctl status ssh --no-pager
sudo systemctl status nginx --no-pager
sudo systemctl status codex-mobile-relay-control --no-pager12.2 实际监听地址
sudo ss -lntp | grep -E ':(22|80|443|8790)\b'- 22、80、443 应按你的 IPv4/IPv6 配置监听。
- 8790 必须只显示
127.0.0.1:8790,不能是0.0.0.0:8790或公网地址。 - 不应看到云服务器监听 8787。
12.3 控制面健康与 TLS
curl -fsS http://127.0.0.1:8790/api/health
curl -fsS https://accounts.relay.example.com/api/health
openssl s_client -connect accounts.relay.example.com:443 -servername accounts.relay.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates第一条验证账户程序,第二条验证 DNS/Nginx/TLS 到程序的完整链路,第三条检查实际返回证书及有效期。
12.4 SSH 限制是否生效
sudo sshd -T -C user=cmrelay-alice,host=alice.relay.example.com,addr=127.0.0.1 | grep -E 'allowtcpforwarding|allowstreamlocalforwarding|permitlisten|maxsessions|passwordauthentication|permittty'单用户示例至少应包含:
allowtcpforwarding remote
allowstreamlocalforwarding remote
permitlisten none
maxsessions 0
passwordauthentication no
permittty no12.5 隧道和 Unix Socket
用户在客户端看到“隧道已连接”后,服务器执行:
sudo ss -xl | grep codex-mobile.sock
sudo find /run/codex-mobile-relay -type s -name codex-mobile.sock -ls应只出现该设备自己的 Socket。最后用手机蜂窝网络(关闭 Wi-Fi)打开公网连接,发送一条可识别的测试消息,再回电脑核对进入的是同一会话。至此才算端到端验收完成。
13. 按症状排障
| 症状 | 通常位于哪一层 | 检查命令/动作 |
|---|---|---|
| 域名不存在 / NXDOMAIN | DNS 记录、委派或缓存 | Resolve-DnsName / getent ahosts;确认权威 DNS 中的 A/AAAA/通配符。 |
| 443 超时 | 云安全组、UFW、Nginx/Caddy 未监听 | ufw status、ss -lntp、云控制台安全组;从外网而不是服务器本机测试。 |
| 证书名称不匹配/过期 | TLS 证书或 SNI | openssl s_client -servername …;检查证书是否覆盖账户域名和设备通配符,执行续期演练。 |
| 账户中心公网 502 | Nginx 到 127.0.0.1:8790 | curl http://127.0.0.1:8790/api/health;查看 systemd 状态和日志。 |
| 设备域名 502 | 隧道未连或 Unix Socket 不存在/无权限 | ss -xl、find /run/codex-mobile-relay;看客户端隧道日志和 Nginx error log。 |
| SSH 连接超时/拒绝 | 22 安全组/UFW、sshd、客户端网络封锁 | 从用户 Ubuntu 测试 ssh -vvv SSH别名并保存错误;服务器看 journalctl -u ssh。 |
Permission denied (publickey) | 用户、私钥、authorized_keys 或文件权限 | 核对 SSH 别名、IdentityFile、公钥指纹和账号是否被撤销;不要启用密码认证兜底。 |
remote port forwarding failed for listen path | Socket 已占用/无权限,或 OpenSSH 转发策略错误 | 确认脚本配置为 AllowTcpForwarding remote、PermitListen none、AllowStreamLocalForwarding remote;清理失效隧道后重试。 |
| 主机公钥变化 | 服务器重装、host key 轮换或中间人风险 | 停止连接,通过云控制台/管理员独立渠道核对新 Ed25519 指纹;确认变更后重新登记,不要关闭校验。 |
| 公网网页可开但 App 不能实时更新 | WebSocket Upgrade、代理超时或 CDN | 检查 Nginx/Caddy WebSocket 配置和日志,绕过 CDN 直连源站复测。 |
| 手机提示等待允许/无权限 | 应用鉴权,不是网络 | 电脑端 “设备管理”核对并允许;检查 token/remote guard 是否已重置。 |
常用日志
sudo journalctl -u codex-mobile-relay-control -n 200 --no-pager
sudo journalctl -u ssh -n 200 --no-pager
sudo tail -n 200 /var/log/nginx/error.log
sudo tail -n 200 /var/log/nginx/codex-mobile-relay-control.error.log14. 升级、备份、撤销和安全
14.1 升级账户中心
上传经过验证的新维护者包,在新包根目录使用原参数重新运行,并加 --skip-admin:
sudo bash scripts/install_relay_control_plane.sh --account-domain accounts.relay.example.com --base-domain relay.example.com --ssh-host relay.example.com --tls-certificate /etc/letsencrypt/live/relay.example.com/fullchain.pem --tls-certificate-key /etc/letsencrypt/live/relay.example.com/privkey.pem --skip-admin升级后重复“逐层验收”,尤其检查 sshd -t、nginx -t和两个健康接口。
14.2 必须备份的路径
/var/lib/codex-mobile-relay-control/state.json
/etc/codex-mobile-relay/
/var/lib/codex-mobile-relay/
/etc/nginx/sites-available/codex-mobile-*.conf
/etc/ssh/sshd_config.d/60-codex-mobile-relay.conf
/etc/letsencrypt/备份包含密码哈希、令牌摘要、公钥、审计和证书私钥,应加密、限制访问并做恢复演练。不能只“有一个压缩包”却从未验证能恢复。
14.3 撤销疑似泄露的单用户租户
sudo bash scripts/revoke_shared_relay_tenant.sh --tenant alice撤销会清空公钥、终止该账号活动隧道并删除 Socket。恢复时必须生成新密钥并重新开通;随后让用户在电脑端重置连接,轮换应用 token、remote guard 和手机设备授权。
14.4 不可省略的安全规则
- 每台电脑独立密钥,不共用 root 或管理员 SSH 私钥。
- SSH 私钥只留在用户 Ubuntu;账户中心只接收公钥。
- 不要靠隐藏域名/IP 防盗用;用一次性密钥、设备上限、限流、审计和撤销控制风险。
- 不要信任公网请求自带的
X-Forwarded-For。前面有 CDN 时,只信任其官方固定地址段并正确配置 real IP。 - 及时安装操作系统、OpenSSH、Nginx/Caddy 和 Node.js 安全更新,并监控证书到期、服务离线和磁盘空间。
- 当前账户中心使用单进程原子 JSON 状态,适合个人/小团队。多节点、高并发或合规场景应升级为事务数据库、集中日志、MFA、告警和短期 SSH 证书。
官方协议参考
- Microsoft:WSL 网络与 portproxy ↗
- Nginx:反向代理与 Unix Socket ↗
- Nginx:WebSocket 代理 ↗
- OpenSSH:sshd_config 权限选项 ↗
- Certbot:通配符证书与续期说明 ↗
普通用户完成服务器接入后的操作见公网访问(用户侧)。