Files
update-client/docs/architecture.md
T

7.5 KiB
Raw Blame History

更新客户端架构

目的

更新客户端强制执行一条受控的产品入口路径,并执行事务性更新,而不会将可编辑的安装文件作为信任边界的一部分。父项目 SimCAE 拥有集成和交付的控制权。

本文档描述了运行时合约。服务器发布、管理员界面、数据库模式和对象存储实现均在此仓库之外。

产品入口规则

在每个支持的操作系统上,必须通过 Launcher 进入发布版本。如果某个平台尚未提供可用的启动器和更新运行时,其发布配置或打包必须失败。直接启动应用程序不是可接受的备用方案。

调试版本可以禁用启动门以供开发者迭代使用。但带有此例外的构建版本不得分发。

组件

启动器

启动器 是面向用户的入口。它:

  1. 加载嵌入式产品配置和机器范围的可变状态。
  2. 建立或验证设备凭证。
  3. 加载并验证已签名的版本策略。
  4. 检查防回滚、系统时钟倒退和离线有效性条件。
  5. 将已安装的版本与已签名的清单进行比对验证。
  6. 在有网络连接时检查服务器是否允许更新。
  7. 当需要更新时启动 Updater
  8. 否则创建一个短期有效的一次性票据并启动 SimCAE。

在需要提升权限才能进行机器范围状态或安装更新的平台上,发布启动器必须通过平台原生机制请求所需权限。

主程序

交付的 SimCAE 可执行文件实现了合同中的主应用程序部分。此仓库中的 MainApp 是一个集成示例,不是第二个产品可执行文件。

该应用程序必须:

  • 在正常启动前消耗并验证 --ticket-file=<path>
  • 拒绝在受控发布构建中缺失、过期、重复或不匹配的票证;
  • 仅在启动达到更新器预期的健康点后,将 ok\n 写入 --health-file=<path>

在当前集成中,启动器拥有已签名策略和已安装文件完整性检查。应用程序独立验证短期票证;将策略或清单验证移至 SimCAE 需要单独的共享运行时协议,且不得由本文件暗示。

启动票证阻止了对 Launcher 的随意绕过。它不是已签名策略、清单验证或服务器授权的替代品。

更新器

Updater 拥有更新事务。它:

  • 获取已签名的目标清单和授权下载位置;
  • 在进行任何文件操作前验证路径;
  • 比较本地文件与完整清单;
  • 仅下载缺失或不匹配的文件;
  • 在安装前验证签名、大小和哈希值;
  • 备份被替换或删除的文件;
  • 安装阶段内容;
  • 在必要时将锁定文件的替换委托给 Bootstrap
  • 使用启动票和健康文件请求启动更新后的应用程序;
  • 仅在健康确认后提交;
  • 在失败时恢复上一版本。

下载可以是增量的,但完整性验证始终针对完整的签名清单。

引导启动

Bootstrap 有意设计得非常小。它会等待更新程序进程释放被锁定的文件,执行已批准的替换方案,并重新启动更新程序以继续验证。它必须拒绝不安全的相对路径,且不得擅自制定更新策略。

通用运行时

共享库提供配置访问、原生状态、设备标识、HTTP 请求、签名策略处理、本地防回滚状态、清单完整性验证以及启动票操作。共享辅助工具不会削弱组件特定的检查。

启动序列

用户
-> 启动器
-> 嵌入式产品配置
-> 机器状态和设备凭证
-> 签名策略和清单验证
-> 是否需要更新? -> 更新器 -> 需要时启动引导程序
-> 创建一次性票据
-> SimCAE
-> 消耗票据
-> 重新验证策略和完整性
-> 按请求报告启动健康状态

设备激活是机器识别,而不是终端用户登录。普通客户端启动不会创建管理员会话,也不应接收服务器的发布权限。

更新事务

一个持久的事务必须至少区分这些阶段:

  1. 初始化事务标识符和暂存区。
  2. 验证目标清单和所有暂存的负载。
  3. 记录已更改和过时的路径。
  4. 备份当前文件。
  5. 安装未被锁定的文件。
  6. 当需要时,将锁定文件的工作交给 Bootstrap
  7. 启动候选版本并等待其健康标记。
  8. 提交并删除临时数据仅在成功验证后进行。
  9. 在任何失败后回滚并验证恢复的版本。

在进程启动时,必须在开始新事务之前恢复中断的事务。在持久化状态仍需要恢复时,不得删除暂存和备份数据。

信任边界

数据或机制 权限 说明
服务器私有签名密钥 仅限服务器 从未运送到客户。
已签署的版本策略 服务器 控制运行、更新、降级和离线决策。
签名的清单 服务器 定义了完整的受保护文件集合和预期的哈希值。
签名的设备凭证 服务器 绑定应用程序、频道、安装、设备和许可证身份。它不是用户会话。
嵌入式产品配置 构建流水线 防止随意覆盖安装文件;可由管理员检查和修改。
原生可变状态 本地机器 保持注册和安装状态;它不涉及授权。
一次性启动票 本地启动器 证明预期的启动器启动了此进程。它不授予服务器权限。
健康标识 候选流程 信号提交或回滚的启动完成;它不能证明包的真实性。

带签名的 JSON 合约必须使用确定性序列化,从已签名的负载中省略签名字段,标识算法和密钥 ID,并支持受控的公钥轮换窗口。验证失败将导致系统关闭。

首次安装可能尚无当前版本的本地清单缓存。当设备在线、当前版本已在服务器显式发布且没有更高目标版本时,Launcher 会通过与 Updater 相同的校验逻辑获取当前版本清单,验证签名和身份后写入缓存,再执行完整文件校验。服务器未发布当前版本、返回无效版本 ID、清单签名无效或缓存写入失败时,启动保持关闭失败。此流程不会自动创建或发布服务器版本。

运行时布局

父安装程序拥有最终的布局。预期的可执行文件关系为:

<install-root>/
bin/
Launcher<platform-suffix>
Updater<platform-suffix>
Bootstrap<platform-suffix>
SimCAE<platform-suffix>
config/
<仅在需要时缓存已签名的运行时>

安装目录中不应包含可编辑的产品策略 JSON 文件。更新缓存、备份、已签名策略缓存和机器状态必须使用集成运行时选择的位置,并且必须遵守平台的写入权限规则。

失败策略

  • 无效签名、不安全路径、身份不匹配、策略回滚以及受保护文件损坏将导致系统关闭。
  • 一个临时的网络故障可能会使用仍然有效的已签名离线策略;它不能静默地创建新的允许项。
  • 过期的离线策略或被禁止的版本会阻止启动。
  • 失败的候选健康触发器会导致回滚。
  • 回滚失败会留下可恢复的状态和诊断错误;它不能报告更新成功。

接受场景

发布接受必须涵盖:

  • 通过 Launcher 正常启动;
  • 直接 SimCAE 启动拒绝;
  • 首次设备注册及后续离线启动;
  • 更新到较新的已发布版本;
  • 篡改的受保护文件被拒绝;
  • 篡改的策略或清单被拒绝;
  • 强制更新和禁用版本行为;
  • 频道选择和禁止降级行为;
  • 中断更新恢复;
  • 候选失败和成功回滚;
  • 从实际安装位置启动并更新,通过正常的最终用户交互并需要提升权限。