用 Cloudflare 自建 Paseo 中继
不买 VPS,把 Paseo 的 WebSocket relay 部署到 Cloudflare Workers 和 Durable Objects,并绑定到自己的子域名。
Paseo 的手机端连接桌面 daemon 时,可以走官方 relay,也可以自建中继。本文记录我把中继部署到 Cloudflare Workers、绑定 relay.t-bruce.work,并让手机实际发消息验证成功的完整过程。
这套方案不需要额外购买 VPS。Paseo 主仓库里已经包含一个 Cloudflare Durable Objects 适配器,空闲 WebSocket 可以进入 hibernation,个人低频使用通常足够。
前置条件
- 一个已托管在 Cloudflare 的域名;
- 本机能运行 Node.js 和
wrangler; - Paseo Desktop / daemon 已经安装;
- 手机端已安装 Paseo;
- 域名准备用一个独立子域名,例如
relay.example.com。
我的博客主域名继续由 Cloudflare Pages 提供,只把 relay.t-bruce.work 分配给 Paseo relay,两者互不影响。
先理解两层安全
Cloudflare 提供的是平台层保障:
- 自动 TLS;
- DDoS 防护;
- Workers 运行隔离;
- custom domain 证书管理。
Paseo 自己提供的是应用层端到端加密。中继只能看到连接元数据、消息大小、时间和 session 路由信息,不能读取你的 agent 对话或代码内容。
真正敏感的是 pairing QR code / pairing link。它相当于信任锚,不要截图发给别人,也不要提交到仓库。
拉取与版本一致
Paseo Desktop 当前是 0.8.0,因此我部署同一版本的 relay 源码:
git clone --depth 1 --branch v0.8.0 https://github.com/getpaseo/paseo.git paseo-relay-deploy
cd paseo-relay-deploy/packages/relay
替换 Wrangler 配置
原仓库的 wrangler.toml 是官方部署配置,不能直接使用。用下面内容替换:
name = "my-paseo-relay"
main = "src/cloudflare-adapter.ts"
compatibility_date = "2024-12-01"
routes = [{ pattern = "relay.example.com", custom_domain = true }]
[observability]
enabled = true
[[durable_objects.bindings]]
name = "RELAY"
class_name = "RelayDurableObject"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["RelayDurableObject"]
把 my-paseo-relay 和 relay.example.com 换成自己的名字和子域名。
关键是不要保留这段官方配置:
[vars]
PASEO_RELAY_UPSTREAM = "https://paseo-relay-next.fly.dev"
如果保留它,Worker 会把请求转发到官方上游,而不是使用你自己的 Durable Object。
登录并部署
npx wrangler login
npx wrangler deploy
如果账号以前没有使用过 Workers,Cloudflare 可能提示需要先初始化 workers.dev 子域。可以在 Dashboard 打开一次 Workers 页面完成初始化,再重新执行部署。
部署成功后会看到类似输出:
Deployed my-paseo-relay triggers
relay.example.com
验证 relay Worker
先测试健康检查:
curl -fsS https://relay.example.com/health
应返回:
{"status":"ok"}
再测试 relay 路由是否存在:
curl -i https://relay.example.com/ws
缺省参数时返回 400 Missing serverId parameter 是正常的。
也可以做一次 WebSocket 握手:
node <<'NODE'
const url =
'wss://relay.example.com/ws' +
'?serverId=manual-test&role=server&v=2';
const ws = new WebSocket(url);
ws.onopen = () => {
console.log('websocket open');
ws.close();
};
ws.onerror = event => {
console.error('websocket error', event);
process.exit(1);
};
ws.onclose = () => process.exit(0);
NODE
配置 Paseo daemon
编辑 ~/.paseo/config.json,把 relay 配置合并进现有 daemon 对象:
{
"version": 1,
"daemon": {
"relay": {
"enabled": true,
"endpoint": "relay.example.com:443",
"publicEndpoint": "relay.example.com:443",
"useTls": true,
"publicUseTls": true
}
}
}
注意 endpoint 不写协议头,格式必须是 host:port。
然后重启 daemon:
paseo daemon restart
检查状态:
paseo daemon status
Relay 一栏应显示:
wss://relay.example.com:443
重新配对手机
旧 pairing link 里保存的是旧 relay endpoint,必须重新生成。
在 Paseo Desktop 中执行:
Settings → your host → Pair a device → Enable relay
然后用手机扫描新的 QR code,或粘贴新的 pairing link。
不要继续使用旧的官方 relay 配对链接。
确认真实消息链路
手机连接后,本机日志里应能看到 relay 控制连接:
relay_control_connected
也能看到来自外部客户端的连接:
{
"transport": "relay",
"peer": "external",
"msg": "Client connected via hello"
}
如果从手机发送一条测试消息,daemon 会启动对应 agent turn。这一步完成后,说明链路已经从:
手机 → Cloudflare relay → 本机 Paseo daemon → agent
完整打通。
费用与注意事项
Cloudflare Workers 和 Durable Objects 有免费额度。SQLite-backed Durable Objects 也可用于 Free plan,个人低频连接通常可以先零成本试用。
需要注意:
- Worker 建连会计 request;
- Durable Object 收到 WebSocket 消息时按 Cloudflare 的规则折算 request;
- 空闲且可 hibernation 的 WebSocket 不持续计 duration;
- 高频 agent 输出会增加请求和活跃时长。
如果只是个人手机偶尔控制家里的 daemon,通常不需要急着升级。可以先观察 Cloudflare Dashboard 的 Workers 和 Durable Objects 用量。
常见问题
博客域名会受影响吗?
不会,只要 relay 使用独立子域名。不要把博客主域名的 route 让给 Worker。
需要额外购买 Cloudflare VPS 吗?
不需要。这里用的是 Cloudflare Workers 和 Durable Objects,不是传统 VPS。
能开启 Cloudflare Access 保护 relay 吗?
不建议。Paseo 手机端和 daemon 不会完成 Access 的浏览器登录流程,容易导致 relay 无法连接。
旧手机连接为什么还走官方 relay?
旧 pairing link 已经保存了旧 endpoint。删除旧连接,重新生成并扫描新的 pairing link。