# Tauri v2 Windows 应用 CI/CD 部署指南 > **方案版本**: v1.0(2026-04-02) > **适用范围**: Tauri v2 + GitHub Actions + 阿里云 OSS + Windows 平台 > **验证状态**: 已在 dev-manager-tauri 项目实际跑通(v0.1.9 构建成功) > > 本文档是一套完整可复用的部署方案,涵盖从密钥生成到客户端自动更新的全链路。 > 新 Tauri 项目可直接参照本方案配置 CI/CD,避免重复踩坑。 --- ## 整体架构 ``` 开发者 push tag (v*) │ ▼ GitHub Actions (windows-latest) ├── 1. 构建 Tauri 应用(NSIS 安装包 + 签名) ├── 2. 创建 GitHub Release 并上传安装包 └── 3. 上传到阿里云 OSS + 生成 latest.json │ ▼ 客户端 tauri-plugin-updater └── 定期检查 OSS 上的 latest.json → 发现新版本 → 下载 .exe → 验证签名 → 安装更新 ``` **为什么用阿里云 OSS?** GitHub Releases 在国内访问慢且不稳定,OSS 提供国内 CDN 加速。 --- ## 一、前置准备 ### 1.1 生成签名密钥 ```bash pnpm tauri signer generate -w "$HOME\.tauri\your-app.key" ``` 输出示例: ``` Your keypair was generated successfully Private: C:\Users\xxx\.tauri\your-app.key Public: C:\Users\xxx\.tauri\your-app.key.pub TAURI_SIGNING_PRIVATE_KEY=dW50cnVzdGVkIGNvbW1lbnQ6IH...(一长串 base64) TAURI_SIGNING_PRIVATE_KEY_PASSWORD= ``` > **Windows 注意事项**: > - 不要用 `~` 作为路径前缀,Windows 的 PowerShell 会把 `~` 当作字面量目录名创建 > - 使用 `$HOME` 或完整路径代替 ### 1.2 获取公钥 base64(用于 tauri.conf.json) ```powershell $content = Get-Content "$HOME\.tauri\your-app.key.pub" -Raw $content = $content -replace "`r`n", "`n" $bytes = [System.Text.Encoding]::UTF8.GetBytes($content) [Convert]::ToBase64String($bytes) ``` 将输出的 base64 字符串填入 `tauri.conf.json` 的 `plugins.updater.pubkey`。 ### 1.3 创建阿里云 OSS Bucket - 创建 Bucket(如 `appforwin`),区域按需选择(如 `oss-cn-hangzhou`) - 设置 Bucket 为**公共读**(否则客户端无法下载更新) - 创建一个 RAM 用户,授予 OSS 读写权限,记录 AccessKey ID 和 Secret --- ## 二、配置 tauri.conf.json 关键配置项(**每一项都不能少**): ```jsonc { "bundle": { "active": true, "targets": "all", "createUpdaterArtifacts": true, // ⚠️ 必须!Tauri v2 需要显式开启,否则不生成签名文件 "windows": { "nsis": {} } }, "plugins": { "updater": { "pubkey": "<公钥 base64,见 1.2 步骤>", "endpoints": [ "https://./latest.json" ], "dialog": false // 推荐 false,用代码控制更新 UI } } } ``` ### 2.2 配置 Capabilities 权限 `src-tauri/capabilities/default.json` 中必须添加以下权限,否则前端调用 updater / process API 会被拦截: ```jsonc { "permissions": [ "core:default", // ... 其他权限 ... { "identifier": "http:default", "allow": [ { "url": "https://*.aliyuncs.com/**" } // ⚠️ 允许访问 OSS 域名 ] }, "updater:default", // ⚠️ 必须!否则报 updater.check not allowed "process:default" // ⚠️ 必须!否则 relaunch() 无法调用 ] } ``` ### 踩坑记录 | 问题 | 现象 | 原因 | |------|------|------| | **缺少 `createUpdaterArtifacts: true`** | 构建成功但只有 `.exe`,没有 `.exe.sig` | Tauri v2 默认不生成更新包,必须显式开启 | | **pubkey 填成了私钥** | 构建报错 `failed to decode pubkey: Base64 conversion failed` | pubkey 是公钥的 base64,不是私钥 | | **pubkey 与私钥不匹配** | 构建成功但客户端更新时签名验证失败 | 重新生成密钥后要同步更新两处 | | **缺少 `updater:default` 权限** | 点击检查更新报 `updater.check not allowed` | Tauri v2 capabilities 需显式声明 updater 权限 | | **缺少 `process:default` 权限** | 更新后 `relaunch()` 无响应 | 同上,需声明 process 权限 | | **http 权限未放行 OSS 域名** | 报 `Could not fetch a valid release JSON` | capabilities 的 http allow 列表需包含 OSS 域名 | | **OSS Bucket 为私有** | 同上错误,curl 返回 `AccessDenied` | Bucket ACL 必须设为**公共读** | --- ## 三、配置 GitHub Secrets 在仓库 Settings → Actions → Secrets and variables → Actions 中添加: | Secret 名称 | 值 | 说明 | |---|---|---| | `TAURI_SIGNING_PRIVATE_KEY` | `pnpm tauri signer generate` 输出的那一长串 base64 | **不是** .key 文件内容,是终端输出的 base64 值 | | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | 密钥密码(无密码则留空) | 密码错误会导致签名静默失败 | | `OSS_KEY_ID` | 阿里云 AccessKey ID | | | `OSS_KEY_SECRET` | 阿里云 AccessKey Secret | | | `OSS_BUCKET` | Bucket 名称(如 `appforwin`) | | | `OSS_ENDPOINT` | Endpoint 域名(如 `oss-cn-hangzhou.aliyuncs.com`) | 不带 `https://` | ### 踩坑记录 | 问题 | 现象 | 原因 | |------|------|------| | **私钥格式错误** | 有 `.exe` 但没有 `.exe.sig`,无报错 | 存了 .key 文件原始内容(2行),应该存 base64 编码值(1行) | | **密钥密码不匹配** | 同上,签名静默失败 | 密码为空时 Secret 也要创建但值留空 | --- ## 四、配置 GitHub Actions Workflow 权限 **必须操作**(否则无法创建 Release): 1. 仓库 → Settings → **Actions → General**(不是 Secrets 页面) 2. 滚到页面最底部 → **Workflow permissions** 3. 选择 **Read and write permissions** 4. 点 Save ### 踩坑记录 | 问题 | 现象 | 原因 | |------|------|------| | **未改 Workflow permissions** | `Error: Resource not accessible by integration` | GITHUB_TOKEN 默认只读,无法创建 Release | | **改了权限但 Re-run 仍失败** | 同上错误 | Re-run 复用旧 token,需要 push 新 tag 触发全新 run | | **tauri-action 创建 Release 仍失败** | 即使设了 Read and write 仍报错 | 用 `softprops/action-gh-release@v2` 先创建 Release 作为 workaround | --- ## 五、Workflow 文件模板 `.github/workflows/release.yml`: ```yaml name: Release on: push: tags: - 'v*' jobs: release: runs-on: windows-latest permissions: contents: write steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: 9 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'pnpm' - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: './src-tauri -> target' - run: pnpm install --frozen-lockfile # 验证签名密钥(提前暴露配置问题) - name: Verify signing key shell: pwsh env: TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} run: | if ([string]::IsNullOrEmpty($env:TAURI_SIGNING_PRIVATE_KEY)) { Write-Error "TAURI_SIGNING_PRIVATE_KEY is EMPTY!" exit 1 } Write-Host "TAURI_SIGNING_PRIVATE_KEY is set" # 先创建 Release(workaround:tauri-action 创建 Release 可能遇到权限问题) - name: Create Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.ref_name }} name: 'App Name ${{ github.ref_name }}' body: | ## 更新内容 请查看提交记录了解详情。 draft: false prerelease: false # 构建 + 上传到已有 Release - name: Build & Upload uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} with: tagName: ${{ github.ref_name }} releaseName: 'App Name ${{ github.ref_name }}' releaseDraft: false prerelease: false # 上传到阿里云 OSS + 生成 latest.json(国内用户更新源) - name: Upload to OSS & generate latest.json shell: pwsh env: OSS_KEY_ID: ${{ secrets.OSS_KEY_ID }} OSS_KEY_SECRET: ${{ secrets.OSS_KEY_SECRET }} OSS_BUCKET: ${{ secrets.OSS_BUCKET }} OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT }} run: | # 下载 ossutil $oss = "$env:GITHUB_WORKSPACE\ossutil64.exe" Invoke-WebRequest -Uri "https://gosspublic.alicdn.com/ossutil/1.7.17/ossutil-v1.7.17-windows-amd64.zip" -OutFile oss.zip Expand-Archive oss.zip -DestinationPath oss_tmp $exeFound = Get-ChildItem oss_tmp -Filter "ossutil*.exe" -Recurse | Select-Object -First 1 Copy-Item $exeFound.FullName $oss $tag = "${{ github.ref_name }}" $version = $tag.TrimStart('v') $nsisDir = "$env:GITHUB_WORKSPACE\src-tauri\target\release\bundle\nsis" $all = Get-ChildItem $nsisDir # Tauri v2 更新格式:.exe + .exe.sig(不是 v1 的 .nsis.zip) $installer = $all | Where-Object { $_.Name.EndsWith('-setup.exe') } | Select-Object -First 1 $sigFile = $all | Where-Object { $_.Name.EndsWith('-setup.exe.sig') } | Select-Object -First 1 if (-not $installer) { Write-Error "installer not found!"; exit 1 } $bucket = "oss://$env:OSS_BUCKET" $endpoint = "https://$env:OSS_ENDPOINT" & $oss config -e $endpoint -i $env:OSS_KEY_ID -k $env:OSS_KEY_SECRET -L CH & $oss cp $installer.FullName "$bucket/releases/$tag/$($installer.Name)" -f if ($sigFile) { & $oss cp $sigFile.FullName "$bucket/releases/$tag/$($sigFile.Name)" -f $date = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ') $baseUrl = "https://$env:OSS_BUCKET.$env:OSS_ENDPOINT/releases/$tag" $sig = (Get-Content $sigFile.FullName -Raw).Trim() $json = [ordered]@{ version = $version pub_date = $date notes = "版本 $tag 已发布" platforms = [ordered]@{ 'windows-x86_64' = [ordered]@{ signature = $sig url = "$baseUrl/$($installer.Name)" } } } | ConvertTo-Json -Depth 5 $json | Out-File -FilePath latest.json -Encoding utf8NoBOM & $oss cp latest.json "$bucket/latest.json" -f Write-Host "latest.json uploaded" } else { Write-Warning "sigFile not found - skipping updater upload" } ``` --- ## 六、Tauri v2 更新包格式变化(vs v1) 这是最容易踩坑的地方: | | Tauri v1 | Tauri v2 | |---|---|---| | **更新包格式** | `.nsis.zip` + `.nsis.zip.sig` | `.exe` + `.exe.sig` | | **生成条件** | 设置 `TAURI_PRIVATE_KEY` | 设置 `TAURI_SIGNING_PRIVATE_KEY` **+** `createUpdaterArtifacts: true` | | **latest.json url** | 指向 `.nsis.zip` | 指向 `-setup.exe` | | **签名静默失败** | 会报错 | **不报错**,只是不生成 `.sig` 文件 | > **关键**:Tauri v2 签名失败时**完全静默**——构建照常成功,只是少了 `.sig` 文件。 > 排查方法:检查 `bundle/nsis/` 目录下是否有 `.exe.sig` 文件。 --- ## 七、发版流程(日常操作) ```bash # 1. 更新版本号(三处必须保持一致!) # - src-tauri/tauri.conf.json → "version": "0.2.0" # - src-tauri/Cargo.toml → version = "0.2.0" # - package.json → "version": "0.2.0" # 2. 提交代码 git add -A git commit -m "release: v0.2.0" # 3. 打 tag 并推送(PowerShell 不支持 &&,分开执行) git tag v0.2.0 git push git push --tags # 4. 等待 GitHub Actions 自动完成: # - 构建 → 签名 → 创建 Release → 上传 OSS → 生成 latest.json # 约 10 分钟完成 ``` > **注意**:版本号必须三处同步,否则 `latest.json` 中的 version 与客户端内部版本不一致,可能导致重复提示更新。 --- ## 八、客户端自动更新 客户端侧的完整实现(插件安装、权限配置、前端 UI 状态机、踩坑记录)已独立成文档: **→ [tauri-updater-guide.md](./tauri-updater-guide.md)** --- ## 九、完整踩坑清单速查 | # | 问题 | 解决方案 | |---|------|---------| | 1 | `createUpdaterArtifacts` 未设置 | `tauri.conf.json` → `bundle.createUpdaterArtifacts: true` | | 2 | 私钥格式错误(存了原始内容而非 base64) | GitHub Secret 存 `tauri signer generate` 输出的 base64 值 | | 3 | pubkey 填了私钥的 base64 | pubkey 要用公钥文件的 base64 编码 | | 4 | pubkey 与私钥不配对 | 重新生成后两处都要更新 | | 5 | `Resource not accessible by integration` | Settings → Actions → General → Workflow permissions → Read and write | | 6 | 改了权限 Re-run 仍失败 | push 新 tag 触发全新 workflow run | | 7 | tauri-action 创建 Release 权限问题 | 用 `softprops/action-gh-release@v2` 预创建 | | 8 | ossutil 路径找不到 | 下载 zip → 解压 → 用绝对路径调用 | | 9 | PowerShell `-Filter *.nsis.zip` 不匹配多后缀 | 用 `Where-Object { $_.Name.EndsWith(...) }` | | 10 | Tauri v2 找 `.nsis.zip` 找不到 | v2 格式是 `.exe` + `.exe.sig`,不是 `.nsis.zip` | | 11 | Windows 上 `~` 路径问题 | 用 `$HOME` 代替 `~` | | 12 | PowerShell `&&` 语法错误 | PowerShell 5 不支持 `&&`,用 `;` 或分开执行 | | 13 | `updater.check not allowed` | `capabilities/default.json` 添加 `updater:default` 权限 | | 14 | `relaunch()` 无效 | `capabilities/default.json` 添加 `process:default` 权限 | | 15 | `Could not fetch a valid release JSON` | 检查:1) OSS Bucket 是否公共读 2) capabilities http allow 是否包含 OSS 域名 | | 16 | 版本号不一致导致重复更新提示 | 三处版本号必须同步:`tauri.conf.json`、`Cargo.toml`、`package.json` | --- ## 十、OSS 目录结构 ``` / ├── latest.json ← 客户端检查更新的入口 └── releases/ ├── v0.1.0/ │ ├── app_0.1.0_x64-setup.exe │ └── app_0.1.0_x64-setup.exe.sig ├── v0.2.0/ │ ├── app_0.2.0_x64-setup.exe │ └── app_0.2.0_x64-setup.exe.sig └── ... ``` `latest.json` 示例: ```json { "version": "0.2.0", "pub_date": "2026-04-02T04:28:01Z", "notes": "版本 v0.2.0 已发布", "platforms": { "windows-x86_64": { "signature": "<.exe.sig 文件内容>", "url": "https://./releases/v0.2.0/app_0.2.0_x64-setup.exe" } } } ```