name: Secrets CLI - Build & Release on: push: branches: [main] paths: - 'src/**' - 'Cargo.toml' - 'Cargo.lock' - '.gitea/workflows/secrets.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: write env: BINARY_NAME: secrets CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 CARGO_TERM_COLOR: always RUST_BACKTRACE: short jobs: # ========== 版本检查(只跑一次,供后续 job 共用)========== version: name: 检查版本 runs-on: debian outputs: version: ${{ steps.version.outputs.version }} tag: ${{ steps.version.outputs.tag }} tag_exists: ${{ steps.version.outputs.tag_exists }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: 检查版本 id: version run: | version=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/') tag="secrets-${version}" echo "version=${version}" >> $GITHUB_OUTPUT echo "tag=${tag}" >> $GITHUB_OUTPUT if git rev-parse "refs/tags/${tag}" >/dev/null 2>&1; then echo "tag_exists=true" >> $GITHUB_OUTPUT echo "版本 ${tag} 已存在" else echo "tag_exists=false" >> $GITHUB_OUTPUT echo "将创建新版本 ${tag}" fi - name: 创建 Tag if: steps.version.outputs.tag_exists == 'false' run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" git push origin "${{ steps.version.outputs.tag }}" # ========== 矩阵构建 ========== build: name: Build (${{ matrix.target }}) needs: version continue-on-error: true # 某平台失败/超时不阻断其他平台和 notify timeout-minutes: 30 # runner 不在线时 30 分钟后放弃 strategy: fail-fast: false matrix: include: - runner: debian target: x86_64-unknown-linux-musl archive_suffix: x86_64-linux-musl os: linux - runner: darwin-arm64 target: aarch64-apple-darwin archive_suffix: aarch64-macos os: macos - runner: windows target: x86_64-pc-windows-msvc archive_suffix: x86_64-windows os: windows runs-on: ${{ matrix.runner }} steps: # ========== 环境准备 ========== - name: 安装依赖 (Linux) if: matrix.os == 'linux' run: | sudo apt-get update sudo apt-get install -y jq curl git pkg-config musl-tools binutils if ! command -v cargo &>/dev/null; then echo "安装 Rust 工具链..." curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable source "$HOME/.cargo/env" rustup component add rustfmt clippy fi rustup target add ${{ matrix.target }} echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: 安装依赖 (macOS) if: matrix.os == 'macos' run: | brew install jq if ! command -v cargo &>/dev/null; then echo "安装 Rust 工具链..." curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable source "$HOME/.cargo/env" rustup component add rustfmt clippy fi rustup target add ${{ matrix.target }} echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: 安装依赖 (Windows) if: matrix.os == 'windows' shell: pwsh run: | # 检查 Rust 是否已安装 if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { Write-Host "安装 Rust 工具链..." Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile rustup-init.exe .\rustup-init.exe -y --default-toolchain stable Remove-Item rustup-init.exe } rustup component add rustfmt clippy rustup target add ${{ matrix.target }} - uses: actions/checkout@v4 with: fetch-depth: 0 # ========== Cargo 缓存 ========== - name: 缓存 Cargo 依赖 uses: actions/cache@v4 with: path: | ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db target key: cargo-secrets-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} restore-keys: | cargo-secrets-${{ matrix.target }}- - name: 检查代码格式 if: matrix.os != 'windows' run: cargo fmt -- --check - name: 检查代码格式 (Windows) if: matrix.os == 'windows' shell: pwsh run: cargo fmt -- --check - name: 运行 Clippy 检查 if: matrix.os != 'windows' run: cargo clippy --release --target ${{ matrix.target }} -- -D warnings - name: 运行 Clippy 检查 (Windows) if: matrix.os == 'windows' shell: pwsh run: cargo clippy --release --target ${{ matrix.target }} -- -D warnings - name: 构建 if: matrix.os != 'windows' env: GIT_TAG: ${{ needs.version.outputs.version }} run: cargo build --release --target ${{ matrix.target }} --verbose - name: 构建 (Windows) if: matrix.os == 'windows' shell: pwsh env: GIT_TAG: ${{ needs.version.outputs.version }} run: cargo build --release --target ${{ matrix.target }} --verbose - name: Strip 二进制 (Linux) if: matrix.os == 'linux' run: strip target/${{ matrix.target }}/release/${{ env.BINARY_NAME }} - name: Strip 二进制 (macOS) if: matrix.os == 'macos' run: strip -x target/${{ matrix.target }}/release/${{ env.BINARY_NAME }} # ========== 上传 Release 产物 ========== - name: 上传 Release 产物 (Linux/macOS) if: needs.version.outputs.tag_exists == 'false' && matrix.os != 'windows' env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | [ -z "$RELEASE_TOKEN" ] && echo "跳过:未配置 RELEASE_TOKEN" && exit 0 tag="${{ needs.version.outputs.tag }}" binary="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" archive="${{ env.BINARY_NAME }}-${tag}-${{ matrix.archive_suffix }}.tar.gz" tar -czf "$archive" -C "$(dirname $binary)" "$(basename $binary)" # 查找已有 Release(由首个完成的 job 创建,后续 job 直接上传) release_url="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" release_id=$(curl -sS -H "Authorization: token $RELEASE_TOKEN" \ "${release_url}/tags/${tag}" | jq -r '.id // empty') if [ -z "$release_id" ]; then release_id=$(curl -sS -H "Authorization: token $RELEASE_TOKEN" \ -H "Content-Type: application/json" \ -X POST "$release_url" \ -d "{\"tag_name\": \"${tag}\", \"name\": \"${tag}\", \"body\": \"Release ${tag}\"}" \ | jq -r '.id') fi upload_url="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${release_id}/assets" curl -sS -H "Authorization: token $RELEASE_TOKEN" \ -F "attachment=@${archive}" \ "$upload_url" echo "已上传: ${archive} → Release ${tag}" - name: 上传 Release 产物 (Windows) if: needs.version.outputs.tag_exists == 'false' && matrix.os == 'windows' shell: pwsh env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | if (-not $env:RELEASE_TOKEN) { Write-Host "跳过:未配置 RELEASE_TOKEN"; exit 0 } $tag = "${{ needs.version.outputs.tag }}" $binary = "target\${{ matrix.target }}\release\${{ env.BINARY_NAME }}.exe" $archive = "${{ env.BINARY_NAME }}-${tag}-${{ matrix.archive_suffix }}.zip" Compress-Archive -Path $binary -DestinationPath $archive $headers = @{ "Authorization" = "token $env:RELEASE_TOKEN"; "Content-Type" = "application/json" } $release_url = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" # 查找已有 Release $existing = Invoke-RestMethod -Uri "${release_url}/tags/${tag}" -Headers $headers -ErrorAction SilentlyContinue if ($existing.id) { $release_id = $existing.id } else { $body = @{ tag_name = $tag; name = $tag; body = "Release ${tag}" } | ConvertTo-Json $release_id = (Invoke-RestMethod -Uri $release_url -Method Post -Headers $headers -Body $body).id } $upload_url = "${release_url}/${release_id}/assets" $upload_headers = @{ "Authorization" = "token $env:RELEASE_TOKEN" } $form = @{ attachment = Get-Item $archive } Invoke-RestMethod -Uri $upload_url -Method Post -Headers $upload_headers -Form $form Write-Host "已上传: ${archive} → Release ${tag}" # ========== 汇总通知 ========== notify: name: 发送通知 needs: [version, build] if: always() && github.event_name == 'push' runs-on: debian steps: - uses: actions/checkout@v4 - name: 发送通知 continue-on-error: true env: WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} run: | [ -z "$WEBHOOK_URL" ] && exit 0 tag="${{ needs.version.outputs.tag }}" tag_exists="${{ needs.version.outputs.tag_exists }}" build_result="${{ needs.build.result }}" if [ "$build_result" = "success" ]; then status_text="构建成功 ✅" else status_text="构建失败 ❌" fi commit_title=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "N/A") workflow_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}" if [ "$build_result" != "success" ]; then payload=$(jq -n \ --arg title "${{ env.BINARY_NAME }} ${status_text}" \ --arg commit "$commit_title" \ --arg version "$tag" \ --arg author "${{ github.actor }}" \ --arg url "$workflow_url" \ '{msg_type: "text", content: {text: "\($title)\n提交:\($commit)\n版本:\($version)\n作者:\($author)\n详情:\($url)"}}') elif [ "$tag_exists" = "false" ]; then payload=$(jq -n \ --arg title "${{ env.BINARY_NAME }} ${status_text}" \ --arg commit "$commit_title" \ --arg version "$tag" \ --arg author "${{ github.actor }}" \ --arg url "$workflow_url" \ '{msg_type: "text", content: {text: "\($title)\n🆕 新版本已发布 (linux / macOS / windows)\n提交:\($commit)\n版本:\($version)\n作者:\($author)\n详情:\($url)"}}') else payload=$(jq -n \ --arg title "${{ env.BINARY_NAME }} ${status_text}" \ --arg commit "$commit_title" \ --arg version "$tag" \ --arg author "${{ github.actor }}" \ --arg url "$workflow_url" \ '{msg_type: "text", content: {text: "\($title)\n🔄 重复构建\n提交:\($commit)\n版本:\($version)\n作者:\($author)\n详情:\($url)"}}') fi curl -sS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL"