# Koring Launcher 自动更新方案(v1 规划稿) > 状态:**M1 完成;M2 主进程更新模块完成(2026-08-28,UI 待做)** > 目标平台:**Windows 优先**(NSIS exe 安装包),macOS/Linux 后续复用同一套架构 > 决策记录: > - 安装器模式:**保持 assisted 安装器(`oneClick: false` + 可改安装目录)→ 每次更新整包下载** > - 更新托管:**GitHub Releases** > - 当前交付:**M1 基础设施 + M2 主进程更新模块(GitHub 优先 + 加速源兜底)已完成** --- ## 1. 背景与目标 - 项目:Electron 33 + React 19 + TS + electron-builder 25(Windows NSIS 安装包,`dist:dev/beta/run` 三模式打包) - 现状:`package.json` 版本 1.1.2,无任何更新机制;用户需手动下载新版安装包 - 目标:客户端内自动检查新版本 → 下载 → 静默重装 → 自动重启,先跑通 Windows ## 2. 技术选型 **选 electron-updater**(electron-builder 官方配套,与现有打包链无缝衔接)。 | 方案 | 说明 | 结论 | |---|---|---| | electron-updater | 自动生成 `latest.yml` 清单、SHA512 校验、进度事件、失败重试、断点续传 | ✅ 选用 | | electron-simple-updater | 只支持替换 asar,不支持 NSIS 重装 | ❌ | | update-electron-app | 只面向 GitHub,定制性差 | ❌ | | 自研差分/自建服务 | 灰度、强制更新、统计需要时再评估 | 后期可选 | **版本兼容**:electron-builder 25.1.8 ↔ electron-updater ^6.x(官方同仓库发布,6.x 与 24/25/26 打包器配套)。 ## 3. 关键约束(已确认) ### 3.1 assisted 安装器 → 整包下载 当前 `electron-builder.yml` 为 assisted 安装器: ```yaml nsis: oneClick: false allowToChangeInstallationDirectory: true ``` electron-updater 的 NSIS **差分更新(blockmap 增量)只支持一键安装(per-user oneClick)**; assisted 安装器安装目录不固定,**退化为每次下载完整 `koring-launcher--setup.exe`**。 **已决策:接受整包下载**,不改安装体验。含义: - 每次更新下载全量安装包(预计几十 MB 级别),CDN/带宽按此评估; - 后续若用户量大、包体过大,可再评估改 oneClick 启用差分,或自研增量(代价高,不优先)。 ### 3.2 代码签名(重要) Windows 下 electron-updater 会对下载的安装包做 Authenticode 校验:当前 exe 有签名时,要求新安装包发布者一致; 当前 exe 未签名时校验会被跳过(记警告)。 - **未签名**:开发/内测可跑通全流程,但国内杀软对未签名 exe 误报率高; - **正式对外发布前必须做代码签名**(OV 证书起步,EV 更佳),并让发布流水线对 setup.exe 签名; - 签名后 electron-builder 默认会校验一致性,无需额外配置,但要保证**发布流水线签名证书与产物一致**。 ## 4. 整体架构 ``` ┌───────────────────────────── 客户端 ─────────────────────────────┐ │ React UI (src/) ⇄ preload.ts (contextBridge) ⇄ 主进程 │ │ │ electronAPI.onUpdateStatus() │ │ │ └────────── IPC ────────────────┘ │ │ 主进程 electron/updater.ts(封装 electron-updater) │ │ electron/handlers/update.ts(IPC handler) │ └──────────────────────────────┬──────────────────────────────────┘ │ HTTPS ▼ ┌───────────────────────────┐ │ GitHub Releases (仓库) │ │ · koring-launcher-1.2.1 │ │ -setup.exe │ │ · latest.yml │ └───────────────────────────┘ (国内网络差 → 见 §5.3 加速对策) ``` ## 5. 服务端:GitHub Releases ### 5.1 发布产物 electron-builder 配置 `publish: { provider: github }` 后,`electron-builder --publish always` (或 `--publish` 搭配 CI token)会自动: 1. 打包 Windows 产物; 2. 创建/更新 GitHub Release(tag 取自版本号,如 `v1.2.1`); 3. 上传 `koring-launcher-1.2.1-setup.exe` + `latest.yml`(+ `latest.yml.blockmap`,assisted 模式下不用但会生成)。 `latest.yml` 是更新清单(版本、文件路径、大小、sha512),electron-updater 靠它发现新版本并校验完整性: ```yaml version: 1.2.1 files: - url: koring-launcher-1.2.1-setup.exe sha512: size: 81234567 path: koring-launcher-1.2.1-setup.exe sha512: releaseDate: '2025-01-01T00:00:00.000Z' ``` ### 5.2 配置示例(electron-builder.yml 增加段) ```yaml publish: provider: github owner: repo: <仓库名> # releaseType: release # 默认 release;beta 通道可设 draft/prerelease ``` 打包时 electron-builder 会把 `app-update.yml`(含 provider 信息)写进 `resources/`, electron-updater 在运行时读取它——**没有 publish 配置就不会生成 app-update.yml,更新会直接报错**(M1 验收点)。 ### 5.3 国内网络注意(务必先评估) electron-updater 的 GitHub provider 走 `api.github.com`(发现版本)与 `github.com/.../releases/download/...`(下载), 国内部分网络环境访问慢或不稳定。对策(按需选): - **现状接受**:很多应用直接走 GitHub,配合失败重试 + 手动"下载最新版"兜底按钮; - **CDN 加速(推荐后续做)**:改为 `generic` provider,把 `latest.yml` + exe 同步到 OSS/COS + CDN(或 ghproxy 类代理),`app-update.yml` 指向 CDN URL; - **自建/商业 CDN 代理 GitHub Releases**:等用户量上来再评估。 > 方案设计上保持 provider 可切换:`electron/updater.ts` 只面向 electron-updater 统一 API, > 未来从 `github` 切 `generic` 只需改 electron-builder.yml + 重新打包,业务代码不动。 ## 6. Windows 更新流程(整包下载版) ``` 应用启动 ├─ 加载完成且空闲后(延迟 10~15s)→ 静默 checkForUpdates() │ (避开启动加载与 Minecraft 下载抢带宽;首启/开发模式跳过) ├─ 无更新 → 结束,静默 └─ 有更新 → 发 update:status{state:'available', version} │ ▼ 前端提示"发现新版本 vX.Y.Z"(非强制,可忽略/稍后) │ 用户点"下载更新" ▼ autoUpdater.downloadUpdate() ├─ download-progress → update:status{state:'downloading', percent, speed, ...} ├─ 下载完成 → SHA512 校验(latest.yml)→ update:status{state:'downloaded'} │ ▼ 前端提示"重启并安装"(可暂缓;退出时 autoInstallOnAppQuit 兜底) │ 用户确认 ▼ autoUpdater.quitAndInstall() ├─ 应用退出 → NSIS 静默安装(不弹 UI,沿用原安装目录) └─ 安装完成自动重启 → 运行新版本 失败路径: · 网络失败 → 提示重试(electron-updater 自带断点续传/重试) · 校验失败 → 清除缓存重下;仍失败则提示手动下载最新版 · 静默安装失败 → 提示手动下载;保留旧版本可用 ``` ### 更新状态机(前端 store 用) ``` idle → checking → available → downloading → downloaded → installing → relaunch └─not-available→ idle └─ error → idle(可重试) ``` ## 7. 代码结构规划 ``` electron/ updater.ts # 新增:封装 electron-updater # · 初始化(读 app-update.yml、设日志) # · 守卫:!app.isPackaged 时跳过(开发模式) # · check() / download() / quitAndInstall() / 状态查询 # · 订阅 checking/available/not-available/download-progress/downloaded/error # · 转发为 update:status 事件(带完整 payload) handlers/update.ts # 新增:IPC handler # · update:check → 触发检查 # · update:download → 触发下载 # · update:quitAndInstall → 重启安装 # · update:getState → 查询当前状态/版本信息 main.ts # 改:import { registerUpdateHandlers };注册;启动后延迟静默检查 preload.ts # 改:暴露 # · checkForUpdates() / downloadUpdate() / quitAndInstall() # · onUpdateStatus(cb) → 订阅 update:status(沿用现有 on() 模式) src/ api/update.ts # 新增:IPC 封装 + TS 类型(UpdateStatus) stores/update.ts # 新增:zustand store,维护状态机 components/settings/... # 更新 UI:设置页"关于/更新"区块 # · 当前版本号 + 检查更新按钮 # · 下载进度条(百分比/速度/剩余大小) # · "重启并安装"确认 components/... # 可选:新版可用 toast / 弹窗(非强制打扰) ``` **依赖注意**:`electron-updater` 必须放 **`dependencies`**(不能是 devDependencies), 否则 asar 打包后运行时找不到模块——最常见事故,M1 验收必须覆盖。 ## 8. 版本与多通道 - 沿用现有 `scripts/version.js` 升版(`pnpm version:set 1.2.0`),版本必须严格 semver 递增; - electron-updater 只认 package.json 的 `version`,升版后需重新打包发布; - **dev / beta / run 三模式的通道隔离放加固阶段(M5)**: - GitHub provider 多通道依赖 tag 约定(如 `v1.2.1-beta.1`),行为略绕,先只用默认 latest 通道跑通; - 届时按产品需要决定:beta 走 prerelease/draft release,run 走正式 release,或切 generic + 不同 URL。 ## 9. 里程碑与验收 | 阶段 | 内容 | 验收标准 | |---|---|---| | **M1 基础设施** | ① `dependencies` 加 electron-updater;② electron-builder.yml 加 publish(github);③ 升版打包 `pnpm dist:run` | 产物目录出现 `latest.yml`;解包 asar 的 `resources/app-update.yml` 存在;electron-updater 在 asar 内可 require | | **M2 主进程** | updater.ts + handlers/update.ts + preload 暴露 | 手动触发 `update:check` 能返回状态;事件能推送到渲染进程(dev 下用 `forceDevUpdateConfig` 或打包版验证) | | **M3 前端 UI** | update store + 设置页更新区块 + 启动静默检查 | 完整交互:检查/提示/进度/重启安装/失败重试 | | **M4 联调冒烟** | 发布 v1.2.0 → 再发布 v1.2.1,真机从 1.2.0 升到 1.2.1 | Windows 真机全流程通过,含取消下载、断网重试、校验失败场景 | | **M5 加固** | ~~代码签名~~(SignPath 远程签名已接入 CI,见 §12);国内加速(CDN/generic)评估;通道隔离;强制更新开关;临时文件清理 | 可对外发布 | ## 10. 风险与对策 | 风险 | 影响 | 对策 | |---|---|---| | electron-updater 误放 devDependencies | 打包后更新直接报错 | M1 验收强制检查 asar 内依赖 | | 未配置 publish → 无 app-update.yml | 运行时报 "app-update.yml not found" | M1 验收点;文档示例已给 | | 未签名 exe 被杀软拦截 | 下载/安装被拦,更新失败 | 正式发布前签名(M5);内测接受现状 | | assisted 静默安装弹 UI / 请求管理员 | 更新中断 | M4 用现有 `build/installer-custom.nsh` 专门联调;必要时加 `runAfterFinish`/静默参数处理 | | api.github.com 国内访问差 | 检查/下载慢或失败 | 失败重试 + 手动下载兜底;后续 CDN 加速(§5.3) | | 检查频率过高 | 浪费带宽、触发限流 | 启动延迟 10~15s + 手动按钮;间隔建议 ≥1h | | 磁盘满 / %LOCALAPPDATA% 权限 | 下载/解压失败 | 失败回退提示,清理 `updaterCacheDirName` 缓存 | | 版本号不递增 | 永远查不到更新 | CI/脚本校验 `version:set` 只允许升版 | | 更新失败后应用状态异常 | 用户卡在旧版本 | 保持旧版可用;UI 提供手动下载入口;错误上报(可复用现有 crash 体系) | ## 11. 后续待决策(不阻塞 M1~M4) - 是否需要强制更新 / 最低版本策略 - 国内加速方案何时落地(generic + OSS/COS/CDN) - macOS / Linux 更新的排期(架构已预留,届时分别补 zip/dmg 与 AppImage 产物与签名) ## 12. SignPath 代码签名接入(2026-08-28 落地) **方案**:electron-builder 自定义签名(`win.sign: scripts/signpath-sign.js`), 构建时对内部 exe(`Koring Launcher.exe`、elevate.exe、卸载器)与最终 `setup.exe` 逐个提交 SignPath 远程签名,签名发生在 blockmap / latest.yml 生成之前, **清单 sha512 与 blockmap 自动对应签名后产物**,electron-updater 校验无缝。 **SignPath 后台准备(已确认值)**: - Organization ID:`31ecd033-d59e-492b-a70b-b00a54bbc7c2`(已写入 workflow env) - API Token:用户详情页生成(CI 用途),放入 GitHub Secrets `SIGNPATH_API_TOKEN` - slug:项目 `Koring_Launcher`;签名策略 `Koring_Launcher_Dev_builder`(均写入 workflow env); 产物配置 DEFAULT 在项目仅一个配置时可省略 `SIGNPATH_ARTIFACT_CONFIG_SLUG` **⚠️ 待办约束**: - **测试证书**:当前策略 Purpose 为 **Test signing**(测试证书),用户机器默认不信任 (SmartScreen / 杀软警告;`signtool verify` 会报"不受信任"——预期现象), 正式对外发布需生产证书(OV/EV)+ 对应生产签名策略(届时只换 `SIGNPATH_SIGNING_POLICY_SLUG`)。 - 审批流程:已在 SignPath 后台关闭人工审批(自动批准),CI 可全自动;策略 Purpose 已改为 Release signing。 **新增/改动文件**: - `scripts/signpath-sign.js` — 签名模块(无 token 自动跳过,本地构建不受影响) - `.github/workflows/release.yml` — 手动触发:选择 beta/run → 构建 → 签名 → 发布 GitHub Release(见 §13) - `scripts/release-notes.ps1` — 中文 Release 发布说明生成器 - `electron-builder.yml` — `win.sign` 挂载 **CI Secrets**:仅 `SIGNPATH_API_TOKEN`(必填)+ `SIGNPATH_ARTIFACT_CONFIG_SLUG`(可选); 组织 ID 与 slugs 已在 workflow 中写死。 **2026-08-28 实测结果**(与官方 PowerShell 模块对齐后的真实签名): - 协议:`POST {base}/v1/{orgId}/SigningRequests`(**multipart/form-data**,字段 ProjectSlug / SigningPolicySlug / ArtifactConfigurationSlug? / Description / 文件部件 **Artifact**)→ 响应 **Location 头** = 请求 URL → 轮询 `status`/`isFinalStatus` → 完成后取 **signedArtifactLink** 下载; 认证 `Authorization: Bearer `。 - ✅ 98.3MB setup.exe 提交成功;自动批准生效(InProgress → Completed 无人工介入) - ✅ 小文件端到端签名成功,`signtool verify` 显示签名链 **Issued to: Lingke Network** - ⚠️ 本机到 SignPath 下载 100MB+ 产物极慢(20min+),CI(GitHub Actions)网络环境不受影响; 模块已加下载重试(3 次)+ 临时文件替换 **2026-08-28 CI 首跑教训(配额)**: - ⚠️ **SignPath 年度配额 ≈ 500MB**("Yearly quota for artifact size has been exceeded"), 一次全量签名构建(sha1+sha256 双签)即消耗 ~458MB,直接耗尽配额,发布失败。 - 修复(已落地):`win.signtoolOptions.signingHashAlgorithms: [sha256]` —— 只按 SHA256 单签, 每个文件只签一次,配额减半(全量构建 ~278MB); 可选 `SIGNPATH_ONLY_INSTALLER=true` 只签 setup.exe(~97MB/构建)。 - **内部测试策略(已落地)**:workflow 设置 `SIGNPATH_SKIP_ON_QUOTA=true` —— 配额耗尽时 跳过签名、照常发布(产物未签名,electron-updater 发布者校验自动跳过);首次命中后记住状态, 后续文件不再重复提交。⚠️ 正式发布务必移除该开关。 - **治本仍需升级 SignPath 套餐**或等待年度配额重置;正式发布(run)建议全量签名 + 生产证书。 **验证点(首次 CI 运行)**:确认 SignPath 请求全部 Completed、`latest.yml` 的 sha512 与上传的 signed setup.exe 一致、签名链显示 "Lingke Network"。 ## 13. 发布流水线(手动触发 / BUILD ID / 中文 Release) **触发方式**:Actions 页面 → Run workflow,仅手动触发(不再使用 tag 触发)。 - `mode`:`beta`(测试,发布为 GitHub prerelease)/ `run`(正式,发布为普通 release) - `ref`:构建来源分支/tag/commit(留空 = 默认分支) - `sign`:是否使用 SignPath 签名 - **无 `version` 输入**:base 自动读 `package.json`(版本单一事实源,消除本地/CI 版本双轨) **版本号与 BUILD ID(2026-08-30 起改为 GitHub Run Number)**: - BUILD ID = `github.run_number`(严格递增、无分钟级冲突) - 最终版本 = `{base}-{buildId}`(如 `1.2.0-12`),tag = `v{base}-{buildId}`, electron-builder 产物 = `koring-launcher-{base}-{buildId}-setup.exe`,`latest.yml` 同步更新 - 构建元数据(commit / buildId)由 `scripts/gen-build-info.js` 写入 `src/lib/buildInfo.ts` → 打包进渲染层,VersionCard / 关于页显示**构建来源 commit**(`scripts/version.js build ci` 负责统一设版本) **发布内容**(`gh release create`,中文正文由 `scripts/release-notes.ps1` 生成): - `# Koring Launcher Releases {base}` + 版本信息(当前版本 / 编译状态 BETA/RUN / 签名状态 / **构建来源 commit**) - `## 更新了什么内容`:自上个 `v*` tag 以来的提交记录,每条默认折叠 (`
·Commit 1cf906d
`) - 上传产物:setup.exe + latest.yml + release-notes.md(electron-updater 更新清单) **⚠️ 版本语义注意(electron-updater)**: - `{base}-{buildId}` 属 semver prerelease:同格式版本之间可正常升级(buildId 更大者胜) - 若未来发布**不带** buildId 的稳定版本(如 `1.2.0`),稳定版用户不会自动升级到带 buildId 的构建 - **迁移注意**:从时间 ID(`2608271921`)切换到 Run Number 后,旧格式数值更大(`2608271921 > 12`), 老用户不会自动升级到新格式——切换时应同时提升 base(如 `1.3.0-12 > 1.2.0-2608271921`) ## 14. M2 主进程更新模块(2026-08-28,UI 待做) **新增/改动**: - `electron/updater.ts` — 更新服务:electron-updater(GitHub provider)优先,失败后加速源兜底; 状态机 idle/checking/available/not-available/downloading/downloaded/error,进度事件,`quitAndInstall` - `electron/handlers/update.ts` — IPC:`update:check` / `update:download` / `update:quitAndInstall` / `update:getState`, 状态变化广播 `update:status` 到所有窗口 - `electron/main.ts` — 注册 handler + 启动后 12s 延迟静默检查(开发模式自动跳过) - `electron/preload.ts` + `src/types/electron.d.ts` — 暴露更新 API(UI 未接,待 M3) **加速源兜底(实测)**: - GitHub 直连在本机网络不可用;`gh.ddlc.top` 已实测可代理 `releases/download`(latest.yml + 102MB 安装包)与 `/releases/latest` 页面 - 发现机制(无需 GitHub API):`{镜像}/https://github.com/{owner}/{repo}/releases/latest` 页面 HTML 提取 tag → `autoUpdater.setFeedURL({ provider: 'generic', url: '{镜像}/.../download/{tag}/' })` → 检查/下载 - 镜像列表可用环境变量 `UPDATE_MIRRORS` 覆盖;后续建议自建 OSS/CDN(generic 直连镜像根目录) **状态机与 IPC 契约**(前端 M3 实现时使用): ``` idle → checking → available → downloading → downloaded → quitAndInstall() └─not-available→ idle └─ error → idle(可重试) ``` `update:status` payload:`{ state, manual, version?, currentVersion?, percent?, transferred?, total?, bytesPerSecond?, source?, error? }` ## 15. 更新日志独立页面(2026-08-28) - **独立路由页面** `src/pages/update/index.tsx`(route key `update`),不使用设置页 layout - 顶栏(TitleBar)在 sub 模式下**只显示「返回」+ 页面标题「更新日志」**(routeStore 新增 `titleInBar`, 其余页面仍显示品牌名) - 页面内容:顶部 `VersionCard`,下方 Markdown 渲染当前版本发布说明 (主进程 `update:getReleaseNotes`:GitHub 直连优先 + 加速源兜底,读 release 附件 `release-notes.md`; 当前版本无发布说明时回退最新版本并标注) - **入口**:除 OOBE 与更新日志页本身外,所有 VersionCard 的「检查更新」按钮点击后**跳转到本页**; 在本页内点击则直接执行检查 - **完整下载流程(2026-08-30)**:底部遮罩驱动 —— 检查更新 → 「下载版本更新」→ 进度条(百分比/已下载/总大小/速度)+ **暂停/继续/取消**(基于 electron-updater CancellationToken)→ 「安装更新」(先写入 installing 状态并 flush 配置,再 quitAndInstall) - **发布说明切换**:默认显示当前版本;检测到可用更新后自动切到最新版本(`getReleaseNotes(v{version})`), 退出重进回到当前版本 - **进度持久化**:每次状态/进度变化写入 `Koring.yml` 的 `update` 段 (state/version/percent/transferred/total/source/error);应用启动时清理上次的进行中状态 - 配套改动:发布流水线 `gh release create` 上传 `release-notes.md` 附件 (旧版本发布的 release 无此附件,页面会显示回退/空态)