OpenProgram 正式版本更新设计

本文定义普通 Desktop、CLI/server 与源码开发安装如何发现和应用新版本。稳定安装只消费完整、已发布、已验证的 GitHub Release;GitHub 仓库出现新提交不等于可安装更新。

设计状态:已实现,待首个 updater release目标版本:v0.6.7+默认通道:stable版本源:GitHub Release

1. 已确定的设计约束

  1. stable 只解析 GitHub 上最新的非 draft、非 prerelease 正式 Release。普通用户永远不跟踪 main、任意 commit 或未完成的 tag。
  2. 更新单位是完整平台产物,不做 Python wheel、Web、Program、OCR、Browser 或模型的局部产品更新。新版本必须继续满足统一 capability manifest。
  3. 默认自动检查,首次启动延迟执行;成功检查后每 24 小时检查一次,失败后最早 6 小时重试。网络错误不影响应用启动,也不循环弹窗。
  4. Desktop 与 CLI/server 共用版本解析、Release 选择和 checksum 规则,但安装动作按产物不同实现。
  5. 当前 macOS Desktop 使用 unsigned DMG。应用内负责检查、架构选择、完整下载和 SHA-256 校验,并在校验成功后打开 DMG;不实现自定义后台进程替换正在运行的 .app
  6. macOS/Linux CLI/server 复用现有 release installer:下载到新版本目录,验证完整 runtime 和 worker cold start,成功后原子切换 current,失败时保留原版本。
  7. 源码开发安装继续使用显式 Git ref 的 openprogram upgrade 流程。设置页不能把源码 checkout 更新解释为普通稳定更新。
  8. 当前只提供一个 stable 通道,不显示没有实际行为差异的通道选择器。
  9. 删除 worker 启动时的“检查并自动应用”行为。OPENPROGRAM_IMMUTABLE_RUNTIME=1 的 managed runtime 和 Desktop 不允许进入旧的 Git/PyPI updater;openprogram update 只保留为 openprogram upgrade 的兼容入口。
  10. 统一安装类型只有 managed_releasesource_checkoutunknown。检测时 OPENPROGRAM_IMMUTABLE_RUNTIME=1 优先于 site-packages 等路径启发式规则;正式产品不得显示或使用 pip_wheel 更新语义。
产品语义:“自动检查更新”表示应用定期发现正式 Release。“下载更新”表示选择当前平台/架构的完整产物并校验。“安装更新”只有在现有安装机制能保证验证、切换和失败保留旧版本时才使用该名称。

2. 实施前基线与当前实现

范围v0.6.6 基线当前实现
Release已包含 macOS arm64/x64 unsigned DMG/ZIP、macOS/Linux 完整 runtime、各 runtime checksum 与 release-manifest.json客户端解析 /repos/Fzkuji/OpenProgram/releases/latest,拒绝 draft、prerelease、重复/缺失资产、非 vX.Y.Z tag 和 manifest 不一致的 Release。
Desktop没有更新 IPC、定期检查或下载校验。使用 app.getVersion();主进程实现检查、持久化、调度、下载与 SHA-256 校验,preload 只暴露无 URL 参数的动作,设置页显示状态、发布日期、notes 和字节进度。
CLI/server版本化 installer 已完成 checksum、capability manifest 与 cold start。managed release 的 openprogram upgrade 严格验证 Release manifest 后执行不可变 tag installer;launcher 准备完成后以 os.replace 原子替换已有 current
旧后台 updaterworker 会自动进入 Git/PyPI 更新路径。后台 product apply 与 PyPI/Git 更新实现已删除;immutable runtime 只允许正式 Release 路径,源码 checkout 只允许显式 upgrade。
升级文档Desktop 仅说明手动下载 DMG,CLI 说明重新运行公开安装命令。双语文档说明 v0.6.6 的一次性过渡、自动检查、校验下载、Desktop 安装交接和 CLI 原子升级边界。

3. 官方规范与方案比较

参考明确能力/约束采用方式
Electron autoUpdatermacOS 基于 Squirrel.Mac,原生自动更新要求应用已签名;Electron 本身不为 Linux 提供内置 updater。当前不采用安装模块。保留其事件状态模型,但不调用无法满足当前产物约束的 quitAndInstall()
Electron Updating Applications正式更新依赖平台产物、更新 metadata、检查/下载/重启事件;官方 GitHub 服务也把 macOS 签名列为条件。修改采用。GitHub Release 作为 metadata 和下载源;Desktop 停在经过校验的 DMG 打开步骤。
electron-builder Auto Update可生成 latest-mac.yml 并由 electron-updater 管理下载,但 macOS 自动更新同样要求签名。拒绝新增依赖。现有 Release API、manifest 和 Node/Electron API 足够完成本阶段,不引入未能执行安装的 updater 依赖。
GitHub Releases APIlatest endpoint 返回最新发布的正式 Release,并排除 draft/prerelease;响应包含 tag、正文和 assets。采用。客户端仍再次校验字段和版本格式,并按确切文件名匹配资产。
自定义 Desktop 替换 helper可等待应用退出后移动 .app,但需自行处理权限、DMG/App Translocation、备份、恢复、并发与启动确认。拒绝。新增高风险安装器不能达到现有 CLI 原子升级的验证水平。

4. 统一版本与 Release 模型

GitHub latest正式 Release
vX.Y.Z
严格验证tag、draft、prerelease、manifest
选择产物平台 + CPU 架构 + 完整产品
平台安装Desktop DMG / CLI runtime

5. macOS Desktop 更新

5.1 主进程职责

  1. 启动后读取 userData/update-state.json;默认 automaticChecks=true
  2. 持久化 schema 1 包含 automaticCheckslastAttemptAtlastSuccessAt 和最近一次成功的、经过验证的 release metadata。使用同目录临时文件加 rename 写入;损坏文件、未知 schema 或无效字段回退默认值。
  3. packaged app 启动 30 秒后计算检查计划。成功后下一次自动检查安排在 lastSuccessAt + 24h;失败后安排在 lastAttemptAt + 6h,避免密集重试。手动检查始终跳过成功缓存和失败退避。
  4. 主进程只保留一个定时器和一个进行中的检查 Promise。每次完成、系统 sleep/resume 和 automaticChecks 设置变化后重新计算下一次时间;resume 不补发多个过期检查,最多立即启动一个,然后重新排程。开发模式不执行定时检查,但保留手动测试入口。
  5. 使用 Electron 主进程网络 API 请求 GitHub latest,设置固定 User-Agent、超时和响应大小上限。
  6. process.arch 选择 OpenProgram-X.Y.Z-mac-arm64-unsigned.dmgOpenProgram-X.Y.Z-mac-x64-unsigned.dmg,从 manifest 取得字节数和 SHA-256。
  7. 用户点击下载后显示系统保存对话框;写入同目录临时文件,流式计算 SHA-256,校验字节数和 hash 后原子改名。
  8. 校验成功后打开 DMG;失败时删除临时文件并显示可重试错误。不得打开未校验文件。

5.2 IPC 边界

updates.getState()                         // 只读当前状态
updates.check()                           // 手动检查;主进程强制刷新
updates.setAutomaticChecks(enabled)       // 保存布尔偏好
updates.download()                        // 只下载主进程已验证 metadata 对应的 DMG
updates.openRelease()                     // 打开固定 Release 页面
updates.onState(callback)                 // 状态推送

renderer 不能传入仓库、URL、文件名、hash 或目标命令。主进程持有全部远端和文件系统权限,并只接受布尔偏好和无参数动作。

6. macOS/Linux CLI 与 server 更新

Release 安装

openprogram upgrade 解析 latest,若有新版本则下载该 tag 的版本化 installer,并把目标版本显式传入。

原子切换

installer 在新目录完成 archive checksum、manifest、功能和 cold-start 验证,再切换 current

源码开发

检测到 Git checkout 后继续执行现有 preflight、checkout、依赖、build、probe 和 restart 流程;界面明确标为 development。

7. 设置页信息结构

入口固定在 Settings → General → Application。Desktop 和普通浏览器显示不同的可执行动作,但不按“desktop user/client user”重复分类。非 Desktop 页面通过 owner-auth 保护的只读 GET /api/system/version 取得 host 信息,响应 schema 为 {currentVersion, installType};版本来自当前 Python distribution metadata,安装类型来自统一 detector。detector 先检查 immutable runtime,再区分源码 checkout,否则返回 unknown;该接口不访问 GitHub、不执行更新。

当前版本动态显示实际运行版本与安装类型。
自动检查Desktop 开关,默认开启;只控制检查,不控制安装。
更新状态未检查、检查中、已最新、有更新、下载中、失败。
可用动作Check now、Download and open、View release notes。

8. 安全、隐私与失败处理

情况处理保留状态
离线、超时、GitHub 限流记录简短错误;自动检查静默,手动检查在设置页显示重试。应用和旧版本继续运行。
Release 字段或版本无效拒绝该响应,不下载任何资产。保留最近一次成功 metadata,但标明检查时间。
缺少架构资产/manifest 条目显示“该版本没有当前架构的完整安装包”。不退回错误架构、ZIP、wheel 或源码包。
下载中断/hash/size 不一致删除临时文件;不得打开 DMG 或切换 CLI runtime。已安装版本不变。
并发检查或下载复用同一个进行中的 Promise;按钮进入 disabled 状态。只有一个网络请求/下载任务。
Release Notes 内容设置页显示纯文本摘要;不把 GitHub Markdown 当 HTML 注入。完整正文只在外部 GitHub 页面打开。
更新状态文件损坏/未知 schema/未来时间恢复默认值并立即以临时文件加 rename 重写;失败时留在内存并在下次写入重试。不阻止应用启动,也不把损坏时间戳用于节流。

自动检查会向 GitHub 暴露普通 HTTPS 请求所含的 IP、User-Agent 与请求时间。不开启遥测,不上传 OpenProgram 配置、会话或设备标识。

9. 实现顺序与验收

  1. 旧 updater 收敛:测试证明 worker 启动不再 apply 更新、immutable runtime 不进入 Git/PyPI 路径、update 兼容入口与 upgrade 使用同一正式语义。
  2. 安装类型:测试证明 Desktop/CLI 的 immutable runtime 均为 managed_release,源码仓库为 source_checkout,不存在把正式产品显示为 pip_wheel 的路径。
  3. Desktop 检查与 UI:纯函数测试覆盖版本解析、latest 验证、真实 workflow 嵌套 manifest path 的 basename 唯一匹配、架构资产选择和状态转换;IPC 测试证明 renderer 不能指定 URL;Web 测试覆盖所有状态和非 Desktop 回退。
  4. Desktop 下载:自动化测试通过注入的 fetch/Response 覆盖正常下载、允许的 GitHub 资产重定向、拒绝第三方/HTTP 重定向、重定向上限、body stall、大小不符、hash 不符、取消保存和并发请求;没有把该测试描述为真实 HTTP server 验收。只有校验成功才调用打开 DMG。
  5. 持久化与调度:自动化测试覆盖 24 小时成功缓存、失败后 6 小时内不重复、6 小时到期重试、损坏/未来/未知 schema、原子写入及写失败回滚;main 的 timer、manual IPC 重排和 resume wiring 由源码合同检查覆盖。应用连续运行与实际 sleep/resume 属于首个 updater release 的可见验收,不列为本地自动化已执行。
  6. CLI/Linux:fake GitHub/installer 测试覆盖 --check、无更新、缺资产、安装失败保留旧 current、成功切换且不重启现有 worker、显式 restart 后使用新版本,以及注入 archive/checksum/repository 环境变量不能改变产品命令的正式更新来源。
  7. 发布门禁:release workflow 校验每个 Desktop 资产都存在 manifest 条目,每个 runtime 都有独立 checksum;Release 创建后使用 workflow token 查询 GitHub latest endpoint,确认目标 tag、稳定状态和唯一 manifest 资产。
  8. 可见验收:/Applications/OpenProgram.app 的 default profile/18100 实例中验证设置页、版本号、手动检查、下载进度和失败提示;不启动第二个可见 Desktop 实例。

10. 实现与验证记录

不得宣称:当前 macOS Desktop 不得显示“自动安装”“后台替换”或“重启后自动完成安装”。完成标准是自动发现、经过校验的完整下载和明确安装交接;CLI/server 完成标准是原子切换成功并保留失败前版本。