← 返回文章列表

用 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。