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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
> 不能当作当前分支仍有这些门禁。当前 `desktop-ci.yml` 对 Draft PR 运行 Linux 格式、
> 脚本和桌面测试,非 Draft 另运行 macOS 与 Windows 测试;Core 的源码和构建缓存按
> 系统、架构、工具链与构建脚本身份区分。旧资源复用脚本、旧分片和 lane 复用尚未迁移。
> `desktop-autobuild.yml` 与 `desktop-release.yml` 使用同一个 release 档 Core 缓存键,并共用
> `.github/actions/setup-desktop-windows-build-tools` 安装 MinGW 和 NSIS。AutoBuild 由 `main`
> 上的定时任务运行,GitHub 允许其它分支和 tag 读取默认分支保存的缓存,所以发布时多半能直接
> 复用前一晚编好的 Core;只在 tag 上保存的缓存,其它分支读不到。

Windows 的 Rust 静态库还会依赖 Cargo crate 提供的导入库,不能只传系统库名称给
Go 链接器。桌面构建入口读取对应 target/profile 的 Cargo 构建输出,把实际使用的
Expand Down Expand Up @@ -552,6 +556,8 @@ Swift 测试已经编译完整 Lithe 目标。再生成两个 DMG 会在普通

- `.github/workflows/desktop-ci.yml`
- `.github/workflows/desktop-release.yml`
- `.github/workflows/desktop-autobuild.yml`
- `.github/actions/setup-desktop-windows-build-tools/action.yml`
- `scripts/desktop/build-desktop-core.sh`
- `scripts/desktop/desktop-go.sh`
- `scripts/ci/cancel-closed-pr-runs/`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

状态:已实现

> Viva 迁移状态:本文的 Sparkle、AutoBuild、插件签名和回滚实现及验收记录属于旧客户端。Viva 当前有独立预发布打包工作流,但应用内更新页面仍是占位,尚不能据此宣称渠道、签名验证、差分更新或回滚已接入;这些安全要求必须在迁移时重新落实和验证。
> Viva 迁移状态:本文的 Sparkle、AutoBuild、插件签名和回滚实现及验收记录属于旧客户端。Viva 当前有独立预发布打包工作流 `desktop-release.yml`,以及每天从 `preview` 滚动发布到 prerelease `desktop-AutoBuild` 的 `desktop-autobuild.yml`。后者只构建和发布,沿用正式包的应用身份 `app.lithe.viva`,没有独立的开发版身份、更新渠道和构建时间身份;两条工作流都不传公证凭据,macOS 包不会公证。应用内更新页面仍是占位,尚不能据此宣称渠道、签名验证、差分更新或回滚已接入;这些安全要求必须在迁移时重新落实和验证。

## 先说结论

Expand Down Expand Up @@ -445,6 +445,8 @@ Sparkle 版本间升级、缺失/损坏的 delta、下载中断、权限不足
- `scripts/resources/worktree-resources.json`
- `scripts/resources/reuse-worktree-resources.mjs`
- `.github/workflows/desktop-release.yml`
- `.github/workflows/desktop-autobuild.yml`
- `scripts/ci/desktop-autobuild/publish-autobuild.sh`
- `desktop/internal/settingswindow/window.go`
- `shared/platform-feature-matrix/features/updates-check-download.json`
- `shared/platform-feature-matrix/features/updates-failure-rollback.json`
Expand Down
49 changes: 49 additions & 0 deletions .github/actions/setup-desktop-windows-build-tools/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Set up desktop Windows build tools
description: >-
Puts MinGW-w64 gcc (for the cgo core link) and NSIS makensis (for the installer) on PATH.
Shared by the release and AutoBuild workflows so both package Windows with the same tools.

runs:
using: composite
steps:
- name: Ensure MinGW-w64 and NSIS
shell: bash
run: |
if ! command -v gcc >/dev/null 2>&1; then
choco install mingw -y
gcc_path="$(find /c/ProgramData/chocolatey/lib/mingw /c/tools/mingw64 /c/mingw64 \
-type f -name gcc.exe -print -quit 2>/dev/null || true)"
if [[ -z "$gcc_path" ]]; then
printf '%s\n' 'MinGW-w64 installation did not provide gcc.exe.' >&2
exit 1
fi
mingw_bin="$(dirname "$gcc_path")"
export PATH="$mingw_bin:$PATH"
printf '%s\n' "$(cygpath -m "$mingw_bin")" >> "$GITHUB_PATH"
fi
gcc --version
nsis_x86='/c/Program Files (x86)/NSIS'
nsis_x64='/c/Program Files/NSIS'
if ! command -v makensis >/dev/null 2>&1 && \
[[ ! -f "$nsis_x86/makensis.exe" && ! -f "$nsis_x64/makensis.exe" ]]; then
choco install nsis -y
fi
if ! command -v makensis >/dev/null 2>&1; then
for nsis_dir in "$nsis_x86" "$nsis_x64"; do
if [[ -f "$nsis_dir/makensis.exe" ]]; then
export PATH="$nsis_dir:$PATH"
printf '%s\n' "$(cygpath -m "$nsis_dir")" >> "$GITHUB_PATH"
break
fi
done
fi
if command -v makensis >/dev/null 2>&1; then
makensis /VERSION
elif [[ -f "$nsis_x86/makensis.exe" ]]; then
"$nsis_x86/makensis.exe" /VERSION
elif [[ -f "$nsis_x64/makensis.exe" ]]; then
"$nsis_x64/makensis.exe" /VERSION
else
printf '%s\n' 'NSIS installation did not provide makensis.' >&2
exit 1
fi
215 changes: 215 additions & 0 deletions .github/workflows/desktop-autobuild.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
name: Desktop AutoBuild

# Builds the Viva desktop client (desktop/) from the preview branch every day and publishes it to
# the rolling prerelease desktop-AutoBuild: macOS arm64, Windows x64, and Linux x64 and arm64.
# AutoBuild only builds and publishes. It runs no tests and does not inspect the packages, unlike
# Desktop Release. Its tag is separate from the AutoBuild tag that the SwiftUI and Tauri clients'
# updaters read, and the release is never marked latest.
on:
schedule:
# 10:00 UTC, which is 18:00 in China (UTC+8).
- cron: "0 10 * * *"
workflow_dispatch:
inputs:
source_branch:
description: "Branch to build"
required: true
type: string
default: preview

permissions:
contents: read

# One publication at a time; a run that starts while another publishes waits for it.
concurrency:
group: desktop-autobuild
cancel-in-progress: false

env:
RELEASE_TAG: desktop-AutoBuild
SOURCE_BRANCH: ${{ github.event_name == 'workflow_dispatch' && inputs.source_branch || 'preview' }}

jobs:
prepare:
name: Pin the source and build identity
if: >-
github.event_name == 'schedule' ||
github.actor == github.repository_owner ||
contains(format(',{0},', vars.LITHE_RELEASE_ACTOR), format(',{0},', github.actor))
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
sha: ${{ steps.identity.outputs.sha }}
version: ${{ steps.identity.outputs.version }}
build: ${{ steps.identity.outputs.build }}
timestamp: ${{ steps.identity.outputs.timestamp }}
skip: ${{ steps.identity.outputs.skip }}

steps:
- name: Check out the source branch
uses: actions/checkout@v7
with:
ref: ${{ env.SOURCE_BRANCH }}

# Every build job checks out this commit, so a push during the run cannot mix two sources.
# A scheduled run whose commit is already published builds nothing.
- name: Record the source commit and build identity
id: identity
env:
GH_TOKEN: ${{ github.token }}
run: |
sha="$(git rev-parse HEAD)"
version="$(sed -n 's/^version = "\([0-9.]*\)"$/\1/p' desktop/viva.toml)"
if [[ -z "$version" ]]; then
printf '%s\n' 'desktop/viva.toml has no [app] version.' >&2
exit 1
fi
read -r build timestamp < <(date -u '+%Y%m%d.%H%M%S %Y-%m-%dT%H:%M:%SZ')
skip=false
if [[ "$GITHUB_EVENT_NAME" == schedule ]]; then
published="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$RELEASE_TAG" --jq .object.sha 2>/dev/null || true)"
if [[ "$published" == "$sha" ]]; then
skip=true
printf '%s\n' "$RELEASE_TAG already holds $SOURCE_BRANCH at $sha; nothing to build." | tee -a "$GITHUB_STEP_SUMMARY"
fi
fi
{
printf 'sha=%s\n' "$sha"
printf 'version=%s\n' "$version"
printf 'build=%s\n' "$build"
printf 'timestamp=%s\n' "$timestamp"
printf 'skip=%s\n' "$skip"
} >> "$GITHUB_OUTPUT"

build:
name: Package ${{ matrix.os }} ${{ matrix.arch }}
needs: prepare
if: needs.prepare.outputs.skip != 'true'
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- os: darwin
arch: arm64
runner: macos-26
rust-target: aarch64-apple-darwin
- os: windows
arch: amd64
runner: windows-2022
rust-target: x86_64-pc-windows-gnu
- os: linux
arch: amd64
runner: ubuntu-22.04
rust-target: x86_64-unknown-linux-gnu
- os: linux
arch: arm64
runner: ubuntu-22.04-arm
rust-target: aarch64-unknown-linux-gnu

steps:
- name: Check out the pinned source
uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.sha }}

- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: desktop/go.mod
cache-dependency-path: desktop/go.sum

- name: Set up Rust
id: rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust-target }}

- name: Ensure Windows build tools
if: matrix.os == 'windows'
uses: ./.github/actions/setup-desktop-windows-build-tools

# The same key as Desktop Release, so the two workflows share one release build of the core.
- name: Restore the core build
uses: actions/cache@v6
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
.artifacts/desktop/lithe-core-rust
key: desktop-core-${{ runner.os }}-${{ runner.arch }}-${{ steps.rust.outputs.cachekey }}-release-${{ matrix.rust-target }}-${{ hashFiles('scripts/desktop/build-desktop-core.sh') }}
restore-keys: |
desktop-core-${{ runner.os }}-${{ runner.arch }}-${{ steps.rust.outputs.cachekey }}-release-${{ matrix.rust-target }}-

# Each platform receives only its own signing secrets.
- name: Package
env:
APPLE_CERTIFICATE: ${{ matrix.os == 'darwin' && secrets.MACOS_SIGNING_CERTIFICATE || '' }}
APPLE_CERTIFICATE_PASSWORD: ${{ matrix.os == 'darwin' && secrets.MACOS_SIGNING_PASSWORD || '' }}
APPLE_SIGNING_IDENTITY: ${{ matrix.os == 'darwin' && vars.MACOS_SIGNING_IDENTITY || '' }}
WINDOWS_CERTIFICATE: ${{ matrix.os == 'windows' && secrets.WINDOWS_SIGNING_CERTIFICATE_BASE64 || '' }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ matrix.os == 'windows' && secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD || '' }}
WINDOWS_TIMESTAMP_URL: ${{ matrix.os == 'windows' && secrets.WINDOWS_TIMESTAMP_SERVER || '' }}
DESKTOP_ARCH: ${{ matrix.arch }}
run: bash scripts/ci/desktop-autobuild/package-autobuild.sh "$DESKTOP_ARCH"

- name: Upload the packages
uses: actions/upload-artifact@v7
with:
name: desktop-autobuild-${{ matrix.os }}-${{ matrix.arch }}
path: dist/autobuild/
if-no-files-found: error
# The packages are already compressed, and the release keeps the copy users download.
compression-level: 0
retention-days: 3

publish:
name: Publish the AutoBuild
needs:
- prepare
- build
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write

steps:
# The publication script comes from the workflow's own commit, not from the branch being built.
- name: Check out the publication script
uses: actions/checkout@v7
with:
ref: ${{ github.workflow_sha }}

- name: Download the packages
uses: actions/download-artifact@v8
with:
pattern: desktop-autobuild-*
merge-multiple: true
path: dist/autobuild

- name: Publish to the rolling prerelease
env:
GH_TOKEN: ${{ github.token }}
SOURCE_SHA: ${{ needs.prepare.outputs.sha }}
DESKTOP_VERSION: ${{ needs.prepare.outputs.version }}
BUILD_NUMBER: ${{ needs.prepare.outputs.build }}
BUILD_TIMESTAMP: ${{ needs.prepare.outputs.timestamp }}
# The same conditions under which package-autobuild.sh signs; a partial pair fails the build.
MACOS_SIGNED: ${{ secrets.MACOS_SIGNING_CERTIFICATE != '' }}
WINDOWS_SIGNED: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_BASE64 != '' && secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD != '' }}
run: bash scripts/ci/desktop-autobuild/publish-autobuild.sh dist/autobuild

- name: Add the release link
env:
SOURCE_SHA: ${{ needs.prepare.outputs.sha }}
BUILD_NUMBER: ${{ needs.prepare.outputs.build }}
run: |
{
printf '%s\n' "Published $SOURCE_BRANCH at \`$SOURCE_SHA\` as build $BUILD_NUMBER."
printf '%s\n' "[Download this build]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$RELEASE_TAG)"
} >> "$GITHUB_STEP_SUMMARY"
6 changes: 5 additions & 1 deletion .github/workflows/desktop-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ jobs:
bash -n scripts/desktop/perf/editor-bench.sh
bash -n scripts/ci/desktop-release/package-release.sh
bash -n scripts/ci/desktop-release/resolve-release-version.sh
bash -n scripts/ci/desktop-autobuild/package-autobuild.sh
bash -n scripts/ci/desktop-autobuild/publish-autobuild.sh
zsh -n scripts/desktop/generate-desktop-icons.sh
zsh -n scripts/desktop/generate-desktop-file-icons.sh

Expand All @@ -84,7 +86,9 @@ jobs:
scripts/desktop/package-desktop.sh \
scripts/desktop/perf/editor-bench.sh \
scripts/ci/desktop-release/package-release.sh \
scripts/ci/desktop-release/resolve-release-version.sh
scripts/ci/desktop-release/resolve-release-version.sh \
scripts/ci/desktop-autobuild/package-autobuild.sh \
scripts/ci/desktop-autobuild/publish-autobuild.sh

- name: Verify shared localization
run: |
Expand Down
40 changes: 1 addition & 39 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,45 +96,7 @@ jobs:

- name: Ensure Windows build tools
if: matrix.os == 'windows'
run: |
if ! command -v gcc >/dev/null 2>&1; then
choco install mingw -y
gcc_path="$(find /c/ProgramData/chocolatey/lib/mingw /c/tools/mingw64 /c/mingw64 \
-type f -name gcc.exe -print -quit 2>/dev/null || true)"
if [[ -z "$gcc_path" ]]; then
printf '%s\n' 'MinGW-w64 installation did not provide gcc.exe.' >&2
exit 1
fi
mingw_bin="$(dirname "$gcc_path")"
export PATH="$mingw_bin:$PATH"
printf '%s\n' "$(cygpath -m "$mingw_bin")" >> "$GITHUB_PATH"
fi
gcc --version
nsis_x86='/c/Program Files (x86)/NSIS'
nsis_x64='/c/Program Files/NSIS'
if ! command -v makensis >/dev/null 2>&1 && \
[[ ! -f "$nsis_x86/makensis.exe" && ! -f "$nsis_x64/makensis.exe" ]]; then
choco install nsis -y
fi
if ! command -v makensis >/dev/null 2>&1; then
for nsis_dir in "$nsis_x86" "$nsis_x64"; do
if [[ -f "$nsis_dir/makensis.exe" ]]; then
export PATH="$nsis_dir:$PATH"
printf '%s\n' "$(cygpath -m "$nsis_dir")" >> "$GITHUB_PATH"
break
fi
done
fi
if command -v makensis >/dev/null 2>&1; then
makensis /VERSION
elif [[ -f "$nsis_x86/makensis.exe" ]]; then
"$nsis_x86/makensis.exe" /VERSION
elif [[ -f "$nsis_x64/makensis.exe" ]]; then
"$nsis_x64/makensis.exe" /VERSION
else
printf '%s\n' 'NSIS installation did not provide makensis.' >&2
exit 1
fi
uses: ./.github/actions/setup-desktop-windows-build-tools

- name: Restore the core build
uses: actions/cache@v6
Expand Down
2 changes: 1 addition & 1 deletion docs/viva-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,5 +570,5 @@ MCP 管理随 Agent 对话失去消费者后,本轮整块删除,后续重新
| 应用名 / 版本 | `CFBundleName` Lithe,版本 0.1.0(Info.plist),发布时覆盖 | `productName` Lithe,0.3.0 | `name = "Lithe Desktop"`,`version = "0.7.0"` | 否(产品名不同,版本线独立) |
| macOS 更新 | Sparkle:`SUFeedURL` 与 `SUPublicEDKey` 在构建时由 `configure-sparkle-app.sh` 写入。Stable 源 `https://github.com/<仓库>/releases/latest/download/appcast-<arch>.xml`;AutoBuild 源 `https://github.com/<仓库>/releases/download/AutoBuild/appcast-preview-<arch>.xml`;公钥来自仓库变量 `SPARKLE_PUBLIC_KEY`(值未在仓库内,未找到);`SURequireSignedFeed`、`SUVerifyUpdateBeforeExtraction` 为 true;说明见 `docs/architecture/macos-updates.md` | — | `viva.toml` 与 `scripts/desktop/package-desktop.sh` 中未找到 Sparkle、appcast、feed、公钥任何配置 | 否。新客户端自建更新通道(新的 appcast 与签名公钥),不衔接旧 Sparkle 增量链;尚未实现(`updates-check-download` 为未开始) |
| Windows 安装与更新 | — | Tauri 打包 `nsis`、`msi`(`tauri.conf.json` `bundle.targets`);Tauri updater 插件,`tauri.conf.json` 中 `pubkey` 为空串、`endpoints` 为空;稳定版发布工作流 `release-windows.yml` 用 secret `TAURI_SIGNING_PRIVATE_KEY` 与变量 `TAURI_UPDATER_PUBLIC_KEY` 签名,产出 `Lithe-<版本>-windows-x64-updater.exe` | `viva.toml [windows]`:`scope = "user"`、`publisher = "Lithe contributors"`;`package-desktop.sh` Windows 默认格式 `nsis,zip`;更新机制未找到 | 部分。同为 NSIS;没有 MSI、更新器与签名公钥配置 |
| 发布工作流 | `release-macos.yml`、`release-preview-macos.yml` | `release-windows.yml`、`release-preview-windows.yml` | `.github/workflows/desktop-release.yml`,标签 `desktop-v*.*.*`,作为 prerelease 发布,避免旧客户端的更新器读到 | 独立 |
| 发布工作流 | `release-macos.yml`、`release-preview-macos.yml` | `release-windows.yml`、`release-preview-windows.yml` | `.github/workflows/desktop-release.yml`,标签 `desktop-v*.*.*`,作为 prerelease 发布,避免旧客户端的更新器读到;`.github/workflows/desktop-autobuild.yml` 每天从 `preview` 构建并滚动发布到 prerelease `desktop-AutoBuild`(macOS arm64、Windows x64、Linux x64/arm64 的 deb 与 tar.gz,不跑测试),标签与旧客户端的 `AutoBuild` 分开 | 独立 |
| 运行时只读边界 | 发行 bundle 运行期不可写(AGENTS.md) | 安装目录只读 | `package-desktop.sh` 注释声明运行时写入不落在 app 或安装目录;`runtime-package-integrity` 仅 macOS 夹具验证 | 一致于规则;Windows/最终包验证未完成 |
Loading
Loading