文档
实验性 源文件

远程访问

从手机或另一台电脑连到你 Mac 上的 Deskfolk:自己部署中继、让 Mac 连上它、配对设备。默认关闭,页面在构建时由仓库里的 docs/remote-access.zh.md 生成。

本页
  1. 它是怎么接起来的
  2. 配对过的设备能做什么
  3. 开始之前
  4. 1. 部署中继
  5. 2. 让 Mac 连上中继
  6. 3. 配对手机
  7. Web Push(可选)
  8. 不对劲的时候

远程访问(实验性)

默认关闭,也还不是做完的功能。 远程访问是供集成测试用的实验原型。发布包目前还不能配对设备:在打包门通过之前,配对只能在源码态、带开发开关的 Deskfolk 上做。独立安全复核(S-rev)、真机主屏幕 WebAuthn(G-uv)、真机 L1 与真机主屏幕 Web Push(G-push)都没有通过。不配对的时候就把公网配对关着,也别把经不起外泄的东西放到这条路上。

远程访问让手机,或者另一台电脑上的浏览器,连到你 Mac 上的 Deskfolk:看会话、回消息、处理等你的事、翻工作区、在你的终端里敲命令。没有 Deskfolk 的云。中继由你自己部署,Mac 只往外连它,中继只转发它自己解不开的密文。

它是怎么接起来的

  • 你的 Mac 仍然是唯一干活的地方。守护进程向中继保持一条出站的控制连接,每连上一台设备再多开一条。Mac 上不监听任何入站连接。
  • 中继是一个跑在 Caddy 后面的小 Bun 服务,放在你自己的服务器和域名上。它用 HTTPS 提供手机端页面——托管信使,一个能装到主屏幕的网页——并在 Mac 和每台设备之间转发不透明的 Noise 帧。它只存登记用的公钥,日志只记固定的事件码,解不开任何一条消息。
  • 每台设备由你在 Mac 旁配对一次。之后它和 Mac 之间走端到端的 Noise IK,中继只负责把字节送到。

单人、单台 Mac、最多 16 台已配对设备。链路断着的时候什么都不排队:Mac 睡了或者连不上,设备上会直接说连不上,不发任何东西;Mac 回来后它自己重连。

配对过的设备能做什么

  • 会话——会话列表、看和发消息、@ 点名、Stop,回答批准卡和提问。等你的事标在会话上,和 Mac 上一样。
  • 工作区——在 Mac 上逐级浏览目录,打开或下载 50 MiB 以内的文件。图片先到缩小的副本,原图点一下就有;音视频边加载边播放。
  • 你的终端——看守护进程持有的 shell 并往里敲,底部两排键补上 Esc、Tab、Ctrl、方向键和粘贴。手指上下滑动可以翻看输出历史,Claude Code、vim、less 这类全屏程序里也行。
  • 专门的「文件」会话——发到那里的文件复制进工作区 inbox/,文字留作给自己的备注,都不叫醒任何 Bot。文件按约 0.9 MB/s 上传,留在中继的共用限流以内,一段 20 MB 的视频大约 23 秒。上传时发送按钮转一圈进度,每个附件写着传了多少;没发出去的附件会留在作曲栏里。
  • 可选的 Web Push——由 Mac 发出的泛化提醒「Deskfolk 有待处理事项」。点开只是重连并回到会话列表,绝不批准任何东西。
  • 维护——状态、脱敏诊断、排空后重启运行时。这些操作和吊销其它设备都需要在本设备上做 WebAuthn 用户验证(面容 ID、触控 ID 或密码),没有点一下确认的替代;普通聊天不需要它。

改工作区目录、让 Mac 连哪个中继、批准新设备,仍然要人在 Mac 旁。

开始之前

  • 一台你能控制的 Linux 服务器:装好 Docker Compose,80 和 443 端口空着,有一个指向它的域名(下文用 relay.example.com)。
  • Mac 上从源码运行 Deskfolk:满足从源码启动的要求,bun 在 PATH 上。
  • 一部浏览器够新的手机。要用 Web Push 的话,iOS 需要 16.4 以上,并把页面添加到主屏幕。

1. 部署中继

在服务器上,进到本仓库的检出目录:

**生成一次性 bootstrap 令牌。**有了它,你的 Mac、也只有你的 Mac 能在这个中继上登记。直接写进检出目录之外的私有文件;别打印出来,别贴进聊天,也别放在命令行参数里。

umask 077
node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))' > /srv/deskfolk-relay/bootstrap

写一份私有 env 文件,同样放在检出目录之外。两个数据目录要事先建好,属主 UID 1000、权限 0700,最好放在限额 1 GiB 的文件系统上。

RELAY_ID=my-relay
RELAY_DOMAIN=relay.example.com
ACME_EMAIL=you@example.com
RELAY_STATE_DIR=/srv/deskfolk-relay/state
CADDY_DATA_DIR=/srv/deskfolk-relay/caddy
RELAY_BOOTSTRAP_FILE=/srv/deskfolk-relay/bootstrap
RELAY_ENABLED=1
RELAY_PAIRING_ENABLED=1
REMOTE_BIND_IP=0.0.0.0

RELAY_ENABLED 和 RELAY_PAIRING_ENABLED 默认都关:没有前者中继什么都不放进来,bootstrap 和配对还要后者。不设 REMOTE_BIND_IP 时 Caddy 只在 127.0.0.1 上发布 80/443;把它开到公网,正是那几道还没通过的安全门要管的一步,想清楚再开。

构建、启动、自检:

docker compose --env-file /srv/deskfolk-relay/remote.env -f deploy/remote/compose.yaml config --quiet
docker compose --env-file /srv/deskfolk-relay/remote.env -f deploy/remote/compose.yaml up -d --build
sh deploy/remote/check-health.sh /srv/deskfolk-relay/remote.env

Caddy 会为你的域名申请证书。现在打开 https://relay.example.com,应该能看到配对页。

不用 Docker 也行:同一个中继可以用 bun apps/relay/dist/main.js 跑(先 pnpm --filter @real-bot/relay build,Bun 1.4.2),环境变量不变;前面放任何一个逐条复刻 deploy/remote/Caddyfile 的 TLS 代理,由它提供 pnpm --filter @real-bot/messenger build:hosted 的产物。页面的 Content-Security-Policy 里带着这次构建的脚本哈希:每次重新构建都用 node deploy/remote/csp.mjs <产物>/index.html <头文件> 重新生成这个头,和产物一起发布,否则页面白屏。

全部变量、限额和恢复规则见自托管部署。

2. 让 Mac 连上中继

把 bootstrap 文件拷到 Mac 上(权限 0600),然后带开发开关从源码启动 Deskfolk:

REAL_BOT_DEV_REMOTE=1 pnpm dev

这个开关把 Mac 的远控身份存在守护进程数据目录的 dev-remote/credentials.json 里,不进钥匙串,并顶替触控 ID 的确认框。发布包没有这个开关:打包门通过之前,它封装好的凭据存储只会回 g_pack_not_verified。

另开一个终端,给这台 Mac 登记一次:

bun apps/daemon/scripts/dev-remote.ts init --origin https://relay.example.com --relay-id my-relay --bootstrap-file ~/deskfolk-bootstrap
bun apps/daemon/scripts/dev-remote.ts status

init 从文件读令牌——从不走命令行参数——打印这台 Mac 的主机 id,中继随即消耗掉这个令牌,不能再用第二次。之后把两台机器上的 bootstrap 文件清空。应用里 设置 → 通用 → 远控(实验性) 这时显示 远控在线。

3. 配对手机

  1. 在 Mac 上 设置 → 通用 → 远控(实验性) 里点 配对新设备。卡片上出现一段以 rb1 开头的一次性配对内容和这台 Mac 的签名指纹,十分钟后失效。(bun apps/daemon/scripts/dev-remote.ts pair 会打印同一段内容并放进剪贴板。)
  2. 把它送到手机上——通用剪贴板、隔空投送,或者一条用完就删的备忘录。它带着一次性密钥,别写进聊天和日志。
  3. 在手机上打开 https://relay.example.com,把它粘进 配对内容。页面会显示中继地址和 Mac 的指纹:和 Mac 上的对一下,一致再点 提交并等待 Mac 确认。配对内容只在提供这个页面的那个中继上有效,粘进来的东西没法把手机指到别处。
  4. Mac 上显示这台设备的名字和它的指纹。和手机上的对一下,再点 批准这台设备。
  5. 手机进入会话列表。把页面添加到主屏幕,以后像应用一样打开。
  6. 需要维护操作的话,在手机上 设置 → 远控(实验性) 里点 登记本设备用户验证。
  7. 把配对关回去:RELAY_PAIRING_ENABLED=0,再执行一次上面的 up -d。已配对的设备照常能用;要配下一台时再打开。

要去掉一台设备,在 Mac 的「已连接设备」列表里点 移除设备。中继立刻忘掉它的公钥,它的链路随即断开。

Web Push(可选)

提醒由 Mac 自己发出,直接发到手机浏览器的推送服务(Apple、Google 或 Mozilla);中继从不代发。消息里只说有事待处理,点开只回到会话列表。

  • **联系方式:**推送服务要求一个运营方联系方式,Mac 上没配之前推送一直暂停(push_contact_required)。通过 Mac 的本机接口配置:先 GET /v1/notifications/push-config 拿到当前 revision,再对同一路径 PATCH {"contact_uri": "mailto:you@example.com", "if_revision": <revision>}。
  • **代理:**Mac 要经代理上网的话,启动时带上 HTTPS_PROXY,例如 HTTPS_PROXY=http://127.0.0.1:7890 REAL_BOT_DEV_REMOTE=1 pnpm dev。只在系统设置里配代理,守护进程是拿不到的。详见 Web Push 代理。
  • **手机上:**在 设置 → 远控(实验性) 里打开 待办推送,再发一条测试。结果会分清已入队、推送服务已接受、首次超时已安排重试;手机上最终弹不弹,取决于浏览器和系统。

不对劲的时候

  • 远控已断开 / 执行主机不可达——Mac 睡了、Deskfolk 没在跑,或者中继挂了。手机会自己退避重连,一回到前台也会马上再试。
  • 远控信任不匹配——Mac 上存的远控身份和它的信任记录对不上了,比如恢复过数据目录,或删掉了 dev-remote/credentials.json。别为了绕过它去清中继的数据库:那是自托管部署里丢失主机密钥的恢复流程,所有设备都要重新配对。
  • 「配对窗口已过期」——在 Mac 上重新开一个;每段配对内容只管十分钟、只能用一次。
  • 重新构建后页面白屏——CSP 头和它哈希的那次构建没有一起发布。

协议、密码学细节和还没通过的门:远控协议。