feat(hardening): 内嵌产品配置 + 系统级状态 + 全量 i18n + 依赖治理 + 资料清理

update-client-hardening 四工作流合入(构建/静态检查已过):

- 配置:不可变产品策略经 UpdateClientResources.cmake 归一化校验后
  内嵌 qrc(URL/相对路径/主程序必须落 install_root/版本/UUID 格式,
  CMake override 写入前复验);可变机器状态迁系统级原生存储;
  运行目录不再落可编辑 app_config.json。
- i18n:生产源全英文(no-Han 检查 41/41),中文进 translations/
  zh_CN 目录;翻译加载收敛 Common/TranslationHelper 唯一入口;
  Bootstrap 补资源文件。
- 依赖:Qt/OpenSSL/架构/Perl 机器路径改 imported targets +
  UpdateClientDependencies.cmake,3rdparty 由父工程契约供给。
- 清理:旧 README/SDK README/3 个重复 PowerShell 打包脚本/PDF 提取
  文本删除,canonical README + 5 份结构化英文文档替代;Launcher
  requireAdministrator manifest;统一 MSVC /utf-8 删运行时编码设置。
This commit is contained in:
Comely
2026-07-10 00:38:23 -07:00
parent b06e003502
commit fe5aa5bbf0
45 changed files with 5033 additions and 4083 deletions
+160
View File
@@ -0,0 +1,160 @@
# 更新客户端架构
## 目的
更新客户端强制执行一条受控的产品入口路径,并执行事务性更新,而不会将可编辑的安装文件作为信任边界的一部分。父项目 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,并支持受控的公钥轮换窗口。验证失败将导致系统关闭。
## 运行时布局
父安装程序拥有最终的布局。预期的可执行文件关系为:
```text
<install-root>/
bin/
Launcher<platform-suffix>
Updater<platform-suffix>
Bootstrap<platform-suffix>
SimCAE<platform-suffix>
config/
<仅在需要时缓存已签名的运行时>
```
安装目录中不应包含可编辑的产品策略 JSON 文件。更新缓存、备份、已签名策略缓存和机器状态必须使用集成运行时选择的位置,并且必须遵守平台的写入权限规则。
## 失败策略
* 无效签名、不安全路径、身份不匹配、策略回滚以及受保护文件损坏将导致系统关闭。
* 一个临时的网络故障可能会使用仍然有效的已签名离线策略;它不能静默地创建新的允许项。
* 过期的离线策略或被禁止的版本会阻止启动。
* 失败的候选健康触发器会导致回滚。
* 回滚失败会留下可恢复的状态和诊断错误;它不能报告更新成功。
## 接受场景
发布接受必须涵盖:
* 通过 `Launcher` 正常启动;
* 直接 SimCAE 启动拒绝;
* 首次设备注册及后续离线启动;
* 更新到较新的已发布版本;
* 篡改的受保护文件被拒绝;
* 篡改的策略或清单被拒绝;
* 强制更新和禁用版本行为;
* 频道选择和禁止降级行为;
* 中断更新恢复;
* 候选失败和成功回滚;
* 从实际安装位置启动并更新,通过正常的最终用户交互并需要提升权限。
+123
View File
@@ -0,0 +1,123 @@
# 构建和打包
## 所有权
父级 SimCAE 仓库拥有发布配置、运行时部署、安装程序元数据以及最终的用户包。此子项目提供运行时目标和可安装的公共资源。
支持的包目标是 `package_installer`。它使用父级 CMake 安装规则和 Qt Installer Framework。 `package_installer_qt` 是一个归档的实验性路径,不得使用,也不应作为后备方案进行修复或作为支持的交付选项进行文档说明。
移除的 `install-sdk.ps1``package-client.ps1``package-sdk.ps1` 工作流不被支持。它们在父级外部组装了部分树 ,这可能导致与产品运行时闭包不一致。
## 前提条件
发布构建需要:
* 与父项目兼容的 CMake 和编译器版本;
* 更新客户端目标所请求的 Qt 模块;
* 通过 CMake 导入目标发现的 OpenSSL
* Qt Installer Framework for `package_installer`;和
* 父仓库中批准的 `3rdparty` 包或由调用者明确提供的 CMake 包提示。
仓库的 CMake 文件不得包含指向工作站的绝对路径,强制 调用者的生成器或架构,或手动选择 Debug 和 Release 库目录。依赖项发现必须生成导入的目标,例如 `Qt5::Core``OpenSSL::SSL``OpenSSL::Crypto`
缺少已批准的包必须停止配置并显示可操作的错误。发布包不得静默回退到不相关的系统包。
## 父级发布构建
从 SimCAE 仓库根目录运行集成工作流:
```powershell
git submodule update --init --recursive
cmake --preset SimCAE-release
cmake --build --preset SimCAE-release --target SimCAE
```
专用的发布预设将发布依赖项和输出分开 来自调试信息和旧的混合构建树。集成构建生成 `Launcher``Updater``Bootstrap` 位于 SimCAE 相同的运行目录中,并将发布启动门构建到 SimCAE 中。
调试开发使用 Debug 预设:
```powershell
cmake --preset SimCAE-debug
cmake --build --preset SimCAE-debug --target SimCAE
```
调试模式可能允许开发者直接启动 SimCAE 进行迭代开发。请勿从调试或混合构建树中验证或打包发布版本。
## 测试
在打包之前运行父级发布测试:
```powershell
ctest --test-dir out/build/SimCAE-release -C Release --output-on-failure
```
聚焦的更新客户端测试可能运行得更早,但它们不会替代父级测试套件、安装树检查或真实机器验收。
## 安装程序
从同一配置的 Release 树构建唯一支持的用户交付版本:
```powershell
cmake --build --preset SimCAE-release --target package_installer
```
该目标通过 `cmake --install` 阶段文件,根据父包定义分离可选的产品组件,并调用 Qt Installer Framework。生成的安装程序以配置的产品版本和平台名称写入 Release 构建目录中。
服务器发布仍然是一个显式的发布操作。构建安装程序不会创建或发布服务器版本、策略、清单或许可证。
## 独立开发者构建
一个独立的更新客户端构建对于集中编译和演示测试很有用。它不是最终用户包。
从父仓库根目录:
```powershell
cmake -S update-client -B out/build/update-client-release
cmake --build out/build/update-client-release --config Release `
--target Launcher Updater Bootstrap MainApp
```
默认情况下,子项目会在父仓库的 `3rdparty` 目录中查找。对于不同的批准布局,请传递 更新客户端第三方库根目录;标准的包特定 CMake 提示仍可用于专注的开发者构建。请保留所有未提交到 CMake 文件中的值。不要在父应用程序选择的运行时上覆盖第二个 Qt 运行时。
独立输出是临时的开发者输出。不要将其压缩或发送给用户。
## 包接受检查清单
在交付前,请对照干净的安装阶段验证以下所有内容:
* LauncherUpdaterBootstrap 和 SimCAE 存在于预期的运行时目录中。
* 已安装的用户入口点和快捷方式启动 Launcher,而不是 SimCAE。
* 在 Release 版本中拒绝直接启动 SimCAE。
* 未安装 app\_config.jsonclient.ini,源 config.json 或示例产品配置。
* 公开验证材料和嵌入式翻译资源存在。
* 没有 PDB、ILK、带有 Debug 后缀的 Qt 运行时或其他 Debug 产物。
* 在预期的安装路径下存在一个且仅有一个 SimCAE 可执行文件。
* 运行时依赖闭包来源于配置的 Release 包;没有 DLL 或共享库解析到开发工作站路径。
* 受保护的文件与已签名的当前版本清单文件匹配。如果支持离线首次启动,其已签名的策略和清单缓存都存在且有效。
* 开发机器上的可变状态不存在。
*`Launcher` 旁边放置一个伪造的 `app_config.json` 文件无法更改应用程序身份、渠道、端点、可执行策略或票务验证。
* 安装、启动、更新、回滚和卸载均从真实安装位置执行。
## 运行时兼容性
更新运行时和 SimCAE 共享已部署的 Qt 和编译器运行时。打包更改不得用不同的 Qt 构建覆盖该运行时。Qt 库中的入口点错误通常表示混合运行时,而不是缺少配置文件。
对于提升系统安装,首先验证首次注册和状态写入,而不应授予整个应用程序目录的写入权限。运行时状态应存储在本机机器存储或批准的数据位置,而不是放在可执行文件旁边。
## 故障排除
### 目标包缺失
确认在配置过程中已找到 Qt Installer Framework,并检查配置输出。不要切换到 `package_installer_qt`
### 发布版本加载调试库
使用专用的发布预设重新配置,并检查所选导入的目标。不要重命名调试二进制文件或在产品树中添加别名。
### 设备注册无法持久化
确认 `Launcher` 具备用于机器范围状态的平台权限,并且已安装的构建正在使用原生状态后端。不要将安装目录中的 `app_config.json` 写入作为变通方法。
### 更新应用程序回滚
检查更新事务的诊断信息,并确认候选进程在配置的超时时间之前已写入请求的健康标记。绝不要强制提交未经验证的候选进程。
+135
View File
@@ -0,0 +1,135 @@
# 配置与安全
## 配置模型
配置分为两个拥有不同所有者的类别:
1. **产品配置**是在配置时选择和验证的。它被嵌入到运行时中,并在二进制文件构建完成后变为只读。
2. **机器状态**在产品安装过程中会发生变化。它由原生系统范围的后端存储,并且永远不会重新定义产品策略。
此拆分将可编辑的 `app_config.json` 从已安装的信任路径中移除。
## 配置时源选择
自动选择使用以下顺序:
1. `config/config.json`,当该文件存在时。
2. `config/app_config.example.json`,否则。
集成构建可能会通过 `UPDATE_CLIENT_PRODUCT_CONFIG_FILE` 传递一个确切的文件;该显式输入具有优先权,必须由配置输出报告。清除缓存变量可恢复自动选择。
`config/config.json` 是一个本地或发布流水线输入。它必须保持未跟踪状态。示例是一个可构建的开发备用方案,而不是生产密钥存储。
配置必须在所选文件格式错误、缺少必需值、值类型错误或路径违反运行时布局协议时失败。所选的 JSON 文件将被规范化为生成的资源并嵌入到 qrc 中。源 JSON 文件和生成的中间文件不得安装。
## 产品配置
产品配置包括定义二进制文件被允许运行的产品的值:
* application ID 和显示名称;
* 发布渠道和客户端协议;
* API 基础 URL 和非特权客户端令牌;
* 启动票验证材料;
* 可执行角色名称和安装布局;
* 更新临时目录策略;
* 请求和健康检查超时;以及
* 目标平台和架构。
所选的源也包含一个 `initial_state` 对象。它仅提供以下可变键的首次运行默认值。它不会将这些键变为不可变策略,之后的运行时更改仍保留在本地状态存储中。
这些值可能在不同构建之间有所不同,但安装的文件不得覆盖它们。在可执行文件旁边放置名为 `app_config.json``client.ini``config.json` 的文件,不是受支持的运行时覆盖。
可执行文件路径必须是相对的、规范化的,并且限定在批准的安装根目录内。它们不得包含遍历段,也不得指向产品外部的任意可执行文件。
## 可变机器状态
机器状态包括:
* 当前安装的版本;
* 许可证注册值;
* 安装和设备标识符;
* 迁移完成标记;
* 单调更新或策略状态未包含在已签名的缓存中。
Qt 客户端在系统范围内使用原生后端存储这些值,并禁用回退查找。在 Windows 上,这是机器级别的注册表位置。其他平台使用等效的原生系统位置。当前逻辑命名空间是 `SimCAE/UpdateClient`,产品状态如下 `products/<app-id>/<channel>/state/`,因此所有更新客户端进程都访问同一个存储,而不会混合产品或频道。
发布启动必须具有读取和更新此状态所需的平台权限。未能持久化所需状态会导致启动错误;不得触发回退到可执行目录 JSON。
已签名的策略、清单和设备凭证缓存,在其格式需要时可以使用经批准的数据文件。此类文件是运行时缓存,而非产品配置,其签名在使用前必须进行验证。
## 版本和发布边界
已安装的版本是本地状态。服务器版本、通道、策略和发布清单是通过服务器工作流程显式发布的。本地构建、Git 版本、头信息值或安装程序文件名不得自动发布或授权服务器发布。
客户端只能在提交并验证的更新事务的一部分中报告其已安装的版本并将其向前推进。
## 安全属性
### 嵌入数据不是机密
qrc 可防止对侧边 JSON 文件进行随意编辑。它不会阻止机器管理员进行二进制检查或修改。因此,客户端中嵌入的值不得包含服务器发布或管理权限。
API 客户端令牌和启动票据材料是客户端凭证。服务器端点仍必须对每个操作进行身份验证,并独立授权设备、许可证、频道和版本。
### 原生状态不是信任根
系统范围的原生存储提高了所有权和访问控制,尤其是在系统安装中。管理员仍然可以对其进行修改。本地版本、许可证文本或迁移标志不能替代服务器验证。
### 签名的服务器数据具有权威性
服务器私钥从不会离开服务器。客户端仅接收公开验证材料。客户端会验证所有适用的签名工件,包括:
* 版本策略;
* 完整文件清单;
* 设备凭证;和
* 离线更新元数据,如支持。
签名的有效负载标识其使用的算法和密钥 ID,并采用确定性的序列化方式。密钥轮换可能会保留当前和立即前一个公钥,以便在受控的迁移窗口期内进行过渡。未知密钥或无效签名将关闭失败。
### 设备身份不等于用户登录
设备发行将应用程序、频道、安装、设备和许可证绑定在一起。它不会创建终端用户会话,也不会授予对管理发布接口的访问权限。
### 启动票证作用范围
一次性票券绑定预期的应用、设备、版本和较短的有效期。应用会一次性消耗该票券。票券验证阻止普通直接启动,但不会使策略或完整性检查变得可选。
### 完整性与回滚
更新程序可能仅下载更改的文件,但受保护的安装会针对完整的签名清单进行验证。在文件操作前会检查路径。先前版本会在候选版本健康状态确认之前进行备份,并在另一次更新前恢复中断的事务。
## 公开验证材料
公钥不是秘密。它们可以作为显式的公共运行时资产嵌入或安装,前提是包拥有其来源,并且客户端不会在没有其他可信锚点的情况下接受任意替换。私钥和服务器管理凭证绝不能进入此仓库或安装程序。
## 发布包规则
已安装的树中不得包含:
* app\_config.json;
* `client.ini`;
* source `config.json`;
* `app_config.example.json`;
* license 或 device state 从构建机器复制过来; 或
* 生成的产品配置中间件。
该包必须包含验证服务器合约所需的二进制文件和公共资源。父目标 `package_installer` 拥有这些安装规则。
## 必要的篡改检查
发布验证必须证明:
1. 添加或更改侧边车 JSON 不会改变嵌入式应用程序 ID、频道、端点、可执行文件名称或票证行为。
2. 更改本地状态无法伪造有效的服务器签名或获取发布权限。
3. 更改受保护的二进制文件或库会被清单验证检测到。
4. 重放或编辑启动票证将被拒绝。
5. 用较旧的已签名序列替换策略或清单会被反回滚状态拒绝。
6. 候选启动失败会导致回滚,而不是版本状态的前进。
## 操作指南
* 请将特定版本的 `config/config.json` 保留在版本控制之外,并通过受控的构建环境提供。
* 限制对发布构建输入的访问,但不要将客户端侧的值描述为不可提取的秘密。
* 通过明确的发布流程轮换服务器签名密钥和客户端令牌。
* 在原生后端和权限边界处诊断状态写入失败。不要使安装目录广泛可写。
* 将更改的端点、频道、可执行文件名称或启动票据密钥视为新的二进制配置,需要重新构建和包验证。
+97
View File
@@ -0,0 +1,97 @@
# 本地化维护
## 政策
英文是生产代码的源语言。用户可见的中文文本只能出现在本地化资源中。生产代码中的 C、C++、头文件和 CMake 文件不得在消息、注释、目标标签或诊断信息中包含汉字。
协议字段、JSON 键、命令行开关、日志标识符、错误代码和文件格式标记是稳定的接口,不应进行翻译。
## Qt 应用程序
`Launcher``Updater` 以及 `MainApp` 集成示例使用 Qt 翻译。
*`QObject` 子类中使用 `tr()`,当该类提供预期的翻译上下文时。
* 在自由函数、静态辅助函数和入口点代码中使用 `QCoreApplication::translate("StableContext", "English source")`
* 保持上下文名称稳定。重命名上下文会使现有的翻译条目失效。
* 使用英文源文本作为回退语言。
* 保持简体中文翻译目录作为已提交的 `.ts` 源文件。
* 通过 CMake 将 `.ts` 编译为 `.qm`,并将编译后的目录嵌入到 qrc 中。
* 安装不可松动、用户可替换的翻译文件,当构建合同需要嵌入式 UI 资源时。
* 仅在 `Common/TranslationHelper` 中保留 qrc 目录路径。应用程序入口点不得构造其他资源路径或加载第二个翻译器。
* 在构建 UI 对象或格式化启动错误之前初始化 `TranslationHelper`。它仅在匹配的语言环境时安装嵌入式目录;其他语言环境使用英文源文本。
* `TranslationHelper` 还会从部署的运行时 `translations/` 目录加载 Qt 的匹配基础目录,以便标准对话框按钮使用相同语言环境。这是独立的 Qt 运行时目录,不是替代应用程序目录路径。
不要仅仅为了满足扫描而将字符串放在翻译调用中。每个用户可见的消息都需要在目录中有一个条目,并且需要经过审核的翻译。
## 引导启动
`Bootstrap` 仍然独立于 Qt。在 Windows 上,可本地化的 UI 文本应放在 Win32 `STRINGTABLE` 资源中,并通过资源标识符加载。英语是默认的资源语言。
其他支持的平台必须使用等效的平台资源机制或经过审核的资源表。不要重新引入源语言条件语句或硬编码的本地化字面量。
机器可读的诊断信息可以保持为稳定的英文文本。任何在对话框中显示的文本都是用户可见的,必须使用资源表。
## 添加或修改文本
1. 编写简洁的英文源字符串。
2. 选择一个现有的稳定上下文或添加一个名称明确的上下文。
3. 在每次翻译中保留占位符,如 `%1``%2`、换行符和标记。
4. 通过 CMake 管理的提取步骤更新翻译目录。
5. 翻译并审阅已更改的条目。
6. 构建嵌入式翻译资源。
7. 启用英文备用和简体中文界面路径。
8. 运行无汉字的源语检查。
避免通过连接翻译片段来构建句子。词序和复数规则因语言而异。翻译完整的信息,并通过占位符传递可变内容。
## 错误来源
错误跨越多个边界,需要不同的处理方式:
| 源文本 | 处理方式 |
| --- | --- |
| 本地用户界面决策 | 翻译完整的用户可见消息。 |
| 本地技术诊断 | 保持稳定的英文细节,并将其封装在翻译后的用户可见摘要中。 |
| 服务器错误代码 | 将稳定的代码映射到本地翻译后的消息;不要将协议 JSON 作为主要用户界面显示。 |
| 操作系统错误 | 保留原生细节以供诊断,并提供一个翻译后的行动导向摘要。 |
| 仅记录事件 | 除非也展示给用户,否则保持英文稳定。 |
不要将本地化文本作为错误代码或事务状态发送回服务器。
在迁移过程中,旧的服务器响应可能仍需要匹配翻译后的短语。请重用 `TranslationHelper` 已拥有的目录;不要为分类加载第二个目录。当该 API 可用时,用稳定的服务器错误代码替换文本匹配。
## 目录和资源审核
在合并 i18n 变更之前,请确认:
* 每个修改过的用户可见源字符串都出现在目录中;
* 主启动/更新流程中没有未完成的条目;
* 占位符在源字符串和翻译之间完全匹配;
* 加速器标记和富文本标记保持有效;
* the `.qm` 输出被嵌入到每个使用它的应用程序中;
* 启动、提升、更新、回滚和致命错误对话框都已涵盖;
* 不可用的语言环境会回退到英文,且不显示空白文本;
* 生成的 `.qm` 路径不依赖于开发者的开发工作站。
## 源代码扫描范围
自动的源代码检查应包括生产代码:
* `*.c`, `*.cc`, `*.cpp`,以及 `*.h`
* `CMakeLists.txt``*.cmake`;和
* 平台源资源,除已批准的本地化表之外。
它应排除翻译目录、已批准的 `STRINGTABLE` 资源、文档、第三方代码、生成的文件和构建输出。排除项必须明确,以便新添加的生产目录默认被检查。
## 运行时验证
自动目录检查是必要的但不够的。发布验收必须至少检查以下内容:
* 首次注册和凭证错误;
* 离线策略和强制更新信息;
* 更新下载、验证和回滚失败;
* 提升失败或取消;
* 直接启动拒绝;以及
* 引导程序替换失败。
请确认文本与实际对话框相符,并且切换操作系统区域设置不会改变协议行为或配置选择。
+129
View File
@@ -0,0 +1,129 @@
# Legacy 配置迁移
## 范围
较旧的 update-client 构建版本会读取并重写可执行文件旁边的 product 数据。在升级安装中,可能存在两种格式:
* `config/app_config.json`; 和
* client.ini
新版本使用嵌入式产品配置加上本地机器范围的状态存储。旧文件仅作为迁移输入,它们永远不会覆盖嵌入式应用程序标识、服务器端点、通道、可执行策略或票据配置。
此迁移与配置时的 `config/config.json` 无关。后者是受控的源代码树或构建流水线输入,从不会从安装中读取。
## 迁移优先级
迁移仅在原生状态存储尚未为此产品安装完成迁移时运行。
1. 现有有效的原生状态胜利,且不读取遗留文件。
2. 否则,导入允许的可变值 `config/app_config.json` 在存在且有效时。
3.`client.ini` 中填充仍缺失的允许值(当存在时)。
4. 验证导入的值并将它们原子性地持久化到原生存储中。
5. 仅在状态写入成功后记录迁移完成。
迁移必须是幂等的。对旧文件的后续编辑不应改变已迁移的安装。
## 允许的值
只能导入可变的安装状态:
| 旧值 | 新所有者 | 迁移规则 |
| --- | --- | --- |
| current\_version | 原生安装版本状态 | 从未签名的旧文件中导入;从嵌入的构建状态初始化,并且仅通过经过验证的更新事务进行升级。 |
| license\_key | Native enrollment state | Import as enrollment input; the server still validates it. |
| device\_id | 原生设备状态 | 当有有效签名设备凭证时,优先使用其值。 |
| installation\_id | 原生安装状态 | 仅在语法有效时导入;否则通过当前状态服务生成。 |
| 单调策略/更新标记 | 原生状态或签名缓存 | 仅通过专用验证导入;永远不要信任未签名的较低序列。 |
迁移时不得将这些遗留值作为运行时覆盖导入:
* `app_id``app_name`;
* `channel``client_protocol`;
* `api_base_url``client_token`;
* launch\_token;
* 可执行文件名或 `install_root`;
* 临时目录布局;
* 平台或架构;或
* 请求和健康检查策略。
这些值只能来自构建期间选择的嵌入式产品配置。
## 旧版 INI 映射参考
旧的 INI 格式将值分组如下。此表仅用于支持有限的迁移阅读器和法医诊断。
| Section | Keys previously used |
| --- | --- |
| App | app\_id, app\_name, channel, current\_version, client\_protocol, launch\_token |
| 许可协议 | license\_key |
| 服务器 | api\_base\_url, client\_token |
| 更新 | request\_timeout\_ms, temp\_folder, device\_id |
| 设备 | installation\_id |
| 运行时 | install\_root, main\_executable, launcher\_executable, updater\_executable, bootstrap\_executable, health\_check\_timeout\_ms |
只有上述可变子集可能离开此解析器。该表不会将其他键识别为支持的设置。
## 验证与失败
* 解析旧版 JSON 和 INI 文件而不更改文件内容。
* 在编写本地状态之前,应用严格的长度和格式检查。
* 不要将未签名的策略序列向后复制。
* 不要将遗留路径接受为可执行文件或更新根。
* 将所有导入的状态原子地写入,然后设置迁移完成标记。
* 如果持久化失败,请报告一个可操作的错误并保留迁移未完成状态,以便在权限或存储问题解决后可以重试。
* 不要在每次启动时都回退到读取旧文件。
格式错误的旧文件不得阻止干净安装与当前产品配置的注册。保留足够的诊断信息以识别被拒绝的来源,但不要显示许可证材料或客户端令牌。
## 已签名的旧文件
旧的安装可能还包含:
* `config/client_identity.dat`;
* `config/version_policy.dat`;
* `config/local_state.json`; 和
* update/manifest\_cache/
这些不是产品配置。只有在当前签名、身份、通道、版本、过期时间和防回滚检查通过后,才能重复使用已签名的设备凭证、策略或清单。未签名的本地状态只是一个迁移提示,不能降低已记录的序列号或延长离线有效性。
迁移实现应将接受的状态移动到当前批准的存储或缓存位置。它不得继续将可执行目录视为可写运行时数据库。
## 打包规则
父级 `package_installer` 输出中不得包含遗留配置或状态。特别是,不要打包:
* app\_config.json;
* `client.ini`;
* 开发机器的设备凭证;
* 本地策略状态;
* 交易状态;或
* 更新下载和备份目录。
存在迁移代码用于从旧版本升级,而不是为了维持旧的软件包结构。
## 操作规程
对于现有安装:
1. 通过受支持的安装程序/更新路径安装已签名的新包。
2. 通过 `Launcher` 启动,并使用原生机器状态所需的平台权限。
3. 确认设备注册、策略验证和已安装版本状态。
4. 确认第二次启动成功,无需查阅已更改的旧文件。
5. 确认直接应用启动仍被阻止。
迁移成功后,旧文件可以归档用于事件分析,或通过批准的清理路径删除。它们的持续存在不应影响运行时策略。
## 迁移测试
覆盖至少:
* 无遗留文件的干净安装;
* 仅 JSON 迁移;
* 仅 INI 迁移;
* JSON 优先,INI 作为缺失可变值的后备;
* 现有原生状态带有恶意的遗留覆盖;
* 格式错误的 JSON 和格式错误的 INI 值;
* 无效或过期的签名遗留凭证;
* 原生状态写入失败后成功重试;
* 迁移完成后重复启动;以及
* 迁移后的侧车配置被篡改。