Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

WebRTC 构建机器与数据通道

构建机器是真正运行你构建的组件。它直接和你的浏览器或 MCP 客户端 讲 WebRTC,在每个通道上强制执行基于许可的权限,在沙箱里跑构建本身。

一台构建机器的解剖

一台构建机器是一台带以下东西的 Linux 主机:

  • aegis-agent systemd 服务
  • 工具链 store(装着已安装的 IDF 版本和工具链)
  • 沙箱运行环境
  • 到构建服务器的出站 HTTPS 访问(无需入站端口)
  • 一个能作为 peer 的 WebRTC 栈

重要的是,构建机器永远不为构建流程开放入站 TCP 端口。它向构建服务器 建立出站连接来定期查看任务,然后通过构建服务器提供的 ICE 服务建立到 客户端的出站 WebRTC peer 连接。

定期查看任务

构建机器跑一个循环:

每 2 秒:
  GET ${CONTROL_BASE_URL}/agents/jobs?agent_id=...
  对每个新任务:
    本地验证许可签名
    如果许可中的 peer 证书指纹匹配我们即将收到的 offer:
      用配置的 ICE 服务打开 WebRTC peer 连接
      按许可中允许的通道协商通道
      运行构建,流式传送结果

这个循环在 INFO 级别是静默的 —— 只有错误会被记录。这是有意的; 聒噪的日志难读。要看查看活动,运维设 RUST_LOG=debug

三个数据通道(服务端强制执行)

当 peer 连接协商通道时,构建机器强制执行:

  • 通道名白名单 —— 只有许可中 allowed_channels 列出的通道会被 接受。任何不在白名单里的通道,构建机器在 ondatachannel 时立即拒绝 (并关闭)。
  • 每通道的 handler —— espctlptyfirmware 各有一个专用 handler,知道消息格式并产生结构化事件。即使许可允许了未知名字, 也会被拒(因为没有 handler)。
  • 带宽限制器 —— 每通道的滑动窗口字节计数器,按许可配置。 超过预算的突发会触发反压(写开始阻塞),而不是断开连接。
  • 消息率限制器 —— 同样的形状,但计的是消息而不是字节。用来 防御那些发大量小消息的病态紧密循环。

构建怎么跑

espctl 通道一旦打开,构建机器收到 BuildRequest 消息后:

  1. 构建机器在 /var/lib/aegis/workspace/{job_id}/ 下创建一个工作区。
  2. 如果请求包含 project_bundle(一段 base64 编码的 git bundle, ≤ 50 MB),构建机器把它写到临时文件,在沙箱外跑 git clone <bundle 文件> {workspace}/src
  3. 构建机器准备一个干净的沙箱配置:
    • {workspace}/src 读写挂载
    • 把 store 中相关的 IDF 版本只读挂载
    • 挂载一个小的可写 /tmp 用作构建临时空间
    • 丢弃所有 capabilities,禁止网络访问,禁止新挂载
  4. 在沙箱里,构建机器跑 idf.py build(或者配方指定的命令)。
  5. 编译进行中,构建机器读取子进程的 stdout 和 stderr,把行复用到 pty 通道上作为原始字节,在 espctl 通道上发送结构化的 PipelineEvent 消息(例如“phase: compiling, progress 0.42“)。
  6. 构建结束后,构建机器从工作区读取结果 .bin 文件,对内容计算 SHA-256,把字节作为 chunk 通过 firmware 通道送回(后面跟着一个 最终 manifest 消息,包含 SHA-256 和总大小)。
  7. 在可配置的延迟之后或者 peer 断开时,构建机器清理工作区。

线缆格式

espctl 通道上的消息默认对浏览器客户端是 JSON,对原生 Rust 客户端 是 bincode 编码的。构建机器从第一个字节自动检测编码。schema 在 aegis-proto crate 里;在 minor 版本之间稳定。

pty 通道是原始字节 —— 没有帧化,构建机器不加任何转义码。子进程写 到 TTY 上的任何东西都进通道。

firmware 通道使用一个微小的分块帧化:一个 header 消息声明 num_chunks + total_size + sha256,后面跟 N 个原始二进制 chunk。

数据队列上限和吞吐量

如果你在调性能,有一个微妙的点值得知道:

WebRTC 数据通道有一个可配置的每通道发送队列。 生产构建机器把那个队列上限设为 128 KB(#[cfg(test)] 构建用 128 MB 以避免阻塞单元测试,这可能误导随便做的基准测试)。

在 500 ms RTT 的 TURN 中继连接上,大致是:

128 KB / 500 ms = 256 KB/s 有效吞吐量

…对日志流和小固件镜像够用,但在大 *.bin 文件(~1 MB 起)上你 会注意到。无 TURN 中继的直连点对点连接显著更快。

失败模式

ICE 永远不收敛 —— 数据通道永远不打开。构建机器的 on_open handler 永远不触发,但 peer 连接状态在 ~5 秒后转到 Failed。客户端总应该 实现一个快速失败,在等待 on_open 的同时监视 Failed/Disconnected/ Closed 状态。

沙箱启动失败 —— 沙箱拒绝启动(缺 capability、宿主侧 seccomp 问题)。构建立即失败,在 espctl 通道上发结构化错误;数据通道保持 打开,客户端能读到。

构建进程超内存 —— 沙箱的内存 cgroup 杀掉子进程。构建机器在 结构化错误中报告这是一次构建失败,带 OOM 信号。数据通道保持打开。

许可在构建中过期 —— 构建机器在过期后拒绝颁发新的许可,但飞行 中的构建会跑完。构建服务器不会试图回溯撤销许可。如果你需要构建可 中断,用 build.cancel

另见