
04-OTA不砖机updaterd的签名验证、健康门控与自动回滚引子给机器人刷机为什么比给手机刷机难一万倍大家好我是黒漂技术佬。上一篇我们认识了 7 个守护进程其中有一个管生杀大权的家伙——updaterd负责把新固件刷进 microduck。给设备做 OTAOver-The-Air 空中升级对做过物联网的人来说是个熟悉的噩梦。但给机器人做 OTA噩梦的等级还要再上三层因为机器人有一个手机没有的致命特性手机刷机失败最坏结果是变砖返厂。机器人固件更新失败可能当场摔在地上——物理性的砖。想象这个场景鸭子正在桌上走OTA 触发新固件加载到一半控制环卡顿了一下——失去平衡“啪”800 克的鸭子拍在桌面上。或者更糟新固件装好了但步态策略是坏的一启动就原地转圈、撞墙、抽搐。所以 microduck 的 OTA 设计有一个非常明确的北极星指标无论什么情况都不能让机器人处于不可恢复的状态。宁可回滚到旧版也绝不允许刷死。这期文章我们就来拆解 updaterd 的完整设计签名验证 → 原子切换 → 健康门控 → 自动回滚一条完整的刷机安全链。一、先看发布模型整目录切换而不是打补丁microduck 的更新模型一句话就能说清swapped not patched整体切换不打补丁。新固件不是在旧固件基础上改几个文件而是一个完整的、自洽的新版本目录整体替换旧版本。/opt/robot/daemon/ ├── releases/ │ ├── v1.2.0/ # 旧版本完整目录 │ │ ├── robotd │ │ ├── updaterd │ │ ├── configd │ │ ├── btd │ │ ├── padd │ │ ├── mediad │ │ ├── tofd │ │ ├── policies/ # 神经网络策略文件 │ │ └── default-config.toml │ └── v1.3.0/ # 新版本完整目录新装的 ├── current - releases/v1.2.0 # ★ 软链接指向当前生效版本 └── rollback/ # 回滚时把 current 指回这里current是一个软链接。切换版本 改一个软链接指向而不是搬动任何文件。这是整个 OTA 设计的基石也是 Unix 哲学里用指针做状态切换的经典应用。为什么整体切换优于增量打补丁维度增量补丁整体切换升级包大小小大但嵌入式系统通常是整镜像差距没那么大一致性麻烦——如果中途失败系统处于半新半旧状态天然一致——目录要么完整存在要么不存在回滚难——要撤销补丁简单——软链接指回去就行依赖管理组件间版本耦合容易出问题整版本自洽不会出现A 是新的、B 是旧的对于一套 7 个 daemon 的紧密耦合系统“整体切换几乎是唯一正确选择——它把系统处于什么状态这个问题简化成了软链接指向哪个目录”。二、升级流水线从 GitHub 到鸭子落地updaterd 的完整升级流程这是整个设计的核心值得画下来┌────────────┐ ┌──────────────┐ ┌──────────────────┐ │ GitHub │ │ updaterd │ │ robotd │ │ Release │ │ │ │ │ │ │ │ ① 下载验签 │ │ │ │ (签名包) │────►│ ② 校验SHA-256 │ │ │ │ │ │ ③ 解压(zstd) │ │ │ │ │ │ ④ 原子落盘 │ │ │ │ │ │ ⑤ 切换current │ │ │ │ │ │ ⑥ 重启服务 │────►│ ⑦ 启动自检 │ │ │ │ │◄────│ ⑧ 回报health │ │ │ │ ⑨ 健康门控 │ │ │ │ │ │ 健康→保留 │ │ │ │ │ │ 不健康→回滚 │ │ │ └────────────┘ └──────────────┘ └──────────────────┘第 1 步签名验证minisign升级包在 CI 构建时就用私钥签了名。updaterd 下载后第一件事是验签——用内置的公钥验证包的签名是否合法。这保证了包确实来自官方 CI不是中间人篡改的包没有被任何人在传输过程中动过手脚。使用的签名工具是minisign——一个比 GPG 更轻量、更现代的签名方案。嵌入式场景选 minisign 而不是 GPG理由和选 JSON-RPC 而不是 D-Bus 一样够用、轻、现代。第 2~3 步完整性校验 解压验签通过后再校验 SHA-256 哈希然后解压格式是 zstd tar——zstd 压缩率高且解压速度快适合嵌入式。注意一个实现细节文档提到递归删除树用spawn_blocking同步执行——因为删目录树是 CPU/IO 密集操作不能阻塞 tokio 的异步运行时。异步世界里CPU 密集操作要交给专门的阻塞线程池——这是 Rust 异步编程的经典正确姿势细节见真章。第 4~5 步原子落盘 切换新版本先完整落盘到releases/v1.3.0/这个过程失败不影响当前版本——因为 current 还指向旧版落盘成功后原子地把current软链接切换到新版本。“原子的意思是切换操作要么成功要么失败不存在中间状态。哪怕切换的瞬间断电重启后系统依然是一个完整可用的状态最多是 old 或 new 其中之一绝不会是半新半旧”。第 6~8 步重启 健康门控这是整个设计最精彩的部分。服务重启后updaterd 不会立刻宣布升级成功。它会去问 robotd 的robot.health接口“新固件跑起来了吗状态健康吗”健康→ 升级确认保留新版本不健康进程起不来、控制环异常、状态机报错→自动回滚到旧版本。升级完成 → 重启 → 问 robot.health ├── healthy → ✅ 保留新版本 └── unhealthy → 自动回滚旧版本这个健康门控health gate的价值怎么强调都不过分它把升级是否成功的判定从安装完成推迟到了运行良好。很多 OTA 系统的 bug 恰恰在于把装上了当成了成功了——装上不等于能跑。第 9 步boot counter 兜底还有一个更深的保险boot counter启动计数器。假设新固件装好后能启动但启动后 30 秒才崩溃比如某个延迟初始化的模块炸了——此时健康门控可能已经确认过健康了来不及回滚。怎么办boot counter 的思路每次开机启动计数器 1只有健康运行超过一定时长后才清零。如果系统反复崩溃重启计数器持续增长到阈值引导程序就判定这个版本有毒自动回退到上一个版本。这和我们熟悉的 Android Recovery 模式、路由器双分区dual-bank设计是同一个思想只不过实现更轻。“用计数器识别 crash-loop”是嵌入式可靠性设计的经典手段值得所有做设备的团队抄作业。三、日志刷机过程也要可审计升级是高风险操作所以 updaterd 对日志的执念也写进了设计更新历史记录在/var/lib/robot/updater/update-log.jsonlfsync 追加、原子重写、保留 200 条、跨断电存活。拆开看每一条都是讲究fsync 追加写完必须刷盘——防止断电丢日志升级时最容易断电原子重写日志文件结构不被写坏保留 200 条嵌入式存储有限日志也设上限跨断电存活任何时刻掉电日志记录都不损坏。为什么要这么较真因为现场排障时最需要的就是上次升级到底发生了什么的完整记录。设备厂商最怕的售后场景是用户说升完级就坏了但你既没有升级日志、也没有版本记录只能嗯嗯我们查一下然后不了了之。审计日志是升级系统的安全气囊。另外还有一个贴心的设计robotctl version会报告四维版本信息——系统版、安装版、运行版、回滚版——让运维一眼看出当前装的版本和实际跑的版本是否一致。这个版本分歧可视化在排查 OTA 半成功状态时极其有用。四、工程哲学为什么这套设计高级技术细节讲完了我想聊聊更本质的东西——这套 OTA 设计背后的工程哲学。哲学一把失败当作默认假设整个 updaterd 的设计从签名、原子切换、健康门控到 boot counter本质上是一句话升级会失败不是异常而是常态。系统设计的目标不是防止失败而是保证失败后可恢复。验签失败放弃安装旧版继续跑解压失败目录不完整current 不切换启动不健康自动回滚崩溃循环boot counter 兜底。每一层失败都有对应的恢复路径没有任何一层失败会导致不可恢复。这就是面向失败设计design for failure的完整实践。哲学二恢复路径要越来越简单注意一个趋势这套系统的恢复手段是分层的——第一层健康门控回滚软件自动 第二层boot counter 回退引导程序自动 第三层SSH / 重新刷镜像人工最后的保险越往下手段越原始、越可靠、越不依赖出故障的系统本身。最底层的重新刷镜像甚至不依赖鸭子上的任何软件——只要硬件没坏总能救回来。永远保留一条不依赖系统自身的逃生通道这是嵌入式系统的生存法则。哲学三升级决策权和升级操作权分离细心的读者可能已经发现触发升级的指令来自客户端robotctl update apply但执行升级的是独立的 updaterd验证健康的是 robotd而这些都是互不信任的独立进程。updaterd 不会因为客户端说升级就盲从——它要自己验签robotd 不会因为updaterd 说健康就放行——健康是它自己报告的就算 updaterd 被攻破它也只能改固件文件改不了 robotd 的实时控制进程隔离。权力分散 接口互相校验这套思路在安全系统里叫最小信任least trust——每个组件只信任它该信任的最小集合。五、我们能学到什么把这篇的干货提炼成给读者的四句话整体切换优于增量补丁。用软链接做版本切换天然一致、回滚简单。你的设备不管有没有 OTA版本管理都该这么设计。“装上了≠成功了”。升级确认必须等运行健康而不是安装完成。健康门控health gate是 OTA 系统最值得抄的设计。面向失败设计。每一层失败都要有恢复路径而且恢复手段要越往下越简单、越不依赖系统自身。日志要能跨断电。fsync 原子写 上限管理。升级日志是售后的安全气囊平时看不见出事就是救命稻草。小结updaterd 的 OTA 设计本质上是一套**把失败当常态的可靠性工程**签名验证挡外敌原子切换保一致健康门控验真相boot counter 兜崩溃审计日志留证据。每一环单独看都不算新奇但串成一条完整的安全链就成了教科书。这种设计最打动我的地方在于它所有机制的目标不是升级成功而是**“任何时候系统都可恢复”**。这个目标设定决定了整个系统的气质。到这里microduck 的运维三件套控制、通信、升级就拆完了。但还差最后一块拼图也是这只鸭子最神奇的地方——它那些会走路、会站起来的本领不是程序员一行行写出来的而是在仿真里用强化学习自己练出来的。下一篇我们聊聊 sim2real从 MuJoCo 仿真到真鸭子落地PPO 策略是怎么穿越仿真与现实的鸿沟的。我是黒漂技术佬咱们下篇见。