Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor/commands/release-pr-tag.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ description: 从 release 分支发起合并 master 的 PR、打 v 标签并依
- 在**要打标签的提交**上(通常为当前 release 分支 HEAD,或合并到 `master` 后的合并提交):
- `git tag -a vX.Y.Z -m "vX.Y.Z: <简短说明>"`
- `git push origin vX.Y.Z`
- 推送 `v*.*.*` 会触发 `.github/workflows/release.yml`,在 GitHub 上**自动创建 Release**(含 `generate_release_notes`)。
- 推送 `v*.*.*` 会触发 `.github/workflows/release.yml`:**多平台 `tauri build`**、上传安装包与 `latest.json`(应用内更新),并 **生成 Release 说明**(`generateReleaseNotes`)。需先在仓库 Actions Secrets 配置 `TAURI_SIGNING_PRIVATE_KEY`(详见 [docs/桌面应用-Release与自动更新.md](../../docs/桌面应用-Release与自动更新.md))。
- **注意**:若希望 tag **只指向 `master` 上的合并提交**,应在 **合并 PR 之后**再在 `master` 上打 tag;若先在 release 分支打 tag,则 tag 指向 release 分支提交,与 `master` 合并提交 SHA 不同(可按团队规范二选一)。

## 6. 关注 Actions
Expand Down
105 changes: 105 additions & 0 deletions .cursor/plans/tauri_自动构建与更新_5a344555.plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
name: Tauri 自动构建与更新
overview: 在现有「推送 v* tag 创建 GitHub Release」基础上,扩展为多平台 `tauri build` 并上传安装包;在桌面端接入 Tauri 2 官方 `tauri-plugin-updater`,从 GitHub Releases 拉取更新元数据并实现检查/下载/安装。平台代码签名(Apple/微软)对内测可省略;Tauri 更新签名(自建密钥对,免费)与平台证书是两回事,实施时按官方要求配置。
todos:
- id: ci-release-matrix
content: 扩展 .github/workflows/release.yml:matrix(macOS/Windows)、Bun+sidecar+前端、tauri build、上传 Release 资产,版本与 tag 对齐
status: completed
- id: signing-secrets
content: 配置 Tauri 更新签名密钥(tauri signer 生成,入 GitHub Secrets);平台证书(Apple/Windows)按是否对外分发再决定是否接入 CI
status: completed
- id: updater-plugin
content: Cargo/npm 增加 tauri-plugin-updater;lib.rs 注册;capabilities 授权;tauri.conf.json 配置 endpoints
status: completed
- id: ui-check-update
content: SettingsModal(或关于页)用 @tauri-apps/plugin-updater + Naive UI 实现检查/下载/重启提示
status: completed
- id: e2e-verify
content: 连续两个 tag 验证从旧版到新版的 updater 全流程
status: completed
isProject: false
---

# Tauri 打 tag 后自动构建 + 应用内自动更新

## 结论

**可以做到。** 你当前是 Tauri 2(`[apps/desktop/src-tauri/Cargo.toml](apps/desktop/src-tauri/Cargo.toml)`),已有 tag 触发但**只建 Release 说明、未打安装包**(`[/.github/workflows/release.yml](.github/workflows/release.yml)` 注释里也写了「可追加 tauri build」)。补齐 CI 构建产物 + 官方 updater 插件后,客户端即可检测新版本并走下载安装流程。

```mermaid
flowchart LR
tag_push[Push_tag_vX]
gh_release[GitHub_Release_资产]
updater_json[Updater_元数据]
app[桌面应用]
tag_push --> gh_release
gh_release --> updater_json
app -->|check| updater_json
app -->|download| gh_release
```



## 与「PR」的关系

- 你选择 **tag-only**:合 PR 不会自动发版;发版节奏是 **改版本号 → 打 tag → CI 构建并挂到 Release**。这与「给最终用户自动更新」是匹配的(每个 Release 对应明确版本)。
- 若将来要在 **每个 PR** 出包,可另加 workflow 只上传 `actions/upload-artifact`,**不要**和正式 updater 共用同一通道,避免版本混乱。

## 1. CI:tag 推送时多平台构建并上传 Release

**现状**:`[release.yml](.github/workflows/release.yml)` 在 `ubuntu-latest` 上只跑 `softprops/action-gh-release`,无二进制。

**目标**:

- 使用 **matrix**:至少 `macos-latest`、`windows-latest`(Linux `.deb`/AppImage 视需求再加)。
- 复用与 CI 一致的步骤:checkout → `oven-sh/setup-bun` → `bun install --frozen-lockfile` → `sync:changelog`(或确保 tag 前已同步)→ `packages/sidecar` build → 前端 build → **Tauri 构建**。
- 推荐用官方维护的 `[tauri-apps/tauri-action](https://github.com/tauri-apps/tauri-action)`(需确认当前文档中 Tauri v2 的 `projectPath` / `args` 写法),或自行 `cd apps/desktop && bun run tauri build` 后把 `src-tauri/target/release/bundle/...` 里的产物 `upload` 到同一 Release。
- **版本与 tag 一致**:打 `v0.1.4` 前,需把 `[apps/desktop/package.json](apps/desktop/package.json)`、`[Cargo.toml](apps/desktop/src-tauri/Cargo.toml)`、`[tauri.conf.json](apps/desktop/src-tauri/tauri.conf.json)` 里的 `version` 升为 `0.1.4`(与 tag 去掉 `v` 后一致),否则 updater 比对会乱。

**权限**:`contents: write` 已具备;若用 `tauri-action` 附带创建/更新 Release,需按其文档配置 `releaseId` 或与现有 `action-gh-release` 分工(常见做法是单一 job 用 tauri-action 同时 build+upload,避免两个 action 抢 Release)。

## 2. 应用内自动更新:Tauri 2 updater 插件

**依赖与配置**(按官方 Tauri 2 文档):

- Rust:`tauri-plugin-updater`,并在 `[lib.rs](apps/desktop/src-tauri/src/lib.rs)` `Builder` 上 `.plugin(tauri_plugin_updater::Builder::new().build())`(具体 API 以文档为准)。
- 前端:`@tauri-apps/plugin-updater`,在设置页(例如 `[SettingsModal.vue](apps/desktop/src/components/SettingsModal.vue)` 关于区域)增加「检查更新」:调用 `check()` / `downloadAndInstall()`,并用 Naive UI 的反馈组件提示进度与重启。
- **Capabilities**:在 `[capabilities/default.json](apps/desktop/src-tauri/capabilities/default.json)` 为 updater 增加对应 `updater:allow-check` 等权限(以插件文档列出的 permission 为准)。

**更新源**:最省事是 **GitHub Releases**(私有仓需 token 策略;公开仓通常可直接用)。`tauri.conf.json` 的 `plugins.updater` 需配置 `endpoints`(指向生成的 `latest.json` 或 GitHub API 兼容端点,以 Tauri 2 当前 schema 为准)。

### 签名是什么?没有「平台证书」能实现么?

日常说的「签名」容易混在一起,这里拆成两层:


| 类型 | 是什么 | 没有它会怎样 | 和「本地能打包安装」的关系 |
| -------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **操作系统级代码签名**(Apple Developer ID / Windows 代码签名证书) | 向系统证明「这个 .app/.dmg/.exe 来自某个注册开发者」 | macOS:他人从网上下载常被 Gatekeeper 拦截,需在「隐私与安全性」里点「仍要打开」,或右键打开;Windows:SmartScreen 可能警告 | **你在本机**用 `tauri build` 出来的包,自己双击、或给信任你的人用,**完全可以不办证书就安装**。本地没有证书 ≠ 不能打包。 |
| **Tauri 更新通道签名**(`TAURI_PRIVATE_KEY` 等) | 用 `tauri signer generate` **免费生成**的密钥对:给「更新清单 + 安装包」做校验,防止 CDN/GitHub 上包被篡改 | 官方 updater 流程通常**要求**配置这一对(公钥打进应用,私钥只在 CI);**这不是**买 Apple/微软证书 | 与「本地能不能装」无关,是**自动更新**安全模型的一部分;具体是否可在无签名模式下仅作开发验证,以 Tauri 2 插件文档为准。 |


**结论**:

- **没有 Apple/微软平台证书**:完全可以实现「CI 出安装包 + 小范围内测 +(在配置好 Tauri 更新密钥的前提下)应用内检查更新」。代价是陌生用户从网络首次安装时系统警告更多,需要说明如何「仍要打开」。
- **本地没有签名也能打包安装**:正常;系统对你本机构建的二进制往往不会像对待「未知来源下载」那样严格。

CI Secrets 方向:`TAURI_PRIVATE_KEY`、`TAURI_KEY_PASSWORD`(更新通道,自建);若以后要对公网用户友好,再增加 macOS 公证与 Windows 签名相关 secret(`APPLE_`*、证书等)。

## 3. 与 sidecar 的关系

`[tauri.conf.json](apps/desktop/src-tauri/tauri.conf.json)` 已把 `xunji-sidecar` 打进 `bundle.resources`。**完整应用更新**时,新安装包会带上新 sidecar;只要每次发版前 sidecar 构建进同一 pipeline,无需单独更新 sidecar。

## 4. 风险与注意点

- **首版无 updater 的用户**:老包没有 updater 代码时,只能手动下载一次带 updater 的版本。
- **破坏性变更**:数据库迁移等已在 `[migrations.rs](apps/desktop/src-tauri/src/storage/migrations.rs)` 处理;发版仍建议在 CHANGELOG 中说明。
- **changelog 校验**:CI 里有 `sync:changelog` 检查(`[ci.yml](.github/workflows/ci.yml)`);release workflow 中要么跑同样检查,要么约定发版前在 PR 里已同步。

## 5. 建议实施顺序

1. 扩展 `release.yml`:matrix + sidecar + 前端 + `tauri build`,确认 Release 上出现 `.dmg`/`.msi`(或你需要的 targets)。
2. 生成 Tauri 更新密钥并写入 Secrets;平台证书可后补。本地/CI 用无平台签名先跑通 `tauri build` 与 updater(与最终对外体验分开迭代)。
3. 接入 `tauri-plugin-updater` + `tauri.conf` endpoints + 前端「检查更新」。
4. 用两个连续 tag 做一次端到端:旧版 app 检查更新 → 下载 → 安装重启。

105 changes: 103 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,124 @@
# 推送形如 v0.1.2 的 tag 时自动创建 GitHub Release(含自动生成的提交说明)。
# 若后续需要上传各平台安装包,可在此 job 中追加 matrix 与 tauri build。
# 推送语义化版本 tag(如 v0.1.3)时:
# 1) 与原先一致:在 ubuntu 上由 softprops 创建/更新 GitHub Release 并 generate_release_notes(PR 合并后再打 tag 的流程不变)。
# 2) 多平台 tauri build,向同一 Release 上传安装包与 latest.json(供应用内 updater)。
# 依赖 Secrets:TAURI_SIGNING_PRIVATE_KEY(必填);TAURI_SIGNING_PRIVATE_KEY_PASSWORD(可选)。
name: Release

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
# 指向 master 的 PR:预检 changelog 等与发版相关的条件,避免仅依赖打 tag 后才发现问题(完整构建见 CI)
pull_request:
branches: [master]

permissions:
contents: write

jobs:
# ── 与历史 release.yml 一致:先建 Release + 自动生成说明(无构建)────────────────
github-release:
if: github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
release_id: ${{ steps.create_release.outputs.id }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Create GitHub Release
id: create_release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# ── 打 tag 后:多平台构建并上传到上述 Release ─────────────────────────────────
publish-tauri:
if: github.event_name == 'push'
needs: github-release
strategy:
fail-fast: false
matrix:
include:
- platform: macos-latest
args: --target aarch64-apple-darwin
- platform: macos-latest
args: --target x86_64-apple-darwin
- platform: windows-latest
args: ''

runs-on: ${{ matrix.platform }}
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Verify changelog sync
run: |
bun run sync:changelog
git diff --exit-code apps/desktop/src/data/app-changelog.md

- name: Require updater signing secret
shell: bash
env:
KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "$KEY" ]; then
echo '::error::未配置 TAURI_SIGNING_PRIVATE_KEY。请阅读 docs/桌面应用-Release与自动更新.md 生成密钥并写入仓库 Secrets。'
exit 1
fi

- name: Install Rust stable
if: matrix.platform != 'macos-latest'
uses: dtolnay/rust-toolchain@stable

- name: Install Rust stable (macOS cross targets)
if: matrix.platform == 'macos-latest'
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin,x86_64-apple-darwin

- name: Build and upload to GitHub Release
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:
projectPath: apps/desktop
tauriScript: bun run tauri
releaseId: ${{ needs.github-release.outputs.release_id }}
includeUpdaterJson: true
updaterJsonPreferNsis: true
args: ${{ matrix.args }}

# ── PR → master:发版前预检(与 tag 构建共用 changelog 校验逻辑)────────────────
release-pr-precheck:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Verify changelog sync
run: |
bun run sync:changelog
git diff --exit-code apps/desktop/src/data/app-changelog.md
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ dist/

# Tauri
apps/desktop/src-tauri/target/
# 自动更新签名私钥(`tauri signer generate -w *.key`),切勿提交
*.updater.key

# Bun
packages/sidecar/dist/
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

> 面向使用者的更新说明。实现细节见 `docs/CHANGELOG.md`。

## 0.1.4(2026-03-25)

### 新增

- **应用内更新**:桌面端集成自动更新(设置中可检查更新并安装新版本);Release 构建会生成更新所需产物,详见 `docs/桌面应用-Release与自动更新.md`。

---

## 0.1.3(2026-03-25)

### 新增
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@xunji/desktop",
"private": true,
"version": "0.1.3",
"version": "0.1.4",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -14,6 +14,8 @@
"dependencies": {
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2",
"@tauri-apps/plugin-process": "^2",
"@tauri-apps/plugin-updater": "^2",
"@vueuse/core": "^14.2.1",
"@xunji/shared": "workspace:*",
"highlight.js": "^11.11.1",
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "xunji-desktop"
version = "0.1.3"
version = "0.1.4"
description = "寻迹 - AI 编程知识管理平台"
authors = ["XunJi"]
edition = "2021"
Expand Down Expand Up @@ -29,6 +29,10 @@ env_logger = "0.11"
thiserror = "1"
serde_yaml = "0.9"
tauri-plugin-dialog = "2"
# 应用内检查更新、下载安装包(与 tauri.conf plugins.updater 配套)
tauri-plugin-updater = "2"
# 安装更新后重启进程(前端 relaunch)
tauri-plugin-process = "2"
# workspace.json 中 folder 对非 ASCII 路径常使用百分号编码,需按 URL 解析后还原本地路径
url = "2"

Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"permissions": [
"core:default",
"dialog:allow-open",
"dialog:allow-save"
"dialog:allow-save",
"updater:default",
"process:default"
]
}
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/gen/schemas/acl-manifests.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/gen/schemas/capabilities.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"default":{"identifier":"default","description":"主窗口默认 IPC 能力(含 Tauri core 默认集)","local":true,"windows":["*"],"permissions":["core:default","dialog:allow-open","dialog:allow-save"]}}
{"default":{"identifier":"default","description":"主窗口默认 IPC 能力(含 Tauri core 默认集)","local":true,"windows":["*"],"permissions":["core:default","dialog:allow-open","dialog:allow-save","updater:default","process:default"]}}
Loading
Loading