热修通道技术方案¶
| 文档版本 | v1.0 / 2026-07-20 |
| 状态 | 待评审 |
| 对应 P0 | P0-B1(热修通道技术调研与方案) |
| 技术负责人 | 李勇 |
| 产品负责人 | 王双勇 |
| 关联文档 | POS:核心平台稳定性与热修通道.md(PRD)、docs/research/hotfix-channel-industry-survey.md(业界调研)、docs/adr/(决策记录)、CONTEXT.md(术语表) |
1. 结论摘要¶
可行。热修通道可以在现有 LiveUpdate 升级体系与增量包 Jenkins 流水线上扩展建成,无需重构客户端代码结构、无需新建服务端基础设施。
五条核心结论:
- 热修单位是整个主程序 exe,不拆动态库、不做站点规则外置化。因为站点适配逻辑(Netflix / Amazon / Disney+ / Hulu)100% 硬编码 C++ 并静态链接进主程序,规则外置化是代价巨大的重构;而"整件替换"恰是 Sparkle / Squirrel / Chrome 组件更新的通行做法。
- Windows 侧复用扩展 LiveUpdate 机器(liveupdate.exe + libupdateex + Copy.exe),下载、文件替换、进程编排、重启全部现成。macOS 侧需新建热修模块(现状连客户端内自动升级都没有),但清单协议与服务端完全共用。
- 安全能力必须从零建。现有升级链路无任何非对称验签(唯一校验是 patch 重建后的 MD5)、TLS 证书校验被关闭、配置里的证书字段是死字段。热修通道的安全性完全由新引入的 Ed25519 双层签名承担。
- CI 侧同样是扩展而非新建:
create_ini.py加type=hotfixsection、CreateUpdate_new.py加HotfixPatchWork子类、Jenkinsfile 加签名 stage 与热修参数。 - 最大未知区是下发服务(10.10.2.17 update DB + 下发接口,源码未见)。灰度参数最终落在这一侧,需在开发启动前确认接口形态。
2. 现状分析(均有代码证据)¶
2.1 站点适配逻辑的载体形态¶
| 平台 | 载体 | 修复方式 |
|---|---|---|
| Netflix / Amazon / Disney+ / Hulu | 硬编码 C++(DownloadControlXxx + XxxStreamDownloader + 协议签名类 + CWVreqlicXxx),静态链接进主程序 |
改 C++ → 重编 → 发版 |
| YouTube | 外部 yt-dlp.exe 子进程 |
换二进制即可(已有在线更新通道) |
网站到实现类的分发是编译期静态表(iStreamProductManager.cpp),不读配置、不拉服务端。CEF 仅作"带 CDM 的抓包浏览器",全库仅一处 JS 注入且是编译进 C++ 的字面量。PRD 设想的"站点适配规则热下发"在现状下不存在实现基础。
2.2 现有更新通道¶
- Windows:主程序写
update_config.xml→ 拉起 liveupdate.exe → 三级 URL(JSON 版本查询 → XML 更新信息 → 增量 cfg)→ 下载补丁 zip → bspatch 重建 → 杀主程序 → Copy.exe 替换 → 重启。 - macOS:socket 协议取版本 XML → 弹窗 → 浏览器打开下载页由用户手动下载 dmg。无客户端内下载/安装。
- 组件级先例:
YoutubeDLUpdater已实现"带版本查询 → JSON 返回 url+md5 → 下载 → MD5 校验 → 解压安装",证明组件热更模式可行。
2.3 安全现状(关键短板)¶
| 项目 | 现状 |
|---|---|
| 非对称验签 | 完全不存在(客户端、CI 双侧) |
| 内容校验 | 仅 MD5,且只校验 bspatch 重建结果;补丁 zip 本身不校验 |
| TLS | CURLOPT_SSL_VERIFYPEER=0(校验关闭),更新信息接口为明文 HTTP |
LiveUpdate_CrtFile |
死字段,只读入写回无任何使用;且来自可篡改的 update_config.xml,不能作信任根 |
| Copy.exe 备份 | 仅为绕文件锁,下轮更新即删除;无回滚、无整体成败返回值 |
2.4 灰度与遥测的可用原料¶
- 稳定设备标识:物理 MAC(
machine_id),抗重装、可伪造(灰度场景够用)。 - 遥测:ELK 事件通道(
app-api-*.dvdfab.cn/api/common_json_post/,按 operate/work/lifecycle 分索引);崩溃走 Crashpad →report.dvdfab.cn。 - 端点配置:编译期烘焙进
product.ini(rcc 资源),无在线刷新机制。
2.5 CI 侧现状¶
- Jenkins 流水线纯手动触发;
create_ini.py连 MySQL 查历史版本、防版本回滚、生成{PID}_{from}_{to}section;CreateUpdate_new.py从两个已发布安装包解包做目录树差分(bsdiff4),产出 zip + MD5 txt + 多镜像 CDN 下载配置,落 NAS。 force_add_list.txt已把StreamFab64.exe强制排除在 bsdiff 之外(加壳签名二进制差分触发杀软/签名问题、压缩率不如全量、客户端有 bspatch 回归),走全量 zip 投递。- 增量流水线无签名步骤(签名在上游打包流水线:VMProtect 加壳 → signtool + GlobalSign 时间戳);产物校验只有 MD5。
- 无
post { failure },构建失败不通知。
3. 方案设计¶
3.1 总体架构¶
发布侧(Jenkins)
├── 热修分支构建(复用现有构建流水线:VMProtect 加壳 + Authenticode/Developer ID 签名)
├── 热修包生成(create_ini.py type=hotfix → HotfixPatchWork → 单 exe 全量 zip)
├── Ed25519 内容签名(私钥进 Jenkins 凭据库)
├── 双人审批节点(产品 + 技术)
└── 发布清单(含灰度参数)+ 上传 CDN
服务端
├── 热修清单接口(app-api 家族新增,HTTPS 强制校验)
└── 灰度参数下发(percent / canary 白名单 / exclude 名单 / urgent 标记)
客户端
├── 清单拉取与 Ed25519 验签(拒绝旧版本清单)
├── 灰度分桶判定(hash(machine_id + 热修ID) % 100 < percent)
├── 热修包下载 + 文件级验签
├── 预提交自检(--selftest 子进程)
├── safe 备份 → 替换 → 重启
├── 启动标记健康检查 → 连续 2 次失败自动回退
└── 遥测上报(fetch/verify/selftest/swap/rollback)
3.2 热修内容范围(第一版)¶
| 受管文件 | 说明 |
|---|---|
| 主程序 exe | 覆盖全部 C++ 站点适配故障,全量 zip 投递 |
yt-dlp.exe |
收编现有独立更新通道,统一签名与灰度 |
| 配置开关文件 | 第一版仅实现站点维护开关 |
不在第一版范围:原生动态库补丁下发、画质降级等其他配置开关、自动指标回滚、管理后台 UI。
3.3 基线定向¶
热修只针对发布时的最新正式版。清单按基线版本定向,客户端携带自身版本查询;旧版本用户的修复路径是升级到最新正式版。与 CI 现有 {PID}_{from}_{to} 的版本对命名天然对齐。
3.4 签名方案¶
双层签名:
| 层 | 算法 | 覆盖对象 | 信任锚 |
|---|---|---|---|
| 平台层(复用现有) | Authenticode / Developer ID + 公证 | 热修 exe 本身 | 系统 CA / Gatekeeper |
| 应用层(新建) | Ed25519 | 清单 + 每个受管文件 | 公钥硬编码进客户端 |
要点:
- 清单带单调递增版本号 + 过期时间,客户端拒绝比本地已见更旧的清单(防 rollback / freeze / mix-and-match 攻击,依据 TUF 威胁模型)。
- 公钥硬编码进二进制,不使用
LiveUpdate_CrtFile(可篡改,不能作信任根);另预埋一把备用公钥供应急轮换。 - 私钥进 Jenkins 凭据库,与现有代码签名证书同级管理;签名动作在流水线的双人审批节点之后执行。
- 热修链路强制 HTTPS + 打开
VERIFYPEER/VERIFYHOST(签名为主、TLS 为辅,两层都要)。
3.5 灰度策略¶
分桶:客户端侧计算 hash(machine_id + 热修ID) % 100 < percent。
节奏(保守,P0 标准周期 = 确认后次日全量):
| 档位 | 范围 | 观察窗口 | 观察指标 |
|---|---|---|---|
| canary | 白名单内部设备(清单显式列 machine_id) | 半天 | 故障确已修复、无崩溃 |
| 灰度 | 10% | 过夜(覆盖晚间使用高峰) | 崩溃率、下载成功率对比未命中人群 |
| 全量 | 100% | 持续 | 同上 + QOS 恢复确认 |
canary 档不可跳过(CrowdStrike Channel File 291 的直接教训:内容通道为求快绕过分级发布导致全量事故)。percent 调整 = 人工改清单 + Jenkins 流水线发布,留审计记录。第一版不做自动放量、不做管理后台。
3.6 回滚机制¶
按故障模式分派:
| 故障模式 | 由谁处置 |
|---|---|
| 热修 exe 起不来 / 启动即崩 | 只有本地机制(客户端已失去联网自愈能力) |
| 程序能跑但指标劣化 | 服务端两层(人工决策) |
本地三防线(核心原则:验证与切换动作由"尚未被替换的、已验证健康的代码"执行):
- 预提交自检:旧 exe 以子进程拉起新 exe 的
--selftest模式(加载 + 核心模块初始化 + 返回 0),不通过则不提交切换。这在用户真实机器上真实拉起过一次,可拦住绝大多数"起不来"。 - 启动标记计数:新 exe 在
main()最开头(早于 Qt/CEF 初始化的纯 C 代码)写标记,健康检查通过后清除;下次启动发现未清除则计数 +1,连续 2 次自动换回 safe exe 并上报。 - 回退执行者不在热修范围内:承担文件切换的组件(Windows 的 Copy.exe 体系 / macOS 的替换逻辑)永不走热修通道更新,只随完整版本更新。
残余风险窗口:自检通过且崩溃发生在 main() 头部标记之前——极小,可接受。
服务端两层(人工):
- percent 归零截断扩散(分钟级)。
- 强制回滚:发布"内容为旧版本、清单版本号前进"的新清单(兼容防降级校验),已命中用户按正常热修流程被修回去(小时级)。
指标回滚第一版走人工:ELK 看板按热修版本维度对比崩溃率/下载成功率,值班负责人或项目 DRI 判断,Jenkins 一键执行。自动阈值回滚留后续演进(业界无公开成熟规范,误触发风险高)。
3.7 生效方式与用户控制¶
生效:下载、验签、自检、暂存全程静默;重启生效。
- 默认:下次自然重启静默生效。
- 清单标
urgent(发布审批时决定,P0 故障标记):弹轻提示引导重启;用户有活跃下载任务时延迟到任务完成后再弹;同一热修最多提示 1~2 次,之后安静等自然重启。
用户控制边界:给用户控制"何时生效"的权利,不给"是否修复"的选择,留隐藏通道兜极端情况。
- 拒绝重启提示 = 推迟生效,不是拒绝热修。
- UI 不提供"关闭热修"设置(避免在网版本碎片化污染 VIP 平台成功率指标;Chrome 组件更新同例)。
- 隐藏逃生门:标记文件禁用热修拉取(沿用
ignoreDataStatistics模式),供客服排查与极端兜底。 - 个体"热修后变差":客服指引隐藏开关 + 本地 safe exe 换回;群体性走指标回滚;清单支持 exclude 名单按 machine_id 排除个别设备。
3.8 时序:一次完整的 P0 热修¶
D0 上午 故障确认与定级 → 热修分支开发 → 内部验证
D0 下午 Jenkins 热修流水线(构建 → 加壳签名 → Ed25519 签名 → 双人审批 → 发布)
→ canary 档(内部白名单),观察半天
D0 傍晚 canary 通过 → percent=10 发布
D0 夜间 过夜观察(晚间高峰),ELK 看板监控崩溃率/成功率
D1 上午 指标正常 → percent=100 全量
指标异常 → percent 归零 或 强制回滚
4. 实现改造点¶
4.1 客户端(Windows)¶
| 改造项 | 位置 | 说明 |
|---|---|---|
| 热修模式 | RunMode 增 hotfix;Update_HotFix 更新类型 |
与 Update_Replace/Update_NewSetup 并列 |
| 清单验签 | UpdateManager::StartDownloadUpdateCfgFile / ParseUpdateInfo |
下载后、信任内容前验签;失败中止,不回退明文 cfg |
| 文件验签 | CAppFileManager::CheckFileValid |
现成的"下载物 → 校验"钩子,旁挂 VerifySignature |
| 灰度分桶 | 新增(清单解析后) | hash(machine_id + 热修ID) % 100 < percent |
| 预提交自检 | 替换编排流程中,killApp 之前 |
子进程拉起新 exe --selftest |
| safe 备份与回滚 | FabUpdateCopy.cpp 拷贝循环 |
备份清单、失败回滚已替换文件、替换后复核签名通过才删备份、WinMain 返回整体成败;停止无条件删除上轮备份 |
| 优雅退出 | 替换编排 | 热修不用 TASKKILL /F,改为用户点击重启后应用自行退出 |
| 传输加固 | DownloadManager.cpp / 更新信息请求 |
打开 VERIFYPEER/VERIFYHOST,去掉 SetSecure(false) |
| 启动标记 | 主程序 main() 头部 |
写标记 + 健康检查后清除 |
| 自检模式 | 主程序 | 实现 --selftest |
4.2 客户端(macOS)¶
需新建热修模块(LiveUpdate 是纯 Windows 体系),但清单协议、签名方案、灰度逻辑、遥测字段与 Windows 完全共用。需实现:清单拉取验签、分桶、下载、自检、bundle 替换与 safe 备份、启动标记、重启。替换粒度倾向整 bundle(Sparkle 模式),需验证公证细节。
4.3 CI / Jenkins¶
| 改造项 | 位置 |
|---|---|
type=hotfix section |
create_ini.py 的 make_ini(已有 type=plugin 分支可参照) |
HotfixPatchWork(PatchWork) |
CreateUpdate_new.py(参照 PluginPatchWork),只覆写 create_patch() 为单文件全量 |
| 热修参数 | Jenkinsfile 增 HOTFIX / ROLLOUT_PERCENT / TARGET_BUILDS |
| Ed25519 签名 stage | Jenkinsfile(Authenticode 照抄上游 sign_downloader.bat) |
| 双人审批节点 | Jenkinsfile(签名与发布之前) |
post { failure } 通知 |
Jenkinsfile(热修是紧急通道,失败必须响) |
可直接复用:MySQL 版本真源与防版本回滚、_verify_patch_integrity 完整性钩子、MD5 + CDN 对象名指纹、多镜像 CDN 下载配置、TEST_MODE 全套隔离(作为灰度前验证通道)、邮件 + 飞书通知链路。
4.4 服务端¶
app-api家族新增热修清单接口(参照youtube_dl_update模式):客户端带客户端类型 + 版本查询,返回签名清单。- 清单发布权限与版本发布系统权限分离(隔离体现在权限和流程,不是机器)。
- 灰度参数存储与下发(依赖下发服务,见 §6)。
4.5 遥测¶
ELK 新增热修维度事件(照 CDatastatsHelper 的成熟模式):hotfix_fetch / verify / selftest / swap / rollback,payload 带热修版本、基线版本、灰度档位、失败原因。看板需支持"命中热修 vs 未命中"分组对比崩溃率与下载成功率;Crashpad 崩溃需能按热修版本维度聚合。
5. 风险与应对¶
| 风险 | 应对 |
|---|---|
| 通道被劫持 | Ed25519 双层签名(清单 + 文件)、公钥硬编码、清单版本单调 + 过期时间、强制 HTTPS 严格校验 |
| 热修引入新故障 | canary 不可跳过 + 过夜灰度 + 三防线本地回滚 + 服务端两层回滚 |
| 热修把程序修死 | 预提交自检(真实机器拉起验证)+ 启动标记连续 2 次回退 + 回退执行者不走热修 |
| 替换中途失败致损坏 | Copy.exe 改造为备份清单 + 失败回滚 + 替换后复核签名 |
| 版本兼容性 | 严格按基线版本定向,只支持最新正式版 |
| 热修滥用 | 双人审批节点固化在流水线;受管文件白名单;仅 P0/P1 故障使用 |
| 杀软误报 | 热修 exe 必须走与正式版完全相同的 VMProtect 加壳 + 代码签名流程 |
| 包体积 | 主 exe 全量投递(差分因加壳签名问题不可用),第一版接受;体积实测待补 |
6. 遗留问题(开发启动前需解决)¶
| # | 问题 | 阻塞程度 |
|---|---|---|
| 1 | 下发服务接口形态:灰度分流、清单选择逻辑在 10.10.2.17 update DB + 未见源码的下发服务里;热修灰度参数最终落在这一侧 | 高,需负责人确认或获取源码 |
| 2 | 双平台主 exe 实际体积(决定下发带宽成本与用户等待) | 中 |
| 3 | macOS 替换粒度:整 bundle vs 仅主二进制,公证细节验证 | 中 |
| 4 | 清单 JSON 格式与热修版本命名语义定义 | 中 |
| 5 | update.xml 目录节点 MD5 为空的已知缺陷(若 Ed25519 清单需覆盖其语义完整性,宜先修) |
低 |
| 6 | Ed25519 密钥生成、备份、轮换的具体操作规程 | 低 |
PRD 对齐提醒:PRD 3.2 节把"站点适配规则更新"列为热修内容第一类,但技术现状下它的实现形态是"整 exe 替换"而非规则数据下发。建议同步更新 PRD 表述,避免评审理解错位。
7. 与 PRD 完成标准的对照¶
| PRD P0-B1 完成标准 | 本文档对应章节 |
|---|---|
| 输出热修通道技术可行性报告 | §1 结论摘要、§2 现状分析 |
| 明确客户端热修加载机制 | §3.2 内容范围、§4.1/4.2 客户端改造 |
| 明确下发通道隔离方案 | §3.1 架构、§4.4 服务端(权限与流程隔离) |
| 明确签名与完整性校验方案 | §3.4 签名方案 |
| 明确快速回滚机制 | §3.6 回滚机制 |
| 输出技术方案并通过评审 | 本文档 |
| PRD P0-B2 完成标准 | 本方案支持情况 |
|---|---|
| 热修通道与完整版本发布流程隔离 | 独立 Jenkins 流水线 + 独立清单接口 + 独立审批权限 |
| 热修包具备签名与完整性校验 | Ed25519 双层签名(§3.4) |
| 支持灰度下发和快速回滚 | §3.5 灰度、§3.6 回滚 |
| 完成至少 1 次真实 P0/P1 故障修复 | 主 exe 热修可覆盖全部 C++ 站点适配故障 |
| 未引入新的 P0/P1 故障 | canary + 过夜灰度 + 三防线回滚 |