RustDesk iOS 定制:接入私有 Rustk API Server,改善移动端远控体验
现有远程控制环境采用 Rustk API Server、hbbs 和 hbbr 组合部署:账号与地址簿服务只在 Tailnet 内开放,远控信令和中继保留公网入口,私有网络由自建 Headscale 管理。
这次基于 RustDesk 1.5.0 定制 iOS 客户端,重点解决两件事:让应用自身访问私有 API,同时让 iPhone、iPad 上的设备选择、画面方向和触摸操作更顺手。
这套部署将账号管理、连接协调和会话数据传输分开处理:
- Rustk API Server:提供账号登录、设备管理和地址簿同步,服务端口为
21114,仅通过 Tailnet 私网访问。 - hbbs:提供设备注册、ID 查询和连接协调,主入口为公网
21116;当前部署使用支持 TCP KeyExchange 的补丁版本。 - hbbr:使用官方 RustDesk OSS 中继服务,主入口为公网
21117,负责需要中继时的会话数据转发。 - Headscale:自建的 Tailscale 控制服务,负责节点注册、授权及网络信息分发。它与 Rustk API Server 是两个独立服务,不承担远程桌面流量中继。
下图展示定制后的 iOS 客户端如何接入现有部署。按服务职责划分逻辑链路,不表示这些组件必须部署在不同物理主机上;图中省略了域名、节点地址及其他辅助端口。
对于这套部署,客户端只需要让 API 请求走内嵌 Tailnet,ID 和中继继续走原有网络。远程画面并不经过 Rustk API Server;图中画的是中继连接,具备直连条件时仍由原有 RustDesk 连接机制处理。
原客户端访问私有 API 时,需要设备先具备到该私网的连通性。定制的目标是将这部分网络能力收进 RustDesk 应用,让它以独立节点加入 Headscale 管理的网络,不再要求用户先切换到另一个 App 建立系统 VPN。
定制内容与实现思路
1. 按服务分流,保留原来的协议与认证
内嵌网络基于官方 Go tsnet。Flutter 管理设置和状态,Swift 通过 MethodChannel 负责节点生命周期与 Keychain,Go 编译为 iOS 静态库提供网络能力,Rust 则继续处理现有 HTTP 和 TCP 协议。
对于 Rustk API Server,请求沿着以下路径进入私网:
登录 / 设备 / 地址簿
→ 原有 Flutter / Rust HTTP 入口
→ 受限的本机转发端口
→ 内嵌 tsnet 节点
→ Tailnet 内的 Rustk API Server
转发器只接受明确配置的目标,原有账号认证、Server Key 和 secure TCP / KeyExchange 流程继续保留;HTTPS 的证书校验也仍由 Rust 客户端完成。不能为了让第三方 API 或补丁版 hbbs 连通,就跳过已有的校验。
API、ID、Relay 分别提供分流开关。内嵌 Tailscale 总开关默认关闭;在本文部署中,开启后只需启用 API 分流。这样账号和地址簿可访问私有服务,而其他平台和未启用分流的连接继续沿用原来的路径。
如果以后把 hbbs/hbbr 也迁入 Tailnet,可以再开启对应开关。不过当前内嵌 ID 分流只覆盖 TCP 信令和在线查询,启用后会要求会话走中继路径,尚未接入原生 UDP、P2P 等完整能力。
2. 明确 Headscale 配置和节点状态
设置页增加独立的控制服务器地址,用于填写自建 Headscale 的 URL。它决定节点向哪里注册;Rustk API、ID 和中继地址仍在原有“ID/中继服务器”中配置,两类设置各管各的职责。
授权通过控制服务器生成的链接完成,支持打开浏览器,也提供复制链接入口。Headscale 可以按部署配置采用管理员注册或 OIDC 登录,客户端沿用其注册流程。更换控制服务器时,会提示清除本应用原来的节点身份后重新授权,避免跨网络复用身份。
首页同时增加 Tailnet 状态条:连接中、已连接、需要授权、等待设备批准和连接失败都能直接看到;已连接时展示节点 IP,点击可进入设置。这样 API 不可用时,首先能区分网络尚未接通和业务服务异常。

3. 围绕移动端的使用顺序调整界面
首页以地址簿和设备列表为中心,保留手动输入 ID,同时提供收藏、最近连接、在线设备等入口。iPhone 使用紧凑的分段入口,较宽的 iPad 窗口使用侧栏,继续复用现有设备模型和地址簿同步逻辑。
进入远程会话时,iPhone 按配置请求横屏,退出后恢复首页方向;iPad 默认保留系统方向。悬浮工具栏集中放置键盘、鼠标模式、显示、旋转和更多操作,空闲时收起,减少对远程画面的遮挡。
“显示”菜单新增“双指缩放和拖动画布”选项,复用现有的画布锁定能力。关闭后,普通双指手势不会再缩放、平移本地画布,远端鼠标点击和拖拽仍沿用原有处理。这里不重写手势识别或坐标换算,减少横屏、键盘和触摸模式之间的相互影响。


4. 配置与身份独立于安装包保存
服务器地址、Headscale 地址和界面开关保存在应用用户数据区。画布手势选项也会持久化,下一次会话和重启应用后恢复上次选择。
节点身份加密存放在 Library/Application Support/embedded-tailnet,解密密钥保存在 Keychain。普通断开和禁用会保留身份,清除本地身份、授权新节点或更换控制服务器才会主动重置。
正常覆盖更新应保留这些数据。使用 LiveContainer 时,要继续使用同一个应用标识、数据容器和 Keychain;新建容器、清除数据或更换设备不能按普通更新处理。节点身份目录不参与设备备份,密钥也绑定设备,因此复制普通配置文件不等于迁移了授权。
构建与使用中需要注意的坑
控制服务器必须在加入网络之前可达
Headscale 地址负责建立节点身份,不能只在“加入这个 Tailnet 后”才能访问,否则注册流程会形成循环依赖。配置时也要区分节点名称、控制服务器地址和业务 API 地址:早期状态里出现的 localhost 是授权前 SDK 返回的系统主机名,并不是要连接的服务器;现在授权前显示配置的节点名,获得网络信息后再显示实际名称和 IP。
节点启动完成也不代表授权链接已经生成。授权入口需要等待状态变化,并在超时或浏览器打开失败时给出反馈;只在点击瞬间检查一次链接,会表现为按钮没有反应。当前实现有等待窗口和复制链接入口,但不会假设浏览器一定能自动跳回应用。
Rust 与 Go 要在最终应用阶段一起链接
新增 Go 静态库后,如果 Cargo 先尝试生成独立的 Rust 动态库,Go 提供的 _rd_tailnet_port 等符号此时还没有参与链接,会出现未定义符号错误。
iOS 构建因此明确生成 Rust 静态库:
cargo rustc --locked --features flutter,hwcodec --release \
--target aarch64-apple-ios --lib --crate-type staticlib
随后由 Xcode 把 liblibrustdesk.a 和 libEmbeddedTailnet.a 一起链接进应用。这个调整只涉及 iOS 构建,不需要改变其他平台的产物类型。
缓存中的 Flutter SDK 可能已经打过补丁
缓存整个 Flutter SDK 时,修改过的源码也可能被缓存。恢复缓存后再次无条件执行 git apply,会报 patch does not apply,即使补丁本身兼容该 Flutter 版本。
处理方式是在应用前用 git apply --reverse --check 判断是否已经包含补丁:已应用则跳过,否则正常应用。不完整或确实不兼容的补丁仍然报错,避免用忽略退出码的方式隐藏问题。
构建环境也需要统一。CI 固定 Flutter 3.24.5、Rust 1.75、Go 1.26.5 和 Xcode 16.4;开发机使用更高版本 Flutter 时,可能先撞上现有依赖的 API 兼容问题,不能直接当作新功能本身的错误。
Xcode 归档和可导入的 IPA 是两种产物
.xcarchive.zip 是构建归档,不能直接作为 LiveContainer 的 IPA 使用。unsigned 模式需要把归档里的 Runner.app 按 Payload/Runner.app 结构重新打包,并检查 ZIP 完整性。
最终 iOS artifact 只上传 .ipa,不附带体积更大的 Xcode 归档;许可证和第三方声明保留在应用资源中。未签名 IPA 可供 LiveContainer 导入或后续重签名使用,但不等于能够通过 iOS 普通安装流程直接安装。
另外,Fork 需要启用 Actions,workflow_dispatch 工作流也需要先存在于默认分支,Actions 页面才有相应的手动运行入口,再选择要构建的分支。具体规则见 GitHub 手动运行工作流说明。
当前范围
这轮改动集中在 iOS:按现有部署接入私有 API,保留原来的远控协议,再补齐移动端界面和配置持久化。增强逻辑主要放在独立模块,共享入口只增加必要的条件判断,方便后续同步上游。
自动横屏受 iOS 窗口策略限制,后台连接受系统生命周期限制;内嵌网络也不提供通用系统 VPN 的全部能力。完整体验仍应结合实际 Headscale、Rustk API Server、hbbs/hbbr 和设备验证,尤其是账号登录、地址簿同步、连接协商及覆盖更新后的身份复用。
相关实现保存在 suhli/rustdesk,配置和实现细节可参考对应提交的 iOS 增强文档。

