version 1.0.6 #34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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') }} |