Skip to content

revise UI

revise UI #36

Workflow file for this run

name: Build & Release
# 触发条件:
# - 推送到 feature/build 分支(出单一安装包:内置测试/正式两套配置,登录页选择环境)
# - 推送 release/v* 标签(例如 release/v1.0.4,额外创建 GitHub Release)
# - 手动触发(workflow_dispatch)
on:
push:
branches: [ feature/build ]
tags: [ 'release/v*' ]
workflow_dispatch:
# 同一分支/标签的旧构建在新构建开始时取消,避免排队浪费 runner 时长
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
jobs:
# ============================================================
# Windows — MSVC2022 + Qt 6.7.3 + Qt IFW → .exe 安装包
# 单一安装包 DataSafebox_<version>.exe,内置测试/正式两套配置
# ============================================================
build-windows:
name: Windows
runs-on: windows-2022
steps:
- name: Checkout
uses: actions/checkout@v4
# 安装 Qt(含 WebEngine)。IFW 不从这里取——见下方 vendored 步骤。
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: '6.7.3'
host: 'windows'
target: 'desktop'
arch: 'win64_msvc2019_64' # Qt 6.7.3 ships no msvc2022 build; this is ABI-compatible with MSVC 2022
modules: 'qtwebengine qtwebchannel qtpositioning qtlocation qtquick3d'
cache: true
# 取用仓库自带的预编译 Qt IFW 4.10(binarycreator + installerbase,静态链接 Qt,
# 经 release 资产分发)。aqt 的 tools_ifw 只到 4.7:其 installerbase 基于旧版 Qt、
# 不含 zh_CN,导致向导显示英文且样式陈旧;4.10 基于 Qt 6.7.2、内嵌 zh_CN 且带
# Windows 11 风格,从而统一安装向导的语言与外观。
- name: Fetch vendored Qt IFW 4.10
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release download ifw-4.10 --repo ${{ github.repository }} `
--pattern ifw-4.10.0-win64.zip --dir ifw-zip
New-Item -ItemType Directory -Force ifw-bin | Out-Null
tar -xf ifw-zip/ifw-4.10.0-win64.zip -C ifw-bin
$bc = Get-ChildItem -Path ifw-bin -Recurse -Filter binarycreator.exe | Select-Object -First 1
if (-not $bc) { Write-Error 'binarycreator.exe not found in vendored IFW asset'; exit 1 }
"IFW_BIN=$($bc.DirectoryName)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Host "IFW_BIN=$($bc.DirectoryName)"
# vcpkg 缓存 + 安装 sentry-native(x64-windows)
- name: Cache vcpkg
uses: actions/cache@v4
with:
path: C:/vcpkg/installed
key: vcpkg-windows-sentry-${{ hashFiles('vcpkg.json') }}
- name: Install sentry-native (vcpkg)
shell: pwsh
env:
VCPKG_ROOT: C:/vcpkg
# --classic:本仓库根有 vcpkg.json,默认会进入 manifest 模式而拒绝单包安装
run: C:/vcpkg/vcpkg.exe install sentry-native --triplet x64-windows --classic
# 下载并解压 DSCC SDK(环境无关单包;私有仓库,需要 DSCC_TOKEN)
- name: Download DSCC SDK
shell: pwsh
env:
GH_TOKEN: ${{ secrets.DSCC_TOKEN }}
run: |
$asset = "dscc-sdk-win64.zip"
gh release download feature-build --repo YeeZTech/data-safebox-core `
--pattern $asset --dir dscc-zip
New-Item -ItemType Directory -Force dscc-sdk | Out-Null
tar -xf "dscc-zip/$asset" -C dscc-sdk
$hdr = Get-ChildItem -Path dscc-sdk -Recurse -Filter active_notify.h -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match 'include\\dscc\\core\\common' } | Select-Object -First 1
if (-not $hdr) { Write-Error 'DSCC SDK header not found after extraction'; exit 1 }
$dscc = $hdr.FullName -replace '\\include\\dscc\\core\\common\\active_notify\.h$',''
"DSCC_DIR=$dscc" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Host "DSCC_DIR=$dscc"
# 将敏感配置从 GitHub Secrets 写入 secrets.env(qmake 构建时读取;
# CASDOOR_CLIENT_ID 测试/正式两个值都注入,单一安装包运行时按所选环境取用)
- name: Write secrets env file
shell: pwsh
env:
SOKETI_APP_KEY: ${{ secrets.SOKETI_APP_KEY }}
CUSTOMER_SERVICE_TOKEN: ${{ secrets.CUSTOMER_SERVICE_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
CASDOOR_CLIENT_ID_TEST: ${{ secrets.CASDOOR_CLIENT_ID_TEST }}
CASDOOR_CLIENT_ID_PROD: ${{ secrets.CASDOOR_CLIENT_ID_PROD }}
run: |
$lines = @(
"SOKETI_APP_KEY=$env:SOKETI_APP_KEY"
"CASDOOR_CLIENT_ID_TEST=$env:CASDOOR_CLIENT_ID_TEST"
"CASDOOR_CLIENT_ID_PROD=$env:CASDOOR_CLIENT_ID_PROD"
"CUSTOMER_SERVICE_TOKEN=$env:CUSTOMER_SERVICE_TOKEN"
"SENTRY_DSN=$env:SENTRY_DSN"
)
[IO.File]::WriteAllText((Join-Path $PWD 'secrets.env'), ($lines -join "`n") + "`n")
Write-Host "Wrote secrets.env"
# 编译 + 打包(build_installer.bat 内部完成 lrelease/qmake/nmake/windeployqt/IFW)
- name: Build & package installer
shell: cmd
env:
SENTRY_ROOT_DIR: C:/vcpkg/installed/x64-windows
# DSCC_DIR 与 IFW_BIN 已通过 GITHUB_ENV 注入
run: call builder\windows\build_installer.bat
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: DataSafebox-windows
path: builder/windows/DataSafebox_*.exe
if-no-files-found: error
retention-days: 30
# ============================================================
# macOS (Apple Silicon) — Clang + Qt 6.7.3 + macdeployqt → .dmg
# 单一安装包 DataSafebox_<version>.dmg,内置测试/正式两套配置
# ============================================================
build-macos:
name: macOS
runs-on: macos-14 # Apple Silicon (arm64) — 与 DSCC macos-arm64 包匹配
steps:
- name: Checkout
uses: actions/checkout@v4
# 安装 Qt(含 WebEngine)
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: '6.7.3'
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtwebengine qtwebchannel qtpositioning qtlocation qtquick3d'
cache: true
# 构建依赖:图标渲染(librsvg) + pkg-config + sentry-native(arm64) + Pillow
- name: Install build dependencies (Homebrew)
run: brew install librsvg pkg-config sentry-native
- name: Install Pillow
run: python3 -m pip install --break-system-packages --quiet Pillow
# 下载并解压 DSCC SDK(环境无关单包;私有仓库,需要 DSCC_TOKEN)
- name: Download DSCC SDK
env:
GH_TOKEN: ${{ secrets.DSCC_TOKEN }}
run: |
asset="dscc-sdk-macos-arm64.zip"
gh release download feature-build --repo YeeZTech/data-safebox-core \
--pattern "$asset" --dir dscc-zip
mkdir -p dscc-sdk
tar -xf "dscc-zip/$asset" -C dscc-sdk
header="$(find dscc-sdk -path '*/include/dscc/core/common/active_notify.h' | head -1)"
if [ -z "$header" ]; then echo 'DSCC SDK header not found after extraction'; exit 1; fi
dscc="${header%/include/dscc/core/common/active_notify.h}"
dscc="$(cd "$dscc" && pwd)" # 转绝对路径:build_dmg.sh 在 build/macos 子目录里编译/链接
echo "DSCC_DIR=$dscc" >> "$GITHUB_ENV"
echo "DSCC_DIR=$dscc"
# 代码签名 + 公证准备:把 Developer ID 证书(.p12) 与 notarytool 凭据
# 放进一个临时 keychain。secrets 缺失时跳过(dmg 退化为 ad-hoc、不公证)。
- name: Set up code signing & notarization
env:
CERT_P12_BASE64: ${{ secrets.MACOS_CERT_P12_BASE64 }}
CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_PWD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
run: |
if [ -z "$CERT_P12_BASE64" ]; then
echo "::warning::MACOS_CERT_P12_BASE64 未配置 → dmg 使用 ad-hoc 签名、不公证"
exit 0
fi
KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db"
KEYCHAIN_PWD="$(openssl rand -base64 24)"
# 1) 创建/解锁临时 keychain,设默认 + 加入搜索列表 + 6h 不自动锁
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
security default-keychain -s "$KEYCHAIN_PATH"
# 2) 导入 Developer ID Application 证书(含私钥)
echo "$CERT_P12_BASE64" | openssl base64 -d -A > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN_PATH" -P "$CERT_PASSWORD" \
-T /usr/bin/codesign -T /usr/bin/security
rm -f "$RUNNER_TEMP/cert.p12"
# 允许 codesign 在无 UI 环境下使用私钥
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PWD" "$KEYCHAIN_PATH" >/dev/null
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
# 3) 在同一 keychain 里建立 notarytool 凭据 profile(等价你本地 store-credentials)
xcrun notarytool store-credentials "datasafebox-notary" \
--apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_PWD" \
--keychain "$KEYCHAIN_PATH"
# 4) 告诉 build_dmg.sh 从该 keychain 读取 notary profile
echo "NOTARY_KEYCHAIN=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
echo "macOS signing & notary profile ready"
# 将敏感配置从 GitHub Secrets 写入 secrets.env(qmake 构建时读取;
# CASDOOR_CLIENT_ID 测试/正式两个值都注入,单一安装包运行时按所选环境取用)
- name: Write secrets env file
env:
SOKETI_APP_KEY: ${{ secrets.SOKETI_APP_KEY }}
CUSTOMER_SERVICE_TOKEN: ${{ secrets.CUSTOMER_SERVICE_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
CASDOOR_CLIENT_ID_TEST: ${{ secrets.CASDOOR_CLIENT_ID_TEST }}
CASDOOR_CLIENT_ID_PROD: ${{ secrets.CASDOOR_CLIENT_ID_PROD }}
run: |
{
echo "SOKETI_APP_KEY=$SOKETI_APP_KEY"
echo "CASDOOR_CLIENT_ID_TEST=$CASDOOR_CLIENT_ID_TEST"
echo "CASDOOR_CLIENT_ID_PROD=$CASDOOR_CLIENT_ID_PROD"
echo "CUSTOMER_SERVICE_TOKEN=$CUSTOMER_SERVICE_TOKEN"
echo "SENTRY_DSN=$SENTRY_DSN"
} > secrets.env
echo "Wrote secrets.env"
# 编译 + 打包(build_dmg.sh 内部完成 lrelease/qmake/make/macdeployqt/签名/公证/制作 dmg)
- name: Build & package dmg
run: |
export QT_DIR="$QT_ROOT_DIR"
export SENTRY_ROOT_DIR="$(brew --prefix sentry-native)"
chmod +x builder/macos/build_dmg.sh
./builder/macos/build_dmg.sh
# 静态自检:bundle 内每个 dylib 的 @rpath 依赖都必须就位。
# (-mt boost 缺失这类问题不会让 build_dmg.sh 失败,但 app 启动即崩,故加此校验)
- name: Verify bundled dylib closure
run: |
APP="build/macos/DataSafebox.app"; FW="$APP/Contents/Frameworks"
missing=0
for lib in "$APP/Contents/MacOS/DataSafebox" "$FW"/*.dylib; do
[ -e "$lib" ] || continue
while read -r dep; do
d="${dep#@rpath/}"
[ -e "$FW/$d" ] || { echo "UNMET: $(basename "$lib") -> $dep"; missing=1; }
done < <(otool -L "$lib" | awk '/@rpath\/.*\.dylib/{print $1}')
done
if [ "$missing" = 0 ]; then echo "dylib closure OK"; else echo "::error::bundle has unresolved @rpath dylibs"; exit 1; fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: DataSafebox-macos
path: builder/macos/DataSafebox_*.dmg
if-no-files-found: error
retention-days: 30
# 清理临时 keychain(保险措施;runner 本身也是一次性的)
- name: Clean up keychain
if: always()
run: security delete-keychain "$RUNNER_TEMP/signing.keychain-db" 2>/dev/null || true
# ============================================================
# Release:推送 release/v* 标签时,把单一安装包 exe + dmg 发布到 GitHub Release
# ============================================================
release:
name: Create GitHub Release
needs: [ build-windows, build-macos ]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/release/v')
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/DataSafebox-windows/DataSafebox_*.exe
artifacts/DataSafebox-macos/DataSafebox_*.dmg
generate_release_notes: true
draft: false
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }}