- Introduced BlueprintModal in ProjectCard for project blueprint visualization. - Added state management for displaying the BlueprintModal. - Implemented Blueprint-related interfaces and functions in commands.ts for handling blueprint data.
15 KiB
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 生成签名密钥
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)
$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
关键配置项(每一项都不能少):
{
"bundle": {
"active": true,
"targets": "all",
"createUpdaterArtifacts": true, // ⚠️ 必须!Tauri v2 需要显式开启,否则不生成签名文件
"windows": {
"nsis": {}
}
},
"plugins": {
"updater": {
"pubkey": "<公钥 base64,见 1.2 步骤>",
"endpoints": [
"https://<bucket>.<endpoint>/latest.json"
],
"dialog": false // 推荐 false,用代码控制更新 UI
}
}
}
2.2 配置 Capabilities 权限
src-tauri/capabilities/default.json 中必须添加以下权限,否则前端调用 updater / process API 会被拦截:
{
"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):
- 仓库 → Settings → Actions → General(不是 Secrets 页面)
- 滚到页面最底部 → Workflow permissions
- 选择 Read and write permissions
- 点 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:
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文件。
七、发版流程(日常操作)
# 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 状态机、踩坑记录)已独立成文档:
九、完整踩坑清单速查
| # | 问题 | 解决方案 |
|---|---|---|
| 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 目录结构
<bucket>/
├── 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 示例:
{
"version": "0.2.0",
"pub_date": "2026-04-02T04:28:01Z",
"notes": "版本 v0.2.0 已发布",
"platforms": {
"windows-x86_64": {
"signature": "<.exe.sig 文件内容>",
"url": "https://<bucket>.<endpoint>/releases/v0.2.0/app_0.2.0_x64-setup.exe"
}
}
}