自托管 Relay Hub
Relay Hub 是一个可选的、自托管的服务端,它能把 xacpx 变成一个多租户的远程遥控看板。你的各个 xacpx 实例会通过 WebSocket 主动向外拨号连接到 Hub 并注册;随后你即可从任意浏览器登录 Web 看板,在同一个地方驱动每个实例的会话——聊天、定时任务和编排。
本指南将带运维人员从零开始,搭建一个已配对实例并正常运行的 Hub。
架构速览
xacpx instance A ─┐ ┌─ browser (token login)
xacpx instance B ─┤ WSS (dial-out) │ HTTPS + WSS
xacpx instance C ─┴──────────────► RELAY HUB ◄───────────┘
:8787 HTTP API + web /ws + instance gateway
│
relay.db (SQLite)- 单端口(默认)。 HTTP API、看板的
/ws广播与实例 WebSocket 网关(xacpx 实例在此注册)全部共用 8787——连接器通过根路径上的 WebSocket 升级完成注册。仅当你想要一个可独立配置防火墙的专用网关端口时,才传入--ws-port。 - 多租户。 每个 token 用户永远只能看到属于自己的实例和会话;服务端会在每次代理调用上打上身份标记。令牌、实例凭据和 Web 会话 Cookie 均以哈希形式存储。
- 数据真相源仍在实例侧。 Hub 会为看板缓存近期消息,但它并不拥有你的会话——会话归实例所有。
环境要求
- Node.js ≥ 22.13(使用内置的
node:sqlite)或 Bun ≥ 1.2(使用bun:sqlite)。无需编译原生数据库插件。 - 一台可被你的实例和浏览器访问到的主机。对于 localhost 之外的任何部署,你都需要一个负责 TLS 终止的反向代理(参见 TLS 与反向代理)。
- 要配对实例,每个实例都需要
xacpxCLI(即快速开始中的常规安装)。
1. 获取服务端
全局安装 relay Hub。Web 看板已内嵌在包内,所以这一条安装就够了——无需单独构建看板、无需 --web-root:
npm i -g @ganglion/xacpx-relay这会把 xacpx-relay 二进制放进你的 PATH。内嵌看板会在启动时自动检测;start 命令会打印它解析出的路径(见下文)。
改为从源码运行(开发 / 贡献)
克隆仓库并构建服务端——build:relay 同时会构建看板并把它嵌入到 packages/relay/dist/relay-web:
git clone https://github.com/gadzan/xacpx
cd xacpx
bun install && bun run build:relay此时入口是 packages/relay/dist/cli.js——下文中所有 xacpx-relay <command> 都用 node packages/relay/dist/cli.js <command> 代替。
2. 快速上手(无需任何参数)
认证完全基于令牌——没有密码、没有管理员账户、没有邀请流程。一个令牌就是一个用户。签发令牌后启动服务端:
# 步骤 A — 创建用户 + 令牌(DB 自动创建于 ~/.xacpx-relay/relay.db)
xacpx-relay add token
# 步骤 B — 启动服务端(使用相同的默认 DB;看板自动检测)
xacpx-relay startadd token 会只打印一次令牌:
access token: bBS9nN2W2MwdrdksoLTLrQeMLMah9M5flTOyEcBbIHc
(store it now — not shown again)
hint: use this token for web login AND: xacpx channel add relay --url <host> --token <token>start 会确认运行状态:
xacpx-relay listening: http :8787, instance gateway: merged on http :8787 (path / or /gateway), db ~/.xacpx-relay/relay.db, dashboard: /usr/lib/node_modules/@ganglion/xacpx-relay/dist/relay-web打开 http://<host>:8787,将令牌粘贴到 Access token 输入框,即可登录。
3. 令牌管理
Hub 一共有 4 条 CLI 命令,所有标志均为可选:
add token — 创建用户 + 登录令牌
xacpx-relay add token [--label <备注>] [--db <路径>]每次调用都会创建一个独立的用户,并只打印一次访问令牌。同一个令牌可用于:
- 登录 Web 看板(粘贴到 Access token 输入框)。
- 配对 xacpx 实例(在
channel add relay中传入--token <T>)。
在多个实例上复用同一个令牌,可将它们归属于同一个用户。
ls — 列出令牌
xacpx-relay ls [--db <路径>]显示:短 ID、备注标签、创建日期、已配对实例数量。
rm token — 吊销令牌(及其用户)
xacpx-relay rm token <值或ID> [--db <路径>]删除该令牌对应的用户,并级联删除其实例、Web 会话和缓存消息。吊销后,该令牌的 Web 会话将在下次请求时失效;已打开的看板 /ws 长连接会在下次重连时才断开。若要立即强制断开所有会话:停止 Hub,执行 sqlite3 <db> "DELETE FROM web_sessions;",然后重启。
start — 启动服务端
xacpx-relay start \
[--db <路径>] \
[--web-root <目录>] \
[--host 0.0.0.0] \
[--http-port 8787] \
[--ws-port <n>] \
[--history-retention-days 30] \
[--request-timeout-ms 120000] \
[--trust-proxy]| 标志 | 默认值 | 用途 |
|---|---|---|
--db <路径> | ~/.xacpx-relay/relay.db | SQLite 数据库文件。目录会自动创建。 |
--http-port <n> | 8787 | HTTP API 以及看板的 /ws 广播。 |
--ws-port <n> | (已合并) | 省略则把实例网关合并到 HTTP 端口(单端口默认)。传入一个端口则会启动一个可单独配置防火墙的专用网关监听器。 |
--host <地址> | 0.0.0.0 | 绑定地址。 |
--web-root <目录> | (自动检测) | 看板资源目录。自动解析包内嵌入的看板(cli.js 旁边的 dist/relay-web);仅在需要覆盖时才传入。 |
--history-retention-days <n> | 30 | 超过此天数的缓存消息会被每小时清理一次(同时硬性上限为每会话 2000 条)。 |
--request-timeout-ms <n> | 120000 | 代理到实例的每次请求超时时间。 |
--trust-proxy | (关闭) | 信任 X-Forwarded-For 用于限速。在反向代理后面运行时传入此参数;绝不在直接暴露于公网时使用(否则会导致 IP 伪造)。 |
没有 stop/status 子命令——请用 Ctrl-C / SIGTERM 停止 Hub(生产环境请在 systemd、pm2 或 Docker 下运行以管理生命周期)。
限速说明
Hub 会对每个客户端 IP 进行限速,并设有全局失败上限。在反向代理后面运行时,传入 --trust-proxy,使 Hub 使用 X-Forwarded-For 中的真实客户端 IP 进行限速,而非代理的回环地址。
4. 配对 xacpx 实例
挂载实例
在运行 xacpx 实例的机器上,使用步骤 2 中创建的同一个令牌添加 relay 连接器频道:
xacpx plugin add @ganglion/xacpx-channel-relay
xacpx channel add relay --url relay.example.com --token <T> [--name home-pc]
xacpx restart把 --url 指向与看板相同的主机——裸域名会解析为 wss://relay.example.com,合并后的网关与该主机共用。xacpx plugin add 会从 npm 安装连接器,并自动拉取其依赖 @ganglion/xacpx-relay-protocol。实例侧的 xacpx 核心需 ≥ 0.11.0(连接器的 peer 要求)。
从源码检出目录配对(开发)
如果你从仓库检出目录运行实例,workspace 已经链接好了 channel-relay 与 relay-protocol——跳过 plugin add,直接运行 xacpx channel add relay … 即可。
--url 简写规则
--url 接受以下多种形式;连接器会自动将其规范化为完整的 WebSocket URL:
| 传入的值 | 解析结果 |
|---|---|
relay.example.com(裸域名) | wss://relay.example.com |
1.2.3.4(IP) | ws://1.2.3.4:8787 |
1.2.3.4:9000 | ws://1.2.3.4:9000 |
localhost | ws://localhost:8787 |
host:9000 | ws://host:9000 |
ws://… 或 wss://… | 原样使用 |
http://… 或 https://… | 映射为 ws://… / wss://… |
IPv6
不支持未加方括号的裸 IPv6 地址。请使用 [::1]:8787 格式。
配对工作原理
首次连接时,实例会用访问令牌换取一个长期有效的专属凭据,以 0600 权限写入 <xacpx-home>/relay/credential.json——绝不会写入 config.json(后者只保存 url/name)。令牌仅在首次配对时使用——后续所有重连均使用存储的凭据。
一个令牌,多个实例
可以在多台机器(如 home-pc、work-laptop)上复用同一个令牌,它们都会在看板中归属于同一个用户。
从双端口(0.1.0)布局升级
默认已改为单端口。早先针对旧 8788 网关配对过的连接器,其 config.json 里冻结了 ws://<host>:8788(URL 只在 channel add 时规范化一次),所以它会继续连 :8788。请重新运行 xacpx channel add relay --url <host> …(或直接改存储的 url),让它指向合并后的 HTTP 端口。裸域名连接器(wss://host,不带端口)不受影响——它们本就落在合并网关上。
回到看板,实例上线后会出现在左栏,并带有一个绿色圆点。选中某个会话即可聊天;打开任务面板(右栏,或移动端的 Tasks 按钮)可查看定时任务与编排任务。
TLS 与反向代理
Hub 只说明文 HTTP 和 WS。对于任何非 localhost 的部署,请在反向代理处终止 TLS,并转发那一个 HTTP 端口。实例随后应使用 wss:// 连接。
看板的实时更新会在 HTTP 端口上发起 WebSocket 升级,因此代理也必须在该路由上允许升级。合并后的实例网关通过根路径上的 WebSocket 升级搭乘同一端口,因此这一条被代理的路由即可同时覆盖两者。
Caddy
relay.example.com {
reverse_proxy 127.0.0.1:8787
}Caddy 会自动为看板的 /ws 和网关根路径代理 WebSocket 升级——无需额外配置。
nginx
server {
listen 443 ssl;
server_name relay.example.com;
# ssl_certificate ...; ssl_certificate_key ...;
location / {
proxy_pass http://127.0.0.1:8787;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}进阶:专用网关端口(--ws-port)
如果你用 xacpx-relay start --ws-port 8788 启动 Hub,实例网关会获得自己的端口,而不再搭乘 HTTP 端口。届时你可以再加一个被代理的域名(gateway.example.com → 8788),并让连接器指向 wss://gateway.example.com。仅当你想把网关与看板分开配置防火墙时才需要这样做。
应该暴露哪些端口
| 端口 | 面向对象 | 是否公开暴露? |
|---|---|---|
| 8787 | 浏览器(看板 + API + 看板 /ws)以及 xacpx 实例(合并后的网关) | 是,需置于 TLS 之后 |
| 8788 | 仅当你启用 --ws-port 时——专用实例网关 | 是,需置于 TLS 之后 |
| — | relay.db | 绝不——它是一个本地文件 |
令牌与用户管理
- 添加更多用户/实例: 再次运行
add token——每次调用都会创建一个独立的用户。在多个实例上复用同一个令牌,可将它们归属于同一个用户。 - 审计:
ls显示所有令牌的备注标签、创建日期和实例数量。 - 吊销:
rm token <值或ID>删除该用户,并级联删除其实例、Web 会话和缓存消息。该令牌的 Web 会话将在下次请求时失效;已打开的/ws长连接会在重连时才断开。 - 强制全局重新登录: 停止 Hub,执行
sqlite3 <db> "DELETE FROM web_sessions;",然后重启。 - 自动 GC: 一个每小时运行的维护循环会清理超过
--history-retention-days(以及每会话 2000 条上限)的缓存消息,并删除过期的 Web 会话。无需配置 cron。
持久化与备份
所有数据都存放在 --db 指定的那个 SQLite 文件中(默认为 ~/.xacpx-relay/relay.db)。备份时,停止 Hub(或在空闲时刻做快照)并复制该文件:
cp ~/.xacpx-relay/relay.db /backups/relay-$(date +%F).db丢失它就意味着丢失所有令牌用户、实例注册信息和缓存历史——届时实例需要重新配对。
在 systemd 下运行(示例)
# /etc/systemd/system/xacpx-relay.service
[Unit]
Description=xacpx relay hub
After=network.target
[Service]
# 使用已安装二进制的绝对路径——用 `command -v xacpx-relay` 查出来。
ExecStart=/usr/bin/xacpx-relay start --host 127.0.0.1
Restart=on-failure
User=xacpx
[Install]
WantedBy=multi-user.target绑定到 127.0.0.1,让你的反向代理面向公网。DB 和看板会从默认位置自动检测;仅在需要非默认路径时才添加 --db。
常见问题排查
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 看板 404 / 空白页 | start 打印了 dashboard: (none)——没找到内嵌的看板 | 看板随 @ganglion/xacpx-relay 一起出厂;重装该包即可。源码检出时用 bun run build:relay 重建(它会把看板嵌入 dist/relay-web)。 |
| 实例始终不变绿 | 网关 URL 错误、令牌被吊销或输入有误 | 把 --url 指向与看板相同的主机(合并后的网关与 HTTP 端口共用);仅当你以 --ws-port 启动 Hub 时,才使用单独的 :8788 主机。如果令牌已被吊销,重新运行 add token 并重新配对。 |
自定义 --db 未生效 | --db 在不同命令间传入不一致 | 在 add token 和 start 中传入相同的 --db 路径;默认的 ~/.xacpx-relay/relay.db 是固定的绝对路径,省略 --db 是安全的。 |
| 经过代理后实时更新卡住 | 代理未在 8787 上转发 WebSocket 升级 | 在看板路由上允许 Upgrade/Connection 请求头。 |
另请参阅
docs/relay-module.md—— 服务端 + 连接器内部实现。docs/relay-web-module.md—— 看板架构。- 设计规范:
docs/superpowers/specs/2026-06-13-relay-hub-design.md。