☰
MooTool Next Tauri 自动更新清单架构解析:双层清单与独立发布通道的设计与实践
2026/10/16 2:51:28 网站建设 项目流程
  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

本文基于 next-tauri/doc/adr/008-updater-manifest.md 展开。MooTool 仓库同时维护 Java、Electron 与 Tauri 三条桌面产品线,如何让 Tauri 客户端拥有稳定的latest.json静态更新源,又不破坏各产品线的独立发布边界,是本文要解决的核心问题。读完本文你将理解双层更新清单(根update-manifest.json+next-tauri-updater/latest.json)的职责划分、Rust Core 侧的校验逻辑、CI 发布与人工提升(promotion)流水线,以及防回退与幂等策略的工程细节。

背景:多产品线共存带来的更新源冲突

MooTool 仓库是一个多产品线仓库,同时维护:

  • Java 桌面版(使用v*tag 和version_summary.json更新清单);
  • MooTool Next Electron(使用next-electron-v*tag 和独立的 Electron 更新清单);
  • MooTool Next Tauri(基于 Tauri 2、Rust、React 与 TypeScript 的独立产品线)。

ADR-008 明确指出问题的本质:

  • Tauri Updater 需要一个平台级的静态latest.json(版本号、发布日期、发行说明、各平台下载 URL 与 updater 签名),这是tauri-plugin-updater的常规消费格式;
  • 仓库又需要一个统一入口来表达不同产品的状态与历史;
  • 如果直接让 Tauri 客户端读取 Electron 清单,或让多个产品共用同一个稳定更新 URL,就会破坏独立产品边界——一个产品的回滚、发布或失败会连带影响另一条产品线。

因此,这份 ADR(状态:已采纳,日期:2026-08-17)确定了两层清单的架构方案。

双层清单:根清单只做“产品发现”,latest.json才是“更新源”

第一层:根update-manifest.json只负责产品发现

仓库根目录的 update-manifest.json 是仓库级统一入口,使用schemaVersion: 1,下面挂载java、next-electron、next-tauri三个产品节点。以 Tauri 节点为例(当前仓库状态):

"next-tauri": { "displayName": "MooTool Next Tauri", "status": "active", "updaterManifestUrl": "https://github.com/rememberber/MooTool/releases/download/next-tauri-updater/latest.json", "releases": [ { "version": "0.1.0-rc.1", "title": "MooTool Next Tauri 0.1.0-rc.1", "prerelease": true, "releaseUrl": "https://github.com/rememberber/MooTool/releases/tag/next-tauri-v0.1.0-rc.1", "updaterManifestUrl": "https://github.com/rememberber/MooTool/releases/download/next-tauri-updater/latest.json", "assets": [ ] } ] }

关键约定(继承自 ADR-008 决策点 1、3):

  • Tauri 客户端固定读取products.next-tauri,验证产品名、状态和专属更新 URL,不解析其他产品节点;
  • 首次正式提升前,products.next-tauri.status保持planned;首次成功提升后才改为active(决策点 6 的结果)。当前仓库中该节点已是active,并记录了0.1.0-rc.1、0.1.0-rc.2两个候选版本;
  • 只有发布脚本允许修改该节点,Java/Electron 的版本、标签、资产与清单节点不在 Tauri 流程范围内。

第二层:next-tauri-updater/latest.json是 Tauri Updater 的稳定静态清单

Tauri 客户端实际消费的更新源,是next-tauri-updater这个独立 Release 中的latest.json。其结构在 next-tauri/scripts/prepare-release-assets.mjs 中定义:

{ "version": "0.1.0-rc.2", "notes": "发行说明正文", "pub_date": "2026-09-05T22:31:09Z", "release_url": "https://github.com/rememberber/MooTool/releases/tag/next-tauri-v0.1.0-rc.2", "platforms": { "darwin-x86_64": { "signature": "base64 minisign 签名", "url": "https://github.com/rememberber/MooTool/releases/download/next-tauri-v0.1.0-rc.2/MooTool-Next-Tauri-0.1.0-rc.2-mac-x64.app.tar.gz" }, "darwin-aarch64": { "signature": "...", "url": "...mac-arm64.app.tar.gz" }, "windows-x86_64": { "signature": "...", "url": "...win-x64-setup.exe" }, "linux-x86_64": { "signature": "...", "url": "...linux-x64.AppImage" } } }

它覆盖 ADR-008 决策点 2 要求的所有字段:版本、发布日期、发行说明、Release URL,以及 macOS x64/arm64、Windows x64、Linux x64 的下载 URL 与 updater 签名。pub_date由发布工作流从 tag 提交时间解析为 RFC 3339 字符串(见 next-tauri-release.yml 中git show -s --format=%cI "${tag}^{commit}"),Rust Core 侧再格式化为前端可展示的日期。

Rust Core 侧:更新源固定且不可由渲染层注入

ADR-008 决策点 5(结果段)强调“更新源固定且由 Rust Core 校验,不能由渲染层注入任意端点”。这一约定在 next-tauri/src-tauri/src/commands/update.rs 中直接落地:

const PRODUCT_ID: &str = "next-tauri"; const PRODUCT_NAME: &str = "MooTool Next Tauri"; const UPDATE_REGISTRY_URL: &str = "https://raw.githubusercontent.com/rememberber/MooTool/master/update-manifest.json"; const UPDATE_MANIFEST_URL: &str = "https://github.com/rememberber/MooTool/releases/download/next-tauri-updater/latest.json"; const MAX_REGISTRY_BYTES: usize = 1024 * 1024; const MAX_RELEASE_NOTES_BYTES: usize = 64 * 1024;

根清单的解析与校验链

fetch_product_feed()使用仅 HTTPS、最多 3 次重定向、15 秒超时的reqwest客户端拉取根清单,并限制响应不超过 1 MiB(read_limited)。随后product_feed_from_registry()(update.rs)执行五层校验:

  1. schema 校验:schema_version != 1报update_manifest_invalid;
  2. 产品存在性:根清单缺少next-tauri节点报update_product_missing;
  3. 产品身份:displayName必须等于MooTool Next Tauri,否则报update_product_mismatch;
  4. 状态机:planned返回ProductFeed::Inactive(客户端直接判定“无更新”,不发起任何网络请求);不是active则报update_product_inactive;
  5. 端点白名单:updaterManifestUrl必须精确等于编译期常量UPDATE_MANIFEST_URL,否则报update_manifest_untrusted。

其中第 5 步是“端点不可注入”的关键实现——即使渲染层被攻破或根清单被篡改,只要updaterManifestUrl不是 Tauri 专属稳定通道,更新就会被拒绝。

单元测试对边界的验证

同一文件末尾的测试模块直接印证了 ADR 的约束:

  • planned_product_does_not_contact_an_update_endpoint:planned状态返回Inactive,不访问更新端点;
  • active_product_accepts_only_the_tauri_channel_endpoint:只接受 Tauri 通道 URL,伪造的example.invalid/electron.json会被拒绝并返回update_manifest_untrusted;
  • product_identity_and_schema_are_enforced:错误 schema 与错误产品名分别返回update_manifest_invalid、update_product_mismatch;
  • release_notes_are_truncated_on_a_unicode_boundary:发行说明超 64 KiB 时在 Unicode 边界截断;
  • publication_date_uses_javascript_compatible_rfc3339:发布日期以 RFC 3339 输出,前端new Date()可直接解析。

客户端完整更新流程:检查 → 下载 → 取消 → 安装 → 重启

Rust 命令层

update.rs暴露四个 Tauri Command:

  • check_for_product_update(L115-L172):读取当前版本 → 获取产品 feed → 用updater_builder().endpoints(vec![endpoint]).timeout(30s)构建 updater 并check()。仅当raw_json.release_url以https://github.com/rememberber/MooTool/releases/tag/next-tauri-v开头时才透传给前端;返回available / upToDate / inactive三种状态;
  • install_product_update:通过update.download_and_install()流式回调进度事件(Started / Progress / Finished / Installed),并用tokio::select!支持取消(cancel_install唤醒Notify使下载中止,返回cancelled错误码);
  • cancel_product_update/relaunch_after_product_update:分别取消进行中的下载、在重启前flush_window_state后调用app.restart()。

前端调用层

next-tauri/src/platform/api/updateApi.ts 将上述命令封装为ProductUpdateApi(check / install / cancel / relaunch),并在非 Tauri 环境(浏览器预览)降级为browserProductUpdateApi,明确提示“浏览器预览不能安装桌面更新”。事件契约定义在 next-tauri/src/platform/contracts/update.ts,ProductUpdateEvent涵盖started / progress / finished / cancelled / installed。对应的前端测试 updateApi.test.ts 断言只允许调用这 4 个产品专属命令。

“设置 → 关于与更新”界面

next-tauri/src/features/settings/UpdateCard.tsx 是用户可感知的完整闭环:点击“检查更新”调用check_for_product_update,展示最新版本、发布日期与发行说明;有可用更新时展示“安装并重启”按钮;下载期间实时渲染百分比进度条,并允许取消。这印证了 README 中的描述:设置中的更新入口“只读取根清单中的products.next-tauri,再进入 Tauri 专属静态更新通道,不会读取 Electron 更新节点”。

发布流水线:Draft Pre-release 先行,人工发布后才提升稳定通道

ADR-008 决策点 3 是发布流程的核心纪律:next-tauri-v{version}首先生成 Draft Pre-release;只有人工发布后,next-tauri-promote-update.yml才验证所有公开资产 URL、提升latest.json,随后只更新根清单的products.next-tauri。

第一步:.github/workflows/next-tauri-release.yml构建并生成 Draft

  • 触发条件为next-tauri-v*tag(支持workflow_dispatch指定已有 tag 重跑);
  • preparejob 校验 tag 格式、package.json版本一致、release-notes/{version}.md存在,并执行 check-release-boundaries.mjs;
  • buildjob 在四个平台(macOS Intel x64、macOS ARM64、Windows x64、Linux x64)构建,要求注入TAURI_SIGNING_PRIVATE_KEY/TAURI_SIGNING_PRIVATE_KEY_PASSWORD,并在上传前分别完成 macOS codesign 验证 + DMG 首次启动、Windows 静默安装/启动/卸载、Linux AppImage/deb 安装/启动/卸载的冒烟检查;
  • draft-releasejob 调用prepare-release-assets.mjs prepare统一命名安装包、计算 SHA-512、读取.sig签名文件,生成latest.json与next-tauri-release.json,然后用gh release create "${RELEASE_TAG}" --draft --prerelease --latest=false创建Draft Pre-release。

关键点在于**--latest=false**(决策点 5):无论是版本 Release 还是更新通道,都显式不竞争仓库级Latest,避免 Tauri 的发布状态干扰 Java/Electron 的用户下载页。check-release-boundaries.mjs会硬性断言两个工作流都包含--latest=false,以及tauri.conf.json中bundle.targets为all、createUpdaterArtifacts为true、plugins.updater.endpoints为空数组(静态端点只能经products.next-tauri解析)、Windows 安装模式为passive等边界。

第二步:.github/workflows/next-tauri-promote-update.yml人工发布后的提升

该工作流由release.published事件触发,且仅当 tag 以next-tauri-v开头时运行。它完整实现决策点 3、4:

  1. 断言被发布的是一个非 Draft 的 Pre-release;
  2. validate-promotion:校验latest.json与next-tauri-release.json版本一致、四个platformskey 齐全、每个签名格式合法、每个 URL 都落在next-tauri-v{version}独立版本 Release 下、release_url一致(对应 prepare-release-assets.mjs 的validatePromotion);
  3. 用curl --head逐一验证所有公开安装包与 updater 资产 URL 可达;
  4. 创建或更新next-tauri-updater通道 Release(同样--latest=false,标题固定为MooTool Next Tauri Update Channel),上传latest.json --clobber,并回读已发布的latest.json逐字节比对,确认稳定通道与本次提升版本完全一致;
  5. 执行update-registry,将products.next-tauri.status置为active、写入updaterManifestUrl与releases,提交并推送update-manifest.json。

防回退与幂等(决策点 4)

updateProductManifest(prepare-release-assets.mjs)在写入根清单前用完整的 SemVer 比较器(含 pre-release 段数值比较)检查:

if (latestExisting && compareVersions(release.version, latestExisting.version) < 0) { throw new Error(`Refusing to downgrade the Tauri update channel from ${latestExisting.version} to ${release.version}`) }

即:拒绝把稳定通道指向低于现有最高版本的 Release;而相同版本写入会先过滤同版本旧条目再幂等重排,因此重复提升不会报错。release:rehearse脚本(见 package.json)可在本地演练整条提升路径。

独立发布、回滚与故障处理

ADR-008 的结果部分承诺的三项收益,在 next-tauri/doc/release-runbook.md 中有完整的操作化落地:

场景处置方式
Draft 未发布修复源码后对同一 tag 重跑,工作流只允许覆盖仍为 Draft 的 Release
已发布但尚未提升修复公开资产或发布更高补丁版本,不要手工激活根清单
已提升不覆盖既有安装包、不把稳定通道降级到旧版本;撤下有问题的 Release 并发布更高补丁版本修复
updater 私钥丢失停止发布,走经过评审的密钥轮换和人工安装方案,不得无提示更换公钥

本地发布门禁(在next-tauri目录):

npm ci npm run check npm audit --audit-level=high

其中npm run check串联发布边界、TypeScript、前端与 Rust 测试、生产 Web 构建、Rustfmt 与 Clippy,并强制package.json、src-tauri/Cargo.toml、src-tauri/tauri.conf.json、release-notes/{version}.md四处版本一致。本地带签名构建示例:

TAURI_SIGNING_PRIVATE_KEY="$HOME/.tauri/mootool-next-tauri.key" \ TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(<"$HOME/.tauri/mootool-next-tauri.key.password")" \ APPLE_SIGNING_IDENTITY=- \ npx tauri build --bundles app,dmg

仓库中的 src-tauri/tauri.conf.json 只保存 updater 公钥(含minisign public key标识),签名私钥通过 GitHub Secrets 注入且不得进入日志或 Artifact;公钥轮换必须走密钥轮换方案而非直接替换。相关签名决策另见 next-tauri/doc/adr/009-code-signing.md。

总结:一份 ADR 如何约束整条发布链路

ADR-008 的价值在于把“多产品线共存”的架构约束显式化,并通过仓库代码、脚本与 CI 逐层落地:

  • 根 update-manifest.json是仓库统一入口,只承担产品发现;products.next-tauri是 Tauri 客户端的唯一读取节点;
  • next-tauri-updater/latest.json是平台级静态稳定清单,承载 Tauri Updater 所需的全部元数据与签名;
  • commands/update.rs用编译期常量 + 五层校验保证更新源固定、状态机清晰、端点不可注入;
  • prepare-release-assets.mjs与两个 CI 工作流把 Draft → 人工发布 → 提升 → 根清单激活的全过程脚本化,并内置防回退与幂等;
  • 结果:Java、Electron、Tauri 三条产品线可独立发布、回滚与演进;Draft 或资产不完整的 Release 不会被客户端发现;相同或更低版本不会被错误提示为更新。

如需完整阅读决策上下文与实操手册,建议依次查看 ADR-008、next-tauri/doc/release-runbook.md 以及上文索引的源码文件。

  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

相关推荐

上一篇:Fathom Lite日志管理成本优化:采样
下一篇:Ladda性能基准测试:与其他加载按钮库的对比分析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询