- 解析版本时不再 exit 1,改为记录 tag_exists=true 并打印警告 - 创建 Tag 步骤:若 tag 已存在则先本地删除再远端删除,再重新打带注释的 tag - 创建 Release 步骤:先查询同名 Release,若存在则 DELETE 旧 Release,再 POST 新建 Made-with: Cursor
264 lines
11 KiB
YAML
264 lines
11 KiB
YAML
# 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"
|