Files
update-client/docs/architecture.md
T

163 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 更新客户端架构
## 目的
更新客户端强制执行一条受控的产品入口路径,并执行事务性更新,而不会将可编辑的安装文件作为信任边界的一部分。父项目 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 请求、签名策略处理、本地防回滚状态、清单完整性验证以及启动票操作。共享辅助工具不会削弱组件特定的检查。
## 启动序列
```text
用户
-> 启动器
-> 嵌入式产品配置
-> 机器状态和设备凭证
-> 签名策略和清单验证
-> 是否需要更新? -> 更新器 -> 需要时启动引导程序
-> 创建一次性票据
-> SimCAE
-> 消耗票据
-> 重新验证策略和完整性
-> 按请求报告启动健康状态
```
设备激活是机器识别,而不是终端用户登录。普通客户端启动不会创建管理员会话,也不应接收服务器的发布权限。
## 更新事务
一个持久的事务必须至少区分这些阶段:
1. 初始化事务标识符和暂存区。
2. 验证目标清单和所有暂存的负载。
3. 记录已更改和过时的路径。
4. 备份当前文件。
5. 安装未被锁定的文件。
6. 当需要时,将锁定文件的工作交给 `Bootstrap`
7. 启动候选版本并等待其健康标记。
8. 提交并删除临时数据仅在成功验证后进行。
9. 在任何失败后回滚并验证恢复的版本。
在进程启动时,必须在开始新事务之前恢复中断的事务。在持久化状态仍需要恢复时,不得删除暂存和备份数据。
## 信任边界
| 数据或机制 | 权限 | 说明 |
| --- | --- | --- |
| 服务器私有签名密钥 | 仅限服务器 | 从未运送到客户。 |
| 已签署的版本策略 | 服务器 | 控制运行、更新、降级和离线决策。 |
| 签名的清单 | 服务器 | 定义了完整的受保护文件集合和预期的哈希值。 |
| 签名的设备凭证 | 服务器 | 绑定应用程序、频道、安装、设备和许可证身份。它不是用户会话。 |
| 嵌入式产品配置 | 构建流水线 | 防止随意覆盖安装文件;可由管理员检查和修改。 |
| 原生可变状态 | 本地机器 | 保持注册和安装状态;它不涉及授权。 |
| 一次性启动票 | 本地启动器 | 证明预期的启动器启动了此进程。它不授予服务器权限。 |
| 健康标识 | 候选流程 | 信号提交或回滚的启动完成;它不能证明包的真实性。 |
带签名的 JSON 合约必须使用确定性序列化,从已签名的负载中省略签名字段,标识算法和密钥 ID,并支持受控的公钥轮换窗口。验证失败将导致系统关闭。
首次安装可能尚无当前版本的本地清单缓存。当设备在线、当前版本已在服务器显式发布且没有更高目标版本时,Launcher 会通过与 Updater 相同的校验逻辑获取当前版本清单,验证签名和身份后写入缓存,再执行完整文件校验。服务器未发布当前版本、返回无效版本 ID、清单签名无效或缓存写入失败时,启动保持关闭失败。此流程不会自动创建或发布服务器版本。
## 运行时布局
父安装程序拥有最终的布局。预期的可执行文件关系为:
```text
<install-root>/
bin/
Launcher<platform-suffix>
Updater<platform-suffix>
Bootstrap<platform-suffix>
SimCAE<platform-suffix>
config/
<仅在需要时缓存已签名的运行时>
```
安装目录中不应包含可编辑的产品策略 JSON 文件。更新缓存、备份、已签名策略缓存和机器状态必须使用集成运行时选择的位置,并且必须遵守平台的写入权限规则。
## 失败策略
* 无效签名、不安全路径、身份不匹配、策略回滚以及受保护文件损坏将导致系统关闭。
* 一个临时的网络故障可能会使用仍然有效的已签名离线策略;它不能静默地创建新的允许项。
* 过期的离线策略或被禁止的版本会阻止启动。
* 失败的候选健康触发器会导致回滚。
* 回滚失败会留下可恢复的状态和诊断错误;它不能报告更新成功。
## 接受场景
发布接受必须涵盖:
* 通过 `Launcher` 正常启动;
* 直接 SimCAE 启动拒绝;
* 首次设备注册及后续离线启动;
* 更新到较新的已发布版本;
* 篡改的受保护文件被拒绝;
* 篡改的策略或清单被拒绝;
* 强制更新和禁用版本行为;
* 频道选择和禁止降级行为;
* 中断更新恢复;
* 候选失败和成功回滚;
* 从实际安装位置启动并更新,通过正常的最终用户交互并需要提升权限。