# 应用自动更新 ## 概述 基于 `electron-updater` 实现应用内自动更新,支持 macOS / Windows (NSIS) / Linux 自动下载安装,Windows MSI 安装引导到下载页(优先 OSS 安装包直链)。 **强制使用 latest.json**:应用与 Release 流程均以 OSS 上的 `latest.json` 为**唯一数据源**。版本检查、各平台下载地址、签名与大小均来自该文件;不直接依赖 GitHub Release 或 electron-builder 生成的 yml 作为来源。 **更新源(macOS / Linux 应用内升级)**:检查与下载均走阿里云 OSS。 - **检查更新**:**仅**拉取 OSS `latest/latest.json` 获取最新版本;其中 `platforms` 已包含各平台完整的 OSS 下载包地址(url、signature、size)。若有更新则将 electron-updater 的 feed 设为版本化 OSS 路径 `${OSS_BASE}/electron-v${version}`。 - **下载安装包**:安装包地址**仅**来自 `latest.json` 的 `platforms`(已是完整 OSS URL)。electron-updater 实际下载时从 feed 请求的 `latest-mac.yml` / `latest-linux.yml` 等,**必须**由同一份 `latest.json` 的 `platforms` 在 Release 流程中派生生成,不得使用 GitHub 自带的 yml。 - **失败处理**:OSS 不可达或元数据异常时,直接报错并引导用户前往下载页,不回退到 GitHub。 ## 核心文件 | 文件 | 职责 | |------|------| | `src/main/services/autoUpdater.ts` | 更新服务:检查、下载、安装、状态管理 | | `src/renderer/components/pages/AboutPage.tsx` | 关于页面:手动检查更新 UI | | `src/main/window/trayManager.ts` | 托盘菜单:检查更新入口 | | `src/main/ipc/appHandlers.ts` | IPC 处理:checkUpdate / downloadUpdate / installUpdate / openReleasesPage | | `src/preload/index.ts` | preload 暴露 API | | `src/shared/types/updateTypes.ts` | 类型定义:UpdateState / UpdateInfo / UpdateProgress | | `dev-app-update.yml` | 开发模式更新源配置 | ## 更新流程 ### 1. 启动自动检查 ``` App 启动 → 延迟 10s → checkForUpdates() │ ├── 无更新 → 静默,不提示 │ ├── 有更新 → 检查 skipped version │ │ │ ├── 已跳过 → 不弹窗 │ │ │ └── 未跳过 → 弹窗提示 │ │ │ ├── "立即更新" → 下载 → 二次确认 → 重启安装 │ │ │ └── "跳过此版本" → 记录到文件,下次不再提示 │ └── 失败 → 静默记录日志 ``` ### 2. 手动检查(About 页面) ``` 用户点击 "检查更新" │ ├── 检查中... (loading 状态) │ ├── 无更新 → 提示 "当前已是最新版本",重置按钮 │ ├── 有更新 → 显示 "下载更新" 按钮 │ │ │ └── 点击 → 下载(显示进度条)→ 完成 → Modal 确认 → 重启安装 │ └── 错误 → 显示错误信息 + 重试按钮 ``` ### 3. 手动检查(托盘菜单) 与 About 页面类似,但使用原生 `dialog.showMessageBox` 弹窗交互。 ## Windows 安装类型检测 通过检查 NSIS 卸载程序文件是否存在来区分安装方式: ```typescript // NSIS 安装会生成: {appDir}/Uninstall {productName}.exe // MSI 安装由 Windows Installer 管理,无此文件 function detectInstallerType(): InstallerType { const nsisUninstaller = path.join(appDir, `Uninstall ${productName}.exe`); return fs.existsSync(nsisUninstaller) ? 'nsis' : 'msi'; } ``` | 安装类型 | 自动更新 | 行为 | |----------|---------|------| | NSIS (.exe) | 支持 | 下载 → 重启安装 | | MSI (.msi) | 不支持 | 引导到下载页(按 MSI 优先解析下载包) | | macOS (.dmg) | 支持 | 下载 → 重启安装 | | Linux (.AppImage/.deb) | 支持 | 下载 → 重启安装 | ## 跳过版本 - 用户在启动弹窗中选择"跳过此版本"后,版本号写入 `~/.qimingclaw/.skipped-update-version` - 下次启动时,若远程最新版本与已跳过版本相同,不弹窗 - 出现更新的版本(高于已跳过版本)时,重新弹窗提示 - 手动检查更新(About 页面 / 托盘菜单)不受跳过逻辑影响 ## 安装前清理 `installUpdate()` 在调用 `quitAndInstall()` 之前,先执行 `cleanupAllProcesses()`: - 停止 Computer Server - 销毁 Unified Agent Service - 终止 Agent Runner / Lanproxy / File Server 进程 - 清理 MCP Proxy - 停止所有 Engine 进程 ## 开发模式 - 通过 `dev-app-update.yml` 配置更新源(GitHub) - `forceDevUpdateConfig = true` 启用开发模式检查 - 允许检查更新(验证 API 通路) - 下载和安装被阻止(Squirrel.Mac bundle ID 不匹配会导致崩溃) - `autoInstallOnAppQuit = false` ## OSS 单源策略(强制 latest.json) 所有更新元数据**强制以 latest.json 为准**:应用端只读 OSS `latest/latest.json` 做版本判断与地址解析;Release 流程只产出并上传由该文件派生的 yml,禁止使用或上传 GitHub 自带的 yml。 | 步骤 | 策略 | |--------------|------| | 检查更新 | 仅 OSS latest.json | | Feed 基地址 | 仅 OSS 版本化路径 | | 下载安装包 | 仅 OSS(yml 必须由 latest.json 的 platforms 派生) | | OSS 异常 | 报错 + 引导下载页,不回退 GitHub | **数据源规则**:OSS 上**只**维护并信任 `latest.json`(含 version、notes、pub_date、platforms 多架构与完整 OSS 下载地址)。Release 流程(`.github/workflows/release-electron.yml`)**必须**根据该 `latest.json` 的 `platforms` 生成 electron-updater 所需的 `latest-mac.yml`、`latest-linux.yml`、`latest.yml`,并写入 release-assets 后一并上传;**禁止**使用或上传 GitHub 自带的 yml;yml 内下载地址为相对路径(相对 feed 基地址即 OSS 版本化路径)。 本地触发 OSS 同步时,可在本 crate 下执行 `scripts/sync-oss.sh `(如 `electron-v0.8.0`),会触发上述 workflow 并轮询直至完成;依赖 `gh`、`jq`。 ## 自定义更新源 通过环境变量 `QIMING_UPDATE_SERVER` 可指定自定义更新服务器: ```bash QIMING_UPDATE_SERVER=http://localhost:8080/updates npm run dev ``` 使用 `generic` provider,适合本地测试。 ## UpdateState 类型 ```typescript interface UpdateState { status: 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'downloaded' | 'error'; version?: string; progress?: { percent: number; bytesPerSecond: number; transferred: number; total: number }; error?: string; canAutoUpdate?: boolean; // false 表示 MSI 安装,需手动下载 } ``` ## IPC 通道 | 通道 | 方向 | 说明 | |------|------|------| | `app:checkUpdate` | renderer → main | 手动检查更新 | | `app:downloadUpdate` | renderer → main | 下载更新 | | `app:installUpdate` | renderer → main | 重启安装 | | `app:getUpdateState` | renderer → main | 获取当前更新状态 | | `app:openReleasesPage` | renderer → main | 打开下载页(从 latest.json 解析 OSS 安装包直链) | | `update:status` | main → renderer | 推送更新状态变化 |