lobehub/.github/workflows/release-desktop-stable.yml
Innei 959c210e86
feat(desktop): add local update testing scripts and stable channel API version check (#11474)
* chore: stable updater

*  feat: add local update testing scripts and configuration

- Introduced scripts for local update testing, including setup, server management, and manifest generation.
- Added `dev-app-update.local.yml` for local server configuration.
- Implemented `generate-manifest.sh` to create update manifests.
- Created `run-test.sh` for streamlined testing process.
- Updated `README.md` with instructions for local testing setup and usage.
- Enhanced `UpdaterManager` to allow forced use of dev update configuration in packaged apps.

Signed-off-by: Innei <tukon479@gmail.com>

* 🐛 fix(desktop): update UpdaterManager test mocks for new exports

Add missing mock exports for @/modules/updater/configs:
- isStableChannel
- githubConfig
- UPDATE_SERVER_URL

Add mock for @/env with getDesktopEnv
Add setFeedURL method to autoUpdater mock

*  feat: add Conductor setup scripts and configuration

*  feat: enhance update modal functionality and refactor modal hooks

- Added `useUpdateModal` for managing update modal state and behavior.
- Refactored `UpdateModal` to utilize new modal management approach.
- Improved `useWatchBroadcast` integration for handling update events.
- Removed deprecated `createModalHooks` and related components from `FunctionModal`.
- Updated `AddFilesToKnowledgeBase` and `CreateNew` modals to use new modal context for closing behavior.

This refactor streamlines modal management and enhances the user experience during update processes.

Signed-off-by: Innei <tukon479@gmail.com>

* update flow (#11513)

* ci: simplify desktop release workflow and add renderer tarball

* 👷 ci: fix s3 upload credentials for desktop release

* 🐛 fix(ci): use compact jq output for GitHub Actions matrix

Add -c flag to jq commands to produce single-line JSON output,
fixing "Invalid format" error when setting GITHUB_OUTPUT.

* 🐛 fix(ci): add administration permission to detect self-hosted runner

The /actions/runners API requires administration:read permission
to list repository runners.

* 🔧 refactor(ci): use workflow input for self-hosted runner selection

Replace API-based runner detection with workflow input parameter since
GITHUB_TOKEN lacks permission to call /actions/runners API.

- Add `use_self_hosted_mac` input (default: true)
- Release events always use self-hosted runner
- Manual dispatch can toggle via input

* feat(updater): add stable channel support with fallback mechanism

- Configure electron-builder to generate stable-mac.yml for stable channel
- Update CI workflow to handle both stable and latest manifest files
- Implement fallback to GitHub provider when primary S3 provider fails
- Reset to primary provider after successful update check

* 🐛 fix(updater): remove invalid channel config from electron-builder

- Remove unsupported 'channel' property from electron-builder config
- Create stable*.yml files from latest*.yml in workflow instead
- This ensures electron-updater finds correct manifest for stable channel

* 🐛 fix(updater): use correct channel based on provider type

- S3 provider: channel='stable' → looks for stable-mac.yml
- GitHub provider: channel='latest' → looks for latest-mac.yml

This fixes the 404 error when falling back to GitHub releases,
which only have latest-mac.yml files.

* refactor(env): remove unused OFFICIAL_CLOUD_SERVER and update env defaults

Update environment variable handling by removing unused OFFICIAL_CLOUD_SERVER and setting defaults for UPDATE_CHANNEL and UPDATE_SERVER_URL from process.env during build stage.

* 🐛 fix(ci): add version prefix to stable manifest URLs for S3

S3 directory structure: stable/{version}/xxx.dmg
So stable-mac.yml URLs need version prefix:
  url: LobeHub-2.1.0-arm64.dmg → url: 2.1.1/LobeHub-2.1.0-arm64.dmg

*  feat(ci): add renderer tar manifest for integrity verification

Creates stable-renderer.yml with SHA512 checksum for lobehub-renderer.tar.gz
This allows the desktop app to verify renderer tarball integrity before extraction.

* 🐛 fix(ci): fix YAML syntax error in renderer manifest generation

*  feat(ci): archive manifest files in version directory

* refactor(ci): update desktop release workflows to streamline build process

- Removed unnecessary dependencies in the build job for the desktop beta workflow.
- Introduced a new gate job to conditionally proceed with publishing based on the success of previous jobs.
- Updated macOS file merging to depend on the new gate job instead of the build job.
- Simplified macOS runner selection logic in the stable workflow by using GitHub-hosted runners exclusively.

Signed-off-by: Innei <tukon479@gmail.com>

* refactor(electron): reorganize titlebar components and update imports

- Moved titlebar components to a new directory structure for better organization.
- Updated import paths for `SimpleTitleBar`, `TitleBar`, and related constants.
- Introduced new components for connection management and navigation within the titlebar.
- Added constants for title bar height to maintain consistency across components.

This refactor enhances the maintainability of the titlebar code and improves the overall structure of the Electron application.

Signed-off-by: Innei <tukon479@gmail.com>

* feat(ci): add release notes handling to desktop stable workflow

- Enhanced the desktop stable release workflow to include release notes.
- Updated output variables to capture release notes from the GitHub event.
- Adjusted environment variables in subsequent jobs to utilize the new release notes data.

This addition improves the clarity and documentation of releases by ensuring that release notes are included in the workflow process.

Signed-off-by: Innei <tukon479@gmail.com>

* 🐛 fix: call onClose after knowledge base modal closes

* 🧪 test: fix UpdaterManager update channel mocks

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-15 17:26:19 +08:00

461 lines
18 KiB
YAML
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.

name: Release Desktop Stable
# ============================================
# Stable 频道发版工作流
# ============================================
# 触发条件: 发布不含 pre-release 标识的 release (如 v2.0.0)
#
# 与 Beta 的区别:
# 1. 仅响应 stable 版本 tag (不含 beta/alpha/rc/nightly)
# 2. 使用 STABLE 专用的 Umami 配置
# 3. 额外上传到 S3 更新服务器
# 4. 构建时注入 UPDATE_SERVER_URL 让客户端从 S3 检查更新
#
# 需要配置的 Secrets (S3 相关, 统一 UPDATE_ 前缀):
# - UPDATE_AWS_ACCESS_KEY_ID
# - UPDATE_AWS_SECRET_ACCESS_KEY
# - UPDATE_S3_BUCKET (S3 存储桶名称)
# - UPDATE_S3_REGION (可选, 默认 us-east-1)
# - UPDATE_S3_ENDPOINT (可选, 用于 R2/MinIO 等 S3 兼容服务)
# - UPDATE_SERVER_URL (客户端检查更新的 URL)
# ============================================
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version to build (e.g., 2.0.0)'
required: true
type: string
build_mac:
description: 'Build macOS (ARM64)'
required: false
type: boolean
default: true
build_windows:
description: 'Build Windows'
required: false
type: boolean
default: true
build_linux:
description: 'Build Linux'
required: false
type: boolean
default: true
skip_s3_upload:
description: 'Skip S3 upload (for testing)'
required: false
type: boolean
default: true
skip_github_release:
description: 'Skip GitHub release upload (for testing)'
required: false
type: boolean
default: true
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
permissions: read-all
env:
NODE_VERSION: '24.11.1'
jobs:
# ============================================
# 检查版本信息
# ============================================
check-stable:
name: Check Release Version
runs-on: ubuntu-latest
outputs:
is_stable: ${{ steps.check.outputs.is_stable }}
version: ${{ steps.check.outputs.version }}
is_manual: ${{ steps.check.outputs.is_manual }}
release_notes: ${{ steps.check.outputs.release_notes }}
steps:
- name: Check release info
id: check
run: |
# 判断触发方式
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
# 手动触发: 使用输入的版本号
version="${{ inputs.version }}"
echo "is_manual=true" >> $GITHUB_OUTPUT
echo "is_stable=true" >> $GITHUB_OUTPUT
echo "version=${version}" >> $GITHUB_OUTPUT
echo "release_notes=" >> $GITHUB_OUTPUT
echo "🔧 Manual trigger: version=${version}"
else
# Release 触发: 从 tag 提取版本号
version="${{ github.event.release.tag_name }}"
version="${version#v}"
echo "is_manual=false" >> $GITHUB_OUTPUT
echo "version=${version}" >> $GITHUB_OUTPUT
release_body="${{ github.event.release.body }}"
{
echo "release_notes<<EOF"
printf '%s\n' "$release_body"
echo "EOF"
} >> $GITHUB_OUTPUT
# 检查是否为 stable 版本 (不含 beta/alpha/rc/nightly)
if [[ "$version" == *"beta"* ]] || [[ "$version" == *"alpha"* ]] || [[ "$version" == *"rc"* ]] || [[ "$version" == *"nightly"* ]]; then
echo "is_stable=false" >> $GITHUB_OUTPUT
echo "⏭️ Skipping: $version is not a stable release"
else
echo "is_stable=true" >> $GITHUB_OUTPUT
echo "✅ Stable release detected: $version"
fi
fi
# ============================================
# 配置构建矩阵 (检查自托管 Runner)
# ============================================
configure-build:
needs: [check-stable]
if: needs.check-stable.outputs.is_stable == 'true'
name: Configure Build Matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Generate Matrix
id: set-matrix
run: |
# 基础矩阵
static_matrix='[]'
# Windows
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]] || [[ "${{ inputs.build_windows }}" == "true" ]]; then
static_matrix=$(echo "$static_matrix" | jq -c '. + [{"os": "windows-2025", "name": "windows-2025"}]')
fi
# Linux
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]] || [[ "${{ inputs.build_linux }}" == "true" ]]; then
static_matrix=$(echo "$static_matrix" | jq -c '. + [{"os": "ubuntu-latest", "name": "ubuntu-latest"}]')
fi
# macOS (ARM64)
# 使用 GitHub Hosted Runner
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]] || [[ "${{ inputs.build_mac }}" == "true" ]]; then
echo "Using GitHub-Hosted Runner for macOS ARM64"
arm_entry='{"os": "macos-14", "name": "macos-arm64"}'
static_matrix=$(echo "$static_matrix" | jq -c --argjson entry "$arm_entry" '. + [$entry]')
fi
# 输出
echo "matrix={\"include\":$static_matrix}" >> $GITHUB_OUTPUT
# ============================================
# 多平台构建
# ============================================
build:
needs: [check-stable, configure-build]
if: needs.check-stable.outputs.is_stable == 'true'
name: Build Desktop App
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure-build.outputs.matrix) }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup build environment
uses: ./.github/actions/desktop-build-setup
with:
node-version: ${{ env.NODE_VERSION }}
- name: Set package version
run: npm run workflow:set-desktop-version ${{ needs.check-stable.outputs.version }} stable
# macOS 构建
- name: Build artifact on macOS
if: runner.os == 'macOS'
run: npm run desktop:build
env:
UPDATE_CHANNEL: stable
UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }}
RELEASE_NOTES: ${{ needs.check-stable.outputs.release_notes }}
APP_URL: http://localhost:3015
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_FOR_PULL_REQUEST: true
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_STABLE_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_STABLE_DESKTOP_BASE_URL }}
# Windows 构建
- name: Build artifact on Windows
if: runner.os == 'Windows'
run: npm run desktop:build
env:
UPDATE_CHANNEL: stable
UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }}
RELEASE_NOTES: ${{ needs.check-stable.outputs.release_notes }}
APP_URL: http://localhost:3015
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_STABLE_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_STABLE_DESKTOP_BASE_URL }}
TEMP: C:\temp
TMP: C:\temp
# Linux 构建
- name: Build artifact on Linux
if: runner.os == 'Linux'
run: |
npm run desktop:build
tar -czf apps/desktop/release/lobehub-renderer.tar.gz -C out .
env:
UPDATE_CHANNEL: stable
UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }}
RELEASE_NOTES: ${{ needs.check-stable.outputs.release_notes }}
APP_URL: http://localhost:3015
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_STABLE_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_STABLE_DESKTOP_BASE_URL }}
- name: Upload artifacts
uses: ./.github/actions/desktop-upload-artifacts
with:
artifact-name: release-${{ matrix.name }}
# ============================================
# 合并 macOS 多架构文件
# ============================================
merge-mac-files:
needs: [build, check-stable]
name: Merge macOS Release Files
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: release
pattern: release-*
merge-multiple: true
- name: List downloaded artifacts
run: ls -R release
- name: Install yaml only for merge step
run: |
cd scripts/electronWorkflow
if [ ! -f package.json ]; then
echo '{"name":"merge-mac-release","private":true}' > package.json
fi
bun add --no-save yaml@2.8.1
- name: Merge latest-mac.yml files
run: bun run scripts/electronWorkflow/mergeMacReleaseFiles.js
- name: Upload artifacts with merged macOS files
uses: actions/upload-artifact@v6
with:
name: merged-release
path: release/
retention-days: 1
# ============================================
# 发布到 GitHub Releases
# ============================================
publish-github:
needs: [merge-mac-files, check-stable]
name: Publish to GitHub Release
runs-on: ubuntu-latest
# 手动触发时可选择跳过
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.skip_github_release) }}
permissions:
contents: write
steps:
- name: Download merged artifacts
uses: actions/download-artifact@v7
with:
name: merged-release
path: release
- name: List final artifacts
run: ls -R release
- name: Upload to Release
uses: softprops/action-gh-release@v1
with:
# 手动触发时使用输入的版本号创建 tag
tag_name: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', needs.check-stable.outputs.version) || github.event.release.tag_name }}
# 手动触发时创建为 draft
draft: ${{ github.event_name == 'workflow_dispatch' }}
files: |
release/stable*
release/latest*
release/*.dmg*
release/*.zip*
release/*.exe*
release/*.AppImage
release/*.deb*
release/*.snap*
release/*.rpm*
release/*.tar.gz*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ============================================
# 发布到 S3 更新服务器
# ============================================
# S3 目录结构:
# s3://bucket/
# stable/
# stable-mac.yml ← electron-updater 检查更新 (stable channel)
# stable.yml ← Windows (stable channel)
# stable-linux.yml ← Linux (stable channel)
# latest-mac.yml ← fallback for GitHub provider
# {version}/ ← 版本目录
# *.dmg, *.zip, *.exe, ...
# ============================================
publish-s3:
needs: [merge-mac-files, check-stable]
name: Publish to S3
runs-on: ubuntu-latest
# 手动触发时可选择跳过
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.skip_s3_upload) }}
steps:
- name: Download merged artifacts
uses: actions/download-artifact@v7
with:
name: merged-release
path: release
- name: List artifacts to upload
run: |
echo "📦 Artifacts to upload to S3:"
ls -lah release/
echo ""
echo "📋 Version: ${{ needs.check-stable.outputs.version }}"
- name: Upload to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.UPDATE_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.UPDATE_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.UPDATE_S3_REGION || 'us-east-1' }}
S3_BUCKET: ${{ secrets.UPDATE_S3_BUCKET }}
S3_ENDPOINT: ${{ secrets.UPDATE_S3_ENDPOINT }}
VERSION: ${{ needs.check-stable.outputs.version }}
run: |
if [ -z "$S3_BUCKET" ]; then
echo "⚠️ UPDATE_S3_BUCKET is not configured, skipping S3 upload"
echo ""
echo "To enable S3 upload, configure the following secrets:"
echo " - UPDATE_AWS_ACCESS_KEY_ID"
echo " - UPDATE_AWS_SECRET_ACCESS_KEY"
echo " - UPDATE_S3_BUCKET"
echo " - UPDATE_S3_REGION (optional, defaults to us-east-1)"
echo " - UPDATE_S3_ENDPOINT (optional, for S3-compatible services)"
exit 0
fi
# 构建端点参数
ENDPOINT_ARG=""
if [ -n "$S3_ENDPOINT" ]; then
ENDPOINT_ARG="--endpoint-url $S3_ENDPOINT"
echo "📡 Using custom S3 endpoint: $S3_ENDPOINT"
fi
echo "🚀 Uploading to S3 bucket: $S3_BUCKET"
echo "📁 Target path: s3://$S3_BUCKET/stable/"
echo ""
# 1. 上传安装包到版本目录
echo "📦 Uploading release files to s3://$S3_BUCKET/stable/$VERSION/"
for file in release/*.dmg release/*.zip release/*.exe release/*.AppImage release/*.deb release/*.rpm release/*.snap release/*.tar.gz; do
if [ -f "$file" ]; then
filename=$(basename "$file")
echo " ↗️ $filename"
aws s3 cp "$file" "s3://$S3_BUCKET/stable/$VERSION/$filename" $ENDPOINT_ARG
fi
done
# 2. 创建 stable*.yml (从 latest*.yml 复制,并修改 URL 加上版本目录前缀)
# electron-updater 在 channel=stable 时会找 stable-mac.yml
# S3 目录结构: stable/{version}/xxx.dmg所以 URL 需要加上 {version}/ 前缀
echo ""
echo "📋 Creating stable*.yml files from latest*.yml..."
for yml in release/latest*.yml; do
if [ -f "$yml" ]; then
stable_name=$(basename "$yml" | sed 's/latest/stable/')
# 复制并修改 URL: 给所有 url 字段加上版本目录前缀
# url: xxx.dmg -> url: {VERSION}/xxx.dmg
sed "s|url: |url: $VERSION/|g" "$yml" > "release/$stable_name"
echo " 📄 Created $stable_name from $(basename $yml) with URL prefix: $VERSION/"
fi
done
# 3. 创建 renderer manifest (用于验证 renderer tar 完整性)
echo ""
echo "📋 Creating renderer manifest..."
RENDERER_TAR="release/lobehub-renderer.tar.gz"
if [ -f "$RENDERER_TAR" ]; then
RENDERER_SHA512=$(shasum -a 512 "$RENDERER_TAR" | awk '{print $1}' | xxd -r -p | base64)
RENDERER_SIZE=$(stat -f%z "$RENDERER_TAR" 2>/dev/null || stat -c%s "$RENDERER_TAR")
RELEASE_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
echo "version: $VERSION" > "release/stable-renderer.yml"
echo "files:" >> "release/stable-renderer.yml"
echo " - url: $VERSION/lobehub-renderer.tar.gz" >> "release/stable-renderer.yml"
echo " sha512: $RENDERER_SHA512" >> "release/stable-renderer.yml"
echo " size: $RENDERER_SIZE" >> "release/stable-renderer.yml"
echo "path: $VERSION/lobehub-renderer.tar.gz" >> "release/stable-renderer.yml"
echo "sha512: $RENDERER_SHA512" >> "release/stable-renderer.yml"
echo "releaseDate: '$RELEASE_DATE'" >> "release/stable-renderer.yml"
echo " 📄 Created stable-renderer.yml with SHA512 checksum"
else
echo " ⚠️ Renderer tar not found, skipping manifest creation"
fi
# 4. 上传 manifest 到根目录和版本目录
# 根目录: electron-updater 需要,会被每次发版覆盖
# 版本目录: 作为存档保留
echo ""
echo "📋 Uploading manifest files..."
for yml in release/stable*.yml release/latest*.yml; do
if [ -f "$yml" ]; then
filename=$(basename "$yml")
echo " ↗️ $filename -> s3://$S3_BUCKET/stable/$filename"
aws s3 cp "$yml" "s3://$S3_BUCKET/stable/$filename" $ENDPOINT_ARG
echo " ↗️ $filename -> s3://$S3_BUCKET/stable/$VERSION/$filename (archive)"
aws s3 cp "$yml" "s3://$S3_BUCKET/stable/$VERSION/$filename" $ENDPOINT_ARG
fi
done
echo ""
echo "✅ S3 upload completed!"
echo ""
echo "📋 Files in s3://$S3_BUCKET/stable/:"
aws s3 ls "s3://$S3_BUCKET/stable/" $ENDPOINT_ARG || true
echo ""
echo "📋 Files in s3://$S3_BUCKET/stable/$VERSION/:"
aws s3 ls "s3://$S3_BUCKET/stable/$VERSION/" $ENDPOINT_ARG || true