From da007348eabd0802eaf3f4f3df1b6a56e227c69e Mon Sep 17 00:00:00 2001 From: voson Date: Sat, 21 Mar 2026 11:18:10 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20=E5=90=88=E5=B9=B6=E4=B8=BA=20ci=20+=20de?= =?UTF-8?q?ploy=20=E4=B8=A4=E4=B8=AA=20job=EF=BC=8Ccheck=20=E5=85=88?= =?UTF-8?q?=E4=BA=8E=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 单台 self-hosted runner 下并行 job 只是排队,多 job 拆分带来的 artifact 传递、重复 checkout、调度延迟反而更慢。 改动: - 原 version/check/build-linux/publish-release 四个 job 合并为单个 ci job - 步骤顺序:版本拦截 → fmt/clippy/test → build → 打 tag → 发 Release - tag 在构建成功后才创建,避免失败提交留下脏 tag - Release 创建+上传+发布合并为单步,去掉草稿中转 - deploy job 仅保留 artifact 下载 + SSH 部署逻辑,不再重复编译 - 整体从 400 行缩减至 244 行 Made-with: Cursor --- .gitea/workflows/secrets.yml | 369 ++++++++++------------------------- 1 file changed, 107 insertions(+), 262 deletions(-) diff --git a/.gitea/workflows/secrets.yml b/.gitea/workflows/secrets.yml index fe19ae6..503f081 100644 --- a/.gitea/workflows/secrets.yml +++ b/.gitea/workflows/secrets.yml @@ -27,175 +27,49 @@ env: MUSL_TARGET: x86_64-unknown-linux-musl jobs: - version: - name: 版本 & Release + ci: + name: 检查 / 构建 / 发版 runs-on: debian + timeout-minutes: 40 outputs: - version: ${{ steps.ver.outputs.version }} tag: ${{ steps.ver.outputs.tag }} - tag_exists: ${{ steps.ver.outputs.tag_exists }} - release_id: ${{ steps.release.outputs.release_id }} + version: ${{ steps.ver.outputs.version }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + # ── 版本检查(提前拦截,避免后续步骤白跑)────────────────────────── - name: 解析版本 id: ver run: | version=$(grep -m1 '^version' crates/secrets-mcp/Cargo.toml | sed 's/.*"\(.*\)".*/\1/') tag="secrets-mcp-${version}" - previous_tag=$(git tag --list 'secrets-mcp-*' --sort=-v:refname | awk -v tag="$tag" '$0 != tag { print; exit }') - echo "version=${version}" >> "$GITHUB_OUTPUT" echo "tag=${tag}" >> "$GITHUB_OUTPUT" - echo "previous_tag=${previous_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}" + echo "错误: 版本 ${tag} 已存在,禁止重复发版。" + echo "请先 bump crates/secrets-mcp/Cargo.toml 中的 version,再执行 cargo build 同步 Cargo.lock。" + exit 1 fi + echo "将创建新版本 ${tag}" - - name: 检测重复版本 - if: steps.ver.outputs.tag_exists == 'true' - run: | - echo "提示: 版本 ${{ steps.ver.outputs.tag }} 已存在,将复用现有 tag 继续构建。" - - - name: 创建 Tag - if: steps.ver.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.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}" - git push origin "${{ steps.ver.outputs.tag }}" - - - name: 解析或创建 Release - id: release - env: - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - run: | - if [ -z "$RELEASE_TOKEN" ]; then - echo "release_id=" >> "$GITHUB_OUTPUT" - exit 0 - fi - - command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) - - tag="${{ steps.ver.outputs.tag }}" - version="${{ steps.ver.outputs.version }}" - release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" - - http_code=$(curl -sS -o /tmp/release.json -w '%{http_code}' \ - -H "Authorization: token $RELEASE_TOKEN" \ - "${release_api}/tags/${tag}") - - if [ "$http_code" = "200" ]; then - release_id=$(jq -r '.id // empty' /tmp/release.json) - if [ -n "$release_id" ]; then - echo "已找到现有 Release: ${release_id}" - echo "release_id=${release_id}" >> "$GITHUB_OUTPUT" - exit 0 - fi - fi - - previous_tag="${{ steps.ver.outputs.previous_tag }}" - if [ -n "$previous_tag" ]; then - changes=$(git log --pretty=format:'- %s (%h)' "${previous_tag}..HEAD") - else - changes=$(git log --pretty=format:'- %s (%h)') - fi - [ -z "$changes" ] && changes="- 首次发布" - - body=$(printf '## 变更日志\n\n%s' "$changes") - - payload=$(jq -n \ - --arg tag "$tag" \ - --arg name "secrets-mcp ${version}" \ - --arg body "$body" \ - '{tag_name: $tag, name: $name, body: $body, draft: true}') - - http_code=$(curl -sS -o /tmp/create-release.json -w '%{http_code}' \ - -H "Authorization: token $RELEASE_TOKEN" \ - -H "Content-Type: application/json" \ - -X POST "$release_api" \ - -d "$payload") - - if [ "$http_code" = "201" ] || [ "$http_code" = "200" ]; then - release_id=$(jq -r '.id // empty' /tmp/create-release.json) - fi - - if [ -n "$release_id" ]; then - echo "已创建草稿 Release: ${release_id}" - echo "release_id=${release_id}" >> "$GITHUB_OUTPUT" - else - echo "⚠ 创建 Release 失败 (HTTP ${http_code}),跳过产物上传" - cat /tmp/create-release.json 2>/dev/null || true - echo "release_id=" >> "$GITHUB_OUTPUT" - fi - - # check 与 build-linux 并行执行,互不阻塞 - check: - name: 质量检查 (fmt / clippy / test) - needs: [version] - runs-on: debian - timeout-minutes: 15 - steps: - - name: 安装 Rust + # ── Rust 工具链 ────────────────────────────────────────────────────── + - name: 安装 Rust 与 musl 工具链 run: | + sudo apt-get update -qq + sudo apt-get install -y -qq pkg-config musl-tools binutils jq if ! command -v rustup >/dev/null 2>&1; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "${RUST_TOOLCHAIN}" echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" fi source "$HOME/.cargo/env" 2>/dev/null || true - rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal --component rustfmt --component clippy + rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal \ + --component rustfmt --component clippy rustup default "${RUST_TOOLCHAIN}" - rustc -V - cargo -V - - - uses: actions/checkout@v4 - - - name: 缓存 Cargo - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry/index - ~/.cargo/registry/cache - ~/.cargo/git/db - target - key: cargo-check-${{ env.RUST_TOOLCHAIN }}-${{ hashFiles('Cargo.lock') }} - restore-keys: | - cargo-check-${{ env.RUST_TOOLCHAIN }}- - cargo-check- - - - run: cargo fmt -- --check - - run: cargo clippy --locked -- -D warnings - - run: cargo test --locked - - build-linux: - name: Build Linux (musl) - needs: [version] - runs-on: debian - timeout-minutes: 25 - steps: - - name: 安装依赖 - run: | - sudo apt-get update - sudo apt-get install -y pkg-config musl-tools binutils curl - if ! command -v rustup >/dev/null 2>&1; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "${RUST_TOOLCHAIN}" - echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - fi - source "$HOME/.cargo/env" 2>/dev/null || true - rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal - rustup default "${RUST_TOOLCHAIN}" - rustup target add ${{ env.MUSL_TARGET }} --toolchain "${RUST_TOOLCHAIN}" - rustc -V - cargo -V - - - uses: actions/checkout@v4 + rustup target add "${MUSL_TARGET}" --toolchain "${RUST_TOOLCHAIN}" + rustc -V && cargo -V - name: 缓存 Cargo uses: actions/cache@v4 @@ -210,10 +84,21 @@ jobs: cargo-${{ env.MUSL_TARGET }}-${{ env.RUST_TOOLCHAIN }}- cargo-${{ env.MUSL_TARGET }}- + # ── 质量检查(先于构建,失败即止)────────────────────────────────── + - name: fmt + run: cargo fmt -- --check + + - name: clippy + run: cargo clippy --locked -- -D warnings + + - name: test + run: cargo test --locked + + # ── 构建(质量检查通过后才执行)──────────────────────────────────── - name: 构建 secrets-mcp (musl) run: | - cargo build --release --locked --target ${{ env.MUSL_TARGET }} -p secrets-mcp - strip target/${{ env.MUSL_TARGET }}/release/${{ env.MCP_BINARY }} + cargo build --release --locked --target "${MUSL_TARGET}" -p secrets-mcp + strip "target/${MUSL_TARGET}/release/${MCP_BINARY}" - name: 上传构建产物 uses: actions/upload-artifact@v3 @@ -222,46 +107,89 @@ jobs: path: target/${{ env.MUSL_TARGET }}/release/${{ env.MCP_BINARY }} retention-days: 3 - - name: 上传 Release 产物 - if: needs.version.outputs.release_id != '' + # ── 创建 Tag(构建成功后才打,避免留下指向失败提交的 tag)────────── + - name: 创建 Tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "${{ steps.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}" + git push origin "${{ steps.ver.outputs.tag }}" + + # ── Release(可选,需配置 RELEASE_TOKEN)─────────────────────────── + - name: 创建并发布 Release + if: env.RELEASE_TOKEN != '' env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - [ -z "$RELEASE_TOKEN" ] && exit 0 - command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) + tag="${{ steps.ver.outputs.tag }}" + version="${{ steps.ver.outputs.version }}" + release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" - tag="${{ needs.version.outputs.tag }}" - bin="target/${{ env.MUSL_TARGET }}/release/${{ env.MCP_BINARY }}" - archive="${{ env.MCP_BINARY }}-${tag}-x86_64-linux-musl.tar.gz" + previous_tag=$(git tag --list 'secrets-mcp-*' --sort=-v:refname | awk -v t="$tag" '$0 != t { print; exit }') + if [ -n "$previous_tag" ]; then + changes=$(git log --pretty=format:'- %s (%h)' "${previous_tag}..HEAD") + else + changes=$(git log --pretty=format:'- %s (%h)') + fi + [ -z "$changes" ] && changes="- 首次发布" + body=$(printf '## 变更日志\n\n%s' "$changes") + + payload=$(jq -n \ + --arg tag "$tag" \ + --arg name "secrets-mcp ${version}" \ + --arg body "$body" \ + '{tag_name: $tag, name: $name, body: $body, draft: false}') + + http_code=$(curl -sS -o /tmp/release.json -w '%{http_code}' \ + -H "Authorization: token $RELEASE_TOKEN" \ + -H "Content-Type: application/json" \ + -X POST "$release_api" -d "$payload") + + if [ "$http_code" != "201" ] && [ "$http_code" != "200" ]; then + echo "创建 Release 失败 (HTTP ${http_code})" + cat /tmp/release.json || true + exit 1 + fi + release_id=$(jq -r '.id' /tmp/release.json) + + bin="target/${MUSL_TARGET}/release/${MCP_BINARY}" + archive="${MCP_BINARY}-${tag}-x86_64-linux-musl.tar.gz" tar -czf "$archive" -C "$(dirname "$bin")" "$(basename "$bin")" sha256sum "$archive" > "${archive}.sha256" - release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}" - release_url="${release_api}/assets" - curl -fsS -H "Authorization: token $RELEASE_TOKEN" \ - "$release_api" -o /tmp/release-assets.json - for asset_name in "$archive" "${archive}.sha256"; do - asset_ids=$(jq -r --arg name "$asset_name" '.assets[]? | select(.name == $name) | .id' /tmp/release-assets.json) - if [ -n "$asset_ids" ]; then - while IFS= read -r asset_id; do - [ -z "$asset_id" ] && continue - echo "删除已有产物: ${asset_name} (${asset_id})" - curl -fsS -X DELETE -H "Authorization: token $RELEASE_TOKEN" \ - "${release_url}/${asset_id}" - done <<< "$asset_ids" - fi + asset_url="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${release_id}/assets" + curl -fsS -H "Authorization: token $RELEASE_TOKEN" -F "attachment=@${archive}" "$asset_url" + curl -fsS -H "Authorization: token $RELEASE_TOKEN" -F "attachment=@${archive}.sha256" "$asset_url" + echo "Release ${tag} 已发布" - curl -fsS -H "Authorization: token $RELEASE_TOKEN" \ - -F "attachment=@${asset_name}" "$release_url" - done + # ── 飞书汇总通知 ───────────────────────────────────────────────────── + - name: 飞书通知 + if: always() + env: + WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} + run: | + [ -z "$WEBHOOK_URL" ] && exit 0 + tag="${{ steps.ver.outputs.tag }}" + commit="${{ github.event.head_commit.message }}" + [ -z "$commit" ] && commit="${{ github.sha }}" + url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}" + result="${{ job.status }}" + if [ "$result" = "success" ]; then icon="✅"; else icon="❌"; fi + msg="secrets-mcp 构建&发版 ${icon} + 版本:${tag} + 提交:${commit} + 作者:${{ github.actor }} + 详情:${url}" + payload=$(jq -n --arg text "$msg" '{msg_type: "text", content: {text: $text}}') + curl -sS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL" - deploy-mcp: + deploy: name: 部署 secrets-mcp - needs: [version, check, build-linux] + needs: [ci] if: | - needs.check.result == 'success' && - needs.build-linux.result == 'success' && - (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feat/mcp' || github.ref == 'refs/heads/mcp') + github.ref == 'refs/heads/main' || + github.ref == 'refs/heads/feat/mcp' || + github.ref == 'refs/heads/mcp' runs-on: debian timeout-minutes: 10 steps: @@ -278,16 +206,15 @@ jobs: DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} run: | if [ -z "$DEPLOY_HOST" ] || [ -z "$DEPLOY_USER" ] || [ -z "$DEPLOY_SSH_KEY" ]; then - echo "部署跳过:请在仓库 Actions 中配置 vars.DEPLOY_HOST、vars.DEPLOY_USER 与 secrets.DEPLOY_SSH_KEY" + echo "部署跳过:请配置 vars.DEPLOY_HOST、vars.DEPLOY_USER 与 secrets.DEPLOY_SSH_KEY" exit 0 fi echo "$DEPLOY_SSH_KEY" > /tmp/deploy_key chmod 600 /tmp/deploy_key - SCP="scp -i /tmp/deploy_key -o StrictHostKeyChecking=no" - - $SCP /tmp/artifact/${{ env.MCP_BINARY }} \ + scp -i /tmp/deploy_key -o StrictHostKeyChecking=no \ + "/tmp/artifact/${MCP_BINARY}" \ "${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/secrets-mcp.new" ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no "${DEPLOY_USER}@${DEPLOY_HOST}" " @@ -297,7 +224,6 @@ jobs: sleep 2 sudo systemctl is-active secrets-mcp && echo '服务启动成功' || (sudo journalctl -u secrets-mcp -n 20 && exit 1) " - rm -f /tmp/deploy_key - name: 飞书通知 @@ -306,94 +232,13 @@ jobs: WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} run: | [ -z "$WEBHOOK_URL" ] && exit 0 - command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) - tag="${{ needs.version.outputs.tag }}" + tag="${{ needs.ci.outputs.tag }}" url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}" result="${{ job.status }}" if [ "$result" = "success" ]; then icon="✅"; else icon="❌"; fi - msg="secrets-mcp 部署${icon} + msg="secrets-mcp 部署 ${icon} 版本:${tag} 作者:${{ github.actor }} 详情:${url}" payload=$(jq -n --arg text "$msg" '{msg_type: "text", content: {text: $text}}') curl -sS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL" - - publish-release: - name: 发布草稿 Release - needs: [version, check, build-linux] - if: always() && needs.version.outputs.release_id != '' - runs-on: debian - timeout-minutes: 5 - steps: - - name: 发布草稿 - env: - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - run: | - [ -z "$RELEASE_TOKEN" ] && exit 0 - - check_r="${{ needs.check.result }}" - linux_r="${{ needs.build-linux.result }}" - if [ "$check_r" != "success" ] || [ "$linux_r" != "success" ]; then - echo "质量检查或构建未成功(check=${check_r}, build=${linux_r}),保留草稿 Release" - exit 0 - fi - - release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}" - http_code=$(curl -sS -o /tmp/publish-release.json -w '%{http_code}' \ - -H "Authorization: token $RELEASE_TOKEN" \ - -H "Content-Type: application/json" \ - -X PATCH "$release_api" \ - -d '{"draft":false}') - - if [ "$http_code" != "200" ]; then - echo "发布草稿 Release 失败 (HTTP ${http_code})" - cat /tmp/publish-release.json 2>/dev/null || true - exit 1 - fi - echo "Release 已发布" - - - name: 飞书汇总通知 - if: always() - env: - WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} - run: | - [ -z "$WEBHOOK_URL" ] && exit 0 - command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) - - tag="${{ needs.version.outputs.tag }}" - tag_exists="${{ needs.version.outputs.tag_exists }}" - commit="${{ github.event.head_commit.message }}" - [ -z "$commit" ] && commit="${{ github.sha }}" - url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}" - - check_r="${{ needs.check.result }}" - linux_r="${{ needs.build-linux.result }}" - publish_r="${{ job.status }}" - - icon() { case "$1" in success) echo "✅";; skipped) echo "⏭";; *) echo "❌";; esac; } - - if [ "$check_r" = "success" ] && [ "$linux_r" = "success" ] && [ "$publish_r" = "success" ]; then - status="发布成功 ✅" - elif [ "$check_r" != "success" ]; then - status="检查失败 ❌" - elif [ "$linux_r" != "success" ]; then - status="构建失败 ❌" - else - status="发布失败 ❌" - fi - - if [ "$tag_exists" = "false" ]; then - version_line="🆕 新版本 ${tag}" - else - version_line="🔄 重复构建 ${tag}" - fi - - msg="secrets-mcp ${status} - ${version_line} - check $(icon "$check_r") | linux $(icon "$linux_r") | Release $(icon "$publish_r") - 提交:${commit} - 作者:${{ github.actor }} - 详情:${url}" - - payload=$(jq -n --arg text "$msg" '{msg_type: "text", content: {text: $text}}') - curl -sS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL"