跳转至

热修通道技术方案

文档版本 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 流水线上扩展建成,无需重构客户端代码结构、无需新建服务端基础设施。

五条核心结论:

  1. 热修单位是整个主程序 exe,不拆动态库、不做站点规则外置化。因为站点适配逻辑(Netflix / Amazon / Disney+ / Hulu)100% 硬编码 C++ 并静态链接进主程序,规则外置化是代价巨大的重构;而"整件替换"恰是 Sparkle / Squirrel / Chrome 组件更新的通行做法。
  2. Windows 侧复用扩展 LiveUpdate 机器(liveupdate.exe + libupdateex + Copy.exe),下载、文件替换、进程编排、重启全部现成。macOS 侧需新建热修模块(现状连客户端内自动升级都没有),但清单协议与服务端完全共用。
  3. 安全能力必须从零建。现有升级链路无任何非对称验签(唯一校验是 patch 重建后的 MD5)、TLS 证书校验被关闭、配置里的证书字段是死字段。热修通道的安全性完全由新引入的 Ed25519 双层签名承担。
  4. CI 侧同样是扩展而非新建create_ini.pytype=hotfix section、CreateUpdate_new.pyHotfixPatchWork 子类、Jenkinsfile 加签名 stage 与热修参数。
  5. 最大未知区是下发服务(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 起不来 / 启动即崩 只有本地机制(客户端已失去联网自愈能力)
程序能跑但指标劣化 服务端两层(人工决策)

本地三防线(核心原则:验证与切换动作由"尚未被替换的、已验证健康的代码"执行):

  1. 预提交自检:旧 exe 以子进程拉起新 exe 的 --selftest 模式(加载 + 核心模块初始化 + 返回 0),不通过则不提交切换。这在用户真实机器上真实拉起过一次,可拦住绝大多数"起不来"。
  2. 启动标记计数:新 exe 在 main() 最开头(早于 Qt/CEF 初始化的纯 C 代码)写标记,健康检查通过后清除;下次启动发现未清除则计数 +1,连续 2 次自动换回 safe exe 并上报。
  3. 回退执行者不在热修范围内:承担文件切换的组件(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)

改造项 位置 说明
热修模式 RunModehotfixUpdate_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.pymake_ini(已有 type=plugin 分支可参照)
HotfixPatchWork(PatchWork) CreateUpdate_new.py(参照 PluginPatchWork),只覆写 create_patch() 为单文件全量
热修参数 JenkinsfileHOTFIX / 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 + 过夜灰度 + 三防线回滚