# 发布指南 ## 当前你要做的操作(v2.1.1 macOS) 本机已打出 **Tauri 窗口布局** 的 Developer ID 签名 DMG(不是手搓 `hdiutil`,Finder 里是大图标 + 拖到 Applications): ```text release/v2.1.1/macos-arm64/OpsXShell_2.1.1_aarch64.dmg ``` 这份包**还没公证、没 staple**。别人下载后仍可能被 Gatekeeper 拦。你这边只差公证: 1. Finder 里弹出 `/Volumes/OpsXShell`(挂载中无法 staple)。 2. 开 Clash:`proxy-on`(公证走 HTTPS;时间戳构建阶段已经用 HTTP 回退签过了)。 3. 不要把 App 专用密码发到聊天。终端执行其一: ```bash APPLE_ID='1024way@gmail.com' ./scripts/macos-notarize.sh # 或 bash ~/Documents/apple签名/公证OpsXShell.sh ``` 4. 弹出框里填 **App 专用密码**(appleid.apple.com → 登录与安全 → App 专用密码,标签如 `app notary`)。 5. 脚本会 `notarytool submit --wait --timeout 15m`。第一次可能很久;`--wait` 卡住时等它超时,再跑同一条命令——**已 Accepted 的不会重新上传,只会 staple**。不要对同一份未变化的 DMG 连续第三次 `submit`。 6. 成功标志:`xcrun stapler validate` 通过,且 `codesign -dv` 能看到钉票。把 **这一份** DMG 发出去(尽量不要走微信,隔离属性会更烦)。 7. 改代码后再 `./scripts/build.sh mac-arm64 --quick` 会得到新哈希,必须重新公证。 Windows Authenticode、自动更新仍未做,不要对外写「已公证」直到第 6 步完成。 ## 构建命令 ```bash # 推荐:当前平台完整门禁、打包、验证和 SHA-256 ./scripts/build.sh # macOS Apple Silicon 指定版本 ./scripts/build.sh v2.1.1 mac-arm64 # 构建完成后公证并钉票(新 DMG 必须再跑;已 staple 会直接退出) proxy-on # 本机 Clash;codesign 时间戳另见下文 HTTP 回退 APPLE_ID='你的开发者邮箱' ./scripts/macos-notarize.sh # Windows x86_64(推荐在 Windows CMD/PowerShell 中执行) scripts\build.bat v2.1.1 win-x64 # 本地重复构建时缩短门禁;正式发布不要跳过 ./scripts/build.sh v2.1.1 mac-arm64 --quick ``` `scripts/build.sh` 与 `scripts/build.bat` 面向 v2.1.x,会检查 Node、npm、Rust target 和五处版本元数据。显式传入不同于当前源码的新版本时,脚本会同步版本文件;它们只负责构建,不会自动创建 tag 或推送远程仓库。安装包和 `SHA256SUMS.txt` 输出到: ```text release/v2.1.1/macos-arm64/ release/v2.1.1/windows-x64/ ``` 平台安装包必须在目标系统构建和验证。macOS 不能替代 Windows x86_64 实机打包。 ### Windows 构建环境 建议使用 Windows 10/11 x64,并安装: - Node.js 18+ 与 npm - Rust stable 的 `x86_64-pc-windows-msvc` toolchain - Visual Studio 2022 Build Tools,勾选 **Desktop development with C++** - Windows 10/11 SDK - Microsoft Edge WebView2 Runtime 首次准备: ```bat rustup default stable-x86_64-pc-windows-msvc rustup target add x86_64-pc-windows-msvc npm ci scripts\build.bat v2.1.1 win-x64 ``` 默认执行完整门禁;`--quick` 适合开发期重复构建,正式发布不要使用 `--skip-checks`。输出位于 `release\v2.1.1\windows-x64\`。没有代码签名证书时可以生成开发安装包,但 Windows SmartScreen 可能提示未知发布者。 ## Gitee Release 构建、提交并推送对应 tag 后执行: ```bash # 只检查附件,不访问 Gitee API ./scripts/release.py v2.1.1 --dry-run # 创建或更新 Release,上传安装包和 SHA256SUMS.txt,并核验远程附件 ./scripts/release.py v2.1.1 ``` 发布凭据从 `GITEE_TOKEN` 或 `~/.netrc` 的 `gitee.com` 条目读取,脚本不会输出 Token。脚本只读取 `release/v<版本>/` 下的 `.dmg`、`.exe`、`.msi` 和 `SHA256SUMS.txt`,上传成功后保留本地文件。远程已有同名同大小附件时会跳过;同名但大小不同时停止,避免静默覆盖已发布二进制。 macOS App 位于: ```text src-tauri/target/release/bundle/macos/OpsXShell.app ``` 指定 `aarch64-apple-darwin` target 时,产物位于对应 target 的 `release/bundle` 下。 ## 临时 DMG Tauri 调用 `hdiutil` 制作 DMG 时,可能在异常中断后留下: ```text rw.<进程号>.OpsXShell_<版本>_aarch64.dmg ``` 这些 `rw.*.dmg` 是可写临时镜像,不是发布包。确认没有正在运行的 Tauri/hdiutil 打包任务后可以删除。正式包没有 `rw.<数字>.` 前缀。 如果 DMG 脚本报错 `resource busy`,先检查是否正从临时挂载目录运行 OpsXShell: ```bash hdiutil info lsof +D /Volumes/<临时卷名> ``` 先正常退出该 OpsXShell 进程,再卸载对应的临时卷并重新构建。不要在应用仍从卷内运行时直接删除 `rw.*.dmg`。 ## 签名、公证和自动更新 申请账号和 Developer ID、导入钥匙串的完整步骤见 [download/Apple开发者签名.md](../download/Apple开发者签名.md)。 **当前状态(本机可签 macOS;这份 DMG 待公证;自动更新仍未做):** - 仓库 `tauri.conf.json` 的 `signingIdentity` 仍是 `"-"`,没证书的环境还能打 ad-hoc 开发包。 - 本机若已导入 **Developer ID Application**,把身份写进 **gitignore** 的 `src-tauri/tauri.signing.conf.json`。`./scripts/build.sh` 发现该文件就会按 Developer ID 签名。 - DMG 必须走 Tauri bundler(`bundle.macOS.dmg`:窗口 720×440,应用图标与 Applications 分列)。不要用手搓 `hdiutil create`,Finder 图标会变小。 - `timestamp.apple.com:443` 不通时,构建会用 `scripts/macos-codesign-wrapper.sh` 走 HTTP 时间戳。可设 `CODESIGN_USE_SYSTEM_TIMESTAMP=1` 关闭。 - **每次新打的 DMG 都必须再公证并 staple**:`APPLE_ID='…' ./scripts/macos-notarize.sh`。已钉票会直接退出。`--wait` 卡住时脚本会超时后尝试 staple。 - 证书、CSR、私钥放本机文稿 `~/Documents/apple签名/`,不要进 git。Team 内其他 Mac 桌面应用可复用同一张 Developer ID。 - Windows Authenticode、minisign 自动更新仍未接线。没有凭据、或尚未 staple 时,不得在 README / 界面 / CI 上写「已签名」「已公证」「支持自动更新」。 门禁: - `REQUIRE_SIGNING=true` 时,`scripts/release-preflight.mjs` 检查环境变量齐全,`scripts/verify-release.mjs` 拒绝 macOS ad-hoc、要求 Windows Authenticode `Valid`。 - 未设该变量时,`./scripts/build.sh` 仍可打开发包;若存在 `tauri.signing.conf.json` 则会签 Developer ID。 - CI `.github/workflows/tauri-release.yml` 在 Secrets 齐全时写入 `tauri.signing.conf.json`。本机用同一文件名即可,不要提交。 ### 1. 凭据 | 平台 | 要什么 | 放到哪里 | | -------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | macOS | Apple Developer Program;**Developer ID Application**(不是 Development);App 专用密码 | 本机钥匙串 + gitignore 的 `tauri.signing.conf.json`;CI Secrets:`APPLE_CERTIFICATE`(p12 的 base64)、`APPLE_CERTIFICATE_PASSWORD`、`APPLE_SIGNING_IDENTITY`、`APPLE_ID`、`APPLE_PASSWORD`、`APPLE_TEAM_ID` | | Windows | 代码签名证书(OV/EV PFX) | `WINDOWS_CERTIFICATE_BASE64`、`WINDOWS_CERTIFICATE_PASSWORD` | | 自动更新 | 单独的 **minisign** 密钥对,与 Apple/Authenticode 不是同一把 | 私钥只进 CI Secrets;**公钥**写进仓库的 updater 配置 | `src-tauri/tauri.signing.conf.json` 必须进 `.gitignore`,不得提交。macOS 示例: ```json { "bundle": { "macOS": { "signingIdentity": "Developer ID Application: Logan Li (B8DVUUUF6W)" } } } ``` ### 2. 本地与 CI 同源签名 1. 不要改仓库里的 `tauri.conf.json` 默认 `"-"`。 2. `REQUIRE_SIGNING=true` 时 `verify-release.mjs` 必须失败于 ad-hoc。 3. 正式标签才设 `REQUIRE_SIGNING=true`。日常 PR 保持未签名。 ### 3. macOS 公证(notarytool + staple) 签名成功 ≠ 用户能打开。必须公证并钉票。不要对同一份未变化的 DMG 连续多次 `submit`。 ```bash # 先弹出已挂载的安装盘,再: APPLE_ID='开发者邮箱' ./scripts/macos-notarize.sh # 可选:指定 DMG 路径 APPLE_ID='开发者邮箱' ./scripts/macos-notarize.sh release/v2.1.1/macos-arm64/OpsXShell_2.1.1_aarch64.dmg ``` 脚本会:已 staple 则退出;否则先 staple;没有票据才要 App 专用密码并 `notarytool submit --wait --timeout 15m`,超时仍 staple。开发者后台网页没有这份 DMG 的进度页,用终端或: ```bash xcrun notarytool info --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" xcrun notarytool log --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" ``` 验收(必须在**另一台未关 SIP、未关 Gatekeeper 的 Mac**上): 1. 下载 **staple 之后** 的 DMG,双击打开,拖到「应用程序」,不要右键「打开」、不要 `xattr -d`。 2. 能启动、能连 SSH,菜单里没有 DevTools。 3. `codesign --verify --deep --strict` 且 `codesign -dv` 含 `Notarization Ticket=stapled`,不是 `Signature=adhoc`。 没做完 staple 之前,对外说明不要写「已公证」。 ### 4. Windows Authenticode CI 已有 PFX 导入和 `Get-AuthenticodeSignature` 门禁。本机应对齐: 1. 导入 PFX,把 thumbprint 写入 `tauri.signing.conf.json`(`digestAlgorithm: sha256`,时间戳如 `http://timestamp.digicert.com`)。 2. 对 `.exe` / `.msi` 都验 `Status -eq Valid`,不要只签主程序。 3. 在一台干净 Windows 上安装:SmartScreen 不应再是「未知发布者」。EV 证书对 SmartScreen 更友好,OV 可能仍有一段时间的声誉积累——如实写,不要标「已通过 SmartScreen」。 ### 5. 自动更新(有公钥、HTTPS、回滚才开) 架构规则已经写死:没有签名公钥、HTTPS 更新源和回滚策略,更新必须保持关闭。接入顺序: 1. 生成 minisign 密钥;公钥进 `tauri.conf.json` 的 `plugins.updater.pubkey`;私钥只进 Secrets。 2. 依赖 `tauri-plugin-updater`,capabilities 只开 `updater:allow-check` 与 `updater:allow-download-and-install`。 3. 构建打开 `createUpdaterArtifacts`,每个安装包旁有 `.sig`。 4. 发布时生成 `latest.json`(版本、各平台 URL、签名),放到 HTTPS(Gitee/GitHub Release 附件或静态镜像,建议双 endpoint)。 5. 前端「检查更新」:**公钥和 endpoint 未进正式包之前不要画这个入口**。 6. 回滚:保留上一份 `latest.json`;更新失败或健康检查失败时指回上一版本;文档写清如何撤回错误版本(覆盖 manifest,而不是删掉用户已装的包)。 7. `release.py` 除 `.dmg/.exe/.msi` 外还要上传 `.sig` 与 `latest.json`。 未完成前,设置页、欢迎页、菜单都不要出现「自动更新」或「已是最新」。 ### 6. 发布工作流补齐(与签名独立,但同一闭环) `.github/workflows/tauri-release.yml` 现在只 `upload-artifact`(约 90 天过期),`permissions.contents: read`,不能建 GitHub Release。有凭据并公证之后再改: - tag 与现有 Gitee 流程对齐(不要两套版本号)。 - `contents: write` 后创建 Release,附 SHA256SUMS、安装包、`.sig`、`latest.json`。 - 公证步骤放在「Verify native package」之前或之中;未 staple 不得当正式附件。 GitHub Release 不是「已公证」的替代;没有 notary/Authenticode 时,Release 也只能标开发包。 ## 产物验证 ```bash npm run release:preflight:mac npm run release:verify:mac npm run release:preflight:win npm run release:verify:win ``` 验证脚本检查架构、体积和安装包存在性。正式标签还会验证 macOS 签名/Gatekeeper 或 Windows Authenticode 签名。 安装包还必须包含 `ssh://` 协议注册。macOS 检查 `CFBundleURLTypes`,Windows 安装后检查当前用户 `Software\\Classes\\ssh` 并实际执行一次协议唤起。只验证配置文件不足以证明单实例事件交付正常。 ## 发布前检查 1. `package.json`、`package-lock.json`、`src-tauri/Cargo.toml`、`Cargo.lock` 和 `tauri.conf.json` 版本一致。 2. `npm run acceptance:full` 通过。 3. 目标平台构建和签名通过。 4. 安装后完成输入框复制/粘贴、SFTP 双向拖拽、普通区域右键菜单、SSH/SFTP/代理/跳板和 `ssh://` 单实例唤起验收。 5. 大模型新配置必须先测试后保存;Agent CLI 必须固定当前 SSH 目标,每条命令确认后才执行,critical 命令不得放行。 6. 检查安装包尺寸,没有上传 `rw.*.dmg`、日志、测试密钥或用户数据。 7. 保留上一稳定版本及回滚说明。