Compare commits
6 Commits
34480c16e2
...
61e512c2ae
| Author | SHA1 | Date |
|---|---|---|
|
|
61e512c2ae | |
|
|
95402f4830 | |
|
|
124b953d38 | |
|
|
2b5eaf27c9 | |
|
|
a4d09c9063 | |
|
|
ac05621817 |
|
|
@ -0,0 +1,215 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
workspace_fail() {
|
||||||
|
echo "bootstrap_workspace.sh: $*" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
split_repository_slug() {
|
||||||
|
local repo_slug=$1
|
||||||
|
local owner repo_name
|
||||||
|
|
||||||
|
if [[ "${repo_slug}" != */* ]]; then
|
||||||
|
workspace_fail "repository slug must look like owner/repo: ${repo_slug}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
owner=${repo_slug%%/*}
|
||||||
|
repo_name=${repo_slug#*/}
|
||||||
|
|
||||||
|
printf '%s\n%s\n' "${owner}" "${repo_name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitize_job_name() {
|
||||||
|
local value=${1:-job}
|
||||||
|
|
||||||
|
value=$(printf '%s' "${value}" | tr ' /:@' '-----')
|
||||||
|
value=$(printf '%s' "${value}" | tr -cd '[:alnum:]._-')
|
||||||
|
value=$(printf '%s' "${value}" | sed -E 's/-+/-/g; s/^-//; s/-$//')
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
value="job"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_job_identity() {
|
||||||
|
local run_id=${1:-0}
|
||||||
|
local run_attempt=${2:-1}
|
||||||
|
local job_name=${3:-job}
|
||||||
|
|
||||||
|
printf '%s-%s-%s\n' "${run_id}" "${run_attempt}" "${job_name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_mirror_path() {
|
||||||
|
local mirror_root=$1
|
||||||
|
local owner=$2
|
||||||
|
local repo_name=$3
|
||||||
|
|
||||||
|
printf '%s/%s/%s.git\n' "${mirror_root}" "${owner}" "${repo_name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_mirror_lock_path() {
|
||||||
|
local mirror_root=$1
|
||||||
|
local owner=$2
|
||||||
|
local repo_name=$3
|
||||||
|
|
||||||
|
printf '%s.lock\n' "$(build_mirror_path "${mirror_root}" "${owner}" "${repo_name}")"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_job_workspace_root() {
|
||||||
|
local workspace_root=$1
|
||||||
|
local owner=$2
|
||||||
|
local repo_name=$3
|
||||||
|
local job_identity=$4
|
||||||
|
|
||||||
|
printf '%s/%s/%s/%s\n' "${workspace_root}" "${owner}" "${repo_name}" "${job_identity}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_job_repo_dir() {
|
||||||
|
local workspace_root=$1
|
||||||
|
local owner=$2
|
||||||
|
local repo_name=$3
|
||||||
|
local job_identity=$4
|
||||||
|
|
||||||
|
printf '%s/repo\n' "$(build_job_workspace_root "${workspace_root}" "${owner}" "${repo_name}" "${job_identity}")"
|
||||||
|
}
|
||||||
|
|
||||||
|
with_repo_lock() {
|
||||||
|
local lock_path=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "${lock_path}")"
|
||||||
|
|
||||||
|
if ! command -v flock >/dev/null 2>&1; then
|
||||||
|
workspace_fail "flock is required for mirror synchronization"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec {lock_fd}>"${lock_path}"
|
||||||
|
flock "${lock_fd}"
|
||||||
|
"$@"
|
||||||
|
local status=$?
|
||||||
|
flock -u "${lock_fd}"
|
||||||
|
eval "exec ${lock_fd}>&-"
|
||||||
|
|
||||||
|
return "${status}"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_bare_mirror_unlocked() {
|
||||||
|
local remote_url=$1
|
||||||
|
local mirror_path=$2
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "${mirror_path}")"
|
||||||
|
|
||||||
|
if [ -d "${mirror_path}" ]; then
|
||||||
|
git -C "${mirror_path}" remote set-url origin "${remote_url}"
|
||||||
|
git -C "${mirror_path}" fetch --prune --prune-tags --tags origin
|
||||||
|
else
|
||||||
|
git clone --mirror "${remote_url}" "${mirror_path}" >/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_bare_mirror() {
|
||||||
|
local remote_url=$1
|
||||||
|
local mirror_path=$2
|
||||||
|
local lock_path
|
||||||
|
|
||||||
|
lock_path="${mirror_path}.lock"
|
||||||
|
with_repo_lock "${lock_path}" ensure_bare_mirror_unlocked "${remote_url}" "${mirror_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_job_workspace_from_mirror() {
|
||||||
|
local remote_url=$1
|
||||||
|
local mirror_path=$2
|
||||||
|
local job_workspace=$3
|
||||||
|
local repo_dir=$4
|
||||||
|
|
||||||
|
rm -rf "${job_workspace}"
|
||||||
|
mkdir -p "${job_workspace}"
|
||||||
|
git clone --local "${mirror_path}" "${repo_dir}" >/dev/null
|
||||||
|
git -C "${repo_dir}" remote set-url origin "${remote_url}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_job_workspace() {
|
||||||
|
local job_workspace=$1
|
||||||
|
|
||||||
|
if [ -z "${job_workspace}" ] || [ "${job_workspace}" = "/" ]; then
|
||||||
|
workspace_fail "refusing to remove invalid workspace path: ${job_workspace}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "${job_workspace}"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_job_workspace() {
|
||||||
|
local repo_slug=$1
|
||||||
|
local remote_url=$2
|
||||||
|
local mirror_root=$3
|
||||||
|
local workspace_root=$4
|
||||||
|
local run_id=$5
|
||||||
|
local run_attempt=${6:-1}
|
||||||
|
local job_name=${7:-job}
|
||||||
|
local owner repo_name safe_job_name job_identity mirror_path job_workspace repo_dir
|
||||||
|
local slug_parts
|
||||||
|
|
||||||
|
mapfile -t slug_parts < <(split_repository_slug "${repo_slug}")
|
||||||
|
owner=${slug_parts[0]}
|
||||||
|
repo_name=${slug_parts[1]}
|
||||||
|
safe_job_name=$(sanitize_job_name "${job_name}")
|
||||||
|
job_identity=$(build_job_identity "${run_id}" "${run_attempt}" "${safe_job_name}")
|
||||||
|
mirror_path=$(build_mirror_path "${mirror_root}" "${owner}" "${repo_name}")
|
||||||
|
job_workspace=$(build_job_workspace_root "${workspace_root}" "${owner}" "${repo_name}" "${job_identity}")
|
||||||
|
repo_dir=$(build_job_repo_dir "${workspace_root}" "${owner}" "${repo_name}" "${job_identity}")
|
||||||
|
|
||||||
|
ensure_bare_mirror "${remote_url}" "${mirror_path}"
|
||||||
|
create_job_workspace_from_mirror "${remote_url}" "${mirror_path}" "${job_workspace}" "${repo_dir}"
|
||||||
|
|
||||||
|
printf 'REPO_OWNER=%s\n' "${owner}"
|
||||||
|
printf 'REPO_NAME=%s\n' "${repo_name}"
|
||||||
|
printf 'JOB_IDENTITY=%s\n' "${job_identity}"
|
||||||
|
printf 'MIRROR_PATH=%s\n' "${mirror_path}"
|
||||||
|
printf 'JOB_WORKSPACE=%s\n' "${job_workspace}"
|
||||||
|
printf 'REPO_DIR=%s\n' "${repo_dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
bootstrap_workspace.sh prepare-job-workspace <repo-slug> <remote-url> <mirror-root> <workspace-root> <run-id> <run-attempt> <job-name>
|
||||||
|
bootstrap_workspace.sh cleanup-job-workspace <job-workspace>
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local command=${1:-}
|
||||||
|
|
||||||
|
case "${command}" in
|
||||||
|
prepare-job-workspace)
|
||||||
|
[ $# -eq 8 ] || {
|
||||||
|
print_usage
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
shift
|
||||||
|
prepare_job_workspace "$@"
|
||||||
|
;;
|
||||||
|
cleanup-job-workspace)
|
||||||
|
[ $# -eq 2 ] || {
|
||||||
|
print_usage
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
shift
|
||||||
|
cleanup_job_workspace "$1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_usage
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
set -euo pipefail
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
|
|
@ -31,8 +31,10 @@ env:
|
||||||
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
||||||
|
|
||||||
# ===== 工作区配置 =====
|
# ===== 工作区配置 =====
|
||||||
# 完整克隆的工作目录 - 自建的runner可以复用工作区
|
# 持久化 mirror 缓存目录
|
||||||
WORKSPACE_DIR: "/home/workspace"
|
MIRROR_ROOT: "/data/git-mirrors"
|
||||||
|
# 每个 job 的独立临时工作目录根路径
|
||||||
|
JOB_WORKSPACE_ROOT: "/home/workspace/jobs"
|
||||||
|
|
||||||
# ===== 分支配置 =====
|
# ===== 分支配置 =====
|
||||||
# 主分支名称(用于推送 CHANGELOG 更新)
|
# 主分支名称(用于推送 CHANGELOG 更新)
|
||||||
|
|
@ -41,7 +43,7 @@ env:
|
||||||
|
|
||||||
# ===== 服务器配置 =====
|
# ===== 服务器配置 =====
|
||||||
# Gitea 服务器地址(用于生成头像链接和 API 调用)
|
# Gitea 服务器地址(用于生成头像链接和 API 调用)
|
||||||
GITEA_SERVER: "https://git.mytsl.cn"
|
GITEA_SERVER: "${{ github.server_url }}"
|
||||||
|
|
||||||
# ===== CHANGELOG 配置 =====
|
# ===== CHANGELOG 配置 =====
|
||||||
# CHANGELOG 变更列表标题
|
# CHANGELOG 变更列表标题
|
||||||
|
|
@ -228,87 +230,64 @@ jobs:
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
- name: 📥 克隆仓库
|
- name: 📥 准备隔离仓库
|
||||||
id: clone
|
id: clone
|
||||||
run: |
|
run: |
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo "🚀 开始准备仓库"
|
echo "🚀 开始准备仓库"
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|
||||||
|
REPO_SLUG="${{ github.repository }}"
|
||||||
REPO_NAME="${{ github.event.repository.name }}"
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
|
||||||
|
RUN_ATTEMPT="${{ github.run_attempt }}"
|
||||||
|
JOB_NAME="${{ github.job }}"
|
||||||
|
SERVER_HOST="${GITHUB_SERVER_URL#http://}"
|
||||||
|
SERVER_HOST="${SERVER_HOST#https://}"
|
||||||
|
REMOTE_URL="https://oauth2:${{ env.ACCESS_TOKEN }}@${SERVER_HOST}/${REPO_SLUG}.git"
|
||||||
|
BOOTSTRAP_SCRIPT="/tmp/bootstrap_workspace.sh"
|
||||||
|
BOOTSTRAP_URL="${GITHUB_SERVER_URL}/api/v1/repos/${REPO_SLUG}/media/.gitea/ci/bootstrap_workspace.sh?ref=${GITHUB_SHA}"
|
||||||
|
PREPARED_ENV=$(mktemp)
|
||||||
|
|
||||||
|
if [ -z "$RUN_ATTEMPT" ]; then
|
||||||
|
RUN_ATTEMPT="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JOB_NAME" ]; then
|
||||||
|
JOB_NAME="job"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "📁 仓库名称: $REPO_NAME"
|
echo "📁 仓库名称: $REPO_NAME"
|
||||||
echo "📍 目标目录: $REPO_DIR"
|
echo "🪞 Mirror 根目录: ${{ env.MIRROR_ROOT }}"
|
||||||
|
echo "📦 Job 工作区根目录: ${{ env.JOB_WORKSPACE_ROOT }}"
|
||||||
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
|
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 检查仓库状态
|
curl -fsSL \
|
||||||
if [ -d "$REPO_DIR" ]; then
|
-H "Authorization: token ${{ env.ACCESS_TOKEN }}" \
|
||||||
echo "📂 目录已存在,检查 Git 仓库状态..."
|
"$BOOTSTRAP_URL" \
|
||||||
|
-o "$BOOTSTRAP_SCRIPT"
|
||||||
|
chmod +x "$BOOTSTRAP_SCRIPT"
|
||||||
|
|
||||||
if [ -d "$REPO_DIR/.git" ]; then
|
bash "$BOOTSTRAP_SCRIPT" prepare-job-workspace \
|
||||||
# 目录存在且是有效的 Git 仓库
|
"$REPO_SLUG" \
|
||||||
echo "✓ 发现有效的 Git 仓库,执行增量更新..."
|
"$REMOTE_URL" \
|
||||||
cd "$REPO_DIR"
|
"${{ env.MIRROR_ROOT }}" \
|
||||||
|
"${{ env.JOB_WORKSPACE_ROOT }}" \
|
||||||
|
"${{ github.run_id }}" \
|
||||||
|
"$RUN_ATTEMPT" \
|
||||||
|
"$JOB_NAME" > "$PREPARED_ENV"
|
||||||
|
|
||||||
# 清理工作区
|
# shellcheck source=/dev/null
|
||||||
git clean -fdx
|
source "$PREPARED_ENV"
|
||||||
git reset --hard
|
rm -f "$PREPARED_ENV"
|
||||||
|
|
||||||
# 获取最新代码和标签,同时清理远程已删除的 tag
|
echo "✓ Mirror 路径: $MIRROR_PATH"
|
||||||
echo "📥 拉取最新代码和标签..."
|
echo "✓ Job 工作区: $JOB_WORKSPACE"
|
||||||
git fetch --all --tags --force --prune --prune-tags
|
echo "✓ 仓库目录: $REPO_DIR"
|
||||||
echo "✓ 已同步远程状态(包括已删除的 tag)"
|
echo ""
|
||||||
|
|
||||||
echo "✓ 仓库已更新"
|
|
||||||
else
|
|
||||||
# 目录存在但不是 Git 仓库(可能之前运行失败)
|
|
||||||
echo "⚠️ 目录存在但不是有效的 Git 仓库"
|
|
||||||
echo "🧹 清理损坏的目录..."
|
|
||||||
rm -rf "$REPO_DIR"
|
|
||||||
echo "✓ 已清理"
|
|
||||||
|
|
||||||
# 重新克隆
|
|
||||||
echo "📥 克隆仓库..."
|
|
||||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
|
|
||||||
"$REPO_DIR"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ 克隆失败"
|
|
||||||
cat /tmp/git_clone.log
|
|
||||||
|
|
||||||
# 清理残留
|
|
||||||
if [ -d "$REPO_DIR" ]; then
|
|
||||||
rm -rf "$REPO_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$REPO_DIR"
|
cd "$REPO_DIR"
|
||||||
echo "✓ 仓库已克隆"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# 目录不存在,首次克隆
|
|
||||||
echo "📥 克隆仓库(首次)..."
|
|
||||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
|
|
||||||
"$REPO_DIR"
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
echo "✓ 仓库已克隆"
|
|
||||||
else
|
|
||||||
echo "❌ 克隆失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🔍 验证主分支配置..."
|
echo "🔍 验证主分支配置..."
|
||||||
|
|
@ -325,7 +304,6 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
|
|
||||||
echo "✓ 使用配置的主分支: $MAIN_BRANCH"
|
echo "✓ 使用配置的主分支: $MAIN_BRANCH"
|
||||||
|
|
||||||
# 验证分支是否存在
|
# 验证分支是否存在
|
||||||
|
|
@ -351,7 +329,10 @@ jobs:
|
||||||
# 导出到环境变量供后续步骤使用
|
# 导出到环境变量供后续步骤使用
|
||||||
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||||
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
|
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
|
||||||
|
echo "JOB_WORKSPACE=$JOB_WORKSPACE" >> $GITHUB_ENV
|
||||||
|
echo "MIRROR_PATH=$MIRROR_PATH" >> $GITHUB_ENV
|
||||||
echo "MAIN_BRANCH=$MAIN_BRANCH" >> $GITHUB_ENV
|
echo "MAIN_BRANCH=$MAIN_BRANCH" >> $GITHUB_ENV
|
||||||
|
echo "BOOTSTRAP_SCRIPT=$BOOTSTRAP_SCRIPT" >> $GITHUB_ENV
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ 仓库准备完成"
|
echo "✅ 仓库准备完成"
|
||||||
|
|
@ -1078,16 +1059,6 @@ jobs:
|
||||||
|
|
||||||
检测到此提交由 Bot 创建(包含 `[skip ci]` 标记),为防止无限循环,已跳过执行。
|
检测到此提交由 Bot 创建(包含 `[skip ci]` 标记),为防止无限循环,已跳过执行。
|
||||||
EOFBOT
|
EOFBOT
|
||||||
elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFEXIST'
|
|
||||||
| 📋 执行状态 | ⏭️ 已跳过 (版本已存在) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
⏭️ **工作流已跳过**
|
|
||||||
|
|
||||||
版本 `${{ env.CHANGELOG_VERSION }}` 已存在于 CHANGELOG.md 中。
|
|
||||||
EOFEXIST
|
|
||||||
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
||||||
if [ "${{ steps.changelog.outputs.content_changed }}" = "true" ]; then
|
if [ "${{ steps.changelog.outputs.content_changed }}" = "true" ]; then
|
||||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUCCESS'
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUCCESS'
|
||||||
|
|
@ -1145,7 +1116,7 @@ jobs:
|
||||||
|
|
||||||
- 📝 [查看 CHANGELOG](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.MAIN_BRANCH }}/CHANGELOG.md)
|
- 📝 [查看 CHANGELOG](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.MAIN_BRANCH }}/CHANGELOG.md)
|
||||||
- 🚀 [查看 Releases](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases)
|
- 🚀 [查看 Releases](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases)
|
||||||
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${{ env.MAIN_BRANCH }}/.github/workflows/changelog_and_release.yml)
|
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.MAIN_BRANCH }}/.gitea/workflows/changelog_and_release.yml)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -1175,9 +1146,6 @@ jobs:
|
||||||
if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then
|
if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then
|
||||||
echo "⏭️ 已跳过: Bot 提交检测"
|
echo "⏭️ 已跳过: Bot 提交检测"
|
||||||
echo " 原因: 检测到 [skip ci] 标记,防止无限循环"
|
echo " 原因: 检测到 [skip ci] 标记,防止无限循环"
|
||||||
elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then
|
|
||||||
echo "⏭️ 已跳过: 版本已存在"
|
|
||||||
echo " 版本: ${{ env.CHANGELOG_VERSION }}"
|
|
||||||
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
||||||
echo "📊 执行结果:"
|
echo "📊 执行结果:"
|
||||||
echo " - Tag: ${{ github.ref_name }}"
|
echo " - Tag: ${{ github.ref_name }}"
|
||||||
|
|
@ -1223,4 +1191,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "🧹 清理临时文件..."
|
echo "🧹 清理临时文件..."
|
||||||
rm -rf /tmp/commits.txt /tmp/changelog_updated.txt /tmp/content_changed.txt /tmp/release-body.txt /tmp/payload.json /tmp/release_response.json /tmp/upload_response_*.json
|
rm -rf /tmp/commits.txt /tmp/changelog_updated.txt /tmp/content_changed.txt /tmp/release-body.txt /tmp/payload.json /tmp/release_response.json /tmp/upload_response_*.json
|
||||||
|
if [ -n "${{ env.JOB_WORKSPACE }}" ] && [ "${{ env.JOB_WORKSPACE }}" != "/" ]; then
|
||||||
|
echo "🧹 清理 Job 工作区: ${{ env.JOB_WORKSPACE }}"
|
||||||
|
rm -rf "${{ env.JOB_WORKSPACE }}"
|
||||||
|
fi
|
||||||
echo "✅ 清理完成"
|
echo "✅ 清理完成"
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,14 @@ on:
|
||||||
# ==========================================
|
# ==========================================
|
||||||
env:
|
env:
|
||||||
# ===== Token 配置 =====
|
# ===== Token 配置 =====
|
||||||
# 建议在 Settings -> Secrets 中配置 STATS_TOKEN 以获得更好的权限控制
|
# 请在 Settings -> Secrets 中配置 WORKFLOW secret
|
||||||
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
||||||
|
|
||||||
# ===== 工作区配置 =====
|
# ===== 工作区配置 =====
|
||||||
# 完整克隆的工作目录
|
# 持久化 mirror 缓存目录
|
||||||
WORKSPACE_DIR: "/home/workspace"
|
MIRROR_ROOT: "/data/git-mirrors"
|
||||||
|
# 每个 job 的独立临时工作目录根路径
|
||||||
|
JOB_WORKSPACE_ROOT: "/home/workspace/jobs"
|
||||||
|
|
||||||
# ===== 分支配置 =====
|
# ===== 分支配置 =====
|
||||||
# 徽章数据存储分支(可配置)
|
# 徽章数据存储分支(可配置)
|
||||||
|
|
@ -57,13 +59,13 @@ env:
|
||||||
# 平台类型: github 或 gitea
|
# 平台类型: github 或 gitea
|
||||||
PLATFORM: "gitea"
|
PLATFORM: "gitea"
|
||||||
# Git 服务器 URL(Gitea 示例: https://gitea.example.com)
|
# Git 服务器 URL(Gitea 示例: https://gitea.example.com)
|
||||||
GIT_SERVER_URL: "https://git.mytsl.cn"
|
GIT_SERVER_URL: "${{ github.server_url }}"
|
||||||
# 仓库路径(格式: owner/repo)
|
# 仓库路径(格式: owner/repo)
|
||||||
REPO_PATH: "${{ github.repository }}"
|
REPO_PATH: "${{ github.repository }}"
|
||||||
# Raw 文件基础 URL
|
# Raw 文件基础 URL
|
||||||
# GitHub: https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}
|
# GitHub: https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}
|
||||||
# Gitea: https://gitea.example.com/{owner}/{repo}/raw/branch/{branch}/{path}
|
# Gitea: https://gitea.example.com/{owner}/{repo}/raw/branch/{branch}/{path}
|
||||||
RAW_URL_BASE: 'https://git.mytsl.cn/${{ github.repository }}/raw/branch'
|
RAW_URL_BASE: '${{ github.server_url }}/${{ github.repository }}/raw/branch'
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# 🎨 语言分组配置
|
# 🎨 语言分组配置
|
||||||
|
|
@ -96,18 +98,12 @@ jobs:
|
||||||
|
|
||||||
if [ -z "${{ env.ACCESS_TOKEN }}" ]; then
|
if [ -z "${{ env.ACCESS_TOKEN }}" ]; then
|
||||||
echo "❌ 错误: 未配置访问令牌"
|
echo "❌ 错误: 未配置访问令牌"
|
||||||
echo "请在 Settings -> Secrets 中配置 STATS_TOKEN 或确保 GITHUB_TOKEN 可用"
|
echo "请在 Settings -> Secrets 中配置 WORKFLOW secret"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检测使用的是哪个 token
|
echo "✅ 使用 WORKFLOW secret"
|
||||||
if [ -n "${{ secrets.STATS_TOKEN }}" ]; then
|
echo "token_type=WORKFLOW" >> $GITHUB_OUTPUT
|
||||||
echo "✅ 使用自定义 STATS_TOKEN"
|
|
||||||
echo "token_type=STATS_TOKEN" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "✅ 使用默认 GITHUB_TOKEN"
|
|
||||||
echo "token_type=GITHUB_TOKEN" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🔗 仓库: ${{ github.repository }}"
|
echo "🔗 仓库: ${{ github.repository }}"
|
||||||
echo "🌿 分支: ${{ github.ref_name }}"
|
echo "🌿 分支: ${{ github.ref_name }}"
|
||||||
|
|
@ -177,80 +173,64 @@ jobs:
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
- name: 📥 克隆主仓库
|
- name: 📥 准备隔离仓库
|
||||||
id: clone_main
|
id: clone_main
|
||||||
run: |
|
run: |
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo "🚀 开始准备主仓库"
|
echo "🚀 开始准备主仓库"
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|
||||||
|
REPO_SLUG="${{ github.repository }}"
|
||||||
REPO_NAME="${{ github.event.repository.name }}"
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
RUN_ATTEMPT="${{ github.run_attempt }}"
|
||||||
|
JOB_NAME="${{ github.job }}"
|
||||||
|
SERVER_HOST="${GITHUB_SERVER_URL#http://}"
|
||||||
|
SERVER_HOST="${SERVER_HOST#https://}"
|
||||||
|
REMOTE_URL="https://oauth2:${{ env.ACCESS_TOKEN }}@${SERVER_HOST}/${REPO_SLUG}.git"
|
||||||
|
BOOTSTRAP_SCRIPT="/tmp/bootstrap_workspace.sh"
|
||||||
|
BOOTSTRAP_URL="${GITHUB_SERVER_URL}/api/v1/repos/${REPO_SLUG}/media/.gitea/ci/bootstrap_workspace.sh?ref=${GITHUB_SHA}"
|
||||||
|
PREPARED_ENV=$(mktemp)
|
||||||
|
|
||||||
|
if [ -z "$RUN_ATTEMPT" ]; then
|
||||||
|
RUN_ATTEMPT="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JOB_NAME" ]; then
|
||||||
|
JOB_NAME="job"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "📁 仓库名称: $REPO_NAME"
|
echo "📁 仓库名称: $REPO_NAME"
|
||||||
echo "📍 目标目录: $REPO_DIR"
|
echo "🪞 Mirror 根目录: ${{ env.MIRROR_ROOT }}"
|
||||||
|
echo "📦 Job 工作区根目录: ${{ env.JOB_WORKSPACE_ROOT }}"
|
||||||
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
|
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
|
||||||
echo "🌿 分支: ${{ github.ref_name }}"
|
echo "🌿 分支: ${{ github.ref_name }}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 检查仓库状态
|
curl -fsSL \
|
||||||
if [ -d "$REPO_DIR" ]; then
|
-H "Authorization: token ${{ env.ACCESS_TOKEN }}" \
|
||||||
echo "📂 目录已存在,检查 Git 仓库状态..."
|
"$BOOTSTRAP_URL" \
|
||||||
|
-o "$BOOTSTRAP_SCRIPT"
|
||||||
|
chmod +x "$BOOTSTRAP_SCRIPT"
|
||||||
|
|
||||||
if [ -d "$REPO_DIR/.git" ]; then
|
bash "$BOOTSTRAP_SCRIPT" prepare-job-workspace \
|
||||||
# 目录存在且是有效的 Git 仓库
|
"$REPO_SLUG" \
|
||||||
echo "✓ 发现有效的 Git 仓库,执行增量更新..."
|
"$REMOTE_URL" \
|
||||||
cd "$REPO_DIR"
|
"${{ env.MIRROR_ROOT }}" \
|
||||||
|
"${{ env.JOB_WORKSPACE_ROOT }}" \
|
||||||
|
"${{ github.run_id }}" \
|
||||||
|
"$RUN_ATTEMPT" \
|
||||||
|
"$JOB_NAME" > "$PREPARED_ENV"
|
||||||
|
|
||||||
# 清理工作区
|
# shellcheck source=/dev/null
|
||||||
git clean -fdx
|
source "$PREPARED_ENV"
|
||||||
git reset --hard
|
rm -f "$PREPARED_ENV"
|
||||||
|
|
||||||
# 获取最新代码
|
echo "✓ Mirror 路径: $MIRROR_PATH"
|
||||||
echo "📥 拉取最新代码..."
|
echo "✓ Job 工作区: $JOB_WORKSPACE"
|
||||||
git fetch --all --tags --force
|
echo "✓ 仓库目录: $REPO_DIR"
|
||||||
|
echo ""
|
||||||
echo "✓ 仓库已更新"
|
|
||||||
else
|
|
||||||
# 目录存在但不是 Git 仓库(可能之前运行失败)
|
|
||||||
echo "⚠️ 目录存在但不是有效的 Git 仓库"
|
|
||||||
echo "🧹 清理损坏的目录..."
|
|
||||||
rm -rf "$REPO_DIR"
|
|
||||||
echo "✓ 已清理"
|
|
||||||
|
|
||||||
# 重新克隆
|
|
||||||
echo "📥 克隆仓库..."
|
|
||||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
|
|
||||||
"$REPO_DIR"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ 克隆失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$REPO_DIR"
|
cd "$REPO_DIR"
|
||||||
echo "✓ 仓库已克隆"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# 目录不存在,首次克隆
|
|
||||||
echo "📥 克隆仓库(首次)..."
|
|
||||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
|
||||||
|
|
||||||
git clone \
|
|
||||||
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
|
|
||||||
"$REPO_DIR"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ 克隆失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
echo "✓ 仓库已克隆"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 切换到目标分支
|
# 切换到目标分支
|
||||||
echo "🏷️ 切换到分支: ${{ github.ref_name }}"
|
echo "🏷️ 切换到分支: ${{ github.ref_name }}"
|
||||||
|
|
@ -274,6 +254,9 @@ jobs:
|
||||||
# 导出环境变量
|
# 导出环境变量
|
||||||
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||||
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
|
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
|
||||||
|
echo "JOB_WORKSPACE=$JOB_WORKSPACE" >> $GITHUB_ENV
|
||||||
|
echo "MIRROR_PATH=$MIRROR_PATH" >> $GITHUB_ENV
|
||||||
|
echo "BOOTSTRAP_SCRIPT=$BOOTSTRAP_SCRIPT" >> $GITHUB_ENV
|
||||||
|
|
||||||
echo "✅ 主仓库准备完成"
|
echo "✅ 主仓库准备完成"
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|
@ -747,7 +730,7 @@ jobs:
|
||||||
### Token 配置
|
### Token 配置
|
||||||
|
|
||||||
- 当前使用: **${{ steps.validate_token.outputs.token_type }}**
|
- 当前使用: **${{ steps.validate_token.outputs.token_type }}**
|
||||||
- 推荐配置自定义 `STATS_TOKEN` 以获得更好的权限控制
|
- 需要在 Settings -> Secrets 中配置 `WORKFLOW`
|
||||||
|
|
||||||
### 排除规则
|
### 排除规则
|
||||||
|
|
||||||
|
|
@ -861,7 +844,7 @@ jobs:
|
||||||
|
|
||||||
- 📊 [查看详细统计报告](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/README.md)
|
- 📊 [查看详细统计报告](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/README.md)
|
||||||
- 🎨 [浏览徽章文件](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }})
|
- 🎨 [浏览徽章文件](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }})
|
||||||
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${{ github.ref_name }}/.github/workflows/update_stats_badge.yaml)
|
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ github.ref_name }}/.gitea/workflows/update_stats_badge.yaml)
|
||||||
|
|
||||||
## 📝 语言分布
|
## 📝 语言分布
|
||||||
|
|
||||||
|
|
@ -964,7 +947,9 @@ jobs:
|
||||||
if [ "${{ env.CLEANUP_WORKSPACE }}" == "true" ]; then
|
if [ "${{ env.CLEANUP_WORKSPACE }}" == "true" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "🧹 清理工作区..."
|
echo "🧹 清理工作区..."
|
||||||
rm -rf "${{ env.WORKSPACE_DIR }}"
|
if [ -n "${{ env.JOB_WORKSPACE }}" ] && [ "${{ env.JOB_WORKSPACE }}" != "/" ]; then
|
||||||
|
rm -rf "${{ env.JOB_WORKSPACE }}"
|
||||||
|
fi
|
||||||
echo "✅ 清理完成"
|
echo "✅ 清理完成"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -973,4 +958,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "🧹 清理临时文件..."
|
echo "🧹 清理临时文件..."
|
||||||
rm -rf /tmp/lang_stats /tmp/lang_summary.txt /tmp/total_stats*.json
|
rm -rf /tmp/lang_stats /tmp/lang_summary.txt /tmp/total_stats*.json
|
||||||
|
if [ -n "${{ env.JOB_WORKSPACE }}" ] && [ "${{ env.JOB_WORKSPACE }}" != "/" ]; then
|
||||||
|
echo "🧹 清理 Job 工作区: ${{ env.JOB_WORKSPACE }}"
|
||||||
|
rm -rf "${{ env.JOB_WORKSPACE }}"
|
||||||
|
fi
|
||||||
echo "✅ 清理完成"
|
echo "✅ 清理完成"
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,9 @@ Sessionx.vim
|
||||||
tags
|
tags
|
||||||
# Persistent undo
|
# Persistent undo
|
||||||
[._]*.un~
|
[._]*.un~
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Runner runtime data
|
||||||
|
docker-runner/presets/*/runner-data/
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ Gitea Runner 是 Gitea 的 CI/CD 执行器,类似于 GitLab Runner 或 GitHub Ac
|
||||||
|
|
||||||
本项目提供了标准版和 Buildx 版两种部署方案,通过预设配置快速部署。
|
本项目提供了标准版和 Buildx 版两种部署方案,通过预设配置快速部署。
|
||||||
|
|
||||||
|
- 只想先看仓库总览和最快上手路径:回到 [README.md](./README.md)
|
||||||
|
- 需要查看 workflow 示例和 secret 约定:参考 [WORKFLOW.md](./WORKFLOW.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📂 目录结构
|
## 📂 目录结构
|
||||||
|
|
@ -26,13 +29,16 @@ Gitea Runner 是 Gitea 的 CI/CD 执行器,类似于 GitLab Runner 或 GitHub Ac
|
||||||
```txt
|
```txt
|
||||||
docker-runner/
|
docker-runner/
|
||||||
├── common/ # 通用脚本(所有版本共用)
|
├── common/ # 通用脚本(所有版本共用)
|
||||||
│ ├── check_crlf.sh # Windows 换行符检查工具
|
│ ├── check_crlf.sh # Windows 换行符检查工具(宿主机本地执行)
|
||||||
│ ├── entrypoint.sh # 容器启动脚本
|
│ ├── entrypoint.sh # 容器启动脚本
|
||||||
│ ├── setup.sh # Runner 安装脚本
|
│ ├── setup.sh # Runner 安装脚本
|
||||||
│ ├── upgrade.sh # Runner 升级脚本
|
│ ├── upgrade.sh # Runner 升级脚本
|
||||||
│ ├── register.sh # Runner 注册脚本
|
│ ├── register.sh # Runner 注册脚本
|
||||||
│ └── manage.sh # Runner 管理脚本
|
│ └── manage.sh # Runner 管理脚本
|
||||||
│
|
│
|
||||||
|
├── .gitea/ci/
|
||||||
|
│ └── bootstrap_workspace.sh # workflow 自举脚本(下载后准备 mirror 和独立工作区)
|
||||||
|
│
|
||||||
└── presets/ # 预设配置(选择一个使用)
|
└── presets/ # 预设配置(选择一个使用)
|
||||||
├── standard-ubuntu-22/ # 标准版 (Ubuntu 22.04)
|
├── standard-ubuntu-22/ # 标准版 (Ubuntu 22.04)
|
||||||
│ ├── Dockerfile
|
│ ├── Dockerfile
|
||||||
|
|
@ -47,9 +53,17 @@ docker-runner/
|
||||||
|
|
||||||
**说明:**
|
**说明:**
|
||||||
|
|
||||||
- `common/` 目录中的脚本由所有版本共享,通过 docker-compose.yml 挂载到容器
|
- `common/` 目录中的脚本由所有版本共享,其中 `entrypoint.sh`、`setup.sh`、`upgrade.sh`、`register.sh`、`manage.sh` 会通过 docker-compose.yml 挂载到容器
|
||||||
|
- `check_crlf.sh` 是宿主机本地检查工具,用于在构建前修复 `common/` 目录脚本的换行符和执行权限
|
||||||
- `presets/` 目录中每个子目录是一个完整的预设配置,包含 Dockerfile 和 docker-compose.yml
|
- `presets/` 目录中每个子目录是一个完整的预设配置,包含 Dockerfile 和 docker-compose.yml
|
||||||
- 数据持久化在 `runner-data/` 目录(自动创建),包含 runner 配置、缓存和 act_runner 二进制文件
|
- 数据持久化在 `runner-data/` 目录(自动创建),包含 runner 配置、mirror 缓存和 act_runner 二进制文件
|
||||||
|
|
||||||
|
### 并发与工作区模型
|
||||||
|
|
||||||
|
- 新注册的 runner 默认 `capacity=4`
|
||||||
|
- 共享仓库缓存保存在 `/data/git-mirrors/<owner>/<repo>.git`
|
||||||
|
- 每个 job 会从 mirror 本地克隆到独立目录 `/home/workspace/jobs/<owner>/<repo>/<job-identity>/repo`
|
||||||
|
- job 结束后临时工作目录会自动清理,mirror 会保留以加速后续大仓库同步
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -108,22 +122,30 @@ docker-runner/
|
||||||
cd docker-runner/presets/standard-ubuntu-22/
|
cd docker-runner/presets/standard-ubuntu-22/
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. (可选)检查换行符
|
#### 2. 准备实例配置
|
||||||
|
|
||||||
如果从 Windows 复制文件,建议检查换行符:
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
按需编辑 `.env` 中的 `GITEA_INSTANCE` 和 `GITEA_TOKEN`。
|
||||||
|
|
||||||
|
#### 3. (可选)检查换行符
|
||||||
|
|
||||||
|
如果从 Windows 复制文件,建议先在宿主机执行检查工具,修复 `../../common/` 下脚本的换行符和权限:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
../../common/check_crlf.sh
|
../../common/check_crlf.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3. 构建并启动容器
|
#### 4. 构建并启动容器
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose build
|
docker compose build
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4. 安装 Runner
|
#### 5. 安装 Runner
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose exec gitea-runner /data/setup.sh
|
docker compose exec gitea-runner /data/setup.sh
|
||||||
|
|
@ -131,7 +153,7 @@ docker compose exec gitea-runner /data/setup.sh
|
||||||
|
|
||||||
脚本会自动探测最新 `act_runner` 版本作为默认值,然后提示你确认架构。
|
脚本会自动探测最新 `act_runner` 版本作为默认值,然后提示你确认架构。
|
||||||
|
|
||||||
#### 5. 注册 Runner
|
#### 6. 注册 Runner
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose exec gitea-runner /data/register.sh
|
docker compose exec gitea-runner /data/register.sh
|
||||||
|
|
@ -139,9 +161,9 @@ docker compose exec gitea-runner /data/register.sh
|
||||||
|
|
||||||
输入你的 Gitea 实例 URL 和注册令牌。
|
输入你的 Gitea 实例 URL 和注册令牌。
|
||||||
|
|
||||||
Runner 注册后会自动启动,无需重启容器。
|
Runner 注册后会自动启动,无需重启容器。新注册的 runner 默认并发为 `4`。
|
||||||
|
|
||||||
#### 6. 验证运行状态
|
#### 7. 验证运行状态
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 查看容器日志
|
# 查看容器日志
|
||||||
|
|
@ -165,7 +187,15 @@ cd docker-runner/presets/buildx-ubuntu-22/
|
||||||
cd docker-runner/presets/buildx-archlinux/
|
cd docker-runner/presets/buildx-archlinux/
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. (可选)配置代理
|
#### 2. 准备实例配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
按需编辑 `.env` 中的 `GITEA_INSTANCE` 和 `GITEA_TOKEN`。
|
||||||
|
|
||||||
|
#### 3. (可选)配置代理
|
||||||
|
|
||||||
如果需要代理,编辑 `docker-compose.yml` 取消注释并修改代理地址:
|
如果需要代理,编辑 `docker-compose.yml` 取消注释并修改代理地址:
|
||||||
|
|
||||||
|
|
@ -175,14 +205,14 @@ environment:
|
||||||
- https_proxy=http://host.docker.internal:20122
|
- https_proxy=http://host.docker.internal:20122
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3. 构建并启动
|
#### 4. 构建并启动
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose build
|
docker compose build
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4. 验证 Buildx 初始化
|
#### 5. 验证 Buildx 初始化
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 查看日志,应该看到:
|
# 查看日志,应该看到:
|
||||||
|
|
@ -197,7 +227,7 @@ docker compose exec gitea-runner docker buildx ls
|
||||||
# 应该看到 gitea-multiarch builder
|
# 应该看到 gitea-multiarch builder
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 5. 安装和注册
|
#### 6. 安装和注册
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose exec gitea-runner /data/setup.sh
|
docker compose exec gitea-runner /data/setup.sh
|
||||||
|
|
@ -577,6 +607,9 @@ docker system df
|
||||||
runner-data/
|
runner-data/
|
||||||
├── bin/
|
├── bin/
|
||||||
│ └── act_runner # Runner 可执行文件(持久化)
|
│ └── act_runner # Runner 可执行文件(持久化)
|
||||||
|
├── git-mirrors/ # 持久化 bare mirror 缓存
|
||||||
|
│ └── <owner>/
|
||||||
|
│ └── <repo>.git
|
||||||
├── runners/ # Runner 配置目录
|
├── runners/ # Runner 配置目录
|
||||||
│ └── <runner-name>/
|
│ └── <runner-name>/
|
||||||
│ ├── .runner # 注册信息
|
│ ├── .runner # 注册信息
|
||||||
|
|
@ -586,7 +619,7 @@ runner-data/
|
||||||
└── .configured
|
└── .configured
|
||||||
```
|
```
|
||||||
|
|
||||||
容器重启或重建后,数据不会丢失。
|
容器重启或重建后,数据不会丢失。workflow 的临时工作目录位于 `/home/workspace/jobs/...`,任务结束后会自动清理,不会作为持久化数据保留。
|
||||||
|
|
||||||
### Q10: 如何切换不同预设?
|
### Q10: 如何切换不同预设?
|
||||||
|
|
||||||
|
|
|
||||||
71
README.md
71
README.md
|
|
@ -2,47 +2,56 @@
|
||||||
|
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
|
|
||||||
> 🚀 Gitea Runner Docker 部署模板 + Actions Workflow 示例 + 文档规范
|
> Gitea Runner Docker 部署模板,附带可直接复用的 Actions workflow 示例。
|
||||||
|
|
||||||
## 📖 项目简介
|
## 📖 这个仓库提供什么
|
||||||
|
|
||||||
这是一个完整的 Gitea Runner 模板项目,提供:
|
- `docker-runner/presets/`:可直接部署的 runner 预设
|
||||||
|
- `docker-runner/common/`:安装、注册、升级、管理脚本
|
||||||
|
- `.gitea/workflows/`:可复制的 workflow 示例
|
||||||
|
- `.gitea/ci/bootstrap_workspace.sh`:workflow 工作区自举脚本
|
||||||
|
|
||||||
- 🐳 **Docker 部署方案**:开箱即用的 Runner 容器化部署
|
## ⚙️ 当前默认行为
|
||||||
- 📝 **Workflow 示例**:自动化 CHANGELOG 和 Release 工作流
|
|
||||||
- 📚 **文档规范**:统一的文档格式和版本管理规范
|
|
||||||
- 🔧 **配置指南**:详细的配置说明和最佳实践
|
|
||||||
|
|
||||||
## 📂 文档导航
|
- 新注册的 runner 默认 `capacity=4`
|
||||||
|
- 大仓库会缓存到 `/data/git-mirrors/<owner>/<repo>.git`
|
||||||
|
- 每个 workflow job 使用独立临时目录 `/home/workspace/jobs/<owner>/<repo>/<job-identity>/repo`
|
||||||
|
- job 结束后自动清理临时工作目录,mirror 缓存保留在 `runner-data/`
|
||||||
|
- 每个 preset 通过 `.env.example` 提供实例配置模板,部署时复制为 `.env`
|
||||||
|
|
||||||
### 🚀 Runner
|
## 🚀 快速开始
|
||||||
|
|
||||||
#### [DEPLOYMENT.md](./DEPLOYMENT.md)
|
以标准 Ubuntu 22.04 预设为例:
|
||||||
|
|
||||||
**Gitea Runner Docker 部署完整教程**
|
```bash
|
||||||
|
cd docker-runner/presets/standard-ubuntu-22
|
||||||
|
cp .env.example .env
|
||||||
|
docker compose build
|
||||||
|
docker compose up -d
|
||||||
|
docker compose exec gitea-runner /data/setup.sh
|
||||||
|
docker compose exec gitea-runner /data/register.sh
|
||||||
|
```
|
||||||
|
|
||||||
包含内容:
|
开始前至少需要在 `.env` 中填好 `GITEA_TOKEN`,必要时调整 `GITEA_INSTANCE`。
|
||||||
|
|
||||||
- 📦 标准版 vs Buildx 多架构版选择
|
完整部署步骤、Buildx 版本选择、升级和故障排查见 [DEPLOYMENT.md](./DEPLOYMENT.md)。
|
||||||
- 📝 Dockerfile 和 docker-compose.yml 配置
|
|
||||||
- 🔧 安装、注册、管理脚本详解
|
|
||||||
- ⚙️ 完整的部署流程
|
|
||||||
- ❓ 常见问题和故障排除
|
|
||||||
|
|
||||||
👉 **完整的 Runner 部署教程,从零开始搭建**
|
## 📂 你要找什么
|
||||||
|
|
||||||
---
|
- 部署或运维 runner:看 [DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||||
|
- 使用或定制 workflow:看 [WORKFLOW.md](./WORKFLOW.md)
|
||||||
|
- 查看当前示例 workflow:直接看 `.gitea/workflows/`
|
||||||
|
|
||||||
### 📋 [Workflow](./WORKFLOW.md)
|
## 🗂️ 仓库结构
|
||||||
|
|
||||||
**Gitea Actions 自动化工作流示例**
|
```txt
|
||||||
|
.
|
||||||
包含内容:
|
├── .gitea/
|
||||||
|
│ ├── ci/bootstrap_workspace.sh
|
||||||
- 💡 工作流配置说明
|
│ └── workflows/
|
||||||
- 🔧 如何使用和定制
|
├── docker-runner/
|
||||||
- 🔄 案例:`changelog_and_release.yml` - 自动更新 CHANGELOG 和自动创建 Release
|
│ ├── common/
|
||||||
|
│ └── presets/
|
||||||
👉 **实用的 Actions 工作流,可直接复制使用**
|
├── DEPLOYMENT.md
|
||||||
|
└── WORKFLOW.md
|
||||||
---
|
```
|
||||||
|
|
|
||||||
21
WORKFLOW.md
21
WORKFLOW.md
|
|
@ -2,14 +2,17 @@
|
||||||
|
|
||||||
本目录包含 Gitea Actions 的自动化工作流示例,展示如何使用 Gitea Actions 实现 CI/CD 自动化。
|
本目录包含 Gitea Actions 的自动化工作流示例,展示如何使用 Gitea Actions 实现 CI/CD 自动化。
|
||||||
|
|
||||||
|
- 先看仓库总览和 runner 默认行为:回到 [README.md](./README.md)
|
||||||
|
- 需要部署或维护 runner:参考 [DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📂 文件结构
|
## 📂 文件结构
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
.gitea/workflows/
|
.gitea/workflows/
|
||||||
├── changelog-and-release.yml # CHANGELOG 生成 + Release 创建
|
├── changelog_and_release.yml # CHANGELOG 生成 + Release 创建
|
||||||
├── update_stats_badge.yml # 代码行数的统计
|
├── update_stats_badge.yaml # 代码行数的统计
|
||||||
└── ... # 更多 workflow 待添加
|
└── ... # 更多 workflow 待添加
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -19,7 +22,7 @@
|
||||||
|
|
||||||
### ✅ 已实现
|
### ✅ 已实现
|
||||||
|
|
||||||
#### 1. 📦 自动发布工作流 (`changelog-and-release.yml`)
|
#### 1. 📦 自动发布工作流 (`changelog_and_release.yml`)
|
||||||
|
|
||||||
**功能**:在推送 tag 时自动生成 CHANGELOG 并创建 Release
|
**功能**:在推送 tag 时自动生成 CHANGELOG 并创建 Release
|
||||||
|
|
||||||
|
|
@ -40,13 +43,13 @@ git tag 1.0.0
|
||||||
git push origin 1.0.0
|
git push origin 1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
**文件**:[changelog-and-release.yml](.gitea/workflows/changelog_and_release.yml)
|
**文件**:[changelog_and_release.yml](.gitea/workflows/changelog_and_release.yml)
|
||||||
|
|
||||||
💡 **详细配置**:查看 `changelog-and-release.yml` 文件顶部的 `env` 区域,所有配置项都有详细注释说明
|
💡 **详细配置**:查看 `changelog_and_release.yml` 文件顶部的 `env` 区域,所有配置项都有详细注释说明
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 2. 📊 代码统计徽章工作流 (`update-stats-badge.yml`)
|
#### 2. 📊 代码统计徽章工作流 (`update_stats_badge.yaml`)
|
||||||
|
|
||||||
**功能**:自动统计代码行数并生成徽章数据
|
**功能**:自动统计代码行数并生成徽章数据
|
||||||
|
|
||||||
|
|
@ -68,11 +71,11 @@ git push origin main
|
||||||
# 仓库 → Actions → Update Code Statistics Badge → Run workflow
|
# 仓库 → Actions → Update Code Statistics Badge → Run workflow
|
||||||
```
|
```
|
||||||
|
|
||||||
**配置文件**:[update-stats-badge.yml](.gitea/workflows/update_stats_badge.yaml)
|
**配置文件**:[update_stats_badge.yaml](.gitea/workflows/update_stats_badge.yaml)
|
||||||
|
|
||||||
**markdown引用格式**: ``
|
**markdown引用格式**: ``
|
||||||
|
|
||||||
💡 **详细配置**:查看 `update-stats-badge.yml` 文件顶部的 `env` 区域,包含语言分组、颜色、排除目录等配置
|
💡 **详细配置**:查看 `update_stats_badge.yaml` 文件顶部的 `env` 区域,包含语言分组、颜色、排除目录等配置
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -109,7 +112,7 @@ git push origin main
|
||||||
**检查清单**:
|
**检查清单**:
|
||||||
|
|
||||||
- [ ] Workflow 文件在 `.gitea/workflows/` 目录
|
- [ ] Workflow 文件在 `.gitea/workflows/` 目录
|
||||||
- [ ] 文件名正确(如 `changelog-and-release.yml`)
|
- [ ] 文件名正确(如 `changelog_and_release.yml`)
|
||||||
- [ ] Token 已正确配置(Secret 名称为 `WORKFLOW`)
|
- [ ] Token 已正确配置(Secret 名称为 `WORKFLOW`)
|
||||||
- [ ] Tag 格式正确(数字开头,如 `1.0.0`)
|
- [ ] Tag 格式正确(数字开头,如 `1.0.0`)
|
||||||
- [ ] Gitea Actions 已启用
|
- [ ] Gitea Actions 已启用
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
|
||||||
# 要检查的文件列表
|
# 要检查的文件列表
|
||||||
SCRIPT_FILES=(
|
SCRIPT_FILES=(
|
||||||
|
|
@ -31,9 +32,10 @@ echo ""
|
||||||
|
|
||||||
for file in "${SCRIPT_FILES[@]}"; do
|
for file in "${SCRIPT_FILES[@]}"; do
|
||||||
TOTAL_FILES=$((TOTAL_FILES + 1))
|
TOTAL_FILES=$((TOTAL_FILES + 1))
|
||||||
|
file_path="${SCRIPT_DIR}/${file}"
|
||||||
|
|
||||||
# 检查文件是否存在
|
# 检查文件是否存在
|
||||||
if [ ! -f "$file" ]; then
|
if [ ! -f "${file_path}" ]; then
|
||||||
echo -e "${RED}✗ $file - 文件不存在,跳过${NC}"
|
echo -e "${RED}✗ $file - 文件不存在,跳过${NC}"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
@ -42,14 +44,14 @@ for file in "${SCRIPT_FILES[@]}"; do
|
||||||
|
|
||||||
# 检查换行符
|
# 检查换行符
|
||||||
HAS_CRLF=false
|
HAS_CRLF=false
|
||||||
if file "$file" | grep -qi "CRLF\|with CR"; then
|
if file "${file_path}" | grep -qi "CRLF\|with CR"; then
|
||||||
HAS_CRLF=true
|
HAS_CRLF=true
|
||||||
NEEDS_FIX=true
|
NEEDS_FIX=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
NEEDS_CHMOD=false
|
NEEDS_CHMOD=false
|
||||||
if [ ! -x "$file" ]; then
|
if [ ! -x "${file_path}" ]; then
|
||||||
NEEDS_CHMOD=true
|
NEEDS_CHMOD=true
|
||||||
NEEDS_FIX=true
|
NEEDS_FIX=true
|
||||||
fi
|
fi
|
||||||
|
|
@ -64,13 +66,13 @@ for file in "${SCRIPT_FILES[@]}"; do
|
||||||
|
|
||||||
# 修复换行符
|
# 修复换行符
|
||||||
if [ "$HAS_CRLF" = true ]; then
|
if [ "$HAS_CRLF" = true ]; then
|
||||||
sed -i 's/\r$//' "$file" 2>/dev/null || sed -i '' 's/\r$//' "$file" 2>/dev/null
|
sed -i 's/\r$//' "${file_path}" 2>/dev/null || sed -i '' 's/\r$//' "${file_path}" 2>/dev/null
|
||||||
echo -n -e "${YELLOW}[换行符已修复]${NC} "
|
echo -n -e "${YELLOW}[换行符已修复]${NC} "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 修复权限
|
# 修复权限
|
||||||
if [ "$NEEDS_CHMOD" = true ]; then
|
if [ "$NEEDS_CHMOD" = true ]; then
|
||||||
chmod +x "$file"
|
chmod +x "${file_path}"
|
||||||
echo -n -e "${YELLOW}[权限已修复]${NC} "
|
echo -n -e "${YELLOW}[权限已修复]${NC} "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ try:
|
||||||
|
|
||||||
# 使用实际注册的 labels
|
# 使用实际注册的 labels
|
||||||
config['runner']['labels'] = registered_labels
|
config['runner']['labels'] = registered_labels
|
||||||
config['runner']['capacity'] = 2
|
config['runner']['capacity'] = 4
|
||||||
|
|
||||||
# 启用缓存
|
# 启用缓存
|
||||||
if 'cache' not in config:
|
if 'cache' not in config:
|
||||||
|
|
@ -175,7 +175,7 @@ try:
|
||||||
|
|
||||||
print("✓ Configuration updated using Python")
|
print("✓ Configuration updated using Python")
|
||||||
print(f" - Labels: {registered_labels}")
|
print(f" - Labels: {registered_labels}")
|
||||||
print(f" - Capacity: 2")
|
print(f" - Capacity: 4")
|
||||||
print(f" - Cache enabled: ./cache")
|
print(f" - Cache enabled: ./cache")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
@ -191,7 +191,7 @@ PYEOF
|
||||||
echo "⚠ Python configuration failed, using basic sed..."
|
echo "⚠ Python configuration failed, using basic sed..."
|
||||||
|
|
||||||
# 基本的 sed 修改(只修改简单的值,不动 labels)
|
# 基本的 sed 修改(只修改简单的值,不动 labels)
|
||||||
sed -i 's/capacity: 1/capacity: 2/g' config.yaml || true
|
sed -i 's/capacity: 1/capacity: 4/g' config.yaml || true
|
||||||
sed -i 's/enabled: false/enabled: true/g' config.yaml || true
|
sed -i 's/enabled: false/enabled: true/g' config.yaml || true
|
||||||
sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true
|
sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true
|
||||||
|
|
||||||
|
|
@ -202,7 +202,7 @@ else
|
||||||
echo "⚠ Python3 not found, applying basic configuration..."
|
echo "⚠ Python3 not found, applying basic configuration..."
|
||||||
|
|
||||||
# 基本的 sed 修改
|
# 基本的 sed 修改
|
||||||
sed -i 's/capacity: 1/capacity: 2/g' config.yaml || true
|
sed -i 's/capacity: 1/capacity: 4/g' config.yaml || true
|
||||||
sed -i 's/enabled: false/enabled: true/g' config.yaml || true
|
sed -i 's/enabled: false/enabled: true/g' config.yaml || true
|
||||||
sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true
|
sed -i 's|dir: ""|dir: ./cache|g' config.yaml || true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
GITEA_INSTANCE=https://git.mytsl.cn
|
||||||
|
GITEA_TOKEN=
|
||||||
|
|
@ -11,7 +11,6 @@ services:
|
||||||
- ../../common/upgrade.sh:/data/upgrade.sh:ro
|
- ../../common/upgrade.sh:/data/upgrade.sh:ro
|
||||||
- ../../common/register.sh:/data/register.sh:ro
|
- ../../common/register.sh:/data/register.sh:ro
|
||||||
- ../../common/manage.sh:/data/manage.sh:ro
|
- ../../common/manage.sh:/data/manage.sh:ro
|
||||||
- ../../common/check_crlf.sh:/data/check_crlf.sh:ro
|
|
||||||
- ../../common/entrypoint.sh:/data/entrypoint.sh:ro
|
- ../../common/entrypoint.sh:/data/entrypoint.sh:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
|
@ -21,8 +20,8 @@ services:
|
||||||
# Arch Linux Buildx 配置 - 使用 tonistiigi/binfmt
|
# Arch Linux Buildx 配置 - 使用 tonistiigi/binfmt
|
||||||
- ENABLE_BUILDX=true
|
- ENABLE_BUILDX=true
|
||||||
- BINFMT_METHOD=tonistiigi
|
- BINFMT_METHOD=tonistiigi
|
||||||
- GITEA_INSTANCE=https://git.mytsl.cn
|
- GITEA_INSTANCE=${GITEA_INSTANCE}
|
||||||
- GITEA_TOKEN=
|
- GITEA_TOKEN=${GITEA_TOKEN}
|
||||||
- DEFAULT_RUNNER_NAME=buildx-archlinux
|
- DEFAULT_RUNNER_NAME=buildx-archlinux
|
||||||
- DEFAULT_RUNNER_LABEL=archlinux:host://archlinux:latest,company-server:host://archlinux:latest,buildx-archlinux:host://archlinux:latest
|
- DEFAULT_RUNNER_LABEL=archlinux:host://archlinux:latest,company-server:host://archlinux:latest,buildx-archlinux:host://archlinux:latest
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
GITEA_INSTANCE=https://git.mytsl.cn
|
||||||
|
GITEA_TOKEN=
|
||||||
|
|
@ -11,7 +11,6 @@ services:
|
||||||
- ../../common/upgrade.sh:/data/upgrade.sh:ro
|
- ../../common/upgrade.sh:/data/upgrade.sh:ro
|
||||||
- ../../common/register.sh:/data/register.sh:ro
|
- ../../common/register.sh:/data/register.sh:ro
|
||||||
- ../../common/manage.sh:/data/manage.sh:ro
|
- ../../common/manage.sh:/data/manage.sh:ro
|
||||||
- ../../common/check_crlf.sh:/data/check_crlf.sh:ro
|
|
||||||
- ../../common/entrypoint.sh:/data/entrypoint.sh:ro
|
- ../../common/entrypoint.sh:/data/entrypoint.sh:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
|
@ -20,8 +19,8 @@ services:
|
||||||
|
|
||||||
# Buildx 配置 - 启用多架构构建
|
# Buildx 配置 - 启用多架构构建
|
||||||
- ENABLE_BUILDX=true
|
- ENABLE_BUILDX=true
|
||||||
- GITEA_INSTANCE=https://git.mytsl.cn
|
- GITEA_INSTANCE=${GITEA_INSTANCE}
|
||||||
- GITEA_TOKEN=
|
- GITEA_TOKEN=${GITEA_TOKEN}
|
||||||
- DEFAULT_RUNNER_NAME=buildx-ubuntu-22
|
- DEFAULT_RUNNER_NAME=buildx-ubuntu-22
|
||||||
- DEFAULT_RUNNER_LABEL=ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,buildx-ubuntu-22:host://ubuntu:22.04
|
- DEFAULT_RUNNER_LABEL=ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,buildx-ubuntu-22:host://ubuntu:22.04
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
GITEA_INSTANCE=https://git.mytsl.cn
|
||||||
|
GITEA_TOKEN=
|
||||||
|
|
@ -18,8 +18,8 @@ services:
|
||||||
|
|
||||||
# Standard 配置 - 不启用 Buildx
|
# Standard 配置 - 不启用 Buildx
|
||||||
- ENABLE_BUILDX=false
|
- ENABLE_BUILDX=false
|
||||||
- GITEA_INSTANCE=https://git.mytsl.cn
|
- GITEA_INSTANCE=${GITEA_INSTANCE}
|
||||||
- GITEA_TOKEN=
|
- GITEA_TOKEN=${GITEA_TOKEN}
|
||||||
- DEFAULT_RUNNER_NAME=standard-ubuntu-22
|
- DEFAULT_RUNNER_NAME=standard-ubuntu-22
|
||||||
- DEFAULT_RUNNER_LABEL=ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,standard-ubuntu-22:host://ubuntu:22.04
|
- DEFAULT_RUNNER_LABEL=ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,standard-ubuntu-22:host://ubuntu:22.04
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
REPO_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
has_crlf() {
|
||||||
|
local file_path=$1
|
||||||
|
|
||||||
|
file "${file_path}" | grep -qi "CRLF\|with CR"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_check_crlf_works_from_preset_directory() {
|
||||||
|
local temp_root common_dir preset_dir output_file file_name
|
||||||
|
|
||||||
|
temp_root=$(mktemp -d)
|
||||||
|
common_dir="${temp_root}/common"
|
||||||
|
preset_dir="${temp_root}/preset"
|
||||||
|
output_file="${temp_root}/output.txt"
|
||||||
|
|
||||||
|
mkdir -p "${common_dir}" "${preset_dir}"
|
||||||
|
cp "${REPO_ROOT}/docker-runner/common/check_crlf.sh" "${common_dir}/check_crlf.sh"
|
||||||
|
chmod +x "${common_dir}/check_crlf.sh"
|
||||||
|
|
||||||
|
for file_name in entrypoint.sh setup.sh upgrade.sh register.sh manage.sh; do
|
||||||
|
printf '#!/bin/bash\r\necho test\r\n' > "${common_dir}/${file_name}"
|
||||||
|
chmod 644 "${common_dir}/${file_name}"
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "${preset_dir}"
|
||||||
|
printf 'n\n' | ../common/check_crlf.sh > "${output_file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
! rg -q "文件不存在" "${output_file}" || fail "check_crlf.sh should inspect sibling common scripts even when invoked from preset directory"
|
||||||
|
|
||||||
|
for file_name in entrypoint.sh setup.sh upgrade.sh register.sh manage.sh; do
|
||||||
|
! has_crlf "${common_dir}/${file_name}" || fail "${file_name} should have CRLF fixed"
|
||||||
|
[ -x "${common_dir}/${file_name}" ] || fail "${file_name} should be made executable"
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -rf "${temp_root}"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_check_crlf_works_from_preset_directory
|
||||||
|
|
||||||
|
echo "check_crlf_test.sh: PASS"
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
REPO_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test_preset_compose_uses_env_for_instance() {
|
||||||
|
local file
|
||||||
|
|
||||||
|
for file in \
|
||||||
|
"${REPO_ROOT}/docker-runner/presets/standard-ubuntu-22/docker-compose.yml" \
|
||||||
|
"${REPO_ROOT}/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml" \
|
||||||
|
"${REPO_ROOT}/docker-runner/presets/buildx-archlinux/docker-compose.yml"; do
|
||||||
|
grep -q 'GITEA_INSTANCE=${GITEA_INSTANCE}' "${file}" || fail "${file} should read GITEA_INSTANCE from env"
|
||||||
|
grep -q 'GITEA_TOKEN=${GITEA_TOKEN}' "${file}" || fail "${file} should read GITEA_TOKEN from env"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
test_workflows_do_not_hardcode_company_server() {
|
||||||
|
! rg -q 'https://git\.mytsl\.cn' "${REPO_ROOT}/.gitea/workflows/changelog_and_release.yml" || fail "changelog workflow should not hardcode company server"
|
||||||
|
! rg -q 'https://git\.mytsl\.cn' "${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml" || fail "stats workflow should not hardcode company server"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_stats_workflow_uses_workflow_secret_consistently() {
|
||||||
|
local file
|
||||||
|
|
||||||
|
file="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"
|
||||||
|
|
||||||
|
grep -q 'ACCESS_TOKEN: ${{ secrets.WORKFLOW }}' "${file}" || fail "stats workflow should read ACCESS_TOKEN from WORKFLOW secret"
|
||||||
|
! rg -q 'STATS_TOKEN' "${file}" || fail "stats workflow should not mention legacy STATS_TOKEN secret"
|
||||||
|
! rg -q 'GITHUB_TOKEN' "${file}" || fail "stats workflow should not mention GITHUB_TOKEN in token guidance"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_workflow_docs_and_links_use_actual_paths() {
|
||||||
|
local workflow_doc stats_workflow release_workflow
|
||||||
|
|
||||||
|
workflow_doc="${REPO_ROOT}/WORKFLOW.md"
|
||||||
|
stats_workflow="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"
|
||||||
|
release_workflow="${REPO_ROOT}/.gitea/workflows/changelog_and_release.yml"
|
||||||
|
|
||||||
|
grep -q 'changelog_and_release.yml' "${workflow_doc}" || fail "WORKFLOW.md should mention changelog_and_release.yml"
|
||||||
|
grep -q 'update_stats_badge.yaml' "${workflow_doc}" || fail "WORKFLOW.md should mention update_stats_badge.yaml"
|
||||||
|
! rg -q 'changelog-and-release\.yml' "${workflow_doc}" || fail "WORKFLOW.md should not mention stale changelog-and-release.yml filename"
|
||||||
|
! rg -q 'update-stats-badge\.yml' "${workflow_doc}" || fail "WORKFLOW.md should not mention stale update-stats-badge.yml filename"
|
||||||
|
! rg -q 'update_stats_badge\.yml' "${workflow_doc}" || fail "WORKFLOW.md should not mention stale update_stats_badge.yml filename"
|
||||||
|
|
||||||
|
grep -q '/src/branch/${{ github.ref_name }}/.gitea/workflows/update_stats_badge.yaml' "${stats_workflow}" || fail "stats workflow summary should link to .gitea workflow path"
|
||||||
|
grep -q '/src/branch/${{ env.MAIN_BRANCH }}/.gitea/workflows/changelog_and_release.yml' "${release_workflow}" || fail "release workflow summary should link to .gitea workflow path"
|
||||||
|
! rg -q '/\\.github/workflows/' "${stats_workflow}" || fail "stats workflow should not link to .github/workflows"
|
||||||
|
! rg -q '/\\.github/workflows/' "${release_workflow}" || fail "release workflow should not link to .github/workflows"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_presets_do_not_mount_check_crlf_helper() {
|
||||||
|
! rg -q 'check_crlf\.sh:/data/check_crlf\.sh:ro' "${REPO_ROOT}/docker-runner/presets" || fail "preset compose files should not mount check_crlf helper into containers"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_runner_data_is_gitignored() {
|
||||||
|
local path
|
||||||
|
|
||||||
|
for path in \
|
||||||
|
"docker-runner/presets/standard-ubuntu-22/runner-data/config.yaml" \
|
||||||
|
"docker-runner/presets/buildx-ubuntu-22/runner-data/config.yaml" \
|
||||||
|
"docker-runner/presets/buildx-archlinux/runner-data/config.yaml"; do
|
||||||
|
git -C "${REPO_ROOT}" check-ignore -q "${path}" || fail "${path} should be ignored as runtime runner data"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
test_readme_is_navigation_focused() {
|
||||||
|
local file
|
||||||
|
|
||||||
|
file="${REPO_ROOT}/README.md"
|
||||||
|
|
||||||
|
grep -q '^## 🚀 快速开始$' "${file}" || fail "README.md should provide a quick start section"
|
||||||
|
grep -q '\[DEPLOYMENT.md\](\./DEPLOYMENT.md)' "${file}" || fail "README.md should link to DEPLOYMENT.md"
|
||||||
|
grep -q '\[WORKFLOW.md\](\./WORKFLOW.md)' "${file}" || fail "README.md should link to WORKFLOW.md"
|
||||||
|
grep -q 'cp \.env\.example \.env' "${file}" || fail "README.md quick start should mention copying .env.example"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_preset_env_examples_exist() {
|
||||||
|
local file
|
||||||
|
|
||||||
|
for file in \
|
||||||
|
"${REPO_ROOT}/docker-runner/presets/standard-ubuntu-22/.env.example" \
|
||||||
|
"${REPO_ROOT}/docker-runner/presets/buildx-ubuntu-22/.env.example" \
|
||||||
|
"${REPO_ROOT}/docker-runner/presets/buildx-archlinux/.env.example"; do
|
||||||
|
test -f "${file}" || fail "missing env example: ${file}"
|
||||||
|
grep -q '^GITEA_INSTANCE=https://git.mytsl.cn$' "${file}" || fail "${file} should include company default instance"
|
||||||
|
grep -q '^GITEA_TOKEN=$' "${file}" || fail "${file} should include empty token placeholder"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
test_preset_compose_uses_env_for_instance
|
||||||
|
test_workflows_do_not_hardcode_company_server
|
||||||
|
test_stats_workflow_uses_workflow_secret_consistently
|
||||||
|
test_workflow_docs_and_links_use_actual_paths
|
||||||
|
test_presets_do_not_mount_check_crlf_helper
|
||||||
|
test_runner_data_is_gitignored
|
||||||
|
test_readme_is_navigation_focused
|
||||||
|
test_preset_env_examples_exist
|
||||||
|
|
||||||
|
echo "template_defaults_test.sh: PASS"
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
REPO_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "${REPO_ROOT}/.gitea/ci/bootstrap_workspace.sh"
|
||||||
|
|
||||||
|
assert_eq() {
|
||||||
|
local expected=$1
|
||||||
|
local actual=$2
|
||||||
|
local message=$3
|
||||||
|
|
||||||
|
if [ "${expected}" != "${actual}" ]; then
|
||||||
|
echo "FAIL: ${message}" >&2
|
||||||
|
echo " expected: ${expected}" >&2
|
||||||
|
echo " actual: ${actual}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_repo_path_layout() {
|
||||||
|
local owner repo_name mirror_dir workspace_root mirror_path workspace_root_path repo_dir
|
||||||
|
|
||||||
|
owner="csh"
|
||||||
|
repo_name="actions-template"
|
||||||
|
mirror_dir="/data/git-mirrors"
|
||||||
|
workspace_root="/home/workspace/jobs"
|
||||||
|
|
||||||
|
mirror_path=$(build_mirror_path "${mirror_dir}" "${owner}" "${repo_name}")
|
||||||
|
workspace_root_path=$(build_job_workspace_root "${workspace_root}" "${owner}" "${repo_name}" "123456-1-release")
|
||||||
|
repo_dir=$(build_job_repo_dir "${workspace_root}" "${owner}" "${repo_name}" "123456-1-release")
|
||||||
|
|
||||||
|
assert_eq "/data/git-mirrors/csh/actions-template.git" "${mirror_path}" "mirror path should include owner and bare repo suffix"
|
||||||
|
assert_eq "/home/workspace/jobs/csh/actions-template/123456-1-release" "${workspace_root_path}" "workspace root should include owner repo and job identity"
|
||||||
|
assert_eq "/home/workspace/jobs/csh/actions-template/123456-1-release/repo" "${repo_dir}" "repo dir should live under isolated workspace root"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_job_identity_prefers_run_metadata() {
|
||||||
|
local actual
|
||||||
|
|
||||||
|
actual=$(build_job_identity "123456" "2" "release-job")
|
||||||
|
|
||||||
|
assert_eq "123456-2-release-job" "${actual}" "job identity should include run id attempt and job name"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_sanitize_job_name() {
|
||||||
|
local actual
|
||||||
|
|
||||||
|
actual=$(sanitize_job_name "release notes/job")
|
||||||
|
|
||||||
|
assert_eq "release-notes-job" "${actual}" "job names should be filesystem-safe"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_build_lock_path() {
|
||||||
|
local actual
|
||||||
|
|
||||||
|
actual=$(build_mirror_lock_path "/data/git-mirrors" "csh" "actions-template")
|
||||||
|
|
||||||
|
assert_eq "/data/git-mirrors/csh/actions-template.git.lock" "${actual}" "lock path should sit beside the bare mirror"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_repo_owner_and_name_parsing() {
|
||||||
|
local actual
|
||||||
|
|
||||||
|
actual=$(split_repository_slug "csh/actions-template")
|
||||||
|
|
||||||
|
assert_eq $'csh\nactions-template' "${actual}" "repository slug should split into owner and repo lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_prepare_and_cleanup_workspace() {
|
||||||
|
local temp_root remote_repo seed_repo mirror_root workspace_root env_file
|
||||||
|
local repo_dir mirror_path job_workspace origin_url
|
||||||
|
|
||||||
|
temp_root=$(mktemp -d)
|
||||||
|
remote_repo="${temp_root}/remote.git"
|
||||||
|
seed_repo="${temp_root}/seed"
|
||||||
|
mirror_root="${temp_root}/mirrors"
|
||||||
|
workspace_root="${temp_root}/jobs"
|
||||||
|
env_file="${temp_root}/prepared.env"
|
||||||
|
|
||||||
|
git init -b main "${seed_repo}" >/dev/null
|
||||||
|
git -C "${seed_repo}" config user.name "Test User"
|
||||||
|
git -C "${seed_repo}" config user.email "test@example.com"
|
||||||
|
printf 'hello\n' > "${seed_repo}/README.md"
|
||||||
|
git -C "${seed_repo}" add README.md
|
||||||
|
git -C "${seed_repo}" commit -m "Initial commit" >/dev/null
|
||||||
|
git clone --bare "${seed_repo}" "${remote_repo}" >/dev/null
|
||||||
|
|
||||||
|
prepare_job_workspace \
|
||||||
|
"csh/actions-template" \
|
||||||
|
"${remote_repo}" \
|
||||||
|
"${mirror_root}" \
|
||||||
|
"${workspace_root}" \
|
||||||
|
"123456" \
|
||||||
|
"2" \
|
||||||
|
"release job" > "${env_file}"
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "${env_file}"
|
||||||
|
|
||||||
|
repo_dir="${REPO_DIR}"
|
||||||
|
mirror_path="${MIRROR_PATH}"
|
||||||
|
job_workspace="${JOB_WORKSPACE}"
|
||||||
|
|
||||||
|
if [ ! -d "${mirror_path}" ]; then
|
||||||
|
echo "FAIL: mirror path should exist after preparation" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "${repo_dir}/.git" ]; then
|
||||||
|
echo "FAIL: prepared repo dir should contain a git checkout" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
origin_url=$(git -C "${repo_dir}" remote get-url origin)
|
||||||
|
assert_eq "${remote_repo}" "${origin_url}" "prepared repo should point origin to the requested remote"
|
||||||
|
|
||||||
|
cleanup_job_workspace "${job_workspace}"
|
||||||
|
|
||||||
|
if [ -e "${job_workspace}" ]; then
|
||||||
|
echo "FAIL: cleanup should remove the job workspace" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "${temp_root}"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_register_default_capacity_is_four() {
|
||||||
|
if ! grep -q "config\['runner'\]\['capacity'\] = 4" "${REPO_ROOT}/docker-runner/common/register.sh"; then
|
||||||
|
echo "FAIL: register.sh should default new runner capacity to 4" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_changelog_workflow_uses_workspace_helper() {
|
||||||
|
if ! grep -q ".gitea/ci/bootstrap_workspace.sh" "${REPO_ROOT}/.gitea/workflows/changelog_and_release.yml"; then
|
||||||
|
echo "FAIL: changelog workflow should fetch repo-owned bootstrap helper" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_stats_workflow_uses_workspace_helper() {
|
||||||
|
if ! grep -q ".gitea/ci/bootstrap_workspace.sh" "${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"; then
|
||||||
|
echo "FAIL: stats workflow should fetch repo-owned bootstrap helper" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_presets_do_not_mount_workspace_helper() {
|
||||||
|
if rg -q "workspace\.sh:/data/workspace\.sh" "${REPO_ROOT}/docker-runner/presets"; then
|
||||||
|
echo "FAIL: preset compose files should not mount workspace helper from runner common" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_docs_mention_git_mirrors() {
|
||||||
|
if ! grep -q "/data/git-mirrors" "${REPO_ROOT}/DEPLOYMENT.md"; then
|
||||||
|
echo "FAIL: deployment docs should describe persistent git mirrors" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_repo_path_layout
|
||||||
|
test_job_identity_prefers_run_metadata
|
||||||
|
test_sanitize_job_name
|
||||||
|
test_build_lock_path
|
||||||
|
test_repo_owner_and_name_parsing
|
||||||
|
test_prepare_and_cleanup_workspace
|
||||||
|
test_register_default_capacity_is_four
|
||||||
|
test_changelog_workflow_uses_workspace_helper
|
||||||
|
test_stats_workflow_uses_workspace_helper
|
||||||
|
test_presets_do_not_mount_workspace_helper
|
||||||
|
test_docs_mention_git_mirrors
|
||||||
|
|
||||||
|
echo "workspace_helper_test.sh: PASS"
|
||||||
Loading…
Reference in New Issue