Files
opsxshell/docs/release.md
T

204 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 发布指南
## 构建命令
```bash
# 推荐:当前平台完整门禁、打包、验证和 SHA-256
./scripts/build.sh
# macOS Apple Silicon 指定版本
./scripts/build.sh v2.1.1 mac-arm64
# 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`。
## 签名、公证和自动更新(未完成)
**当前状态:没有签名凭据,公开包是 ad-hoc / 未签名,未公证,没有自动更新。**
没有 `APPLE_*` / Authenticode 证书、minisign 私钥和 HTTPS 更新源之前,不得在 README、界面、Release 说明或 CI 绿勾上写「已签名」「已公证」「支持自动更新」。`tauri.conf.json` 的 `signingIdentity` 现在是 `"-"`;`plugins` 只有 deep-link,没有 updater。
现有门禁只做「有凭据才验、没凭据就标明开发包」:
- `REQUIRE_SIGNING=true` 时,`scripts/release-preflight.mjs` 检查环境变量齐全,`scripts/verify-release.mjs` 拒绝 macOS ad-hoc、要求 Windows Authenticode `Valid`。
- 未设该变量时,`./scripts/build.sh` 仍可打开发包,验证只检查架构和体积。
- CI 工作流 `.github/workflows/tauri-release.yml` 已能在 Secrets 齐全时写入 `src-tauri/tauri.signing.conf.json` 再构建;**本地 `build.sh` 还不会生成这份配置**,所以本机即使导出了同样的环境变量,也不会走出 ad-hoc。
- 全仓库没有 `notarytool` / `staple`。Developer ID 签过但未公证的 `.app`,用户机器上仍会被 Gatekeeper 拦。
- `scripts/release.py` 仍提示「右键打开」——这是 ad-hoc 包的权宜说明,正式公证后必须删掉。
下面是有凭据之后的做法。没齐之前不要改 `signingIdentity`、不要接 updater 插件、不要在设置里放「检查更新」。
### 1. 凭据(先买/申请,再接线)
| 平台 | 要什么 | 放到哪里 |
| -------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| macOS | Apple Developer Program;**Developer ID Application** 证书(`.p12`);App 专用密码 | 本机环境变量 + GitHub/Gitee Secrets:`APPLE_CERTIFICATE`(p12 的 base64)、`APPLE_CERTIFICATE_PASSWORD`、`APPLE_SIGNING_IDENTITY`(如 `Developer ID Application: …`)、`APPLE_ID`、`APPLE_PASSWORD`、`APPLE_TEAM_ID` |
| Windows | 代码签名证书(OV/EV PFX) | `WINDOWS_CERTIFICATE_BASE64`、`WINDOWS_CERTIFICATE_PASSWORD` |
| 自动更新 | 单独的 **minisign** 密钥对,与 Apple/Authenticode 不是同一把 | 私钥只进 CI Secrets;**公钥**写进仓库的 updater 配置 |
Linux 安装包(deb/rpm/AppImage)不走 Apple/Authenticode。自动更新的 `.sig` 是另一套签名,三平台都要。
`src-tauri/tauri.signing.conf.json` 由脚本现场生成,必须进 `.gitignore`,不得提交。
### 2. 本地与 CI 同源签名
目标:本机 `REQUIRE_SIGNING=true ./scripts/build.sh` 与 CI 走同一条路径,都能复现同一类签名产物。
1. `build.sh` / `build.bat` 在签名变量齐全时,生成 `tauri.signing.conf.json`(macOS 写 `bundle.macOS.signingIdentity`;Windows 写 thumbprint / sha256 / timestamp),再 `npx tauri build --config src-tauri/tauri.signing.conf.json`。
2. 不要改仓库里的 `tauri.conf.json` 默认 `"-"`——没凭据的开发机构必须还能打 ad-hoc。
3. `REQUIRE_SIGNING=true` 时 `verify-release.mjs` 必须失败于 ad-hoc / 无效 Authenticode;不要为了绿而关校验。
4. 正式标签才设 `REQUIRE_SIGNING=true`。日常 PR 构建保持未签名。
### 3. macOS 公证(notarytool + staple)
签名成功 ≠ 用户能打开。必须公证并钉票:
```bash
# 构建并 Developer ID 签名之后
xcrun notarytool submit "$DMG" \
--apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID" \
--wait
xcrun stapler staple "$DMG"
# 若 staple 的是 .app,再重新打 DMG,或对 DMG 再 staple
spctl --assess --type install --verbose "$DMG"
```
验收(必须在**另一台未关 SIP、未关 Gatekeeper 的 Mac**上,不要只在构建机看 `spctl`):
1. 下载 DMG,双击打开,拖到「应用程序」,**不要**右键「打开」、不要 `xattr -d`。
2. 能启动、能连 SSH,菜单里没有 DevTools。
3. `codesign --verify --deep --strict` 且 `codesign -dv` 不是 `Signature=adhoc`。
4. 删掉 Release 说明和 `release.py` 里的「右键绕过 Gatekeeper」。
没做完 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. 保留上一稳定版本及回滚说明。