# MCP 分支:仅构建/发布 secrets-mcp(CLI 在 main 分支维护) name: Secrets MCP — Build & Release on: push: paths: - 'crates/**' - 'Cargo.toml' - 'Cargo.lock' - 'deploy/**' - '.gitea/workflows/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: write env: MCP_BINARY: secrets-mcp RUST_TOOLCHAIN: 1.94.0 CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 CARGO_TERM_COLOR: always RUST_BACKTRACE: short MUSL_TARGET: x86_64-unknown-linux-musl jobs: ci: name: 检查 / 构建 / 发版 runs-on: debian timeout-minutes: 40 outputs: tag: ${{ steps.ver.outputs.tag }} 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}" echo "version=${version}" >> "$GITHUB_OUTPUT" echo "tag=${tag}" >> "$GITHUB_OUTPUT" if git rev-parse "refs/tags/${tag}" >/dev/null 2>&1; then echo "⚠ 版本 ${tag} 已存在,将覆盖重新发版。" echo "tag_exists=true" >> "$GITHUB_OUTPUT" else echo "将创建新版本 ${tag}" echo "tag_exists=false" >> "$GITHUB_OUTPUT" fi # ── 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 default "${RUST_TOOLCHAIN}" rustup target add "${MUSL_TARGET}" --toolchain "${RUST_TOOLCHAIN}" rustc -V && cargo -V - name: 缓存 Cargo uses: actions/cache@v4 with: path: | ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db target key: cargo-${{ env.MUSL_TARGET }}-${{ env.RUST_TOOLCHAIN }}-${{ hashFiles('Cargo.lock') }} restore-keys: | 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 "${MUSL_TARGET}" -p secrets-mcp strip "target/${MUSL_TARGET}/release/${MCP_BINARY}" - name: 上传构建产物 uses: actions/upload-artifact@v3 with: name: ${{ env.MCP_BINARY }}-linux-musl path: target/${{ env.MUSL_TARGET }}/release/${{ env.MCP_BINARY }} retention-days: 3 # ── 创建 / 覆盖 Tag(构建成功后才打)─────────────────────────────── - name: 创建 Tag run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" tag="${{ steps.ver.outputs.tag }}" if [ "${{ steps.ver.outputs.tag_exists }}" = "true" ]; then git tag -d "$tag" 2>/dev/null || true git push origin ":refs/tags/$tag" 2>/dev/null || true fi git tag -a "$tag" -m "Release $tag" git push origin "$tag" # ── Release(可选,需配置 RELEASE_TOKEN)─────────────────────────── - name: 创建并发布 Release if: env.RELEASE_TOKEN != '' env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | tag="${{ steps.ver.outputs.tag }}" version="${{ steps.ver.outputs.version }}" release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" 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}') # 若已存在同名 Release 则先删除(覆盖重发) existing_code=$(curl -sS -o /tmp/existing-release.json -w '%{http_code}' \ -H "Authorization: token $RELEASE_TOKEN" \ "${release_api}/tags/${tag}") if [ "$existing_code" = "200" ]; then old_id=$(jq -r '.id // empty' /tmp/existing-release.json) if [ -n "$old_id" ]; then curl -sS -o /dev/null -H "Authorization: token $RELEASE_TOKEN" \ -X DELETE "${release_api}/${old_id}" echo "已删除旧 Release: ${old_id}" fi fi 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" 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} 已发布" # ── 飞书汇总通知 ───────────────────────────────────────────────────── - 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: name: 部署 secrets-mcp needs: [ci] if: | github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feat/mcp' || github.ref == 'refs/heads/mcp' runs-on: debian timeout-minutes: 10 steps: - name: 下载构建产物 uses: actions/download-artifact@v3 with: name: ${{ env.MCP_BINARY }}-linux-musl path: /tmp/artifact - name: 部署到阿里云 ECS env: DEPLOY_HOST: ${{ vars.DEPLOY_HOST }} DEPLOY_USER: ${{ vars.DEPLOY_USER }} DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} run: | if [ -z "$DEPLOY_HOST" ] || [ -z "$DEPLOY_USER" ] || [ -z "$DEPLOY_SSH_KEY" ]; then 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 -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}" " sudo mv /tmp/secrets-mcp.new /opt/secrets-mcp/secrets-mcp sudo chmod +x /opt/secrets-mcp/secrets-mcp sudo systemctl restart secrets-mcp sleep 2 sudo systemctl is-active secrets-mcp && echo '服务启动成功' || (sudo journalctl -u secrets-mcp -n 20 && exit 1) " rm -f /tmp/deploy_key - name: 飞书通知 if: always() env: WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} run: | [ -z "$WEBHOOK_URL" ] && exit 0 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} 版本:${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"