♻️ refactor(ci): 重构为 Prepare 上游 + workflow_run 下游消费者模型

- 新增 prepare.yml:统一检查/安装公共工具并集中 fetch 到共享 bare 仓库
- 统计与发布改为消费成功的 Prepare workflow_run,各自维护 stats/release worktree
- 四个 workflow name 统一为带 emoji 的中文,同步 workflow_run 触发引用
- 系统信息 workflow 保持独立直接触发
- 删除 tests/ 手动验证脚本
This commit is contained in:
csh
2026-07-20 16:21:06 +08:00
parent 610c2b3e74
commit 50efc4bbfa
10 changed files with 528 additions and 977 deletions
+185 -95
View File
@@ -1,9 +1,10 @@
name: 📦 自动发布
on:
push:
tags:
- "[0-9]*"
workflow_run:
workflows: ["🧰 准备环境"]
types:
- completed
concurrency:
group: release-${{ github.repository }}
@@ -16,7 +17,7 @@ env:
# ===== Token 配置 =====
# Gitea/GitHub 访问令牌
# 用途:
# 1. Git 操作:克隆仓库、推送提交
# 1. Git 操作:推送提交(仓库读取由 Prepare 完成)
# 2. API 调用:创建 Release、上传附件
# 权限要求:
# - repo (仓库完整访问权限)
@@ -26,7 +27,7 @@ env:
#
# 💡 提示:同一个 token 可用于所有操作
# 如需分离权限,可配置两个不同的 token:
# - GIT_TOKEN: 用于 git clone/push
# - GIT_TOKEN: 用于 git push
# - API_TOKEN: 用于 API 调用
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
GIT_CONFIG_COUNT: "3"
@@ -38,7 +39,7 @@ env:
GIT_CONFIG_VALUE_2: "never"
# ===== 工作区配置 =====
WORKSPACE_DIR: "/data/workspace"
WORKSPACE_ROOT: "/data/workspace"
WORKSPACE_SLOT: "release"
# ===== 分支配置 =====
@@ -134,10 +135,100 @@ env:
# 4. CHANGELOG内容是叠加而非覆盖
jobs:
route-release:
if: ${{ github.event.workflow_run.conclusion == 'success' && contains(github.event.workflow_run.path, '@refs/tags/') }}
runs-on: standard-ubuntu-22
permissions:
contents: read
outputs:
should_release: ${{ steps.route.outputs.should_release }}
version: ${{ steps.route.outputs.version }}
tag_ref: ${{ steps.route.outputs.tag_ref }}
source_sha: ${{ steps.route.outputs.source_sha }}
env:
UPSTREAM_PATH: ${{ github.event.workflow_run.path }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
steps:
- name: 🧭 恢复并验证 Tag
id: route
shell: bash
run: |
set -euo pipefail
if ! command -v git >/dev/null 2>&1; then
echo "Prepare 成功后仍缺少发布路由所需命令: git" >&2
exit 1
fi
echo "should_release=false" >> "$GITHUB_OUTPUT"
case "$HEAD_SHA" in
''|*[!0-9a-fA-F]*)
echo "无效的上游提交 SHA: $HEAD_SHA" >&2
exit 1
;;
esac
FULL_REF="${UPSTREAM_PATH##*@}"
case "$FULL_REF" in
refs/tags/*) ;;
*)
echo "workflow_run.path 未包含有效 Tag ref: $UPSTREAM_PATH" >&2
exit 1
;;
esac
VERSION="${FULL_REF#refs/tags/}"
case "$VERSION" in
[0-9]*) ;;
*)
echo "跳过非版本 Tag: $VERSION"
exit 0
;;
esac
if ! git check-ref-format "$FULL_REF"; then
echo "数字开头的 Tag ref 格式无效: $FULL_REF" >&2
exit 1
fi
REPO_NAME="${{ github.event.repository.name }}"
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
if [ ! -d "$REPOSITORY_DIR" ] || \
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
exit 1
fi
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2
exit 1
fi
if ! tag_sha=$(git --git-dir="$REPOSITORY_DIR" rev-parse "${FULL_REF}^{commit}" 2>/dev/null); then
echo "共享仓库中不存在可解析的 Tag: $FULL_REF" >&2
exit 1
fi
if [ "$tag_sha" != "$HEAD_SHA" ]; then
echo "Tag/SHA 不匹配: $FULL_REF -> $tag_sha,上游为 $HEAD_SHA" >&2
exit 1
fi
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag_ref=$FULL_REF" >> "$GITHUB_OUTPUT"
echo "source_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "发布路由通过: $VERSION @ $HEAD_SHA"
changelog-and-release:
runs-on: ubuntu-22.04
needs: route-release
if: ${{ needs.route-release.outputs.should_release == 'true' }}
runs-on: standard-ubuntu-22
permissions:
contents: write
env:
VERSION: ${{ needs.route-release.outputs.version }}
TAG_REF: ${{ needs.route-release.outputs.tag_ref }}
SOURCE_SHA: ${{ needs.route-release.outputs.source_sha }}
steps:
- name: ⚙️ 加载配置
@@ -148,7 +239,7 @@ jobs:
echo "======================================"
# 从环境变量读取配置
VERSION="${{ github.ref_name }}"
VERSION="${{ env.VERSION }}"
KEYWORDS="${{ env.PRERELEASE_KEYWORDS }}"
STRIP_REGEX="-($KEYWORDS)[0-9]*$" # 用于 sed 删除后缀
@@ -235,78 +326,91 @@ jobs:
echo "======================================"
echo ""
- name: 📥 准备隔离仓库
id: clone
- name: 📥 对齐发布 Worktree
id: prepare_worktree
shell: bash
run: |
set -euo pipefail
echo "======================================"
echo "🚀 开始准备仓库"
echo "======================================"
REPO_SLUG="${{ github.repository }}"
for command in git python3 curl flock realpath; do
if ! command -v "$command" >/dev/null 2>&1; then
echo "Prepare 成功后仍缺少发布所需命令: $command" >&2
exit 1
fi
done
REPO_NAME="${{ github.event.repository.name }}"
HEAD_SHA="${{ env.SOURCE_SHA }}"
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
WORKSPACE_DIR="${{ env.WORKSPACE_DIR }}"
WORKSPACE_SLOT="${{ env.WORKSPACE_SLOT }}"
REPO_DIR="${WORKSPACE_DIR}/${REPO_NAME}/${WORKSPACE_SLOT}"
REMOTE_URL="${GITHUB_SERVER_URL}/${REPO_SLUG}.git"
REMOTE_REF="refs/remotes/origin/${MAIN_BRANCH}"
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/release"
WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock"
MAIN_REMOTE_REF="refs/remotes/origin/${MAIN_BRANCH}"
if [ -z "$MAIN_BRANCH" ]; then
echo "❌ 错误: 未配置主分支"
exit 1
fi
case "$REMOTE_URL" in
https://*) ;;
*)
echo "❌ 拒绝通过非 HTTPS remote 使用 WORKFLOW token" >&2
case "$HEAD_SHA" in
''|*[!0-9a-fA-F]*)
echo "无效的路由提交 SHA: $HEAD_SHA" >&2
exit 1
;;
esac
if [ -e "$REPO_DIR" ] && [ ! -d "$REPO_DIR/.git" ]; then
echo "❌ Slot 已存在但不是 Git 仓库: $REPO_DIR" >&2
if [ -z "$MAIN_BRANCH" ]; then
echo "未配置 MAIN_BRANCH" >&2
exit 1
fi
if [ ! -d "$REPOSITORY_DIR" ] || \
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
exit 1
fi
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
echo "共享仓库中不存在发布提交: $HEAD_SHA" >&2
exit 1
fi
if ! git --git-dir="$REPOSITORY_DIR" show-ref --verify --quiet "$MAIN_REMOTE_REF"; then
echo "Prepare 未 fetch 配置的主分支: $MAIN_REMOTE_REF" >&2
exit 1
fi
echo "📁 仓库名称: $REPO_NAME"
echo "📦 工作区根目录: $WORKSPACE_DIR"
echo "🎯 Workflow 职责: $WORKSPACE_SLOT"
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
echo ""
if [ ! -f "$WORKTREE_DIR/.git" ]; then
exec 9>"$WORKTREE_LOCK"
flock 9
if [ ! -d "$REPO_DIR/.git" ]; then
mkdir -p "$(dirname "$REPO_DIR")"
git clone --no-checkout "$REMOTE_URL" "$REPO_DIR"
else
git -C "$REPO_DIR" remote set-url origin "$REMOTE_URL"
if [ ! -f "$WORKTREE_DIR/.git" ]; then
if [ -e "$WORKTREE_DIR" ]; then
echo "发布 Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2
exit 1
fi
mkdir -p "$(dirname "$WORKTREE_DIR")"
git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"
fi
flock -u 9
fi
git -C "$REPO_DIR" fetch \
--prune --prune-tags --tags --force origin \
'+refs/heads/*:refs/remotes/origin/*'
if ! git -C "$REPO_DIR" show-ref --verify --quiet "$REMOTE_REF"; then
echo "❌ 配置的远程分支不存在: $MAIN_BRANCH" >&2
common_dir=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir)
if [ "$common_dir" != "$(realpath "$REPOSITORY_DIR")" ]; then
echo "发布 Worktree 不属于共享仓库: $WORKTREE_DIR" >&2
exit 1
fi
git -C "$REPO_DIR" checkout --force -B "$MAIN_BRANCH" "$REMOTE_REF"
git -C "$REPO_DIR" reset --hard "$REMOTE_REF"
git -C "$REPO_DIR" clean -ffdx
echo "✓ 仓库目录: $REPO_DIR"
echo ""
git -C "$WORKTREE_DIR" checkout --detach --force "$HEAD_SHA"
git -C "$WORKTREE_DIR" reset --hard "$HEAD_SHA"
git -C "$WORKTREE_DIR" clean -ffdx
cd "$REPO_DIR"
echo "✓ 主分支 '$MAIN_BRANCH' 已准备并清理"
actual_sha=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
if [ "$actual_sha" != "$HEAD_SHA" ]; then
echo "发布 Worktree 提交不匹配: 期望 $HEAD_SHA,实际 $actual_sha" >&2
exit 1
fi
# 导出到环境变量供后续步骤使用
echo "REPO_DIR=$REPO_DIR" >> "$GITHUB_ENV"
git -C "$WORKTREE_DIR" config user.name "${{ env.GIT_USER_NAME }}"
git -C "$WORKTREE_DIR" config user.email "${{ env.GIT_USER_EMAIL }}"
echo "REPOSITORY_DIR=$REPOSITORY_DIR" >> "$GITHUB_ENV"
echo "REPO_DIR=$WORKTREE_DIR" >> "$GITHUB_ENV"
echo "REPO_NAME=$REPO_NAME" >> "$GITHUB_ENV"
echo "WORKSPACE_SLOT=$WORKSPACE_SLOT" >> "$GITHUB_ENV"
echo "MAIN_BRANCH=$MAIN_BRANCH" >> "$GITHUB_ENV"
echo ""
echo "✅ 仓库准备完成"
echo "======================================"
echo ""
echo "MAIN_REMOTE_REF=$MAIN_REMOTE_REF" >> "$GITHUB_ENV"
echo "发布 Worktree: $WORKTREE_DIR @ $actual_sha"
- name: 🤖 检查 Bot 提交
id: check_bot
@@ -317,7 +421,7 @@ jobs:
cd ${{ env.REPO_DIR }}
COMMIT_MSG=$(git log -1 --pretty=%B ${{ github.ref_name }})
COMMIT_MSG=$(git log -1 --pretty=%B "${{ env.SOURCE_SHA }}")
echo "📝 Tag commit message:"
echo "$COMMIT_MSG"
echo ""
@@ -344,7 +448,7 @@ jobs:
cd ${{ env.REPO_DIR }}
VERSION="${{ github.ref_name }}"
VERSION="${{ env.VERSION }}"
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
echo "🏷️ Tag: $VERSION"
@@ -607,8 +711,12 @@ jobs:
echo ""
echo "💾 写入 CHANGELOG.md..."
# 切换到主分支
git checkout ${{ env.MAIN_BRANCH }}
# 从 Prepare 已 fetch 的远端引用准备主分支,再写入 CHANGELOG
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
MAIN_REMOTE_REF="${{ env.MAIN_REMOTE_REF }}"
git -C "${{ env.REPO_DIR }}" checkout --force -B "$MAIN_BRANCH" "$MAIN_REMOTE_REF"
git -C "${{ env.REPO_DIR }}" reset --hard "$MAIN_REMOTE_REF"
git -C "${{ env.REPO_DIR }}" clean -ffdx
# ============================================
# 更新或创建 CHANGELOG.md
@@ -819,9 +927,6 @@ jobs:
cd ${{ env.REPO_DIR }}
# 确保使用最新的 CHANGELOG
git -C "${{ env.REPO_DIR }}" pull --ff-only origin "${{ env.MAIN_BRANCH }}"
python3 << 'PYSCRIPT'
import re
@@ -871,41 +976,26 @@ jobs:
echo "📄 Draft: ${{ env.RELEASE_IS_DRAFT }}"
echo ""
# 转换 bash 布尔值为 Python 布尔值
if [ "${{ env.RELEASE_IS_DRAFT }}" = "true" ]; then
PY_DRAFT="True"
else
PY_DRAFT="False"
fi
if [ "${{ env.RELEASE_IS_PRERELEASE }}" = "true" ]; then
PY_PRERELEASE="True"
else
PY_PRERELEASE="False"
fi
echo "🔄 转换布尔值: draft=${{ env.RELEASE_IS_DRAFT }} → $PY_DRAFT, prerelease=${{ env.RELEASE_IS_PRERELEASE }} → $PY_PRERELEASE"
echo ""
python3 << EOF
python3 << 'PYSCRIPT'
import json
import os
with open('/tmp/release-body.txt', 'r') as f:
body = f.read()
with open('/tmp/release-body.txt', 'r', encoding='utf-8') as release_body_file:
body = release_body_file.read()
payload = {
"tag_name": "${{ github.ref_name }}",
"name": "${{ env.RELEASE_TITLE_PROCESSED }}",
"tag_name": os.environ["VERSION"],
"name": os.environ["RELEASE_TITLE_PROCESSED"],
"body": body,
"draft": $PY_DRAFT,
"prerelease": $PY_PRERELEASE
"draft": os.environ["RELEASE_IS_DRAFT"] == "true",
"prerelease": os.environ["RELEASE_IS_PRERELEASE"] == "true",
}
with open('/tmp/payload.json', 'w', encoding='utf-8') as f:
json.dump(payload, f, ensure_ascii=False, indent=2)
with open('/tmp/payload.json', 'w', encoding='utf-8') as payload_file:
json.dump(payload, payload_file, ensure_ascii=False, indent=2)
print("✓ Payload prepared")
EOF
PYSCRIPT
echo "🌐 发送 API 请求..."
HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" -X POST \
@@ -1010,7 +1100,7 @@ jobs:
| 项目 | 结果 |
|------|------|
| 🏷️ Tag 版本 | **`${{ github.ref_name }}`** |
| 🏷️ Tag 版本 | **`${{ env.VERSION }}`** |
| 📝 CHANGELOG 版本 | **`${{ env.CHANGELOG_VERSION }}`** |
| 🌿 主分支 | **`${{ env.MAIN_BRANCH }}`** |
| 🌐 Gitea 服务器 | **`${{ env.GITEA_SERVER }}`** |
@@ -1113,7 +1203,7 @@ jobs:
echo " 原因: 检测到 [skip ci] 标记,防止无限循环"
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
echo "📊 执行结果:"
echo " - Tag: ${{ github.ref_name }}"
echo " - Tag: ${{ env.VERSION }}"
echo " - CHANGELOG 版本: ${{ env.CHANGELOG_VERSION }}"
echo " - 主分支: ${{ env.MAIN_BRANCH }}"
echo " - Release 标题: ${{ env.RELEASE_TITLE_PROCESSED }}"
+176
View File
@@ -0,0 +1,176 @@
name: 🧰 准备环境
on:
push:
workflow_dispatch:
concurrency:
group: prepare-${{ github.repository }}
cancel-in-progress: false
env:
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
GIT_CONFIG_COUNT: "3"
GIT_CONFIG_KEY_0: "credential.helper"
GIT_CONFIG_VALUE_0: ""
GIT_CONFIG_KEY_1: '!f() { test "$1" = get || exit 0; printf "%s\n" username=oauth2; printf "%s\n" "password=${ACCESS_TOKEN}"; }; f'
GIT_CONFIG_KEY_2: "credential.interactive"
GIT_CONFIG_VALUE_2: "never"
WORKSPACE_ROOT: "/data/workspace"
jobs:
prepare:
runs-on: standard-ubuntu-22
permissions:
contents: read
steps:
- name: 🔧 检查并安装共享工具
id: tools
shell: bash
run: |
set -euo pipefail
declare -A package_for=(
[git]="git"
[python3]="python3"
[curl]="curl"
[jq]="jq"
[bc]="bc"
[flock]="util-linux"
[realpath]="coreutils"
)
required_commands=(git python3 curl jq bc flock realpath)
missing_commands=()
missing_packages=()
declare -A seen_packages=()
for command in "${required_commands[@]}"; do
if ! command -v "$command" >/dev/null 2>&1; then
package="${package_for[$command]}"
missing_commands+=("$command")
if [[ -z "${seen_packages[$package]+x}" ]]; then
missing_packages+=("$package")
seen_packages[$package]=1
fi
fi
done
installed=false
if [ "${#missing_packages[@]}" -gt 0 ]; then
echo "缺失命令: ${missing_commands[*]}"
echo "准备安装: ${missing_packages[*]}"
if [ "$(id -u)" -eq 0 ]; then
APT=(apt-get)
elif command -v sudo >/dev/null 2>&1; then
APT=(sudo apt-get)
else
echo "无法安装缺失命令;当前用户不是 root 且没有 sudo" >&2
echo "命令: ${missing_commands[*]}" >&2
echo "包: ${missing_packages[*]}" >&2
exit 1
fi
"${APT[@]}" update -qq
"${APT[@]}" install -y -qq "${missing_packages[@]}"
installed=true
fi
for command in "${required_commands[@]}"; do
if ! command -v "$command" >/dev/null 2>&1; then
echo "安装后仍找不到命令: $command (${package_for[$command]})" >&2
exit 1
fi
done
{
echo "## Prepare 环境"
echo ""
echo "| 命令 | 版本 |"
echo "| --- | --- |"
for command in "${required_commands[@]}"; do
version_output=$("$command" --version 2>&1 || true)
version_line=${version_output%%$'\n'*}
version_line=${version_line//|/\\|}
printf '| `%s` | `%s` |\n' "$command" "$version_line"
done
echo ""
if [ "$installed" = true ]; then
echo "安装的包:\`${missing_packages[*]}\`"
else
echo "安装的包:无,所有命令已存在。"
fi
} >> "$GITHUB_STEP_SUMMARY"
echo "installed=$installed" >> "$GITHUB_OUTPUT"
- name: 📥 准备共享 bare 仓库
shell: bash
run: |
set -euo pipefail
REPO_NAME="${{ github.event.repository.name }}"
REMOTE_URL="${{ github.server_url }}/${{ github.repository }}.git"
HEAD_SHA="${{ github.sha }}"
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
case "$HEAD_SHA" in
''|*[!0-9a-fA-F]*)
echo "无效的触发提交 SHA: $HEAD_SHA" >&2
exit 1
;;
esac
case "$REMOTE_URL" in
https://*) ;;
*)
echo "拒绝通过非 HTTPS remote 使用 WORKFLOW token: $REMOTE_URL" >&2
exit 1
;;
esac
if [ -z "$ACCESS_TOKEN" ]; then
echo "未配置 WORKFLOW secret,无法读取仓库" >&2
exit 1
fi
mkdir -p "$(dirname "$REPOSITORY_DIR")"
if [ ! -e "$REPOSITORY_DIR" ]; then
git init --bare "$REPOSITORY_DIR"
else
if [ ! -d "$REPOSITORY_DIR" ]; then
echo "共享仓库路径存在但不是目录: $REPOSITORY_DIR" >&2
exit 1
fi
if ! is_bare=$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null) || \
[ "$is_bare" != "true" ]; then
echo "共享仓库路径不是有效 bare 仓库: $REPOSITORY_DIR" >&2
exit 1
fi
fi
if git --git-dir="$REPOSITORY_DIR" remote get-url origin >/dev/null 2>&1; then
git --git-dir="$REPOSITORY_DIR" remote set-url origin "$REMOTE_URL"
else
git --git-dir="$REPOSITORY_DIR" remote add origin "$REMOTE_URL"
fi
git --git-dir="$REPOSITORY_DIR" fetch \
--prune --prune-tags --tags --force origin \
'+refs/heads/*:refs/remotes/origin/*'
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
echo "共享仓库中不存在触发提交: $HEAD_SHA" >&2
exit 1
fi
resolved_sha=$(git --git-dir="$REPOSITORY_DIR" rev-parse "${HEAD_SHA}^{commit}")
{
echo ""
echo "## Prepare 仓库"
echo ""
echo "- bare 仓库:\`$REPOSITORY_DIR\`"
echo "- 触发提交:\`$resolved_sha\`"
echo "- 远端:\`$REMOTE_URL\`"
} >> "$GITHUB_STEP_SUMMARY"
echo "Prepare 完成: $REPOSITORY_DIR @ $resolved_sha"
+24 -24
View File
@@ -1,4 +1,4 @@
name: Ubuntu System Information
name: 🖥️ Ubuntu 系统信息
on:
push:
@@ -18,75 +18,75 @@ jobs:
echo "====================================="
echo ""
- name: 🖥 显示系统信息
- name: 显示系统信息
run: |
echo "===== 系统版本 ====="
echo "===== 🐧 系统版本 ====="
cat /etc/os-release
echo ""
echo "===== 内核信息 ====="
echo "===== 🧩 内核信息 ====="
uname -a
echo ""
echo "===== 主机名 ====="
echo "===== 🏷️ 主机名 ====="
hostname
echo ""
echo "===== 系统启动时间 ====="
echo "===== ⏱️ 系统启动时间 ====="
uptime
- name: 🧮 显示 CPU 信息
- name: ⚙️ 显示 CPU 信息
run: |
echo "===== CPU 详细信息 ====="
echo "===== ⚙️ CPU 详细信息 ====="
lscpu
echo ""
echo "===== CPU 型号 ====="
echo "===== 🏷️ CPU 型号 ====="
cat /proc/cpuinfo | grep "model name" | head -1
echo ""
echo "===== CPU 核心数 ====="
echo "===== 🔢 CPU 核心数 ====="
nproc
- name: 🧠 显示内存信息
run: |
echo "===== 内存使用情况 ====="
echo "===== 📊 内存使用情况 ====="
free -h
echo ""
echo "===== 内存详细信息 ====="
echo "===== 🧠 内存详细信息 ====="
cat /proc/meminfo | grep -E 'MemTotal|MemFree|MemAvailable'
- name: 💾 显示磁盘信息
run: |
echo "===== 磁盘使用情况 ====="
echo "===== 📊 磁盘使用情况 ====="
df -h
echo ""
echo "===== 磁盘分区信息 ====="
echo "===== 🗂️ 磁盘分区信息 ====="
lsblk
- name: 🌐 显示网络信息
run: |
echo "===== 网络接口 ====="
echo "===== 🔌 网络接口 ====="
cat /proc/net/dev
echo ""
echo "===== 路由表 ====="
echo "===== 🧭 路由表 ====="
cat /proc/net/route
echo ""
echo "===== DNS 配置 ====="
echo "===== 📡 DNS 配置 ====="
cat /etc/resolv.conf
- name: 📦 显示已安装的软件
run: |
echo "===== Python 版本 ====="
echo "===== 🐍 Python 版本 ====="
python3 --version
echo ""
echo "===== Node.js 版本 ====="
echo "===== 🟢 Node.js 版本 ====="
node --version || echo "Node.js 未安装"
echo ""
echo "===== Docker 版本 ====="
echo "===== 🐳 Docker 版本 ====="
docker --version || echo "Docker 未安装"
echo ""
echo "===== Git 版本 ====="
echo "===== 🔀 Git 版本 ====="
git --version
- name: 🔧 显示环境变量
run: |
echo "===== 重要环境变量 ====="
echo "===== 🔧 重要环境变量 ====="
echo "PATH: $PATH"
echo "HOME: $HOME"
echo "USER: $USER"
@@ -95,8 +95,8 @@ jobs:
- name: 📂 显示当前目录
run: |
echo "===== 当前工作目录 ====="
echo "===== 📍 当前工作目录 ====="
pwd
echo ""
echo "===== 目录内容 ====="
echo "===== 📄 目录内容 ====="
ls -lah
@@ -1,9 +1,10 @@
name: 📊 代码统计徽章
on:
push:
branches: [main, master]
workflow_dispatch:
workflow_run:
workflows: ["🧰 准备环境"]
types:
- completed
concurrency:
group: stats-${{ github.repository }}
@@ -25,8 +26,10 @@ env:
GIT_CONFIG_VALUE_2: "never"
# ===== 工作区配置 =====
WORKSPACE_DIR: "/data/workspace"
WORKSPACE_ROOT: "/data/workspace"
WORKSPACE_SLOT: "stats"
SOURCE_BRANCH: ${{ github.event.workflow_run.head_branch }}
SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
# ===== 分支配置 =====
# 徽章数据存储分支(可配置)
@@ -78,7 +81,8 @@ env:
jobs:
update-stats:
runs-on: ubuntu-22.04
if: ${{ github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'master') }}
runs-on: standard-ubuntu-22
permissions:
contents: write
@@ -98,195 +102,105 @@ jobs:
echo "token_type=WORKFLOW" >> $GITHUB_OUTPUT
echo "🔗 仓库: ${{ github.repository }}"
echo "🌿 分支: ${{ github.ref_name }}"
echo "📝 提交: ${GITHUB_SHA:0:7}"
echo "🌿 上游分支: ${{ env.SOURCE_BRANCH }}"
echo "📝 上游提交: ${{ env.SOURCE_SHA }}"
- name: 🔧 检查必需工具
id: check_deps
run: |
echo "======================================"
echo "🔍 检查依赖工具"
echo "======================================"
MISSING_TOOLS=()
# 检查 bc(用于数字计算)
if ! command -v bc &> /dev/null; then
echo "⚠️ bc 未安装"
MISSING_TOOLS+=("bc")
else
echo "✓ bc 已安装"
fi
# 检查 jq(用于 JSON 处理)
if ! command -v jq &> /dev/null; then
echo "⚠️ jq 未安装"
MISSING_TOOLS+=("jq")
else
echo "✓ jq 已安装"
fi
# 如果有缺失的工具,安装它们
if [ ${#MISSING_TOOLS[@]} -gt 0 ]; then
echo ""
echo "📦 安装缺失的工具: ${MISSING_TOOLS[*]}"
# 🔧 智能判断是否需要 sudo
# Docker 环境通常以 root 运行,不需要 sudo
if [ "$EUID" -eq 0 ] || [ "$(id -u)" -eq 0 ]; then
# 当前是 root 用户,直接执行
echo "🔑 检测到 root 权限,直接安装"
apt-get update -qq
apt-get install -y -qq "${MISSING_TOOLS[@]}"
elif command -v sudo &> /dev/null; then
# 有 sudo 命令,使用 sudo
echo "🔑 使用 sudo 安装"
sudo apt-get update -qq
sudo apt-get install -y -qq "${MISSING_TOOLS[@]}"
else
# 既不是 root 也没有 sudo
echo "❌ 错误:没有足够权限安装工具"
echo "请在 Docker 镜像中预装以下工具:"
echo " ${MISSING_TOOLS[*]}"
echo ""
echo "方案1: 使用包含这些工具的镜像"
echo "方案2: 在 Dockerfile 中添加:"
echo " RUN apt-get update && apt-get install -y bc jq"
exit 1
fi
echo "installed=true" >> $GITHUB_OUTPUT
echo "✅ 工具安装完成"
else
echo "installed=false" >> $GITHUB_OUTPUT
echo "✅ 所有工具已就绪"
fi
echo "======================================"
echo ""
- name: 📥 准备隔离仓库
id: clone_main
- name: 📥 对齐统计 Worktree
id: prepare_worktree
shell: bash
run: |
set -euo pipefail
echo "======================================"
echo "🚀 开始准备主仓库"
echo "======================================"
REPO_SLUG="${{ github.repository }}"
for command in git python3 bc flock realpath; do
if ! command -v "$command" >/dev/null 2>&1; then
echo "Prepare 成功后仍缺少统计所需命令: $command" >&2
exit 1
fi
done
REPO_NAME="${{ github.event.repository.name }}"
TARGET_BRANCH="${{ github.ref_name }}"
WORKSPACE_DIR="${{ env.WORKSPACE_DIR }}"
WORKSPACE_SLOT="${{ env.WORKSPACE_SLOT }}"
REPO_DIR="${WORKSPACE_DIR}/${REPO_NAME}/${WORKSPACE_SLOT}"
REMOTE_URL="${GITHUB_SERVER_URL}/${REPO_SLUG}.git"
REMOTE_REF="refs/remotes/origin/${TARGET_BRANCH}"
HEAD_SHA="${{ env.SOURCE_SHA }}"
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/stats"
WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock"
case "$REMOTE_URL" in
https://*) ;;
*)
echo "❌ 拒绝通过非 HTTPS remote 使用 WORKFLOW token" >&2
case "$HEAD_SHA" in
''|*[!0-9a-fA-F]*)
echo "无效的上游提交 SHA: $HEAD_SHA" >&2
exit 1
;;
esac
if [ -e "$REPO_DIR" ] && [ ! -d "$REPO_DIR/.git" ]; then
echo "❌ Slot 已存在但不是 Git 仓库: $REPO_DIR" >&2
if [ ! -d "$REPOSITORY_DIR" ] || \
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
exit 1
fi
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2
exit 1
fi
echo "📁 仓库名称: $REPO_NAME"
echo "📦 工作区根目录: $WORKSPACE_DIR"
echo "🎯 Workflow 职责: $WORKSPACE_SLOT"
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
echo "🌿 分支: $TARGET_BRANCH"
echo ""
if [ ! -f "$WORKTREE_DIR/.git" ]; then
exec 9>"$WORKTREE_LOCK"
flock 9
if [ ! -d "$REPO_DIR/.git" ]; then
mkdir -p "$(dirname "$REPO_DIR")"
git clone --no-checkout "$REMOTE_URL" "$REPO_DIR"
else
git -C "$REPO_DIR" remote set-url origin "$REMOTE_URL"
if [ ! -f "$WORKTREE_DIR/.git" ]; then
if [ -e "$WORKTREE_DIR" ]; then
echo "统计 Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2
exit 1
fi
mkdir -p "$(dirname "$WORKTREE_DIR")"
git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"
fi
flock -u 9
fi
git -C "$REPO_DIR" fetch \
--prune --prune-tags --tags --force origin \
'+refs/heads/*:refs/remotes/origin/*'
if ! git -C "$REPO_DIR" show-ref --verify --quiet "$REMOTE_REF"; then
echo "❌ 触发分支不存在: $TARGET_BRANCH" >&2
common_dir=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir)
if [ "$common_dir" != "$(realpath "$REPOSITORY_DIR")" ]; then
echo "统计 Worktree 不属于共享仓库: $WORKTREE_DIR" >&2
exit 1
fi
git -C "$REPO_DIR" checkout --force -B "$TARGET_BRANCH" "$REMOTE_REF"
git -C "$REPO_DIR" reset --hard "$REMOTE_REF"
git -C "$REPO_DIR" clean -ffdx
echo "✓ 仓库目录: $REPO_DIR"
echo ""
git -C "$WORKTREE_DIR" checkout --detach --force "$HEAD_SHA"
git -C "$WORKTREE_DIR" reset --hard "$HEAD_SHA"
git -C "$WORKTREE_DIR" clean -ffdx
cd "$REPO_DIR"
actual_sha=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
if [ "$actual_sha" != "$HEAD_SHA" ]; then
echo "统计 Worktree 提交不匹配: 期望 $HEAD_SHA,实际 $actual_sha" >&2
exit 1
fi
echo "✅ 仓库准备成功"
echo ""
git -C "$WORKTREE_DIR" config user.name "${{ env.GIT_USER_NAME }}"
git -C "$WORKTREE_DIR" config user.email "${{ env.GIT_USER_EMAIL }}"
# 配置 Git 用户信息
echo "⚙️ 配置 Git 用户信息..."
git config user.name "${{ env.GIT_USER_NAME }}"
git config user.email "${{ env.GIT_USER_EMAIL }}"
# 显示仓库信息
echo "📊 仓库信息:"
echo " - 当前分支: $(git branch --show-current)"
echo " - 最新提交: $(git log -1 --oneline)"
echo " - 远程地址: $(git remote get-url origin | sed 's/oauth2:.*@/oauth2:***@/')"
echo ""
# 导出环境变量
echo "REPO_DIR=$REPO_DIR" >> "$GITHUB_ENV"
echo "REPOSITORY_DIR=$REPOSITORY_DIR" >> "$GITHUB_ENV"
echo "REPO_DIR=$WORKTREE_DIR" >> "$GITHUB_ENV"
echo "REPO_NAME=$REPO_NAME" >> "$GITHUB_ENV"
echo "WORKSPACE_SLOT=$WORKSPACE_SLOT" >> "$GITHUB_ENV"
echo "✅ 主仓库准备完成"
echo "======================================"
echo ""
echo "统计 Worktree: $WORKTREE_DIR @ $actual_sha"
- name: 🌿 准备统计分支
id: prepare_stats_branch
run: |
echo "======================================"
echo "🌿 准备统计分支"
echo "======================================"
set -euo pipefail
cd "${{ env.REPO_DIR }}"
REPOSITORY_DIR="${{ env.REPOSITORY_DIR }}"
WORKTREE_DIR="${{ env.REPO_DIR }}"
BADGE_BRANCH="${{ env.BADGE_BRANCH }}"
SOURCE_SHA="${{ env.SOURCE_SHA }}"
# 获取所有远程分支
git -C "${{ env.REPO_DIR }}" fetch origin --prune
# 检查统计分支是否存在
if git -C "${{ env.REPO_DIR }}" ls-remote --heads origin "${{ env.BADGE_BRANCH }}" | \
grep -q "refs/heads/${{ env.BADGE_BRANCH }}"; then
echo "✅ 统计分支 '${{ env.BADGE_BRANCH }}' 已存在"
# 检出统计分支
git -C "${{ env.REPO_DIR }}" fetch origin \
"+refs/heads/${{ env.BADGE_BRANCH }}:refs/remotes/origin/${{ env.BADGE_BRANCH }}"
git checkout --force -B "${{ env.BADGE_BRANCH }}" "refs/remotes/origin/${{ env.BADGE_BRANCH }}"
echo "📂 当前分支内容:"
ls -la
# 返回主分支
git checkout ${{ github.ref_name }}
echo "branch_exists=true" >> $GITHUB_OUTPUT
if git --git-dir="$REPOSITORY_DIR" show-ref --verify --quiet "refs/remotes/origin/${BADGE_BRANCH}"; then
git -C "$WORKTREE_DIR" checkout --force -B "$BADGE_BRANCH" "refs/remotes/origin/${BADGE_BRANCH}"
echo "branch_exists=true" >> "$GITHUB_OUTPUT"
else
echo "🆕 统计分支 '${{ env.BADGE_BRANCH }}' 不存在,将创建"
if git show-ref --verify --quiet "refs/heads/${{ env.BADGE_BRANCH }}"; then
echo "🧹 删除上次失败运行遗留的本地统计分支"
git branch -D "${{ env.BADGE_BRANCH }}"
if git --git-dir="$REPOSITORY_DIR" show-ref --verify --quiet "refs/heads/${BADGE_BRANCH}"; then
git -C "$WORKTREE_DIR" branch -D "$BADGE_BRANCH"
fi
echo "branch_exists=false" >> $GITHUB_OUTPUT
echo "branch_exists=false" >> "$GITHUB_OUTPUT"
fi
echo "======================================"
echo ""
git -C "$WORKTREE_DIR" checkout --detach --force "$SOURCE_SHA"
- name: 🌱 创建统计分支
if: steps.prepare_stats_branch.outputs.branch_exists == 'false'
@@ -296,26 +210,12 @@ jobs:
echo "======================================"
cd "${{ env.REPO_DIR }}"
# 🔴 安全检查:确认在正确的目录
echo "🔍 当前目录: $(pwd)"
echo "🔍 .git 状态: $([ -d .git ] && echo '✓ 存在' || echo '✗ 不存在')"
if [ ! -d .git ]; then
echo "❌ 错误:.git 目录不存在,无法创建分支"
exit 1
fi
git rev-parse --is-inside-work-tree >/dev/null
# 创建孤立分支(orphan branch
# 📝 说明:孤立分支与主分支没有共同的提交历史,适合存储独立的数据
echo "🌱 创建孤立分支..."
git checkout --orphan ${{ env.BADGE_BRANCH }}
# 🔴 再次确认 .git 仍然存在
if [ ! -d .git ]; then
echo "❌ 严重错误:.git 目录丢失!"
exit 1
fi
git checkout --orphan "${{ env.BADGE_BRANCH }}"
# 🧹 清空所有文件(但保留 .git 目录)
# ❓ 为什么要删除?
@@ -336,16 +236,12 @@ jobs:
# 步骤 2: 安全地删除剩余文件
# 使用更严格的保护,确保不会误删 .git
echo "🔍 查找需要删除的文件..."
find . -maxdepth 1 -type f ! -name '.gitignore' -delete 2>/dev/null || true
find . -maxdepth 1 -type d ! -name '.' ! -name '..' ! -name '.git' ! -name '.gitea' -exec rm -rf {} + 2>/dev/null || true
find . -maxdepth 1 -type f ! -name '.git' ! -name '.gitignore' -delete 2>/dev/null || true
find . -maxdepth 1 -type d ! -name '.' ! -name '..' ! -name '.gitea' -exec rm -rf {} + 2>/dev/null || true
# 🔴 最终检查:确认 .git 仍然完好
echo "🔍 最终检查..."
if [ ! -d .git ]; then
echo "❌ 致命错误:.git 目录在清理过程中被删除!"
echo "这不应该发生,请检查 find 命令"
exit 1
fi
git rev-parse --is-inside-work-tree >/dev/null
echo "✓ .git 目录完好"
echo "✓ 文件清理完成"
echo ""
@@ -371,8 +267,8 @@ jobs:
## 🔄 更新机制
- 每次 push 到主分支时自动更新
- ✅ 可通过 workflow_dispatch 手动触发
- 主分支的 Prepare 成功后自动更新
- ✅ 可在 main/master 上手动运行 Prepare
## 📊 数据来源
@@ -384,7 +280,7 @@ jobs:
EOF
# 创建徽章目录
mkdir -p ${{ env.BADGE_DIR }}
mkdir -p "${{ env.BADGE_DIR }}"
# 添加并提交
git add .
@@ -397,10 +293,9 @@ jobs:
echo "======================================"
echo ""
# 🔴 重要:返回主分支,后续统计需要在主分支进行
echo "🔄 切换回主分支: ${{ github.ref_name }}"
git checkout ${{ github.ref_name }}
echo "✓ 已切换回主分支"
echo "🔄 返回上游提交: ${{ env.SOURCE_SHA }}"
git checkout --detach --force "${{ env.SOURCE_SHA }}"
echo "✓ 已返回上游提交"
echo ""
- name: 📊 统计总代码量
@@ -412,16 +307,11 @@ jobs:
cd "${{ env.REPO_DIR }}"
# 🔴 重要:确保在主分支进行统计(不要统计 stats 分支)
echo "🔍 确认当前分支..."
CURRENT_BRANCH=$(git branch --show-current)
if [ "$CURRENT_BRANCH" != "${{ github.ref_name }}" ]; then
echo "⚠️ 当前在分支: $CURRENT_BRANCH,切换到主分支: ${{ github.ref_name }}"
git checkout ${{ github.ref_name }}
else
echo "✓ 已在主分支: $CURRENT_BRANCH"
ACTUAL_SHA=$(git rev-parse HEAD)
if [ "$ACTUAL_SHA" != "${{ env.SOURCE_SHA }}" ]; then
echo "源码 Worktree 不在上游提交: 期望 ${{ env.SOURCE_SHA }},实际 $ACTUAL_SHA" >&2
exit 1
fi
echo ""
# 构建排除参数
EXCLUDE_FIND_ARGS=()
@@ -465,16 +355,11 @@ jobs:
cd "${{ env.REPO_DIR }}"
# 🔴 重要:确保在主分支进行统计
echo "🔍 确认当前分支..."
CURRENT_BRANCH=$(git branch --show-current)
if [ "$CURRENT_BRANCH" != "${{ github.ref_name }}" ]; then
echo "⚠️ 当前在分支: $CURRENT_BRANCH,切换到主分支: ${{ github.ref_name }}"
git checkout ${{ github.ref_name }}
else
echo "✓ 已在主分支: $CURRENT_BRANCH"
ACTUAL_SHA=$(git rev-parse HEAD)
if [ "$ACTUAL_SHA" != "${{ env.SOURCE_SHA }}" ]; then
echo "源码 Worktree 不在上游提交: 期望 ${{ env.SOURCE_SHA }},实际 $ACTUAL_SHA" >&2
exit 1
fi
echo ""
# 创建临时目录
mkdir -p /tmp/lang_stats
@@ -555,8 +440,8 @@ jobs:
cd "${{ env.REPO_DIR }}"
# 在主分支渲染产物,避免依赖 stats 分支中的脚本文件
git checkout ${{ github.ref_name }}
# 在上游提交渲染产物,避免依赖 stats 分支中的脚本文件
git checkout --detach --force "${{ env.SOURCE_SHA }}"
RENDER_ROOT=$(mktemp -d)
UPDATED_AT=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
@@ -579,7 +464,7 @@ jobs:
--min-lines-threshold "${{ env.MIN_LINES_THRESHOLD }}" \
--lang-summary /tmp/lang_summary.txt
git checkout ${{ env.BADGE_BRANCH }}
git checkout --force "${{ env.BADGE_BRANCH }}"
rm -rf "${{ env.BADGE_DIR }}"
mkdir -p "${{ env.BADGE_DIR }}"
cp -f "$RENDER_ROOT/README.md" README.md
@@ -629,7 +514,7 @@ jobs:
- 语言数: ${{ steps.languages.outputs.language_count }} 种
- 徽章数: ${{ steps.generate_badges.outputs.generated_count }} 个
🔗 触发提交: ${GITHUB_SHA:0:7}
🔗 触发提交: ${{ env.SOURCE_SHA }}
🤖 由 Gitea Actions 自动生成"
git commit -m "$COMMIT_MSG"
@@ -660,7 +545,8 @@ jobs:
| 项目 | 结果 |
|------|------|
| 🔐 Token 类型 | **${{ steps.validate_token.outputs.token_type }}** |
| 🔧 依赖检查 | ${{ steps.check_deps.outputs.installed == 'true' && '✅ 已安装缺失工具' || '✅ 所有工具就绪' }} |
| 🌿 来源分支 | `${{ env.SOURCE_BRANCH }}` |
| 📝 来源提交 | `${{ env.SOURCE_SHA }}` |
| 📊 总代码行数 | **${{ steps.total.outputs.formatted_code }}** 行 |
| 📁 总文件数 | **${{ steps.total.outputs.formatted_files }}** 个 |
| 🌐 语言种类 | **${{ steps.languages.outputs.language_count }}** 种 |
@@ -671,7 +557,7 @@ jobs:
- 📊 [查看详细统计报告](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/README.md)
- 🎨 [浏览徽章文件](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }})
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ github.ref_name }}/.gitea/workflows/update_stats_badge.yaml)
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.SOURCE_BRANCH }}/.gitea/workflows/update_stats_badge.yml)
## 📝 语言分布
@@ -758,12 +644,6 @@ jobs:
echo ""
echo "================================================"
# 如果有警告或特殊情况,也输出
if [ "${{ steps.check_deps.outputs.installed }}" == "true" ]; then
echo ""
echo "ℹ️ 本次运行安装了缺失的依赖工具"
fi
if [ "${{ steps.languages.outputs.language_count }}" == "0" ]; then
echo ""
echo "⚠️ 警告: 未找到符合条件的语言文件"