diff --git a/.gitea/workflows/changelog_and_release.yaml b/.gitea/workflows/changelog_and_release.yaml new file mode 100644 index 0000000..87658f2 --- /dev/null +++ b/.gitea/workflows/changelog_and_release.yaml @@ -0,0 +1,934 @@ +name: 📦 Tag Release Automation + +on: + push: + tags: + - "[0-9]*" + +jobs: + changelog-and-release: + runs-on: ubuntu-22.04 + steps: + - name: ⚙️ Configuration + id: config + run: | + echo "======================================" + echo "⚙️ Loading workflow configuration" + echo "======================================" + + # ======================================== + # 📝 自定义配置区域 - 在此修改所有配置 + # ======================================== + + # 📌 语义版本号规范 (SemVer) + # 格式: MAJOR.MINOR.PATCH[-prerelease] + # + # 开发版本示例: + # - 1.0.0-beta1, 1.0.0-beta2, 1.0.0-beta10 等 + # - 1.2.0-alpha1, 1.2.0-rc1 等 + # + # 正式版本示例: + # - 1.0.0 (首个稳定版,PATCH必须从0开始) + # - 1.1.0 (新增功能,向后兼容) + # - 1.1.1 (Bug修复) + # - 2.0.0 (破坏性变更,不向后兼容) + # + # ⚠️ 重要规则: + # 1. 开发版本都以 .0-beta[N] 或 .0-alpha[N] 结尾 + # 2. 正式发布必须从 .0 开始(如 1.0.0, 1.1.0, 2.0.0) + # 3. 多个开发版本(如1.0.0-beta1, 1.0.0-beta2)会累加到同一个CHANGELOG区域(1.0.0) + # 4. CHANGELOG内容是叠加而非覆盖 + + # Gitea 服务器地址(用于生成头像链接) + GITEA_SERVER="https://git.mytsl.cn" + + # CHANGELOG 配置 + CHANGELOG_SECTION_TITLE="### :pencil: What's Changed" + CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors" + + # CHANGELOG 版本号处理方式 + # - "full": 使用完整 tag 名称(2025.10.31-1-beta → ## :bookmark: 2025.10.31-1-beta) + # - "strip": 去除 pre-release 后缀(1.0.0-beta1 → ## :bookmark: 1.0.0) + # ⚠️ 推荐使用 "strip" 模式以支持多个开发版本叠加到同一CHANGELOG区域 + CHANGELOG_VERSION_MODE="strip" + + # Release 配置 + RELEASE_TITLE="{version}" # 可用变量: {version} + + # Pre-release 配置 + # - "false": 正式版本(会被标记为 latest) + # - "true": 预发布版本(会被标记为 pre-release) + # - "auto": 自动判断(根据 tag 名称中是否包含 alpha/beta/rc) + RELEASE_PRERELEASE_MODE="auto" + + # Draft 配置(是否创建为草稿) + RELEASE_IS_DRAFT="false" # true 或 false + + # 需要忽略的提交模式(支持正则表达式) + IGNORE_PATTERNS=( + "^Merge " + "^:memo: Auto update CHANGELOG" + "\[skip ci\]" + "\[ci skip\]" + "^Bump version" + "^Release v" + "^chore: update config" + "^style: " + "^chore: format" + "^chore: lint" + ) + + # Git 提交消息格式 + COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]" + + # 额外要上传到 Release 的文件(空格分隔) + ADDITIONAL_RELEASE_FILES="" # 例如: "README.md LICENSE docs/guide.pdf" + + # ======================================== + # 以下为自动处理,无需修改 + # ======================================== + + VERSION="${{ github.ref_name }}" + + # 处理 CHANGELOG 版本号 + if [[ "$CHANGELOG_VERSION_MODE" == "strip" ]]; then + # 去除 pre-release 后缀(-beta, -rc1, -alpha 等) + CHANGELOG_VERSION=$(echo "$VERSION" | sed -E 's/-(alpha|beta|rc|pre|preview|dev|test)[0-9]*$//') + echo "📝 CHANGELOG version mode: strip" + echo " Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION" + else + # 使用完整 tag 名称 + CHANGELOG_VERSION="$VERSION" + echo "📝 CHANGELOG version mode: full" + echo " Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION" + fi + + # 处理 Release 标题 + RELEASE_TITLE="${RELEASE_TITLE//\{version\}/$VERSION}" + + # 处理提交消息 + COMMIT_MSG="${COMMIT_MESSAGE//\{version\}/$VERSION}" + + # 智能判断 pre-release + if [[ "$RELEASE_PRERELEASE_MODE" == "auto" ]]; then + if [[ "$VERSION" =~ (alpha|beta|rc|pre|preview|dev|test) ]]; then + RELEASE_IS_PRERELEASE="true" + PRERELEASE_DETECTED="yes (auto-detected: $VERSION contains pre-release keyword)" + else + RELEASE_IS_PRERELEASE="false" + PRERELEASE_DETECTED="no (auto-detected: stable release)" + fi + else + RELEASE_IS_PRERELEASE="$RELEASE_PRERELEASE_MODE" + PRERELEASE_DETECTED="$RELEASE_PRERELEASE_MODE (manually configured)" + fi + + # 导出配置到环境变量 + echo "GITEA_SERVER=$GITEA_SERVER" >> $GITHUB_ENV + echo "CHANGELOG_SECTION_TITLE=$CHANGELOG_SECTION_TITLE" >> $GITHUB_ENV + echo "CHANGELOG_CONTRIBUTORS_TITLE=$CHANGELOG_CONTRIBUTORS_TITLE" >> $GITHUB_ENV + echo "CHANGELOG_VERSION=$CHANGELOG_VERSION" >> $GITHUB_ENV + echo "RELEASE_TITLE=$RELEASE_TITLE" >> $GITHUB_ENV + echo "RELEASE_IS_PRERELEASE=$RELEASE_IS_PRERELEASE" >> $GITHUB_ENV + echo "RELEASE_IS_DRAFT=$RELEASE_IS_DRAFT" >> $GITHUB_ENV + echo "COMMIT_MSG=$COMMIT_MSG" >> $GITHUB_ENV + echo "ADDITIONAL_RELEASE_FILES=$ADDITIONAL_RELEASE_FILES" >> $GITHUB_ENV + + # 导出忽略模式(转换为 JSON 数组格式便于后续处理) + IGNORE_JSON="[" + for pattern in "${IGNORE_PATTERNS[@]}"; do + IGNORE_JSON="${IGNORE_JSON}\"${pattern}\"," + done + IGNORE_JSON="${IGNORE_JSON%,}]" + echo "IGNORE_PATTERNS_JSON=$IGNORE_JSON" >> $GITHUB_ENV + + # 显示配置摘要 + echo "" + echo "📋 Configuration Summary:" + echo " 🏷️ Tag version: $VERSION" + echo " 📝 CHANGELOG version: $CHANGELOG_VERSION" + echo " 🌐 Gitea Server: $GITEA_SERVER" + echo " 📄 CHANGELOG Title: $CHANGELOG_SECTION_TITLE" + echo " 🚀 Release Title: $RELEASE_TITLE" + echo " 🏷️ Pre-release: $PRERELEASE_DETECTED" + echo " 📄 Draft: $RELEASE_IS_DRAFT" + echo " 💬 Commit Message: $COMMIT_MSG" + echo " 📎 Additional Files: ${ADDITIONAL_RELEASE_FILES:-none}" + echo "" + echo "✅ Configuration loaded successfully" + echo "" + + - name: 📂 Checkout repository + run: | + echo "======================================" + echo "🚀 Starting repository checkout" + echo "======================================" + + REPO_NAME="${{ github.event.repository.name }}" + REPO_DIR="/home/workspace/$REPO_NAME" + + echo "📁 Repository: $REPO_NAME" + echo "📍 Target directory: $REPO_DIR" + + if [ -d "$REPO_DIR/.git" ]; then + echo "✓ Repository exists, updating..." + cd "$REPO_DIR" + git clean -fdx + git reset --hard + git fetch --all --tags --force + echo "✓ Repository updated" + else + echo "📥 Cloning repository..." + mkdir -p /home/workspace + git clone https://oauth2:${{ secrets.WORKFLOW }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git "$REPO_DIR" + cd "$REPO_DIR" + echo "✓ Repository cloned" + fi + + echo "🏷️ Checking out tag: ${{ github.ref_name }}" + git checkout -f ${{ github.ref_name }} + echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV + + echo "✅ Checkout completed" + echo "" + + - name: 🔍 Check if bot commit + id: check_bot + run: | + echo "======================================" + echo "🤖 Checking commit author" + echo "======================================" + + cd ${{ env.REPO_DIR }} + LAST_AUTHOR=$(git log -1 --pretty=format:'%an') + + echo "👤 Last commit author: $LAST_AUTHOR" + + if [[ "$LAST_AUTHOR" == "github-actions[bot]" ]]; then + echo "⚠️ Bot commit detected - skipping to prevent loop" + echo "is_bot_commit=true" >> $GITHUB_OUTPUT + else + echo "✓ Non-bot commit - continuing workflow" + echo "is_bot_commit=false" >> $GITHUB_OUTPUT + fi + echo "" + + - name: 📋 Check if version exists in CHANGELOG + if: steps.check_bot.outputs.is_bot_commit == 'false' + id: check_version + run: | + echo "======================================" + echo "🔎 Checking CHANGELOG for existing version" + echo "======================================" + + cd ${{ env.REPO_DIR }} + CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}" + + echo "🏷️ CHANGELOG version to check: $CHANGELOG_VERSION" + + git checkout main || git checkout master + git pull origin main || git pull origin master || true + + if [ -f "CHANGELOG.md" ]; then + echo "✓ CHANGELOG.md found" + if grep -q "## :bookmark: ${CHANGELOG_VERSION}" CHANGELOG.md; then + echo "ℹ️ Version ${CHANGELOG_VERSION} already exists in CHANGELOG" + echo "📝 Will append new commits to existing entry (content accumulation mode)" + echo "version_exists=true" >> $GITHUB_OUTPUT + else + echo "✓ Version ${CHANGELOG_VERSION} not found - will generate new entry" + echo "version_exists=false" >> $GITHUB_OUTPUT + fi + else + echo "ℹ️ CHANGELOG.md does not exist - will create new file" + echo "version_exists=false" >> $GITHUB_OUTPUT + fi + + git checkout ${{ github.ref_name }} + echo "" + + - name: 📝 Generate CHANGELOG + if: steps.check_bot.outputs.is_bot_commit == 'false' + id: changelog + run: | + echo "======================================" + echo "📝 Generating CHANGELOG" + echo "======================================" + + cd ${{ env.REPO_DIR }} + + VERSION="${{ github.ref_name }}" + CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}" + VERSION_EXISTS="${{ steps.check_version.outputs.version_exists }}" + PREVIOUS_TAG=$(git tag --sort=-creatordate | sed -n '2p') + + echo "🏷️ Tag version: $VERSION" + echo "📝 CHANGELOG version: $CHANGELOG_VERSION" + echo "🏷️ Previous version: ${PREVIOUS_TAG:-none (first release)}" + + if [ "$VERSION_EXISTS" = "true" ]; then + echo "ℹ️ Updating existing CHANGELOG entry" + else + echo "ℹ️ Creating new CHANGELOG entry" + fi + + # 从环境变量加载忽略模式 + IGNORE_PATTERNS=( + "^Merge " + "^:memo: Auto update CHANGELOG" + "\[skip ci\]" + "\[ci skip\]" + "^Bump version" + "^Release v" + "^chore: update config" + "^style: " + "^chore: format" + "^chore: lint" + ) + + echo "🔍 Scanning commits..." + TEMP_COMMITS=$(mktemp) + + if [ -z "$PREVIOUS_TAG" ]; then + COMMIT_RANGE="HEAD" + else + COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}" + fi + + git log $COMMIT_RANGE --no-merges --reverse --format="COMMIT_START%n%h%n%an%n%s%nBODY_START%n%b%nBODY_END" > "$TEMP_COMMITS" + + declare -a COMMITS_HASH + declare -a COMMITS_AUTHOR + declare -a COMMITS_MESSAGE + declare -a COMMITS_BODY + + COMMIT_INDEX=0 + CURRENT_HASH="" + CURRENT_AUTHOR="" + CURRENT_MESSAGE="" + CURRENT_BODY="" + IN_BODY=false + + while IFS= read -r line; do + if [[ "$line" == "COMMIT_START" ]]; then + if [ -n "$CURRENT_HASH" ]; then + SHOULD_SKIP=false + for pattern in "${IGNORE_PATTERNS[@]}"; do + if echo "$CURRENT_MESSAGE" | grep -qiE "$pattern"; then + SHOULD_SKIP=true + break + fi + done + + if [ "$SHOULD_SKIP" = false ]; then + COMMITS_HASH[$COMMIT_INDEX]="$CURRENT_HASH" + COMMITS_AUTHOR[$COMMIT_INDEX]="$CURRENT_AUTHOR" + COMMITS_MESSAGE[$COMMIT_INDEX]="$CURRENT_MESSAGE" + COMMITS_BODY[$COMMIT_INDEX]="$CURRENT_BODY" + COMMIT_INDEX=$((COMMIT_INDEX + 1)) + fi + fi + + CURRENT_HASH="" + CURRENT_AUTHOR="" + CURRENT_MESSAGE="" + CURRENT_BODY="" + IN_BODY=false + STATE="HASH" + + elif [[ "$line" == "BODY_START" ]]; then + IN_BODY=true + CURRENT_BODY="" + elif [[ "$line" == "BODY_END" ]]; then + IN_BODY=false + elif [ "$IN_BODY" = true ]; then + if [ -n "$CURRENT_BODY" ]; then + CURRENT_BODY="${CURRENT_BODY}\n${line}" + else + CURRENT_BODY="$line" + fi + else + if [ -z "$CURRENT_HASH" ]; then + CURRENT_HASH="$line" + elif [ -z "$CURRENT_AUTHOR" ]; then + CURRENT_AUTHOR="$line" + elif [ -z "$CURRENT_MESSAGE" ]; then + CURRENT_MESSAGE="$line" + fi + fi + done < "$TEMP_COMMITS" + + if [ -n "$CURRENT_HASH" ]; then + SHOULD_SKIP=false + for pattern in "${IGNORE_PATTERNS[@]}"; do + if echo "$CURRENT_MESSAGE" | grep -qiE "$pattern"; then + SHOULD_SKIP=true + break + fi + done + + if [ "$SHOULD_SKIP" = false ]; then + COMMITS_HASH[$COMMIT_INDEX]="$CURRENT_HASH" + COMMITS_AUTHOR[$COMMIT_INDEX]="$CURRENT_AUTHOR" + COMMITS_MESSAGE[$COMMIT_INDEX]="$CURRENT_MESSAGE" + COMMITS_BODY[$COMMIT_INDEX]="$CURRENT_BODY" + COMMIT_INDEX=$((COMMIT_INDEX + 1)) + fi + fi + + rm -f "$TEMP_COMMITS" + + if [ $COMMIT_INDEX -eq 0 ]; then + echo "⚠️ No valid commits found to add" + echo "changelog_updated=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "✓ Found ${COMMIT_INDEX} valid commits" + echo "" + echo "📄 Building CHANGELOG entry..." + + # 使用配置的标题 + NEW_ENTRY="## :bookmark: ${CHANGELOG_VERSION}\n\n" + NEW_ENTRY="${NEW_ENTRY}${CHANGELOG_SECTION_TITLE}\n" + + REPO_URL="${{ github.server_url }}/${{ github.repository }}" + + for ((i=0; i<$COMMIT_INDEX; i++)); do + hash="${COMMITS_HASH[$i]}" + author="${COMMITS_AUTHOR[$i]}" + message="${COMMITS_MESSAGE[$i]}" + body="${COMMITS_BODY[$i]}" + + echo " [$((i+1))/$COMMIT_INDEX] ${hash:0:7}: ${message:0:60}..." + + COMMIT_LINK="([${hash}](${REPO_URL}/commit/${hash}))" + + if [ -n "$body" ]; then + FULL_MESSAGE="${message}\n${body}" + LINE_COUNT=$(echo -e "$FULL_MESSAGE" | sed '/^$/d' | wc -l) + + CURRENT_LINE=1 + FORMATTED_MESSAGE="" + + while IFS= read -r line; do + [ -z "$line" ] && continue + + if [ $CURRENT_LINE -eq $LINE_COUNT ]; then + FORMATTED_MESSAGE="${FORMATTED_MESSAGE}${line} ${COMMIT_LINK}" + if [ -n "$author" ]; then + FORMATTED_MESSAGE="${FORMATTED_MESSAGE} by @${author}" + fi + else + FORMATTED_MESSAGE="${FORMATTED_MESSAGE}${line} \n" + fi + CURRENT_LINE=$((CURRENT_LINE + 1)) + done <<< "$(echo -e "$FULL_MESSAGE" | sed '/^$/d')" + + NEW_ENTRY="${NEW_ENTRY}- ${FORMATTED_MESSAGE}\n" + else + if [ -n "$author" ]; then + NEW_ENTRY="${NEW_ENTRY}- ${message} ${COMMIT_LINK} by @${author}\n" + else + NEW_ENTRY="${NEW_ENTRY}- ${message} ${COMMIT_LINK}\n" + fi + fi + done + + echo "" + echo "👥 Collecting contributors..." + + if [ -z "$PREVIOUS_TAG" ]; then + CONTRIBUTORS=$(git log --pretty=format:"%an" --no-merges | grep -v "github-actions\[bot\]" | grep -v "dependabot\[bot\]" | grep -v "renovate\[bot\]" | sort -u) + else + CONTRIBUTORS=$(git log ${PREVIOUS_TAG}..${VERSION} --pretty=format:"%an" --no-merges | grep -v "github-actions\[bot\]" | grep -v "dependabot\[bot\]" | grep -v "renovate\[bot\]" | sort -u) + fi + + if [ -n "$CONTRIBUTORS" ]; then + CONTRIBUTOR_COUNT=$(echo "$CONTRIBUTORS" | wc -l) + echo "✓ Found ${CONTRIBUTOR_COUNT} contributors" + + # 使用配置的标题和服务器地址 + NEW_ENTRY="${NEW_ENTRY}\n${CHANGELOG_CONTRIBUTORS_TITLE}\n" + + while IFS= read -r name; do + NEW_ENTRY="${NEW_ENTRY}\n" + NEW_ENTRY="${NEW_ENTRY} \"${name}\"\n" + NEW_ENTRY="${NEW_ENTRY}\n" + done <<< "$CONTRIBUTORS" + fi + + NEW_ENTRY="${NEW_ENTRY}\n---\n\n" + + echo "" + echo "💾 Writing to CHANGELOG.md..." + git checkout main || git checkout master + + if [ -f CHANGELOG.md ]; then + if [ "$VERSION_EXISTS" = "true" ]; then + # 版本已存在,叠加新的条目(不是替换) + echo "🔄 Appending new commits to existing entry for $CHANGELOG_VERSION" + + # 提取现有版本区域的内容 + TEMP_OLD_CONTENT=$(mktemp) + TEMP_NEW_FILE=$(mktemp) + + # 提取旧版本区域的所有内容 + IN_VERSION=false + while IFS= read -r line; do + if [[ "$line" =~ ^##[[:space:]]:bookmark:[[:space:]]${CHANGELOG_VERSION}$ ]]; then + IN_VERSION=true + elif [[ "$line" =~ ^##[[:space:]]:bookmark: ]] && [ "$IN_VERSION" = true ]; then + IN_VERSION=false + fi + + if [ "$IN_VERSION" = true ]; then + echo "$line" >> "$TEMP_OLD_CONTENT" + fi + done < CHANGELOG.md + + # 提取旧的提交记录(在 What's Changed 和 Contributors 之间的内容) + OLD_COMMITS="" + if [ -f "$TEMP_OLD_CONTENT" ]; then + OLD_COMMITS=$(awk ' + /^### :pencil: What'"'"'s Changed/,/^### :busts_in_silhouette: Contributors/ { + if ($0 !~ /^### :pencil: What/ && $0 !~ /^### :busts_in_silhouette:/) { + if ($0 ~ /^-/) print $0 + } + } + ' "$TEMP_OLD_CONTENT") + fi + + # 提取旧的贡献者(在 Contributors 部分的内容) + OLD_CONTRIBUTORS="" + if [ -f "$TEMP_OLD_CONTENT" ]; then + OLD_CONTRIBUTORS=$(awk ' + /^### :busts_in_silhouette: Contributors/,/^---/ { + if ($0 ~ //) print $0 + } + ') + + if [ -n "$NEW_CONTRIBUTORS" ]; then + while IFS= read -r line; do + if [[ "$line" =~ href=\"[^\"]+/([^\"]+)\" ]]; then + username="${BASH_REMATCH[1]}" + if [ -z "${SEEN_CONTRIBUTORS[$username]}" ]; then + SEEN_CONTRIBUTORS[$username]=1 + MERGED_ENTRY="${MERGED_ENTRY}${line}\n" + read -r img_line + MERGED_ENTRY="${MERGED_ENTRY}${img_line}\n" + read -r close_line + MERGED_ENTRY="${MERGED_ENTRY}${close_line}\n" + fi + fi + done <<< "$NEW_CONTRIBUTORS" + fi + + MERGED_ENTRY="${MERGED_ENTRY}\n---\n\n" + + # 替换CHANGELOG中的对应版本区域 + IN_VERSION=false + while IFS= read -r line; do + if [[ "$line" =~ ^##[[:space:]]:bookmark:[[:space:]]${CHANGELOG_VERSION}$ ]]; then + echo -e "${MERGED_ENTRY}" >> "$TEMP_NEW_FILE" + IN_VERSION=true + elif [[ "$line" =~ ^##[[:space:]]:bookmark: ]] && [ "$IN_VERSION" = true ]; then + IN_VERSION=false + echo "$line" >> "$TEMP_NEW_FILE" + elif [ "$IN_VERSION" = false ]; then + echo "$line" >> "$TEMP_NEW_FILE" + fi + done < CHANGELOG.md + + mv "$TEMP_NEW_FILE" CHANGELOG.md + rm -f "$TEMP_OLD_CONTENT" + echo "✓ Appended new commits to existing CHANGELOG entry" + else + # 版本不存在,添加新条目 + echo "➕ Adding new entry for $CHANGELOG_VERSION" + + TEMP_FILE=$(mktemp) + head -n 1 CHANGELOG.md > "$TEMP_FILE" + echo "" >> "$TEMP_FILE" + echo -e "${NEW_ENTRY}" >> "$TEMP_FILE" + tail -n +3 CHANGELOG.md >> "$TEMP_FILE" + mv "$TEMP_FILE" CHANGELOG.md + echo "✓ Added new CHANGELOG entry" + fi + else + echo "# CHANGELOG" > CHANGELOG.md + echo "" >> CHANGELOG.md + echo -e "${NEW_ENTRY}" >> CHANGELOG.md + echo "✓ Created new CHANGELOG.md" + fi + + echo "changelog_updated=true" >> $GITHUB_OUTPUT + echo "✅ CHANGELOG generation completed" + echo "" + + - name: 📤 Commit and push CHANGELOG + if: steps.changelog.outputs.changelog_updated == 'true' + run: | + echo "======================================" + echo "📤 Committing and pushing CHANGELOG" + echo "======================================" + + cd ${{ env.REPO_DIR }} + + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + git add CHANGELOG.md + + if git diff --staged --quiet; then + echo "ℹ️ No changes to commit" + exit 0 + fi + + echo "📝 Creating commit..." + echo "💬 Commit message: $COMMIT_MSG" + git commit -m "$COMMIT_MSG" + + echo "⬆️ Pushing to remote..." + git push origin main + + echo "✅ CHANGELOG pushed to main branch" + echo "" + + - name: 🗑️ Delete old release + if: steps.changelog.outputs.changelog_updated == 'true' + run: | + echo "======================================" + echo "🔍 Checking for existing release" + echo "======================================" + + OLD_ID=$(curl -s -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/tags/${{ github.ref_name }}" \ + | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('id', ''))" 2>/dev/null || echo "") + + if [ -n "$OLD_ID" ]; then + echo "🗑️ Found existing release (ID: $OLD_ID)" + echo "🔄 Deleting old release..." + curl -s -X DELETE -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/$OLD_ID" + echo "✓ Old release deleted" + sleep 2 + else + echo "ℹ️ No existing release found" + fi + echo "" + + - name: 📋 Generate release notes + if: steps.changelog.outputs.changelog_updated == 'true' + run: | + echo "======================================" + echo "📋 Generating release notes" + echo "======================================" + + cd ${{ env.REPO_DIR }} + + python3 << 'PYSCRIPT' + import re + import os + + tag = "${{ github.ref_name }}" + changelog_version = "${{ env.CHANGELOG_VERSION }}" + print(f"🏷️ Tag version: {tag}") + print(f"📝 CHANGELOG version: {changelog_version}") + + changelog_path = 'CHANGELOG.md' + + if not os.path.exists(changelog_path): + print("❌ ERROR: CHANGELOG.md not found") + exit(1) + + print("📖 Reading CHANGELOG.md...") + with open(changelog_path, 'r', encoding='utf-8') as f: + changelog = f.read() + + print(f"🔍 Searching for version entry: {changelog_version}") + pattern = rf'##\s+:bookmark:\s+{re.escape(changelog_version)}\s*\n(.*?)(?=^##\s+:bookmark:|\Z)' + match = re.search(pattern, changelog, re.DOTALL | re.MULTILINE) + + if match: + version_content = match.group(1).strip() + version_content = re.sub(r'\n\s*\n\s*\n+', '\n\n', version_content).strip() + version_content = re.sub(r'^-{3,}\s*\n', '', version_content) + print(f"✓ Found changelog for version {changelog_version}") + print(f"📊 Content length: {len(version_content)} characters") + else: + version_content = "⚠️ No changelog found for this version" + print(f"⚠️ WARNING: Version {changelog_version} not found in CHANGELOG") + + with open('/tmp/release-body.txt', 'w', encoding='utf-8') as f: + f.write(version_content) + + print("✅ Release notes generated successfully") + PYSCRIPT + echo "" + + - name: 🚀 Create release + if: steps.changelog.outputs.changelog_updated == 'true' + run: | + echo "======================================" + echo "🚀 Creating GitHub/Gitea release" + echo "======================================" + + cd ${{ env.REPO_DIR }} + + echo "📦 Preparing release payload..." + echo "📋 Release title: $RELEASE_TITLE" + echo "🏷️ Pre-release: $RELEASE_IS_PRERELEASE" + echo "📄 Draft: $RELEASE_IS_DRAFT" + + # 转换 bash 布尔值为 Python 布尔值 + if [ "$RELEASE_IS_DRAFT" = "true" ]; then + PY_DRAFT="True" + else + PY_DRAFT="False" + fi + + if [ "$RELEASE_IS_PRERELEASE" = "true" ]; then + PY_PRERELEASE="True" + else + PY_PRERELEASE="False" + fi + + echo "🔄 Converting: draft=$RELEASE_IS_DRAFT → $PY_DRAFT, prerelease=$RELEASE_IS_PRERELEASE → $PY_PRERELEASE" + + python3 << EOF + import json + + with open('/tmp/release-body.txt', 'r') as f: + body = f.read() + + payload = { + "tag_name": "${{ github.ref_name }}", + "name": "$RELEASE_TITLE", + "body": body, + "draft": $PY_DRAFT, + "prerelease": $PY_PRERELEASE + } + + with open('/tmp/payload.json', 'w', encoding='utf-8') as f: + json.dump(payload, f, ensure_ascii=False, indent=2) + + print("✓ Payload prepared") + EOF + + echo "🌐 Sending API request..." + HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" -X POST \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d @/tmp/payload.json \ + "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases") + + echo "📡 API Response (HTTP $HTTP_CODE):" + echo "---" + cat /tmp/release_response.json | python3 -m json.tool 2>/dev/null || cat /tmp/release_response.json + echo "---" + + if [ "$HTTP_CODE" != "201" ] && [ "$HTTP_CODE" != "200" ]; then + echo "❌ ERROR: Failed to create release" + exit 1 + fi + + echo "✅ Release created successfully" + echo "" + + - name: 📎 Upload CHANGELOG attachment + if: steps.changelog.outputs.changelog_updated == 'true' + run: | + echo "======================================" + echo "📎 Uploading release attachments" + echo "======================================" + + cd ${{ env.REPO_DIR }} + + echo "🔍 Extracting release ID..." + RELEASE_ID=$(python3 << 'PYSCRIPT' + import json + import sys + + try: + with open('/tmp/release_response.json', 'r') as f: + data = json.load(f) + + if 'id' not in data: + print("❌ ERROR: No 'id' field in response", file=sys.stderr) + sys.exit(1) + + print(data['id']) + except Exception as e: + print(f"❌ ERROR: {e}", file=sys.stderr) + sys.exit(1) + PYSCRIPT + ) + + if [ -z "$RELEASE_ID" ]; then + echo "❌ ERROR: Failed to get release ID" + exit 1 + fi + + echo "✓ Release ID: $RELEASE_ID" + echo "" + + # 上传 CHANGELOG.md + echo "📤 [1/1] Uploading CHANGELOG.md..." + HTTP_CODE=$(curl -s -o /tmp/upload_response.json -w "%{http_code}" -X POST \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -F "attachment=@CHANGELOG.md" \ + "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=CHANGELOG.md") + + if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then + echo "✅ CHANGELOG.md uploaded successfully" + else + echo "⚠️ WARNING: Upload failed (HTTP $HTTP_CODE)" + cat /tmp/upload_response.json + fi + + # 上传额外的文件 + if [ -n "$ADDITIONAL_RELEASE_FILES" ]; then + echo "" + echo "📦 Uploading additional files..." + FILE_INDEX=2 + TOTAL_FILES=$((1 + $(echo "$ADDITIONAL_RELEASE_FILES" | wc -w))) + + for file in $ADDITIONAL_RELEASE_FILES; do + if [ -f "$file" ]; then + echo "📤 [$FILE_INDEX/$TOTAL_FILES] Uploading $(basename $file)..." + HTTP_CODE=$(curl -s -o /tmp/upload_response_${FILE_INDEX}.json -w "%{http_code}" -X POST \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -F "attachment=@${file}" \ + "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=$(basename $file)") + + if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then + echo "✅ $(basename $file) uploaded successfully" + else + echo "⚠️ WARNING: $(basename $file) upload failed (HTTP $HTTP_CODE)" + fi + FILE_INDEX=$((FILE_INDEX + 1)) + else + echo "⚠️ WARNING: File not found: $file" + fi + done + fi + + echo "" + echo "✅ All attachments processed" + echo "" + + - name: 📊 Workflow summary + if: always() + run: | + echo "======================================" + echo "📊 Generating workflow summary" + echo "======================================" + + echo "## 📦 Workflow Execution Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**🏷️ Tag:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "**📂 Repository:** \`${{ github.repository }}\`" >> $GITHUB_STEP_SUMMARY + echo "**🕒 Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### ⚙️ Configuration" >> $GITHUB_STEP_SUMMARY + echo "- **Gitea Server:** \`$GITEA_SERVER\`" >> $GITHUB_STEP_SUMMARY + echo "- **Release Title:** \`$RELEASE_TITLE\`" >> $GITHUB_STEP_SUMMARY + + if [ "$RELEASE_IS_PRERELEASE" = "true" ]; then + echo "- **Release Type:** 🏷️ Pre-release" >> $GITHUB_STEP_SUMMARY + else + echo "- **Release Type:** ✅ Latest (Stable)" >> $GITHUB_STEP_SUMMARY + fi + + if [ "$RELEASE_IS_DRAFT" = "true" ]; then + echo "- **Draft:** 📄 Yes" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then + echo "⏭️ **Status:** Skipped (bot commit detected)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The workflow was skipped to prevent infinite loops." >> $GITHUB_STEP_SUMMARY + elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then + echo "⏭️ **Status:** Skipped (version already exists)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Version \`${{ github.ref_name }}\` already exists in CHANGELOG.md" >> $GITHUB_STEP_SUMMARY + elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then + echo "✅ **Status:** Success" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Completed Actions" >> $GITHUB_STEP_SUMMARY + echo "- ✅ CHANGELOG.md updated" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Changes pushed to main branch" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Release created" >> $GITHUB_STEP_SUMMARY + echo "- ✅ CHANGELOG.md attached to release" >> $GITHUB_STEP_SUMMARY + if [ -n "$ADDITIONAL_RELEASE_FILES" ]; then + echo "- ✅ Additional files uploaded" >> $GITHUB_STEP_SUMMARY + fi + else + echo "ℹ️ **Status:** No updates" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "No valid commits found between tags." >> $GITHUB_STEP_SUMMARY + fi + + echo "" + echo "✅ Workflow summary generated" \ No newline at end of file diff --git a/.gitea/workflows/immortal_wrt.yaml b/.gitea/workflows/immortal_wrt.yaml new file mode 100644 index 0000000..21e5f36 --- /dev/null +++ b/.gitea/workflows/immortal_wrt.yaml @@ -0,0 +1,33 @@ +name: Hello from ImmortalWrt + +on: + push: + branches: + - main + +jobs: + say-hello: + runs-on: company + + steps: + - name: 打印问候 + run: echo "Hello form ImmortalWrt Router!" + + - name: 显示系统信息 + run: | + echo "===== 系统信息 =====" + uname -a + cat /etc/openwrt_release || echo "不是 OpenWrt 环境" + + - name: 显示 CPU 和内存 + run: | + echo "===== CPU 信息 =====" + cat /proc/cpuinfo | grep "model name" | head -1 || echo "ARM 架构" + echo "===== 内存信息 =====" + free -h + + - name: 显示磁盘空间 + run: df -h + + - name: 列出当前目录 + run: ls -la diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3afef97 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,357 @@ +# CHANGELOG + +## :bookmark: 0.0.1 + +### :pencil: What's Changed +- :cycle: refactor ([6739e6a](https://git.mytsl.cn/csh/actions/commit/6739e6a)) by @csh +- :memo: update md ([196df7d](https://git.mytsl.cn/csh/actions/commit/196df7d)) by @csh +- :memo: update readme ([a221ddd](https://git.mytsl.cn/csh/actions/commit/a221ddd)) by @csh + +### :busts_in_silhouette: Contributors + +csh + + +--- + + +## :bookmark: 2025.10.31-23 + +### :pencil: What's Changed +- :cycle: hello ([f76531c](https://git.mytsl.cn/csh/actions/commit/f76531c)) by @csh + +### :busts_in_silhouette: Contributors + + csh + + +--- + + +## :bookmark: 2025.10.31-22-beta + +### :pencil: What's Changed +- sos +sos +sos ([ee64fe8](https://git.mytsl.cn/csh/actions/commit/ee64fe8)) by @csh + +### :busts_in_silhouette: Contributors + + csh + + +--- + + +## :bookmark: 2025.10.31-21-beta + +### :pencil: What's Changed +- :bug: fixbug +:bug: fixbug ([3f21e1a](https://git.mytsl.cn/csh/actions/commit/3f21e1a)) by @csh + +### :busts_in_silhouette: Contributors + + csh + + +--- + + +## :bookmark: 2025.10.31-20-beta + +### :pencil: What's Changed +- :help: helpme +:help: helpme ([69f1112](https://git.mytsl.cn/csh/actions/commit/69f1112)) by @csh + +### :busts_in_silhouette: Contributors + + csh + + +--- + + +## :bookmark: 2025.10.31-19-beta + +### :pencil: What's Changed +- :cycle: 重大更新 +:warning: 哈哈哈 ([d000080](https://git.mytsl.cn/csh/actions/commit/d000080)) by @csh + +### :busts_in_silhouette: Contributors + + csh + + +--- + + +## :bookmark: 2025.10.31-17 + +### :pencil: What's Changed +- :rocket: gogogo +:waring: aaaa ([f27a32c](https://git.mytsl.cn/csh/actions/commit/f27a32c)) by @csh + +### :busts_in_silhouette: Contributors + + csh + + +--- + + +## :bookmark: 2025.10.31-16 + +### :pencil: å˜æ›´å†…容 +- [:rocket:] yes +[:rocket:] amdyes! ([e1f9e4a](https://git.mytsl.cn/csh/actions/commit/e1f9e4a)) by @csh + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-15 + +### :pencil: 变更内容 +- :bug: okok +:bug: okok ([2011842](https://git.mytsl.cn/csh/actions/commit/2011842)) by @csh + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-14 + +### :pencil: 变更内容 +- sos +sos ([6cd3c75](https://git.mytsl.cn/csh/actions/commit/6cd3c75) by @csh + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-13 + +### :pencil: 变更内容 +- :bug: continuetest1 +:bug: continuetest2 ([9a66205](https://git.mytsl.cn/csh/actions/commit/9a66205)) +- csh d0be624 +:memo: Auto update CHANGELOG for 2025.10.31-12 [skip ci] ([](https://git.mytsl.cn/csh/actions/commit/)) by @|github-actions[bot] + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-12 + +### :pencil: 变更内容 +- :bug: bug1 +:bug: bug2 ([e982410](https://git.mytsl.cn/csh/actions/commit/e982410)) +- csh ([](https://git.mytsl.cn/csh/actions/commit/)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-11 + +### :pencil: 变更内容 +- :bug: bug1 +:bug: bug1 ([664f1a6](https://git.mytsl.cn/csh/actions/commit/664f1a6)) +- csh ([](https://git.mytsl.cn/csh/actions/commit/)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-10 + +### :pencil: 变更内容 +- :bug: bu1 +:bug: bu2 ([bad1e99](https://git.mytsl.cn/csh/actions/commit/bad1e99)) +- csh ([](https://git.mytsl.cn/csh/actions/commit/)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-9 + +### :pencil: 变更内容 +- :bug: bu1 +:bug: bu2 ([d37cc3c](https://git.mytsl.cn/csh/actions/commit/d37cc3c)) +- csh ([](https://git.mytsl.cn/csh/actions/commit/)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-8 + +### :pencil: 变更内容 +- :bug: 1 :bug: 2 ([f163913](https://git.mytsl.cn/csh/actions/commit/f163913)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-7 + +### :pencil: 变更内容 +- :bug: t1 :bug: t2 ([ba7ede6](https://git.mytsl.cn/csh/actions/commit/ba7ede6)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-6 + +### :pencil: 变更内容 +- :bug: 多行bug :bug: 多行测试 ([6f4f726](https://git.mytsl.cn/csh/actions/commit/6f4f726)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-5 + +### :pencil: 变更内容 +- :sparkles: 支持多行commit :rocket: 加速测试 ([3b0c44d](https://git.mytsl.cn/csh/actions/commit/3b0c44d)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-4 + +### :pencil: 变更内容 +- - :sparkles: `auto-changelog.yaml`过滤一些没用的提交信息 - :bug: 修复CHANGELOG标题不置顶 ([cc3a9ab](https://git.mytsl.cn/csh/actions/commit/cc3a9ab)) +- 更新 CHANGELOG.md ([a71fa13](https://git.mytsl.cn/csh/actions/commit/a71fa13)) +- 更新 CHANGELOG.md ([181ce32](https://git.mytsl.cn/csh/actions/commit/181ce32)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + + +## :bookmark: 2025.10.31-3 + +### :pencil: 变更内容 +- Merge branch 'main' of https://git.mytsl.cn/csh/actions ([c31da44](https://git.mytsl.cn/csh/actions/commit/c31da44)) +- :bug: 样式错误 ([65a5b5a](https://git.mytsl.cn/csh/actions/commit/65a5b5a)) +- :memo: 自动更新 CHANGELOG for 2025.10.31-2 [skip ci] ([3bdca3f](https://git.mytsl.cn/csh/actions/commit/3bdca3f)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- + +## :bookmark: 2025.10.31-2 + +### :pencil: 变更内容 +- :bug: emoji样式和贡献者图标 ([57eeddf](https://git.mytsl.cn/csh/actions/commit/57eeddf)) +- docs: 自动更新 CHANGELOG for 2025.10.31-1 [skip ci] ([42c4f2d](https://git.mytsl.cn/csh/actions/commit/42c4f2d)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- +- + +## :bookmark: 2025.10.31-1 + +### :pencil: 变更内容 +- 删除 CHANGELOG.md ([5306159](https://git.mytsl.cn/csh/actions/commit/5306159)) +- :bug:: fix cycle and bot ([84eb006](https://git.mytsl.cn/csh/actions/commit/84eb006)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([3582de4](https://git.mytsl.cn/csh/actions/commit/3582de4)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([94c8ab3](https://git.mytsl.cn/csh/actions/commit/94c8ab3)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([8f48c6d](https://git.mytsl.cn/csh/actions/commit/8f48c6d)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([cb31aad](https://git.mytsl.cn/csh/actions/commit/cb31aad)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([dea51a0](https://git.mytsl.cn/csh/actions/commit/dea51a0)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([4269113](https://git.mytsl.cn/csh/actions/commit/4269113)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([3dbdefb](https://git.mytsl.cn/csh/actions/commit/3dbdefb)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([59756ba](https://git.mytsl.cn/csh/actions/commit/59756ba)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([ecc43b4](https://git.mytsl.cn/csh/actions/commit/ecc43b4)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([4afb56b](https://git.mytsl.cn/csh/actions/commit/4afb56b)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([2ce5bef](https://git.mytsl.cn/csh/actions/commit/2ce5bef)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([89b2f77](https://git.mytsl.cn/csh/actions/commit/89b2f77)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([7b2f45b](https://git.mytsl.cn/csh/actions/commit/7b2f45b)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([9f083b8](https://git.mytsl.cn/csh/actions/commit/9f083b8)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([62cb5a2](https://git.mytsl.cn/csh/actions/commit/62cb5a2)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([7459b75](https://git.mytsl.cn/csh/actions/commit/7459b75)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([cd421b6](https://git.mytsl.cn/csh/actions/commit/cd421b6)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([5353b5c](https://git.mytsl.cn/csh/actions/commit/5353b5c)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([14695a7](https://git.mytsl.cn/csh/actions/commit/14695a7)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([52dc518](https://git.mytsl.cn/csh/actions/commit/52dc518)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([a0bef08](https://git.mytsl.cn/csh/actions/commit/a0bef08)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([7a533cd](https://git.mytsl.cn/csh/actions/commit/7a533cd)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([865970d](https://git.mytsl.cn/csh/actions/commit/865970d)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([6ad15b5](https://git.mytsl.cn/csh/actions/commit/6ad15b5)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([21a5243](https://git.mytsl.cn/csh/actions/commit/21a5243)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([f156b69](https://git.mytsl.cn/csh/actions/commit/f156b69)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([ef83267](https://git.mytsl.cn/csh/actions/commit/ef83267)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([9d99e65](https://git.mytsl.cn/csh/actions/commit/9d99e65)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([183011e](https://git.mytsl.cn/csh/actions/commit/183011e)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([6d15881](https://git.mytsl.cn/csh/actions/commit/6d15881)) +- docs: 自动更新 CHANGELOG for 2025.10.31-5 ([48ac173](https://git.mytsl.cn/csh/actions/commit/48ac173)) + +### :busts_in_silhouette: 贡献者 + + csh + + +--- diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 0000000..2f9cc14 --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1,69 @@ +# 模板说明 + +## :label: 图例说明 + +| 图标 | 代码 | 说明 | 图标 | 代码 | 说明 | +| :-----------: | :-------------- | :------- | :-------------------: | :---------------------- | :------------ | +| :tada: | `:tada:` | 首次发布 | :sparkles: | `:sparkles:` | 新功能 | +| :bug: | `:bug:` | Bug 修复 | :rocket: | `:rocket:` | 性能优化 | +| :art: | `:art:` | 代码样式 | :recycle: | `:recycle:` | 代码重构 | +| :package: | `:package:` | 依赖更新 | :lock: | `:lock:` | 安全修复 | +| :warning: | `:warning:` | 废弃警告 | :wrench: | `:wrench:` | 配置变更 | +| :wastebasket: | `:wastebasket:` | 删除功能 | :busts_in_silhouette: | `:busts_in_silhouette:` | 贡献者 | +| :memo: | `:memo:` | 文档更新 | :bookmark: | `:bookmark:` | 发行/版本标签 | + +--- + +## :pushpin: 版本号规范 + +### SemVer - 语义版本号 + +#### 格式 + +```txt +MAJOR.MINOR.PATCH +``` + +#### 字段说明 + +| 字段 | 说明 | 何时递增 | +| --------- | -------- | ------------------ | +| **MAJOR** | 主版本号 | 不兼容的 API 修改 | +| **MINOR** | 次版本号 | 向后兼容的功能新增 | +| **PATCH** | 修订号 | 向后兼容的问题修正 | + +#### 更新规则 + +- **MAJOR(主版本)**:破坏性变更,不向后兼容。递增后 MINOR 和 PATCH 重置为 0 +- **MINOR(次版本)**:新增功能但保持兼容。递增后 PATCH 重置为 0 +- **PATCH(修订)**:仅修复 bug,不新增功能 + +#### 示例 + +```txt +1.0.0 # 首个稳定版 +1.1.0 # 新增功能 +1.1.1 # Bug 修复 +2.0.0 # 破坏性变更 +``` + +#### Pre-release 版本 + +```txt +1.0.0-alpha.1 +1.0.0-beta +1.0.0-rc.1 +1.0.0 +``` + +#### 初始开发阶段 + +```txt +0.1.0 # 初始版本 +0.2.0 # API 未稳定 +1.0.0 # 首个稳定版 ✅ +``` + +**注意**:`0.x.x` 版本可以随时破坏兼容性 + +--- diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..0a0c51e --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,1751 @@ +# 🚀 Gitea Runner Docker 部署完整教程 + +## 📋 目录 + +- [简介](#-简介) +- [版本选择](#-版本选择) +- [版本 1:标准版](#-版本-1标准版推荐) +- [版本 2:Buildx 多架构版](#-版本-2buildx-多架构版) +- [版本对比](#️-版本对比) +- [常见问题](#-常见问题) + +--- + +## 💡 简介 + +Gitea Runner 是 Gitea 的 CI/CD 执行器,类似于 GitLab Runner 或 GitHub Actions Runner,用于执行 Gitea Actions 工作流。 + +--- + +## 🎯 版本选择 + +### 📦 版本 1:标准版(推荐) + +**适合场景:** + +- ✅ 大多数日常使用场景 +- ✅ 只需在 arm64 架构上运行应用 +- ✅ 简单的 CI/CD 流程 +- ✅ 不需要构建多架构容器镜像 + +**特点:** + +- 🪶 轻量级,镜像体积约 400MB +- ⚡ 配置简单,启动快速(5-10 秒) +- 🔒 无需特权模式,更安全 +- 🎯 易于维护 + +### 🚀 版本 2:Buildx 多架构版 + +**适合场景:** + +- ✅ 需要构建 arm64 + amd64 双架构镜像 +- ✅ 发布容器镜像到公共仓库 +- ✅ 跨平台应用开发和测试 +- ✅ 高级 CI/CD 需求 + +**特点:** + +- 🌐 支持 Docker Buildx 多架构构建 +- 🔧 内置 QEMU 跨架构模拟 +- 🤖 自动配置 Buildx builder +- ⚠️ 需要特权模式和 Docker socket + +--- + +## 📦 版本 1:标准版(推荐) + +### 📂 目录结构 + +```txt +gitea-runner/ +├── Dockerfile # 容器构建文件 +├── docker-compose.yml # Docker Compose 配置 +├── entrypoint.sh # 容器启动脚本 +├── setup.sh # Runner 安装脚本 +├── register.sh # Runner 注册脚本 +├── manage.sh # Runner 管理脚本 +└── runner-data/ # 数据持久化目录(自动创建) + └── runners/ # 多个 runner 存储目录 + ├── runner-1/ + │ ├── .runner + │ ├── config.yaml + │ └── cache/ + └── ... +``` + +### 📝 文件配置 + +#### 1️⃣ Dockerfile + +创建 `Dockerfile` 文件: + +```dockerfile +FROM ubuntu:22.04 + +# 设置环境变量避免交互式安装 +ENV DEBIAN_FRONTEND=noninteractive + +# 更新系统并安装必要软件 +RUN apt-get update && apt-get install -y \ + curl \ + git \ + python3 \ + python3-yaml \ + supervisor \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# 创建必要目录 +RUN mkdir -p /data /etc/supervisor/conf.d /var/log/supervisor + +# 设置工作目录 +WORKDIR /data + +# 使用自定义入口点 +ENTRYPOINT ["/data/entrypoint.sh"] +``` + +#### 2️⃣ docker-compose.yml + +创建 `docker-compose.yml` 文件: + +```yaml +services: + gitea-runner: + build: . + container_name: gitea-runner + restart: unless-stopped + volumes: + - ./runner-data:/data + - ./setup.sh:/data/setup.sh:ro + - ./register.sh:/data/register.sh:ro + - ./manage.sh:/data/manage.sh:ro + - ./entrypoint.sh:/data/entrypoint.sh:ro + + # 如果需要在容器内运行 Docker,取消下面的注释 + # - /var/run/docker.sock:/var/run/docker.sock + + environment: + - TZ=Asia/Shanghai + + # 如果需要使用代理,取消下面的注释并修改端口 + # 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP + # - http_proxy=http://host.docker.internal:20122 + # - https_proxy=http://host.docker.internal:20122 + # - HTTP_PROXY=http://host.docker.internal:20122 + # - HTTPS_PROXY=http://host.docker.internal:20122 + # - no_proxy=localhost,127.0.0.1 + + # Linux 系统需要取消下面的注释以支持 host.docker.internal + # extra_hosts: + # - "host.docker.internal:host-gateway" +``` + +#### 3️⃣ entrypoint.sh + +创建 `entrypoint.sh` 文件: + +```bash +#!/bin/bash +set -e + +echo "===================================" +echo "Gitea Runner Container Starting..." +echo "===================================" + +# 定义路径 +PERSISTENT_BIN="/data/bin" +RUNNER_PATH="$PERSISTENT_BIN/act_runner" +SYSTEM_LINK="/usr/local/bin/act_runner" + +# 创建必要目录 +mkdir -p /data/runners +mkdir -p "$PERSISTENT_BIN" +mkdir -p /var/log/supervisor +mkdir -p /var/run + +# 创建主 supervisor 配置文件 +cat > /etc/supervisor/supervisord.conf < $RUNNER_PATH" + fi + + # 验证版本 + RUNNER_VERSION=$("$SYSTEM_LINK" --version 2>/dev/null || echo "unknown") + echo " Version: $RUNNER_VERSION" +elif [ -f "$SYSTEM_LINK" ]; then + # 旧版本可能在系统路径,迁移到持久化目录 + echo "⚠ Found act_runner in system path, migrating to persistent storage..." + cp "$SYSTEM_LINK" "$RUNNER_PATH" + chmod +x "$RUNNER_PATH" + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo " ✓ Migrated to: $RUNNER_PATH" +else + # 没有找到 act_runner + echo "⚠ act_runner not installed yet!" + echo "" + echo "Please run the setup script first:" + echo " docker-compose exec gitea-runner /data/setup.sh" + echo "" + echo "Container is waiting..." + + # 等待 act_runner 安装 + while [ ! -f "$RUNNER_PATH" ] && [ ! -f "$SYSTEM_LINK" ]; do + sleep 10 + done + + # 再次检查并创建链接 + if [ -f "$RUNNER_PATH" ]; then + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo "✓ act_runner detected and linked!" + elif [ -f "$SYSTEM_LINK" ]; then + cp "$SYSTEM_LINK" "$RUNNER_PATH" + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo "✓ act_runner detected and migrated!" + fi +fi + +# 为每个已注册的 runner 创建 supervisor 配置 +echo "" +echo "Scanning for registered runners..." +RUNNER_COUNT=0 + +if [ -d "/data/runners" ]; then + for runner_dir in /data/runners/*/; do + if [ -d "$runner_dir" ]; then + runner_name=$(basename "$runner_dir") + + if [ -f "$runner_dir/.runner" ] && [ -f "$runner_dir/config.yaml" ]; then + echo "Found runner: $runner_name" + + # 创建该 runner 的 supervisor 配置 + cat > "/etc/supervisor/conf.d/runner-${runner_name}.conf" </dev/null | grep -oP 'version \K[0-9.]+' || echo "unknown") + echo "⚠ act_runner already installed (version: $CURRENT_VERSION)" + echo " Location: $INSTALL_PATH" + echo "" + read -p "Do you want to reinstall/upgrade? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Installation cancelled." + exit 0 + fi + rm -f "$INSTALL_PATH" "$SYSTEM_PATH" +fi + +# 获取要安装的版本 +echo "Available versions: https://dl.gitea.com/act_runner/" +echo "" +read -p "Enter version to install (default: 0.2.13): " RUNNER_VERSION +RUNNER_VERSION=${RUNNER_VERSION:-0.2.13} + +ARCH=$(uname -m) +case "$ARCH" in + x86_64) + RUNNER_ARCH="amd64" + ;; + aarch64|arm64) + RUNNER_ARCH="arm64" + ;; + armv7l) + RUNNER_ARCH="arm-7" + ;; + *) + echo "⚠ Unknown architecture: $ARCH" + RUNNER_ARCH="arm64" + ;; +esac + +# 确认架构 +echo "" +echo "Detected architecture: $ARCH -> $RUNNER_ARCH" +read -p "Is this correct? (Y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "" + echo "Available architectures:" + echo " 1. amd64 (x86_64)" + echo " 2. arm64 (aarch64)" + echo " 3. arm-7 (armv7l)" + read -p "Select architecture (1-3): " ARCH_CHOICE + case "$ARCH_CHOICE" in + 1) RUNNER_ARCH="amd64" ;; + 2) RUNNER_ARCH="arm64" ;; + 3) RUNNER_ARCH="arm-7" ;; + *) + echo "Invalid choice. Exiting." + exit 1 + ;; + esac +fi + +DOWNLOAD_URL="https://dl.gitea.com/act_runner/${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-${RUNNER_ARCH}" + +echo "" +echo "Download Configuration:" +echo " Version: $RUNNER_VERSION" +echo " Architecture: $RUNNER_ARCH" +echo " URL: $DOWNLOAD_URL" +echo " Install Location: $INSTALL_PATH (persistent)" +echo "" +read -p "Proceed with download? (Y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "Installation cancelled." + exit 0 +fi + +echo "" +echo "Downloading act_runner..." +echo "" + +# 下载到持久化目录 +if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then + chmod +x "$INSTALL_PATH" + + # 同时创建软链接到系统路径 + ln -sf "$INSTALL_PATH" "$SYSTEM_PATH" + + # 验证安装 + if $INSTALL_PATH --version; then + echo "" + echo "==========================================" + echo "✓ act_runner installed successfully!" + echo "==========================================" + echo "" + echo "Version: $($INSTALL_PATH --version)" + echo "Location: $INSTALL_PATH (persistent storage)" + echo "" + echo "Next steps:" + echo "1. Register the runner:" + echo " docker-compose exec gitea-runner /data/register.sh" + echo "" + echo "2. Restart the container:" + echo " docker-compose restart" + echo "" + echo "Note: act_runner is saved in persistent storage" + echo " and will survive container restarts." + echo "" + else + echo "" + echo "✗ Installation verification failed!" + rm -f "$INSTALL_PATH" "$SYSTEM_PATH" + exit 1 + fi +else + echo "" + echo "✗ Download failed!" + echo "Please check:" + echo " - Internet connection" + echo " - Version number is correct: $RUNNER_VERSION" + echo " - Architecture is correct: $RUNNER_ARCH" + echo " - URL is accessible: $DOWNLOAD_URL" + echo "" + echo "You can check available versions at:" + echo " https://dl.gitea.com/act_runner/" + exit 1 +fi +``` + +#### 5️⃣ register.sh + +创建 `register.sh` 文件: + +```bash +#!/bin/bash +set -e + +echo "==========================================" +echo " Gitea Runner Registration Script " +echo "==========================================" +echo "" + +# 检查 act_runner 是否安装 +if ! command -v act_runner &> /dev/null; then + echo "✗ act_runner is not installed!" + echo "" + echo "Please run the setup script first:" + echo " docker-compose exec gitea-runner /data/setup.sh" + exit 1 +fi + +echo "✓ act_runner found: $(act_runner --version)" +echo "" + +# 获取注册信息并验证 +while true; do + read -p "Enter Gitea instance URL (e.g., https://gitea.example.com): " GITEA_INSTANCE + + # 验证 URL 格式 + if [[ ! "$GITEA_INSTANCE" =~ ^https?:// ]]; then + echo "✗ Error: URL must start with http:// or https://" + echo "" + continue + fi + + # 移除末尾的斜杠 + GITEA_INSTANCE="${GITEA_INSTANCE%/}" + echo "✓ URL validated: $GITEA_INSTANCE" + break +done + +read -p "Enter registration token: " GITEA_TOKEN + +if [ -z "$GITEA_TOKEN" ]; then + echo "✗ Error: Token cannot be empty!" + exit 1 +fi + +read -p "Enter runner name (default: docker-runner): " RUNNER_NAME +RUNNER_NAME=${RUNNER_NAME:-docker-runner} + +# 多个 label(逗号分隔,无空格) +# ubuntu-22.04:host://ubuntu:22.04,ubuntu-20.04:host://ubuntu:20.04,node:docker://node:18 +read -p "Enter runner labels (default: ubuntu-22.04:docker://ubuntu:22.04): " RUNNER_LABELS +RUNNER_LABELS=${RUNNER_LABELS:-ubuntu-22.04:host://ubuntu:22.04} + +# 创建 runner 目录 +RUNNER_DIR="/data/runners/${RUNNER_NAME}" +mkdir -p "$RUNNER_DIR" +cd "$RUNNER_DIR" + +echo "" +echo "Registration Information:" +echo " Instance: $GITEA_INSTANCE" +echo " Name: $RUNNER_NAME" +echo " Labels: $RUNNER_LABELS" +echo " Directory: $RUNNER_DIR" +echo "" + +# 检查是否已经注册 +if [ -f ".runner" ] || [ -f "config.yaml" ]; then + echo "⚠ Runner already exists in this directory!" + read -p "Do you want to re-register? This will overwrite existing configuration. (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Registration cancelled." + exit 0 + fi + + echo "Cleaning up existing runner..." + + # 停止现有 runner + echo "Stopping existing runner..." + supervisorctl stop "runner-${RUNNER_NAME}" 2>/dev/null || true + + # 删除 supervisor 配置 + rm -f "/etc/supervisor/conf.d/runner-${RUNNER_NAME}.conf" + + # 重新加载 supervisor + supervisorctl reread 2>/dev/null || true + supervisorctl update 2>/dev/null || true + + # 删除日志 + rm -f "/var/log/supervisor/runner-${RUNNER_NAME}".*.log* + + # 删除旧配置和缓存 + rm -f .runner config.yaml + rm -rf cache +fi + +# 执行注册 +echo "" +echo "Registering runner..." +act_runner register \ + --instance "$GITEA_INSTANCE" \ + --token "$GITEA_TOKEN" \ + --name "$RUNNER_NAME" \ + --labels "$RUNNER_LABELS" \ + --no-interactive + +if [ ! -f ".runner" ]; then + echo "" + echo "✗ Registration failed! .runner file not created." + exit 1 +fi + +echo "✓ Registration successful!" + +# 生成配置文件 +echo "" +echo "Generating config.yaml..." +act_runner generate-config > config.yaml + +echo "✓ Configuration file generated!" + +# 创建缓存目录 +mkdir -p cache + +# 使用 Python 修改配置(最可靠的方法) +echo "" +echo "Configuring runner settings..." + +if command -v python3 &> /dev/null; then + python3 << PYEOF +import yaml +import sys + +try: + # 读取生成的配置 + with open('config.yaml', 'r') as f: + config = yaml.safe_load(f) + + # 读取 .runner 获取实际注册的 labels + import json + with open('.runner', 'r') as f: + runner_data = json.load(f) + + # 使用 .runner 中的 labels(这是实际注册的) + registered_labels = runner_data.get('labels', []) + + # 修改配置 + if 'runner' not in config: + config['runner'] = {} + + # 使用实际注册的 labels + config['runner']['labels'] = registered_labels + config['runner']['capacity'] = 2 + + # 启用缓存 + if 'cache' not in config: + config['cache'] = {} + config['cache']['enabled'] = True + config['cache']['dir'] = './cache' + + # 保存配置 + with open('config.yaml', 'w') as f: + yaml.dump(config, f, default_flow_style=False, sort_keys=False) + + print("✓ Configuration updated using Python") + print(f" - Labels: {registered_labels}") + print(f" - Capacity: 2") + print(f" - Cache enabled: ./cache") + sys.exit(0) + +except Exception as e: + print(f"✗ Python configuration failed: {e}", file=sys.stderr) + sys.exit(1) +PYEOF + + PYTHON_EXIT=$? + + if [ $PYTHON_EXIT -ne 0 ]; then + echo "" + echo "⚠ Python configuration failed, using basic sed..." + + # 基本的 sed 修改(只修改简单的值,不动 labels) + sed -i 's/capacity: 1/capacity: 2/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 + + echo "✓ Basic configuration applied" + echo " Note: Please manually verify labels in config.yaml match .runner" + fi +else + echo "⚠ Python3 not found, applying basic configuration..." + + # 基本的 sed 修改 + sed -i 's/capacity: 1/capacity: 2/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 + + echo "✓ Basic configuration applied" + echo " Note: Labels will use act_runner defaults" +fi + +# 验证配置文件 +echo "" +echo "Validating configuration..." + +# 检查 YAML 语法 +if command -v python3 &> /dev/null; then + python3 << PYEOF +import yaml +import sys +try: + with open('config.yaml', 'r') as f: + yaml.safe_load(f) + print("✓ config.yaml syntax is valid") + sys.exit(0) +except Exception as e: + print(f"✗ config.yaml syntax error: {e}", file=sys.stderr) + sys.exit(1) +PYEOF + + if [ $? -ne 0 ]; then + echo "" + echo "✗ Configuration file has syntax errors!" + echo " Backup available at: config.yaml.bak" + exit 1 + fi +fi + +# 显示配置摘要 +echo "" +echo "Configuration Summary:" +echo "-------------------------------------------" +echo ".runner labels:" +cat .runner | grep -A 10 '"labels"' | head -15 +echo "" +echo "config.yaml labels:" +grep -A 5 "^ labels:" config.yaml | head -10 + +# 创建 supervisor 配置 +echo "" +echo "Creating supervisor configuration..." +cat > "/etc/supervisor/conf.d/runner-${RUNNER_NAME}.conf" </dev/null || \ + supervisorctl start "runner-${RUNNER_NAME}" + +# 等待启动 +sleep 3 + +# 显示状态 +RUNNER_STATUS=$(supervisorctl status "runner-${RUNNER_NAME}" 2>/dev/null || echo "UNKNOWN") + +echo "" +echo "==========================================" +echo "✓ Runner registered and started!" +echo "==========================================" +echo "" +echo "Runner Information:" +echo " Name: $RUNNER_NAME" +echo " Directory: $RUNNER_DIR" +echo " Status: $RUNNER_STATUS" +echo "" +echo "Configuration files:" +echo " .runner: $(ls -lh .runner 2>/dev/null | awk '{print $5}' || echo 'N/A')" +echo " config.yaml: $(ls -lh config.yaml 2>/dev/null | awk '{print $5}' || echo 'N/A')" +echo "" +echo "Useful commands:" +echo " View logs: docker-compose exec gitea-runner /data/manage.sh logs ${RUNNER_NAME}" +echo " Follow logs: docker-compose exec gitea-runner /data/manage.sh follow ${RUNNER_NAME}" +echo " Check status: docker-compose exec gitea-runner /data/manage.sh status" +echo " Restart: docker-compose exec gitea-runner /data/manage.sh restart ${RUNNER_NAME}" +echo "" + +# 显示最近的日志 +if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.out.log" ]; then + echo "Recent logs:" + echo "-------------------------------------------" + tail -n 20 "/var/log/supervisor/runner-${RUNNER_NAME}.out.log" 2>/dev/null || echo "No logs yet" + echo "" +fi + +# 检查是否有错误 +if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.err.log" ]; then + # 只查找 error/fatal/panic 级别的日志 + ERR_CONTENT=$(grep -E 'level=(error|fatal|panic)' \ + "/var/log/supervisor/runner-${RUNNER_NAME}.err.log" | tail -n 5 2>/dev/null) + + if [ -n "$ERR_CONTENT" ]; then + echo "❌ Recent errors detected:" + echo "-------------------------------------------" + echo "$ERR_CONTENT" + echo "" + echo "Check full error log with:" + echo " docker-compose exec gitea-runner cat /var/log/supervisor/runner-${RUNNER_NAME}.err.log" + fi +fi +``` + +#### 6️⃣ manage.sh + +创建 `manage.sh` 文件: + +```bash +#!/bin/bash + +echo "==========================================" +echo " Gitea Runner Management Tool " +echo "==========================================" +echo "" + +# 函数:列出所有 runners +list_runners() { + echo "Registered Runners:" + echo "-------------------------------------------" + + if [ ! -d "/data/runners" ] || [ -z "$(ls -A /data/runners 2>/dev/null)" ]; then + echo "No runners registered yet." + return + fi + + printf "%-20s %-15s %-30s\n" "Name" "Status" "Log File" + echo "-------------------------------------------" + + for runner_dir in /data/runners/*/; do + if [ -d "$runner_dir" ]; then + runner_name=$(basename "$runner_dir") + + if [ -f "$runner_dir/.runner" ]; then + # 获取状态 + status=$(supervisorctl status "runner-${runner_name}" 2>/dev/null | awk '{print $2}') + [ -z "$status" ] && status="NOT_LOADED" + + log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + printf "%-20s %-15s %-30s\n" "$runner_name" "$status" "$log_file" + fi + fi + done + echo "" +} + +# 函数:查看 runner 详细信息 +show_runner() { + local runner_name=$1 + local runner_dir="/data/runners/${runner_name}" + + if [ ! -d "$runner_dir" ]; then + echo "✗ Runner '$runner_name' not found!" + return 1 + fi + + echo "Runner Details: $runner_name" + echo "-------------------------------------------" + echo "Directory: $runner_dir" + + if [ -f "$runner_dir/config.yaml" ]; then + echo "" + echo "Configuration:" + cat "$runner_dir/config.yaml" + fi + + echo "" + echo "Status:" + supervisorctl status "runner-${runner_name}" 2>/dev/null | awk '{print $2}' + echo "" +} + +# 函数:删除 runner +delete_runner() { + local runner_name=$1 + local runner_dir="/data/runners/${runner_name}" + + if [ ! -d "$runner_dir" ]; then + echo "✗ Runner '$runner_name' not found!" + return 1 + fi + + echo "⚠ Warning: This will permanently delete runner '$runner_name'" + read -p "Are you sure? (y/N): " -n 1 -r + echo + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Deletion cancelled." + return 0 + fi + + echo "Stopping runner..." + supervisorctl stop "runner-${runner_name}" 2>/dev/null || true + + echo "Removing configuration..." + rm -f "/etc/supervisor/conf.d/runner-${runner_name}.conf" + + echo "Deleting runner directory..." + rm -rf "$runner_dir" + + echo "Updating supervisor..." + supervisorctl reread + supervisorctl update + + echo "" + echo "✓ Runner '$runner_name' deleted successfully!" +} + +# 函数:查看 runner 日志 +logs_runner() { + local runner_name=$1 + local lines=${2:-50} + + local log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + if [ ! -f "$log_file" ]; then + echo "✗ Log file not found for runner '$runner_name'" + return 1 + fi + + echo "Showing last $lines lines of '$runner_name' logs:" + echo "-------------------------------------------" + tail -n "$lines" "$log_file" +} + +# 函数:实时查看日志 +follow_logs() { + local runner_name=$1 + local log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + if [ ! -f "$log_file" ]; then + echo "✗ Log file not found for runner '$runner_name'" + return 1 + fi + + echo "Following logs for '$runner_name' (Press Ctrl+C to exit):" + echo "-------------------------------------------" + tail -f "$log_file" +} + +# 函数:启动/停止/重启 runner +control_runner() { + local action=$1 + local runner_name=$2 + + case $action in + start|stop|restart) + echo "${action^}ing runner '$runner_name'..." + supervisorctl "$action" "runner-${runner_name}" + ;; + *) + echo "✗ Invalid action: $action" + return 1 + ;; + esac +} + +# 函数:显示所有 runner 状态 +status_all() { + echo "All Runners Status:" + echo "-------------------------------------------" + supervisorctl status | grep "^runner-" || echo "No runners running." + echo "" +} + +# 主菜单 +show_menu() { + echo "Choose an action:" + echo " 1) List all runners" + echo " 2) Show runner details" + echo " 3) Add new runner" + echo " 4) Delete runner" + echo " 5) Start runner" + echo " 6) Stop runner" + echo " 7) Restart runner" + echo " 8) View runner logs" + echo " 9) Follow runner logs (real-time)" + echo " 10) Show all runners status" + echo " 0) Exit" + echo "" +} + +# 主程序 +if [ $# -eq 0 ]; then + # 交互模式 + while true; do + show_menu + read -p "Enter your choice: " choice + echo "" + + case $choice in + 1) + list_runners + ;; + 2) + read -p "Enter runner name: " runner_name + show_runner "$runner_name" + ;; + 3) + echo "Starting registration process..." + /data/register.sh + ;; + 4) + read -p "Enter runner name to delete: " runner_name + delete_runner "$runner_name" + ;; + 5) + read -p "Enter runner name to start: " runner_name + control_runner start "$runner_name" + ;; + 6) + read -p "Enter runner name to stop: " runner_name + control_runner stop "$runner_name" + ;; + 7) + read -p "Enter runner name to restart: " runner_name + control_runner restart "$runner_name" + ;; + 8) + read -p "Enter runner name: " runner_name + read -p "Number of lines (default 50): " lines + logs_runner "$runner_name" "${lines:-50}" + ;; + 9) + read -p "Enter runner name: " runner_name + follow_logs "$runner_name" + ;; + 10) + status_all + ;; + 0) + echo "Goodbye!" + exit 0 + ;; + *) + echo "Invalid choice!" + ;; + esac + + echo "" + read -p "Press Enter to continue..." + clear + done +else + # 命令行模式 + case $1 in + list|ls) + list_runners + ;; + show|info) + show_runner "$2" + ;; + add|register) + /data/register.sh + ;; + delete|rm|remove) + delete_runner "$2" + ;; + start) + control_runner start "$2" + ;; + stop) + control_runner stop "$2" + ;; + restart) + control_runner restart "$2" + ;; + logs) + logs_runner "$2" "${3:-50}" + ;; + follow) + follow_logs "$2" + ;; + status) + status_all + ;; + *) + echo "Usage: $0 [command] [runner_name]" + echo "" + echo "Commands:" + echo " list - List all runners" + echo " show - Show runner details" + echo " add - Add new runner" + echo " delete - Delete a runner" + echo " start - Start a runner" + echo " stop - Stop a runner" + echo " restart - Restart a runner" + echo " logs [lines] - View runner logs" + echo " follow - Follow runner logs (real-time)" + echo " status - Show all runners status" + echo "" + echo "Or run without arguments for interactive mode." + exit 1 + ;; + esac +fi +``` + +### 🚀 部署步骤 + +#### 1. 设置脚本权限 + +```bash +chmod +x entrypoint.sh setup.sh register.sh manage.sh +``` + +#### 2. 构建并启动容器 + +```bash +docker-compose build +docker-compose up -d +``` + +#### 3. 安装 Runner + +```bash +docker-compose exec gitea-runner /data/setup.sh +``` + +按照提示选择版本和架构。 + +#### 4. 注册 Runner + +```bash +docker-compose exec gitea-runner /data/register.sh +``` + +输入你的 Gitea 实例 URL 和注册令牌。 + +#### 5. 重启容器启动 Runner + +```bash +docker-compose restart +``` + +#### 6. 验证运行状态 + +```bash +# 查看日志 +docker-compose logs -f + +# 查看 runner 状态 +docker-compose exec gitea-runner /data/manage.sh list +docker-compose exec gitea-runner /data/manage.sh status +``` + +--- + +## 🚀 版本 2:Buildx 多架构版 + +### 📝 文件配置差异 + +Buildx 版本与标准版的主要区别在于以下文件: + +#### 1️⃣ Dockerfile(Buildx 版) + +```dockerfile +FROM ubuntu:22.04 + +# 设置环境变量避免交互式安装 +ENV DEBIAN_FRONTEND=noninteractive + +# 更新系统并安装必要软件 +RUN apt-get update && apt-get install -y \ + curl \ + git \ + python3 \ + python3-yaml \ + supervisor \ + ca-certificates \ + gnupg \ + lsb-release \ + qemu-user-static \ + binfmt-support \ + && rm -rf /var/lib/apt/lists/* + +# 安装 Docker(包含 Buildx 插件) +RUN curl -fsSL https://get.docker.com -o get-docker.sh && \ + sh get-docker.sh && \ + rm get-docker.sh + +# 验证安装 +RUN docker --version && \ + qemu-aarch64-static --version && \ + qemu-x86_64-static --version + +# 创建必要目录 +RUN mkdir -p /data /etc/supervisor/conf.d /var/log/supervisor + +# 设置工作目录 +WORKDIR /data + +# 使用自定义入口点 +ENTRYPOINT ["/data/entrypoint.sh"] +``` + +#### 2️⃣ docker-compose.yml(Buildx 版) + +```yaml +services: + gitea-runner: + build: . + container_name: gitea-runner + restart: unless-stopped + privileged: true + volumes: + - ./runner-data:/data + - ./setup.sh:/data/setup.sh:ro + - ./register.sh:/data/register.sh:ro + - ./manage.sh:/data/manage.sh:ro + - ./entrypoint.sh:/data/entrypoint.sh:ro + - /var/run/docker.sock:/var/run/docker.sock + + environment: + - TZ=Asia/Shanghai + + # 如果需要使用代理,取消下面的注释并修改为你的代理地址 + # 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP + - http_proxy=http://host.docker.internal:20122 + - https_proxy=http://host.docker.internal:20122 + - HTTP_PROXY=http://host.docker.internal:20122 + - HTTPS_PROXY=http://host.docker.internal:20122 + # - no_proxy=localhost,127.0.0.1 + + # Linux 系统需要取消下面的注释以支持 host.docker.internal + # extra_hosts: + # - "host.docker.internal:host-gateway" +``` + +#### 3️⃣ entrypoint.sh(Buildx 版) + +```bash +#!/bin/bash +set -e + +echo "===================================" +echo "Gitea Runner Container Starting..." +echo "===================================" + +# 定义路径 +PERSISTENT_BIN="/data/bin" +RUNNER_PATH="$PERSISTENT_BIN/act_runner" +SYSTEM_LINK="/usr/local/bin/act_runner" + +# 创建必要目录 +mkdir -p /data/runners +mkdir -p "$PERSISTENT_BIN" +mkdir -p /data/buildx +mkdir -p /var/log/supervisor +mkdir -p /var/run + +# ============================================ +# 初始化 Docker Buildx 支持 +# ============================================ +echo "" +echo "Initializing Docker Buildx..." + +# 启动 Docker 守护进程(如果使用主机 socket 则跳过) +if [ -S /var/run/docker.sock ]; then + echo "✓ Using host Docker socket" +else + echo "Starting Docker daemon..." + dockerd > /var/log/dockerd.log 2>&1 & + sleep 5 +fi + +# 等待 Docker 就绪 +echo "Waiting for Docker daemon..." +for i in {1..30}; do + if docker info > /dev/null 2>&1; then + echo "✓ Docker daemon is ready" + break + fi + if [ $i -eq 30 ]; then + echo "✗ Docker daemon failed to start" + [ -f /var/log/dockerd.log ] && cat /var/log/dockerd.log + exit 1 + fi + sleep 1 +done + +# 注册 QEMU binfmt +echo "" +echo "Registering QEMU binary formats..." +update-binfmts --enable 2>/dev/null || { + echo "⚠ binfmt_misc not available, trying to mount..." + mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc 2>/dev/null || true + update-binfmts --enable +} + +# 验证多架构支持 +echo "Verifying multi-arch support..." +if docker run --rm arm64v8/alpine uname -m > /dev/null 2>&1; then + echo " ✓ arm64 support verified" +else + echo " ⚠ arm64 verification failed" +fi + +if docker run --rm amd64/alpine uname -m > /dev/null 2>&1; then + echo " ✓ amd64 support verified" +else + echo " ⚠ amd64 verification failed" +fi + +# 配置 Buildx +if [ ! -f "/data/buildx/.configured" ]; then + echo "" + echo "Setting up Buildx for the first time..." + + # 创建 BuildKit 配置 + cat > /data/buildx/buildkitd.toml </dev/null || \ + docker buildx use gitea-multiarch 2>/dev/null + + # 验证 + echo "Verifying Buildx..." + docker buildx inspect --bootstrap > /dev/null 2>&1 + + # 标记为已配置 + touch /data/buildx/.configured + + echo "✓ Buildx configured successfully!" + docker buildx inspect | grep "Platforms:" | head -1 +else + echo "✓ Buildx already configured" + + # 确保 builder 可用 + docker buildx use gitea-multiarch 2>/dev/null || { + echo "⚠ Recreating Buildx builder..." + docker buildx rm gitea-multiarch 2>/dev/null || true + docker buildx create \ + --name gitea-multiarch \ + --driver docker-container \ + --bootstrap \ + --use 2>/dev/null + } +fi + +echo "" + +# ============================================ +# 检查 act_runner 安装 +# ============================================ +# 创建主 supervisor 配置文件 +cat > /etc/supervisor/supervisord.conf < $RUNNER_PATH" + fi + + RUNNER_VERSION=$("$SYSTEM_LINK" --version 2>/dev/null || echo "unknown") + echo " Version: $RUNNER_VERSION" +elif [ -f "$SYSTEM_LINK" ]; then + echo "⚠ Found act_runner in system path, migrating to persistent storage..." + cp "$SYSTEM_LINK" "$RUNNER_PATH" + chmod +x "$RUNNER_PATH" + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo " ✓ Migrated to: $RUNNER_PATH" +else + echo "⚠ act_runner not installed yet!" + echo "" + echo "Please run the setup script first:" + echo " docker-compose exec gitea-runner /data/setup.sh" + echo "" + echo "Container is waiting..." + + while [ ! -f "$RUNNER_PATH" ] && [ ! -f "$SYSTEM_LINK" ]; do + sleep 10 + done + + if [ -f "$RUNNER_PATH" ]; then + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo "✓ act_runner detected and linked!" + elif [ -f "$SYSTEM_LINK" ]; then + cp "$SYSTEM_LINK" "$RUNNER_PATH" + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo "✓ act_runner detected and migrated!" + fi +fi + +# ============================================ +# 配置已注册的 Runners +# ============================================ +echo "" +echo "Scanning for registered runners..." +RUNNER_COUNT=0 + +if [ -d "/data/runners" ]; then + for runner_dir in /data/runners/*/; do + if [ -d "$runner_dir" ]; then + runner_name=$(basename "$runner_dir") + + if [ -f "$runner_dir/.runner" ] && [ -f "$runner_dir/config.yaml" ]; then + echo "Found runner: $runner_name" + + cat > "/etc/supervisor/conf.d/runner-${runner_name}.conf" < 🚀 Gitea Runner Docker 部署模板 + Actions Workflow 示例 + 文档规范 + +## 📖 项目简介 + +这是一个完整的 Gitea Runner 模板项目,提供: + +- 🐳 **Docker 部署方案**:开箱即用的 Runner 容器化部署 +- 📝 **Workflow 示例**:自动化 CHANGELOG 和 Release 工作流 +- 📚 **文档规范**:统一的文档格式和版本管理规范 +- 🔧 **配置指南**:详细的配置说明和最佳实践 + +## 📂 文档导航 + +### 🚀 Runner + +#### [DEPLOYMENT.md](./DEPLOYMENT.md) + +**Gitea Runner Docker 部署完整教程** + +包含内容: + +- 📦 标准版 vs Buildx 多架构版选择 +- 📝 Dockerfile 和 docker-compose.yml 配置 +- 🔧 安装、注册、管理脚本详解 +- ⚙️ 完整的部署流程 +- ❓ 常见问题和故障排除 + +👉 **完整的 Runner 部署教程,从零开始搭建** + +--- + +### 📋 [Workflow](./WORKFLOW.md) + +**Gitea Actions 自动化工作流示例** + +包含内容: + +- 🔄 `changelog_and_release.yml` - 自动更新 CHANGELOG和自动创建Release +- 💡 工作流配置说明 +- 🔧 如何使用和定制 + +👉 **两个实用的 Actions 工作流,可直接复制使用** + +--- + +### 📝 [Conventions](./CONVENTIONS.md) + +**文档编写规范和版本命名规则** + +包含内容: + +- 📊 Emoji 图标使用规范 +- 🔢 版本号命名规则(SemVer) +- 📝 [CHANGELOG](./CHANGELOG.md) 模板 + +👉 **维护项目文档的统一规范,贡献者必读** + +--- diff --git a/WORKFLOW.md b/WORKFLOW.md new file mode 100644 index 0000000..7f3fc8e --- /dev/null +++ b/WORKFLOW.md @@ -0,0 +1,880 @@ +# 📦 Tag Release Automation + +自动生成 CHANGELOG 并创建 Release 的一体化工作流,**支持语义版本号(SemVer)和内容叠加**。 + +--- + +## 📂 文件位置 + +```txt +.gitea/workflows/ +└── changelog-and-release.yaml # CHANGELOG 生成 + Release 创建 +``` + +--- + +## 🚀 快速开始 + +### 1️⃣ 配置 Gitea Token + +#### 生成 Token + +1. 登录 Gitea → 右上角头像 → **设置 (Settings)** → **应用 (Applications)** +2. 点击 **生成新令牌 (Generate New Token)** +3. 配置权限: + - ✅ **repo** - 完整的仓库访问权限(必需) +4. 复制生成的 Token(⚠️ 只显示一次) + +#### 添加 Secret + +**推荐方式:仓库级别配置** + +1. 进入仓库 → **设置 (Settings)** → **Actions** → **Secrets** +2. 点击 **新建 Secret (New Secret)** +3. 填写: + - **名称**:`GITEA_TOKEN` + - **值**:粘贴刚才的 token +4. 点击 **保存** + +💡 **提示**:如果有多个仓库需要使用,可以在个人设置中添加全局 Secret + +### 2️⃣ 部署 Workflow + +```bash +# 创建目录 +mkdir -p .gitea/workflows + +# 复制文件(将文件重命名为 changelog-and-release.yaml) +cp changelog-and-release.yaml .gitea/workflows/ + +# 提交 +git add .gitea/workflows/ +git commit -m "ci: add changelog and release automation" +git push origin main +``` + +### 3️⃣ 自定义配置 + +打开 `changelog-and-release.yaml`,找到 **⚙️ Configuration** 步骤(约第 10-60 行),所有配置都在这里: + +```yaml +- name: ⚙️ Configuration + id: config + run: | + # ======================================== + # 📝 自定义配置区域 - 在此修改所有配置 + # ======================================== + + # 📌 语义版本号规范 (SemVer) + # 格式: MAJOR.MINOR.PATCH[-prerelease] + # + # 示例: 1.0.0, 1.1.0, 1.1.1, 1.0.0-beta1, 2.0.0-rc1 + # + # ⚠️ 重要规则: + # 1. 开发版本以 .0 开始 (如 1.0.0-beta1, 不是 1.0.1-beta1) + # 2. 多个开发版本会累加到同一 CHANGELOG 区域 (推荐使用 strip 模式) + # 3. CHANGELOG 内容是叠加而非覆盖 + # + # 📖 详细规范请参考: CONVENTIONS.md + + # Gitea 服务器地址(用于生成头像链接) + GITEA_SERVER="https://git.mytsl.cn" + + # CHANGELOG 配置 + CHANGELOG_SECTION_TITLE="### :pencil: What's Changed" + CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors" + + # CHANGELOG 版本号处理方式 + # - "full": 使用完整 tag 名称(1.0.0-beta → ## :bookmark: 1.0.0-beta) + # - "strip": 去除 pre-release 后缀(1.0.0-beta1 → ## :bookmark: 1.0.0) + # ⚠️ 推荐使用 "strip" 模式以支持多个开发版本叠加到同一CHANGELOG区域 + CHANGELOG_VERSION_MODE="strip" + + # Release 配置 + RELEASE_TITLE="{version}" # 可用变量: {version} + + # Pre-release 配置 + # - "false": 正式版本(会被标记为 latest) + # - "true": 预发布版本(会被标记为 pre-release) + # - "auto": 自动判断(根据 tag 名称中是否包含 alpha/beta/rc) + RELEASE_PRERELEASE_MODE="auto" + + # Draft 配置(是否创建为草稿) + RELEASE_IS_DRAFT="false" # true 或 false + + # 需要忽略的提交模式 + IGNORE_PATTERNS=( + "^Merge " + "^:memo: Auto update CHANGELOG" + "\[skip ci\]" + "\[ci skip\]" + "^style: " + "^chore: format" + ) + + # Git 提交消息格式 + COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]" + + # 额外要上传到 Release 的文件 + ADDITIONAL_RELEASE_FILES="" # 例如: "README.md LICENSE" +``` + +#### 常见配置示例 + +**修改 Gitea 服务器地址**: + +```bash +GITEA_SERVER="https://your-gitea.com" +``` + +**修改 CHANGELOG 标题为中文**: + +```bash +CHANGELOG_SECTION_TITLE="### :pencil: 变更内容" +CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: 贡献者" +``` + +**配置 CHANGELOG 版本号处理方式**: + +```bash +# ⭐ 推荐:strip 模式(支持内容叠加) +CHANGELOG_VERSION_MODE="strip" +# 所有相同前缀的版本共享同一个 CHANGELOG 区域,内容累积 +# 1.0.0-beta1 → ## :bookmark: 1.0.0 +# 1.0.0-beta2 → ## :bookmark: 1.0.0 (追加内容) +# 1.0.0 → ## :bookmark: 1.0.0 (追加内容) + +# 方式 2: full 模式(每个 tag 独立条目) +CHANGELOG_VERSION_MODE="full" +# 每个 tag 都有独立的 CHANGELOG 条目 +# 1.0.0-beta1 → ## :bookmark: 1.0.0-beta1 +# 1.0.0-beta2 → ## :bookmark: 1.0.0-beta2 +# 1.0.0 → ## :bookmark: 1.0.0 +``` + +**推荐使用 `strip` 模式的原因**: + +1. ✅ **支持内容叠加** - 多个开发版本的提交累积在一起 +2. ✅ **避免重复** - 所有版本的改动集中在一个区域 +3. ✅ **完整历史** - 正式发布时 CHANGELOG 已包含所有开发阶段的提交 +4. ✅ **符合最佳实践** - 大多数开源项目的做法 + +**strip 模式工作流程示例**: + +```bash +# 1. 推送第一个 beta 版本 +git push origin 1.0.0-beta1 +# → CHANGELOG: ## :bookmark: 1.0.0(创建新区域,添加 beta1 的提交) +# → Release: 1.0.0-beta1 (Pre-release 🏷️) + +# 2. 推送第二个 beta 版本 +git push origin 1.0.0-beta2 +# → CHANGELOG: ## :bookmark: 1.0.0(叠加 beta2 的提交到现有区域) +# → Release: 1.0.0-beta2 (Pre-release 🏷️) + +# 3. 推送 rc 版本 +git push origin 1.0.0-rc1 +# → CHANGELOG: ## :bookmark: 1.0.0(继续叠加 rc1 的提交) +# → Release: 1.0.0-rc1 (Pre-release 🏷️) + +# 4. 推送正式版本 +git push origin 1.0.0 +# → CHANGELOG: ## :bookmark: 1.0.0(叠加正式版的提交,现在包含完整历史) +# → Release: 1.0.0 (Latest ✅) +``` + +**生成的 CHANGELOG 示例**(叠加模式): + +```markdown +## :bookmark: 1.0.0 + +### :pencil: What's Changed + +- :sparkles: Add user authentication (来自 beta1) +- :sparkles: Add profile page (来自 beta1) +- :bug: Fix login redirect (来自 beta2) +- :sparkles: Add password reset (来自 beta2) +- :memo: Update documentation (来自 rc1) +- :bug: Fix validation error (来自正式版) + +### :busts_in_silhouette: Contributors + +[所有版本的贡献者,自动去重] + +--- +``` + +**修改 Release 标题**: + +```bash +RELEASE_TITLE="{version}" # → 1.0.0 +RELEASE_TITLE="v{version}" # → v1.0.0 +RELEASE_TITLE="Release {version}" # → Release 1.0.0 +RELEASE_TITLE="🎉 {version}" # → 🎉 1.0.0 +``` + +**配置 Pre-release 标记**: + +```bash +# 方式 1: 自动判断(推荐) +RELEASE_PRERELEASE_MODE="auto" +# tag 包含 alpha/beta/rc/pre/preview/dev/test → Pre-release 🏷️ +# 其他 tag → Latest (Stable) ✅ + +# 方式 2: 始终标记为正式版本 +RELEASE_PRERELEASE_MODE="false" +# 所有 Release 都标记为 Latest ✅ + +# 方式 3: 始终标记为预发布版本 +RELEASE_PRERELEASE_MODE="true" +# 所有 Release 都标记为 Pre-release 🏷️ +``` + +**示例效果**: + +```bash +# auto 模式下: +1.0.0 → Latest ✅ +1.0.0-beta1 → Pre-release 🏷️ +1.0.0-rc1 → Pre-release 🏷️ +1.0.0-alpha1 → Pre-release 🏷️ +1.0.0.dev → Pre-release 🏷️ +``` + +**创建为草稿**: + +```bash +RELEASE_IS_DRAFT="true" # Release 创建后不会立即发布,需要手动发布 +``` + +**添加更多忽略的提交类型**: + +```bash +IGNORE_PATTERNS=( + "^Merge " + "^:memo: Auto update CHANGELOG" + "\[skip ci\]" + "^test: " # 添加:忽略测试 + "^docs: " # 添加:忽略文档 + "^refactor: " # 添加:忽略重构 +) +``` + +**上传额外的文件到 Release**: + +```bash +ADDITIONAL_RELEASE_FILES="README.md LICENSE docs/guide.pdf" +``` + +**自定义提交消息**: + +```bash +COMMIT_MESSAGE="docs: update CHANGELOG for {version}" +COMMIT_MESSAGE="chore(release): update CHANGELOG [{version}]" +``` + +### 4️⃣ 创建 Release + +#### 完整开发流程示例 + +```bash +# === 第一阶段:Alpha 内部测试 === +git commit -m ":sparkles: Add user authentication" +git tag 1.0.0-alpha1 +git push origin 1.0.0-alpha1 + +git commit -m ":sparkles: Add user profile" +git tag 1.0.0-alpha2 +git push origin 1.0.0-alpha2 + +# === 第二阶段:Beta 公开测试 === +git commit -m ":bug: Fix login redirect" +git tag 1.0.0-beta1 +git push origin 1.0.0-beta1 + +git commit -m ":sparkles: Add password reset" +git tag 1.0.0-beta2 +git push origin 1.0.0-beta2 + +# === 第三阶段:RC 发布候选 === +git commit -m ":memo: Update documentation" +git tag 1.0.0-rc1 +git push origin 1.0.0-rc1 + +# === 第四阶段:正式发布 === +git tag 1.0.0 +git push origin 1.0.0 + +# === 后续版本 === +# 新增功能(向后兼容) +git commit -m ":sparkles: Add email notifications" +git tag 1.1.0 +git push origin 1.1.0 + +# Bug 修复 +git commit -m ":bug: Fix email sending" +git tag 1.1.1 +git push origin 1.1.1 + +# 破坏性变更 +git commit -m ":boom: Change API format" +git tag 2.0.0 +git push origin 2.0.0 +``` + +🎉 完成!工作流会自动: + +1. ✅ 加载自定义配置 +2. ✅ 生成/叠加 CHANGELOG 条目 +3. ✅ 合并并去重贡献者 +4. ✅ 提交并推送到 main 分支 +5. ✅ 创建 Release(自动识别 Pre-release) +6. ✅ 上传 CHANGELOG.md 及额外文件 + +--- + +## 📋 工作原理 + +### 触发条件 + +**推送数字开头的 tag**: + +```bash +# ✅ 会触发 +git push origin 1.0.0 +git push origin 1.1.0-beta1 +git push origin 2.0.0-rc1 + +# ❌ 不会触发(不是数字开头) +git push origin v1.0.0 +git push origin release-1.0 +``` + +### 执行流程 + +```txt +Tag Push (1.0.0-beta1) + ↓ +⚙️ 加载配置参数 + ↓ +📂 检出代码 + ↓ +🤖 检查是否为 bot 提交 + ↓ +📋 检查版本是否已存在(1.0.0) + ↓ 已存在 → 叠加模式 + ↓ 不存在 → 创建新条目 +📝 生成 CHANGELOG 条目 + ├─ 提取旧的提交记录(如果存在) + ├─ 提取旧的贡献者(如果存在) + ├─ 追加新的提交记录 + ├─ 合并并去重贡献者 + └─ 生成完整的条目 + ↓ +📤 提交并推送 + ↓ +🗑️ 删除旧 Release + ↓ +🚀 创建新 Release + ├─ 使用配置的标题格式 + ├─ 自动识别 Pre-release + └─ 上传配置的附件列表 + ↓ +✅ 完成 +``` + +### 版本号处理逻辑 + +#### Strip 模式(推荐) + +```txt +Tag: 1.0.0-beta1 + ↓ strip 处理 +CHANGELOG Version: 1.0.0 + ↓ +检查 1.0.0 是否存在? + ├─ 不存在 → 创建新区域 + └─ 已存在 → 叠加内容 + +Tag: 1.0.0-beta2 + ↓ strip 处理 +CHANGELOG Version: 1.0.0 + ↓ +检查 1.0.0 是否存在? + └─ 已存在 → 叠加内容(保留 beta1 的提交) + +Tag: 1.0.0 + ↓ strip 处理 +CHANGELOG Version: 1.0.0 + ↓ +检查 1.0.0 是否存在? + └─ 已存在 → 叠加内容(保留所有之前的提交) +``` + +### 配置加载日志示例 + +执行时会显示配置摘要: + +```txt +====================================== +⚙️ Loading workflow configuration +====================================== + +📋 Configuration Summary: + 🏷️ Tag version: 1.0.0-beta1 + 📝 CHANGELOG version: 1.0.0 + 🌐 Gitea Server: https://git.mytsl.cn + 📄 CHANGELOG Title: ### :pencil: What's Changed + 🚀 Release Title: 1.0.0-beta1 + 🏷️ Pre-release: yes (auto-detected: 1.0.0-beta1 contains pre-release keyword) + 📄 Draft: false + 💬 Commit Message: :memo: Auto update CHANGELOG for 1.0.0-beta1 [skip ci] + 📎 Additional Files: README.md LICENSE + +✅ Configuration loaded successfully +``` + +### 生成的内容示例 + +#### CHANGELOG.md(叠加模式) + +```markdown +## :bookmark: 1.0.0 + +### :pencil: What's Changed + +- :sparkles: Add user authentication ([a1b2c3d](链接)) by @developer1 +- :sparkles: Add user profile ([e4f5g6h](链接)) by @developer1 +- :bug: Fix login redirect ([i7j8k9l](链接)) by @developer2 +- :sparkles: Add password reset ([m0n1o2p](链接)) by @developer2 + +### :busts_in_silhouette: Contributors + + + developer1 + + + developer2 + + +--- +``` + +#### Release + +- **标题**:根据 `RELEASE_TITLE` 生成(例如:`1.0.0-beta1` 或 `v1.0.0-beta1`) +- **标记**: + - `RELEASE_PRERELEASE_MODE="auto"` → 根据 tag 名称自动判断 + - `RELEASE_PRERELEASE_MODE="false"` → Latest ✅ + - `RELEASE_PRERELEASE_MODE="true"` → Pre-release 🏷️ +- **内容**:CHANGELOG 中该版本的完整内容(包含所有叠加的提交) +- **附件**:`CHANGELOG.md` + `ADDITIONAL_RELEASE_FILES` 中的文件 + +**Latest Release 示例**: + +```txt +✅ Latest + +1.0.0 ← RELEASE_TITLE + +### :pencil: What's Changed +- :sparkles: Add user authentication ([a1b2c3d](链接)) +- :sparkles: Add user profile ([e4f5g6h](链接)) +- :bug: Fix login redirect ([i7j8k9l](链接)) +- :sparkles: Add password reset ([m0n1o2p](链接)) + +### :busts_in_silhouette: Contributors +[所有贡献者头像] +``` + +**Pre-release 示例**: + +```txt +🏷️ Pre-release + +1.0.0-beta1 ← RELEASE_TITLE + +### :pencil: What's Changed +- :sparkles: Add user authentication ([a1b2c3d](链接)) +- :sparkles: Add user profile ([e4f5g6h](链接)) +``` + +--- + +## 🎨 配置示例库 + +### 示例 1:标准 SemVer 配置(推荐) + +```bash +GITEA_SERVER="https://git.example.com" +CHANGELOG_SECTION_TITLE="### :pencil: What's Changed" +CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors" +CHANGELOG_VERSION_MODE="strip" # 支持内容叠加 +RELEASE_TITLE="{version}" +RELEASE_PRERELEASE_MODE="auto" # 自动识别 beta/rc 等 +COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]" +``` + +### 示例 2:中文环境 + +```bash +GITEA_SERVER="https://git.example.com" +CHANGELOG_SECTION_TITLE="### :pencil: 变更内容" +CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: 贡献者" +CHANGELOG_VERSION_MODE="strip" +RELEASE_TITLE="版本 {version}" +RELEASE_PRERELEASE_MODE="auto" +COMMIT_MESSAGE="文档: 更新 CHANGELOG [{version}] [skip ci]" +``` + +### 示例 3:详细记录 + +```bash +GITEA_SERVER="https://git.example.com" +CHANGELOG_SECTION_TITLE="### :clipboard: What's Changed in This Version" +CHANGELOG_CONTRIBUTORS_TITLE="### :heart: Special Thanks To" +CHANGELOG_VERSION_MODE="strip" +RELEASE_TITLE="Release {version}" +RELEASE_PRERELEASE_MODE="auto" +RELEASE_IS_DRAFT="false" +COMMIT_MESSAGE=":memo: Automated CHANGELOG update for version {version} [skip ci]" +ADDITIONAL_RELEASE_FILES="README.md LICENSE CHANGELOG.md docs/MIGRATION.md" +IGNORE_PATTERNS=( + "^Merge " + "^:memo: Auto update CHANGELOG" + "\[skip ci\]" + "^Bump version" + "^style: " + "^chore: format" + "^chore: lint" + "^test: " + "^docs: update readme" +) +``` + +### 示例 4:开发分支(始终 Pre-release) + +```bash +GITEA_SERVER="https://git.example.com" +CHANGELOG_SECTION_TITLE="### :test_tube: Development Changes" +CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors" +CHANGELOG_VERSION_MODE="strip" +RELEASE_TITLE="Dev Build {version}" +RELEASE_PRERELEASE_MODE="true" # 始终标记为 pre-release +COMMIT_MESSAGE=":test_tube: Update CHANGELOG for dev build {version} [skip ci]" +``` + +### 示例 5:草稿模式 + +```bash +GITEA_SERVER="https://git.example.com" +CHANGELOG_SECTION_TITLE="### :pencil: What's Changed" +CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors" +CHANGELOG_VERSION_MODE="strip" +RELEASE_TITLE="{version}" +RELEASE_PRERELEASE_MODE="auto" +RELEASE_IS_DRAFT="true" # 创建为草稿 +COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]" +``` + +--- + +## 📖 版本号最佳实践 + +### 快速参考 + +**版本演进示例**: + +```txt +0.1.0 → 0.2.0 → 1.0.0 (首个稳定版) → 1.1.0 (新功能) → 1.1.1 (Bug修复) → 2.0.0 (破坏性变更) +``` + +**何时递增版本号**: + +- **MAJOR**:不兼容的 API 修改、破坏性变更 +- **MINOR**:新增功能但保持向后兼容 +- **PATCH**:Bug 修复、安全补丁 + +**Pre-release 版本命名**: + +```bash +1.0.0-alpha1 # 内部测试,可能不稳定 +1.0.0-beta1 # 公开测试,功能基本完成 +1.0.0-rc1 # 发布候选,准备正式发布 +``` + +**版本号检查清单**: + +- [ ] 格式:`MAJOR.MINOR.PATCH` (不要 `v` 前缀) +- [ ] 开发版本以 `.0` 开始 (如 `1.0.0-beta1`) +- [ ] Pre-release 后缀完整 (`beta1` 不是 `b1`) +- [ ] `CHANGELOG_VERSION_MODE` 设置为 `"strip"` + +📖 **详细说明请参考**:[CONVENTIONS.md](./CONVENTIONS.md) - 包含完整的 SemVer 规范、更新规则和示例 + +--- + +## ❓ 常见问题 + +### Q1: 为什么推荐使用 strip 模式? + +**原因**: + +1. ✅ **支持内容叠加** - 多个开发版本的提交会累积在同一个 CHANGELOG 区域 +2. ✅ **完整的开发历史** - 正式发布时,CHANGELOG 已包含所有开发阶段的提交 +3. ✅ **避免重复** - 不需要为每个 beta/rc 版本创建独立的 CHANGELOG 条目 +4. ✅ **便于阅读** - 所有相关的变更集中在一起 + +**对比**: + +```bash +# Strip 模式 +1.0.0-beta1, 1.0.0-beta2, 1.0.0 → 所有提交在 ## :bookmark: 1.0.0 + +# Full 模式 +1.0.0-beta1 → ## :bookmark: 1.0.0-beta1 +1.0.0-beta2 → ## :bookmark: 1.0.0-beta2 +1.0.0 → ## :bookmark: 1.0.0 +``` + +--- + +### Q2: 如何修改配置? + +**所有配置都在第一个步骤中**,只需编辑 `changelog-and-release.yaml` 的 **⚙️ Configuration** 步骤即可。 + +修改后提交推送: + +```bash +git add .gitea/workflows/changelog-and-release.yaml +git commit -m "ci: update workflow configuration" +git push +``` + +下次推送 tag 时会使用新配置。 + +--- + +### Q3: 版本号格式错误会怎样? + +**常见错误**: + +```bash +❌ v1.0.0 # 不应该有 v 前缀 +❌ 1.0.1-beta1 # 开发版本应该从 .0 开始 +❌ 1.0.0beta1 # 缺少连字符 +❌ 1.0.0-b1 # 后缀应该完整写 beta1 +``` + +**影响**: + +- 工作流仍会执行 +- 但可能无法正确识别为 Pre-release +- CHANGELOG 版本号可能不符合预期 + +--- + +### Q4: 如何查看 CHANGELOG 是否正确叠加? + +**检查步骤**: + +1. 查看 CHANGELOG.md 文件 +2. 找到对应的版本区域(如 `## :bookmark: 1.0.0`) +3. 确认包含所有 beta/rc 版本的提交 + +**示例**: + +```markdown +## :bookmark: 1.0.0 + +### :pencil: What's Changed + +- 提交 1(来自 1.0.0-beta1) +- 提交 2(来自 1.0.0-beta1) +- 提交 3(来自 1.0.0-beta2) +- 提交 4(来自 1.0.0-rc1) +- 提交 5(来自 1.0.0) +``` + +--- + +### Q5: 贡献者重复怎么办? + +**不用担心!** 工作流会自动去重贡献者。 + +如果同一个人在多个版本都有贡献,他们的头像只会出现一次。 + +--- + +### Q6: Workflow 没有触发? + +**检查清单**: + +- [ ] Workflow 文件在 `.gitea/workflows/` 目录下 +- [ ] 文件名为 `changelog-and-release.yaml`(或 `.yml`) +- [ ] Tag 格式正确(数字开头,如 `1.0.0` 或 `1.0.0-beta1`) +- [ ] Runner 已部署并正常运行 +- [ ] 仓库已启用 Actions +- [ ] GITEA_TOKEN 已正确配置 + +--- + +### Q7: 提示 "GITEA_TOKEN not found"? + +**原因**:Token 未正确配置 + +**解决方法**: + +1. 确认已添加名为 `GITEA_TOKEN` 的 Secret +2. 确认 Token 具有 `repo` 权限 +3. 尝试重新生成并配置 Token + +--- + +### Q8: CHANGELOG 没有更新? + +**可能原因**: + +1. 没有新的有效提交 +2. 所有提交都匹配了 `IGNORE_PATTERNS` 中的规则 +3. 版本已存在且是 bot 提交 + +**检查方法**: + +```bash +# 查看两个 tag 之间的提交 +git log $(git describe --tags --abbrev=0 @^)..@ --oneline --no-merges + +# 检查 workflow 日志中的 "Found X valid commits" +``` + +--- + +### Q9: 如何跳过自动化? + +在 commit 消息中添加 `[skip ci]`: + +```bash +git commit -m "docs: update readme [skip ci]" +``` + +**注意**:Tag 推送无法跳过,因为 workflow 在 tag 推送时触发 + +--- + +### Q10: 什么时候使用 full 模式? + +**使用场景**: + +- 你希望每个 tag 都有独立的 CHANGELOG 条目 +- 不需要累积开发版本的提交 +- 每个版本的变更需要完全独立 + +**大多数情况下,推荐使用 strip 模式。** + +--- + +### Q11: Release 标记(Latest/Pre-release)是如何工作的? + +**GitHub/Gitea 的自动标记规则**: + +1. **Latest(最新稳定版)**: + + - 自动标记给最新的非 pre-release 版本 + - 显示绿色的 "Latest" 标签 ✅ + +2. **Pre-release(预发布版)**: + - 通过 API 设置 `prerelease: true` + - 显示橙色的 "Pre-release" 标签 🏷️ + - 不会被标记为 Latest + +**auto 模式识别的关键词**: + +- `alpha`, `beta`, `rc`, `pre`, `preview`, `dev`, `test` + +**示例**: + +```bash +1.0.0 → Latest ✅ +1.0.0-beta1 → Pre-release 🏷️ +1.0.0-rc1 → Pre-release 🏷️ +1.1.0 → Latest ✅ (1.0.0 变为旧版本) +``` + +--- + +## 🔍 调试技巧 + +### 查看配置加载情况 + +每次执行都会在日志开头显示配置摘要: + +``` +====================================== +⚙️ Loading workflow configuration +====================================== + +📋 Configuration Summary: + 🏷️ Tag version: 1.0.0-beta1 + 📝 CHANGELOG version: 1.0.0 + 🌐 Gitea Server: https://git.mytsl.cn + 📝 CHANGELOG Title: ### :pencil: What's Changed + 🚀 Release Title: 1.0.0-beta1 + 🏷️ Pre-release: yes (auto-detected) + 💬 Commit Message: :memo: Auto update CHANGELOG for 1.0.0-beta1 [skip ci] + 📎 Additional Files: none +``` + +### 本地测试配置 + +提取 Configuration 步骤中的 bash 代码,在本地运行测试: + +```bash +#!/bin/bash + +# 模拟环境变量 +export github_ref_name="1.0.0-beta1" + +# 复制你的配置 +GITEA_SERVER="https://git.mytsl.cn" +CHANGELOG_VERSION_MODE="strip" +RELEASE_TITLE="{version}" +COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]" + +# 测试版本处理 +VERSION="$github_ref_name" +if [[ "$CHANGELOG_VERSION_MODE" == "strip" ]]; then + CHANGELOG_VERSION=$(echo "$VERSION" | sed -E 's/-(alpha|beta|rc|pre|preview|dev|test)[0-9]*$//') + echo "Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION" +fi + +# 测试变量替换 +RELEASE_TITLE="${RELEASE_TITLE//\{version\}/$VERSION}" +COMMIT_MSG="${COMMIT_MESSAGE//\{version\}/$VERSION}" + +echo "Release Title: $RELEASE_TITLE" +echo "Commit Message: $COMMIT_MSG" +``` + +### 验证版本号格式 + +```bash +# 测试版本号是否符合规范 +version="1.0.0-beta1" + +# 检查是否数字开头 +if [[ "$version" =~ ^[0-9] ]]; then + echo "✅ 格式正确" +else + echo "❌ 版本号必须以数字开头" +fi + +# 检查是否包含 pre-release 标识 +if [[ "$version" =~ (alpha|beta|rc|pre|preview|dev|test) ]]; then + echo "🏷️ 将被识别为 Pre-release" +else + echo "✅ 将被识别为 Latest" +fi +``` + +--- diff --git a/docker-runner/buildx/Dockerfile b/docker-runner/buildx/Dockerfile new file mode 100644 index 0000000..fa8232d --- /dev/null +++ b/docker-runner/buildx/Dockerfile @@ -0,0 +1,37 @@ +FROM ubuntu:22.04 + +# 设置环境变量避免交互式安装 +ENV DEBIAN_FRONTEND=noninteractive + +# 更新系统并安装必要软件 +RUN apt-get update && apt-get install -y \ + curl \ + git \ + python3 \ + python3-yaml \ + supervisor \ + ca-certificates \ + gnupg \ + lsb-release \ + qemu-user-static \ + binfmt-support \ + && rm -rf /var/lib/apt/lists/* + +# 安装 Docker(包含 Buildx 插件) +RUN curl -fsSL https://get.docker.com -o get-docker.sh && \ + sh get-docker.sh && \ + rm get-docker.sh + +# 验证安装 +RUN docker --version && \ + qemu-aarch64-static --version && \ + qemu-x86_64-static --version + +# 创建必要目录 +RUN mkdir -p /data /etc/supervisor/conf.d /var/log/supervisor + +# 设置工作目录 +WORKDIR /data + +# 使用自定义入口点 +ENTRYPOINT ["/data/entrypoint.sh"] diff --git a/docker-runner/buildx/check_crlf.sh b/docker-runner/buildx/check_crlf.sh new file mode 100644 index 0000000..c5f91c8 --- /dev/null +++ b/docker-runner/buildx/check_crlf.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +echo "==========================================" +echo " 文件格式快速修复工具" +echo "==========================================" +echo "" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# 要检查的文件列表 +SCRIPT_FILES=( + "entrypoint.sh" + "setup.sh" + "register.sh" + "manage.sh" +) + +FIXED_COUNT=0 +TOTAL_FILES=0 + +echo "🔍 检查并修复以下文件:" +printf '%s\n' "${SCRIPT_FILES[@]}" | sed 's/^/ - /' +echo "" +echo "----------------------------------------" +echo "" + +for file in "${SCRIPT_FILES[@]}"; do + TOTAL_FILES=$((TOTAL_FILES + 1)) + + # 检查文件是否存在 + if [ ! -f "$file" ]; then + echo -e "${RED}✗ $file - 文件不存在,跳过${NC}" + continue + fi + + NEEDS_FIX=false + + # 检查换行符 + HAS_CRLF=false + if file "$file" | grep -qi "CRLF\|with CR"; then + HAS_CRLF=true + NEEDS_FIX=true + fi + + # 检查权限 + NEEDS_CHMOD=false + if [ ! -x "$file" ]; then + NEEDS_CHMOD=true + NEEDS_FIX=true + fi + + # 显示状态 + echo -n "📄 $file ... " + + if [ "$NEEDS_FIX" = false ]; then + echo -e "${GREEN}OK${NC}" + continue + fi + + # 修复换行符 + if [ "$HAS_CRLF" = true ]; then + sed -i 's/\r$//' "$file" 2>/dev/null || sed -i '' 's/\r$//' "$file" 2>/dev/null + echo -n -e "${YELLOW}[换行符已修复]${NC} " + fi + + # 修复权限 + if [ "$NEEDS_CHMOD" = true ]; then + chmod +x "$file" + echo -n -e "${YELLOW}[权限已修复]${NC} " + fi + + echo -e "${GREEN}✓${NC}" + FIXED_COUNT=$((FIXED_COUNT + 1)) +done + +echo "" +echo "==========================================" +echo "修复完成" +echo "==========================================" +echo "检查文件: $TOTAL_FILES" +echo "修复文件: $FIXED_COUNT" +echo "" + +if [ $FIXED_COUNT -gt 0 ]; then + echo -e "${GREEN}✓ 所有问题已自动修复!${NC}" + echo "" + echo "建议执行以下命令重启容器:" + echo " docker-compose down" + echo " docker-compose build --no-cache" + echo " docker-compose up -d" + echo "" + + # 询问是否立即重启 + read -p "是否立即重启容器? (y/N): " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "正在重启容器..." + docker-compose down + docker-compose build --no-cache + docker-compose up -d + sleep 3 + echo "" + echo "容器状态:" + docker-compose ps + echo "" + echo "查看日志:" + docker logs gitea-runner --tail=30 + fi +else + echo -e "${GREEN}✓ 所有文件格式正确,无需修复!${NC}" +fi diff --git a/docker-runner/buildx/docker-compose.yml b/docker-runner/buildx/docker-compose.yml new file mode 100644 index 0000000..7d6200b --- /dev/null +++ b/docker-runner/buildx/docker-compose.yml @@ -0,0 +1,28 @@ +services: + gitea-runner: + build: . + container_name: gitea-runner + restart: unless-stopped + privileged: true + volumes: + - ./runner-data:/data + - ./setup.sh:/data/setup.sh:ro + - ./register.sh:/data/register.sh:ro + - ./manage.sh:/data/manage.sh:ro + - ./entrypoint.sh:/data/entrypoint.sh:ro + - /var/run/docker.sock:/var/run/docker.sock + + environment: + - TZ=Asia/Shanghai + + # 如果需要使用代理,取消下面的注释并修改为你的代理地址 + # 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP + - http_proxy=http://host.docker.internal:20122 + - https_proxy=http://host.docker.internal:20122 + - HTTP_PROXY=http://host.docker.internal:20122 + - HTTPS_PROXY=http://host.docker.internal:20122 + # - no_proxy=localhost,127.0.0.1 + + # Linux 系统需要取消下面的注释以支持 host.docker.internal + # extra_hosts: + # - "host.docker.internal:host-gateway" diff --git a/docker-runner/buildx/entrypoint.sh b/docker-runner/buildx/entrypoint.sh new file mode 100644 index 0000000..5c3517a --- /dev/null +++ b/docker-runner/buildx/entrypoint.sh @@ -0,0 +1,234 @@ +#!/bin/bash +set -e + +echo "===================================" +echo "Gitea Runner Container Starting..." +echo "===================================" + +# 定义路径 +PERSISTENT_BIN="/data/bin" +RUNNER_PATH="$PERSISTENT_BIN/act_runner" +SYSTEM_LINK="/usr/local/bin/act_runner" + +# 创建必要目录 +mkdir -p /data/runners +mkdir -p "$PERSISTENT_BIN" +mkdir -p /data/buildx +mkdir -p /var/log/supervisor +mkdir -p /var/run + +# ============================================ +# 初始化 Docker Buildx 支持 +# ============================================ +echo "" +echo "Initializing Docker Buildx..." + +# 启动 Docker 守护进程(如果使用主机 socket 则跳过) +if [ -S /var/run/docker.sock ]; then + echo "✓ Using host Docker socket" +else + echo "Starting Docker daemon..." + dockerd > /var/log/dockerd.log 2>&1 & + sleep 5 +fi + +# 等待 Docker 就绪 +echo "Waiting for Docker daemon..." +for i in {1..30}; do + if docker info > /dev/null 2>&1; then + echo "✓ Docker daemon is ready" + break + fi + if [ $i -eq 30 ]; then + echo "✗ Docker daemon failed to start" + [ -f /var/log/dockerd.log ] && cat /var/log/dockerd.log + exit 1 + fi + sleep 1 +done + +# 注册 QEMU binfmt +echo "" +echo "Registering QEMU binary formats..." +update-binfmts --enable 2>/dev/null || { + echo "⚠ binfmt_misc not available, trying to mount..." + mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc 2>/dev/null || true + update-binfmts --enable +} + +# 验证多架构支持 +echo "Verifying multi-arch support..." +if docker run --rm arm64v8/alpine uname -m > /dev/null 2>&1; then + echo " ✓ arm64 support verified" +else + echo " ⚠ arm64 verification failed" +fi + +if docker run --rm amd64/alpine uname -m > /dev/null 2>&1; then + echo " ✓ amd64 support verified" +else + echo " ⚠ amd64 verification failed" +fi + +# 配置 Buildx +if [ ! -f "/data/buildx/.configured" ]; then + echo "" + echo "Setting up Buildx for the first time..." + + # 创建 BuildKit 配置 + cat > /data/buildx/buildkitd.toml </dev/null || \ + docker buildx use gitea-multiarch 2>/dev/null + + # 验证 + echo "Verifying Buildx..." + docker buildx inspect --bootstrap > /dev/null 2>&1 + + # 标记为已配置 + touch /data/buildx/.configured + + echo "✓ Buildx configured successfully!" + docker buildx inspect | grep "Platforms:" | head -1 +else + echo "✓ Buildx already configured" + + # 确保 builder 可用 + docker buildx use gitea-multiarch 2>/dev/null || { + echo "⚠ Recreating Buildx builder..." + docker buildx rm gitea-multiarch 2>/dev/null || true + docker buildx create \ + --name gitea-multiarch \ + --driver docker-container \ + --bootstrap \ + --use 2>/dev/null + } +fi + +echo "" + +# ============================================ +# 检查 act_runner 安装 +# ============================================ +# 创建主 supervisor 配置文件 +cat > /etc/supervisor/supervisord.conf < $RUNNER_PATH" + fi + + RUNNER_VERSION=$("$SYSTEM_LINK" --version 2>/dev/null || echo "unknown") + echo " Version: $RUNNER_VERSION" +elif [ -f "$SYSTEM_LINK" ]; then + echo "⚠ Found act_runner in system path, migrating to persistent storage..." + cp "$SYSTEM_LINK" "$RUNNER_PATH" + chmod +x "$RUNNER_PATH" + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo " ✓ Migrated to: $RUNNER_PATH" +else + echo "⚠ act_runner not installed yet!" + echo "" + echo "Please run the setup script first:" + echo " docker-compose exec gitea-runner /data/setup.sh" + echo "" + echo "Container is waiting..." + + while [ ! -f "$RUNNER_PATH" ] && [ ! -f "$SYSTEM_LINK" ]; do + sleep 10 + done + + if [ -f "$RUNNER_PATH" ]; then + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo "✓ act_runner detected and linked!" + elif [ -f "$SYSTEM_LINK" ]; then + cp "$SYSTEM_LINK" "$RUNNER_PATH" + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo "✓ act_runner detected and migrated!" + fi +fi + +# ============================================ +# 配置已注册的 Runners +# ============================================ +echo "" +echo "Scanning for registered runners..." +RUNNER_COUNT=0 + +if [ -d "/data/runners" ]; then + for runner_dir in /data/runners/*/; do + if [ -d "$runner_dir" ]; then + runner_name=$(basename "$runner_dir") + + if [ -f "$runner_dir/.runner" ] && [ -f "$runner_dir/config.yaml" ]; then + echo "Found runner: $runner_name" + + cat > "/etc/supervisor/conf.d/runner-${runner_name}.conf" </dev/null)" ]; then + echo "No runners registered yet." + return + fi + + printf "%-20s %-15s %-30s\n" "Name" "Status" "Log File" + echo "-------------------------------------------" + + for runner_dir in /data/runners/*/; do + if [ -d "$runner_dir" ]; then + runner_name=$(basename "$runner_dir") + + if [ -f "$runner_dir/.runner" ]; then + # 获取状态 + status=$(supervisorctl status "runner-${runner_name}" 2>/dev/null | awk '{print $2}') + [ -z "$status" ] && status="NOT_LOADED" + + log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + printf "%-20s %-15s %-30s\n" "$runner_name" "$status" "$log_file" + fi + fi + done + echo "" +} + +# 函数:查看 runner 详细信息 +show_runner() { + local runner_name=$1 + local runner_dir="/data/runners/${runner_name}" + + if [ ! -d "$runner_dir" ]; then + echo "✗ Runner '$runner_name' not found!" + return 1 + fi + + echo "Runner Details: $runner_name" + echo "-------------------------------------------" + echo "Directory: $runner_dir" + + if [ -f "$runner_dir/config.yaml" ]; then + echo "" + echo "Configuration:" + cat "$runner_dir/config.yaml" + fi + + echo "" + echo "Status:" + supervisorctl status "runner-${runner_name}" 2>/dev/null | awk '{print $2}' + echo "" +} + +# 函数:删除 runner +delete_runner() { + local runner_name=$1 + local runner_dir="/data/runners/${runner_name}" + + if [ ! -d "$runner_dir" ]; then + echo "✗ Runner '$runner_name' not found!" + return 1 + fi + + echo "⚠ Warning: This will permanently delete runner '$runner_name'" + read -p "Are you sure? (y/N): " -n 1 -r + echo + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Deletion cancelled." + return 0 + fi + + echo "Stopping runner..." + supervisorctl stop "runner-${runner_name}" 2>/dev/null || true + + echo "Removing configuration..." + rm -f "/etc/supervisor/conf.d/runner-${runner_name}.conf" + + echo "Deleting runner directory..." + rm -rf "$runner_dir" + + echo "Updating supervisor..." + supervisorctl reread + supervisorctl update + + echo "" + echo "✓ Runner '$runner_name' deleted successfully!" +} + +# 函数:查看 runner 日志 +logs_runner() { + local runner_name=$1 + local lines=${2:-50} + + local log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + if [ ! -f "$log_file" ]; then + echo "✗ Log file not found for runner '$runner_name'" + return 1 + fi + + echo "Showing last $lines lines of '$runner_name' logs:" + echo "-------------------------------------------" + tail -n "$lines" "$log_file" +} + +# 函数:实时查看日志 +follow_logs() { + local runner_name=$1 + local log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + if [ ! -f "$log_file" ]; then + echo "✗ Log file not found for runner '$runner_name'" + return 1 + fi + + echo "Following logs for '$runner_name' (Press Ctrl+C to exit):" + echo "-------------------------------------------" + tail -f "$log_file" +} + +# 函数:启动/停止/重启 runner +control_runner() { + local action=$1 + local runner_name=$2 + + case $action in + start|stop|restart) + echo "${action^}ing runner '$runner_name'..." + supervisorctl "$action" "runner-${runner_name}" + ;; + *) + echo "✗ Invalid action: $action" + return 1 + ;; + esac +} + +# 函数:显示所有 runner 状态 +status_all() { + echo "All Runners Status:" + echo "-------------------------------------------" + supervisorctl status | grep "^runner-" || echo "No runners running." + echo "" +} + +# 主菜单 +show_menu() { + echo "Choose an action:" + echo " 1) List all runners" + echo " 2) Show runner details" + echo " 3) Add new runner" + echo " 4) Delete runner" + echo " 5) Start runner" + echo " 6) Stop runner" + echo " 7) Restart runner" + echo " 8) View runner logs" + echo " 9) Follow runner logs (real-time)" + echo " 10) Show all runners status" + echo " 0) Exit" + echo "" +} + +# 主程序 +if [ $# -eq 0 ]; then + # 交互模式 + while true; do + show_menu + read -p "Enter your choice: " choice + echo "" + + case $choice in + 1) + list_runners + ;; + 2) + read -p "Enter runner name: " runner_name + show_runner "$runner_name" + ;; + 3) + echo "Starting registration process..." + /data/register.sh + ;; + 4) + read -p "Enter runner name to delete: " runner_name + delete_runner "$runner_name" + ;; + 5) + read -p "Enter runner name to start: " runner_name + control_runner start "$runner_name" + ;; + 6) + read -p "Enter runner name to stop: " runner_name + control_runner stop "$runner_name" + ;; + 7) + read -p "Enter runner name to restart: " runner_name + control_runner restart "$runner_name" + ;; + 8) + read -p "Enter runner name: " runner_name + read -p "Number of lines (default 50): " lines + logs_runner "$runner_name" "${lines:-50}" + ;; + 9) + read -p "Enter runner name: " runner_name + follow_logs "$runner_name" + ;; + 10) + status_all + ;; + 0) + echo "Goodbye!" + exit 0 + ;; + *) + echo "Invalid choice!" + ;; + esac + + echo "" + read -p "Press Enter to continue..." + clear + done +else + # 命令行模式 + case $1 in + list|ls) + list_runners + ;; + show|info) + show_runner "$2" + ;; + add|register) + /data/register.sh + ;; + delete|rm|remove) + delete_runner "$2" + ;; + start) + control_runner start "$2" + ;; + stop) + control_runner stop "$2" + ;; + restart) + control_runner restart "$2" + ;; + logs) + logs_runner "$2" "${3:-50}" + ;; + follow) + follow_logs "$2" + ;; + status) + status_all + ;; + *) + echo "Usage: $0 [command] [runner_name]" + echo "" + echo "Commands:" + echo " list - List all runners" + echo " show - Show runner details" + echo " add - Add new runner" + echo " delete - Delete a runner" + echo " start - Start a runner" + echo " stop - Stop a runner" + echo " restart - Restart a runner" + echo " logs [lines] - View runner logs" + echo " follow - Follow runner logs (real-time)" + echo " status - Show all runners status" + echo "" + echo "Or run without arguments for interactive mode." + exit 1 + ;; + esac +fi diff --git a/docker-runner/buildx/register.sh b/docker-runner/buildx/register.sh new file mode 100644 index 0000000..70ccdfb --- /dev/null +++ b/docker-runner/buildx/register.sh @@ -0,0 +1,321 @@ +#!/bin/bash +set -e + +echo "==========================================" +echo " Gitea Runner Registration Script " +echo "==========================================" +echo "" + +# 检查 act_runner 是否安装 +if ! command -v act_runner &> /dev/null; then + echo "✗ act_runner is not installed!" + echo "" + echo "Please run the setup script first:" + echo " docker-compose exec gitea-runner /data/setup.sh" + exit 1 +fi + +echo "✓ act_runner found: $(act_runner --version)" +echo "" + +# 获取注册信息并验证 +while true; do + read -p "Enter Gitea instance URL (e.g., https://gitea.example.com): " GITEA_INSTANCE + + # 验证 URL 格式 + if [[ ! "$GITEA_INSTANCE" =~ ^https?:// ]]; then + echo "✗ Error: URL must start with http:// or https://" + echo "" + continue + fi + + # 移除末尾的斜杠 + GITEA_INSTANCE="${GITEA_INSTANCE%/}" + echo "✓ URL validated: $GITEA_INSTANCE" + break +done + +read -p "Enter registration token: " GITEA_TOKEN + +if [ -z "$GITEA_TOKEN" ]; then + echo "✗ Error: Token cannot be empty!" + exit 1 +fi + +read -p "Enter runner name (default: docker-runner): " RUNNER_NAME +RUNNER_NAME=${RUNNER_NAME:-docker-runner} + +# 多个 label(逗号分隔,无空格) +# ubuntu-22.04:host://ubuntu:22.04,ubuntu-20.04:host://ubuntu:20.04,node:docker://node:18 +read -p "Enter runner labels (default: ubuntu-22.04:docker://ubuntu:22.04): " RUNNER_LABELS +RUNNER_LABELS=${RUNNER_LABELS:-ubuntu-22.04:host://ubuntu:22.04} + +# 创建 runner 目录 +RUNNER_DIR="/data/runners/${RUNNER_NAME}" +mkdir -p "$RUNNER_DIR" +cd "$RUNNER_DIR" + +echo "" +echo "Registration Information:" +echo " Instance: $GITEA_INSTANCE" +echo " Name: $RUNNER_NAME" +echo " Labels: $RUNNER_LABELS" +echo " Directory: $RUNNER_DIR" +echo "" + +# 检查是否已经注册 +if [ -f ".runner" ] || [ -f "config.yaml" ]; then + echo "⚠ Runner already exists in this directory!" + read -p "Do you want to re-register? This will overwrite existing configuration. (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Registration cancelled." + exit 0 + fi + + echo "Cleaning up existing runner..." + + # 停止现有 runner + echo "Stopping existing runner..." + supervisorctl stop "runner-${RUNNER_NAME}" 2>/dev/null || true + + # 删除 supervisor 配置 + rm -f "/etc/supervisor/conf.d/runner-${RUNNER_NAME}.conf" + + # 重新加载 supervisor + supervisorctl reread 2>/dev/null || true + supervisorctl update 2>/dev/null || true + + # 删除日志 + rm -f "/var/log/supervisor/runner-${RUNNER_NAME}".*.log* + + # 删除旧配置和缓存 + rm -f .runner config.yaml + rm -rf cache +fi + +# 执行注册 +echo "" +echo "Registering runner..." +act_runner register \ + --instance "$GITEA_INSTANCE" \ + --token "$GITEA_TOKEN" \ + --name "$RUNNER_NAME" \ + --labels "$RUNNER_LABELS" \ + --no-interactive + +if [ ! -f ".runner" ]; then + echo "" + echo "✗ Registration failed! .runner file not created." + exit 1 +fi + +echo "✓ Registration successful!" + +# 生成配置文件 +echo "" +echo "Generating config.yaml..." +act_runner generate-config > config.yaml + +echo "✓ Configuration file generated!" + +# 创建缓存目录 +mkdir -p cache + +# 使用 Python 修改配置(最可靠的方法) +echo "" +echo "Configuring runner settings..." + +if command -v python3 &> /dev/null; then + python3 << PYEOF +import yaml +import sys + +try: + # 读取生成的配置 + with open('config.yaml', 'r') as f: + config = yaml.safe_load(f) + + # 读取 .runner 获取实际注册的 labels + import json + with open('.runner', 'r') as f: + runner_data = json.load(f) + + # 使用 .runner 中的 labels(这是实际注册的) + registered_labels = runner_data.get('labels', []) + + # 修改配置 + if 'runner' not in config: + config['runner'] = {} + + # 使用实际注册的 labels + config['runner']['labels'] = registered_labels + config['runner']['capacity'] = 2 + + # 启用缓存 + if 'cache' not in config: + config['cache'] = {} + config['cache']['enabled'] = True + config['cache']['dir'] = './cache' + + # 保存配置 + with open('config.yaml', 'w') as f: + yaml.dump(config, f, default_flow_style=False, sort_keys=False) + + print("✓ Configuration updated using Python") + print(f" - Labels: {registered_labels}") + print(f" - Capacity: 2") + print(f" - Cache enabled: ./cache") + sys.exit(0) + +except Exception as e: + print(f"✗ Python configuration failed: {e}", file=sys.stderr) + sys.exit(1) +PYEOF + + PYTHON_EXIT=$? + + if [ $PYTHON_EXIT -ne 0 ]; then + echo "" + echo "⚠ Python configuration failed, using basic sed..." + + # 基本的 sed 修改(只修改简单的值,不动 labels) + sed -i 's/capacity: 1/capacity: 2/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 + + echo "✓ Basic configuration applied" + echo " Note: Please manually verify labels in config.yaml match .runner" + fi +else + echo "⚠ Python3 not found, applying basic configuration..." + + # 基本的 sed 修改 + sed -i 's/capacity: 1/capacity: 2/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 + + echo "✓ Basic configuration applied" + echo " Note: Labels will use act_runner defaults" +fi + +# 验证配置文件 +echo "" +echo "Validating configuration..." + +# 检查 YAML 语法 +if command -v python3 &> /dev/null; then + python3 << PYEOF +import yaml +import sys +try: + with open('config.yaml', 'r') as f: + yaml.safe_load(f) + print("✓ config.yaml syntax is valid") + sys.exit(0) +except Exception as e: + print(f"✗ config.yaml syntax error: {e}", file=sys.stderr) + sys.exit(1) +PYEOF + + if [ $? -ne 0 ]; then + echo "" + echo "✗ Configuration file has syntax errors!" + echo " Backup available at: config.yaml.bak" + exit 1 + fi +fi + +# 显示配置摘要 +echo "" +echo "Configuration Summary:" +echo "-------------------------------------------" +echo ".runner labels:" +cat .runner | grep -A 10 '"labels"' | head -15 +echo "" +echo "config.yaml labels:" +grep -A 5 "^ labels:" config.yaml | head -10 + +# 创建 supervisor 配置 +echo "" +echo "Creating supervisor configuration..." +cat > "/etc/supervisor/conf.d/runner-${RUNNER_NAME}.conf" </dev/null || \ + supervisorctl start "runner-${RUNNER_NAME}" + +# 等待启动 +sleep 3 + +# 显示状态 +RUNNER_STATUS=$(supervisorctl status "runner-${RUNNER_NAME}" 2>/dev/null || echo "UNKNOWN") + +echo "" +echo "==========================================" +echo "✓ Runner registered and started!" +echo "==========================================" +echo "" +echo "Runner Information:" +echo " Name: $RUNNER_NAME" +echo " Directory: $RUNNER_DIR" +echo " Status: $RUNNER_STATUS" +echo "" +echo "Configuration files:" +echo " .runner: $(ls -lh .runner 2>/dev/null | awk '{print $5}' || echo 'N/A')" +echo " config.yaml: $(ls -lh config.yaml 2>/dev/null | awk '{print $5}' || echo 'N/A')" +echo "" +echo "Useful commands:" +echo " View logs: docker-compose exec gitea-runner /data/manage.sh logs ${RUNNER_NAME}" +echo " Follow logs: docker-compose exec gitea-runner /data/manage.sh follow ${RUNNER_NAME}" +echo " Check status: docker-compose exec gitea-runner /data/manage.sh status" +echo " Restart: docker-compose exec gitea-runner /data/manage.sh restart ${RUNNER_NAME}" +echo "" + +# 显示最近的日志 +if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.out.log" ]; then + echo "Recent logs:" + echo "-------------------------------------------" + tail -n 20 "/var/log/supervisor/runner-${RUNNER_NAME}.out.log" 2>/dev/null || echo "No logs yet" + echo "" +fi + +# 检查是否有错误 +if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.err.log" ]; then + # 只查找 error/fatal/panic 级别的日志 + ERR_CONTENT=$(grep -E 'level=(error|fatal|panic)' \ + "/var/log/supervisor/runner-${RUNNER_NAME}.err.log" | tail -n 5 2>/dev/null) + + if [ -n "$ERR_CONTENT" ]; then + echo "❌ Recent errors detected:" + echo "-------------------------------------------" + echo "$ERR_CONTENT" + echo "" + echo "Check full error log with:" + echo " docker-compose exec gitea-runner cat /var/log/supervisor/runner-${RUNNER_NAME}.err.log" + fi +fi diff --git a/docker-runner/buildx/setup.sh b/docker-runner/buildx/setup.sh new file mode 100644 index 0000000..e2180fc --- /dev/null +++ b/docker-runner/buildx/setup.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +echo "==========================================" +echo " Gitea Runner Installation Script " +echo "==========================================" +echo "" + +# 持久化安装路径 +INSTALL_PATH="/data/bin/act_runner" +SYSTEM_PATH="/usr/local/bin/act_runner" + +# 创建目录 +mkdir -p /data/bin + +# 检查是否已安装 +if [ -f "$INSTALL_PATH" ]; then + CURRENT_VERSION=$($INSTALL_PATH --version 2>/dev/null | grep -oP 'version \K[0-9.]+' || echo "unknown") + echo "⚠ act_runner already installed (version: $CURRENT_VERSION)" + echo " Location: $INSTALL_PATH" + echo "" + read -p "Do you want to reinstall/upgrade? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Installation cancelled." + exit 0 + fi + rm -f "$INSTALL_PATH" "$SYSTEM_PATH" +fi + +# 获取要安装的版本 +echo "Available versions: https://dl.gitea.com/act_runner/" +echo "" +read -p "Enter version to install (default: 0.2.13): " RUNNER_VERSION +RUNNER_VERSION=${RUNNER_VERSION:-0.2.13} + +ARCH=$(uname -m) +case "$ARCH" in + x86_64) + RUNNER_ARCH="amd64" + ;; + aarch64|arm64) + RUNNER_ARCH="arm64" + ;; + armv7l) + RUNNER_ARCH="arm-7" + ;; + *) + echo "⚠ Unknown architecture: $ARCH" + RUNNER_ARCH="arm64" + ;; +esac + +# 确认架构 +echo "" +echo "Detected architecture: $ARCH -> $RUNNER_ARCH" +read -p "Is this correct? (Y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "" + echo "Available architectures:" + echo " 1. amd64 (x86_64)" + echo " 2. arm64 (aarch64)" + echo " 3. arm-7 (armv7l)" + read -p "Select architecture (1-3): " ARCH_CHOICE + case "$ARCH_CHOICE" in + 1) RUNNER_ARCH="amd64" ;; + 2) RUNNER_ARCH="arm64" ;; + 3) RUNNER_ARCH="arm-7" ;; + *) + echo "Invalid choice. Exiting." + exit 1 + ;; + esac +fi + +DOWNLOAD_URL="https://dl.gitea.com/act_runner/${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-${RUNNER_ARCH}" + +echo "" +echo "Download Configuration:" +echo " Version: $RUNNER_VERSION" +echo " Architecture: $RUNNER_ARCH" +echo " URL: $DOWNLOAD_URL" +echo " Install Location: $INSTALL_PATH (persistent)" +echo "" +read -p "Proceed with download? (Y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "Installation cancelled." + exit 0 +fi + +echo "" +echo "Downloading act_runner..." +echo "" + +# 下载到持久化目录 +if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then + chmod +x "$INSTALL_PATH" + + # 同时创建软链接到系统路径 + ln -sf "$INSTALL_PATH" "$SYSTEM_PATH" + + # 验证安装 + if $INSTALL_PATH --version; then + echo "" + echo "==========================================" + echo "✓ act_runner installed successfully!" + echo "==========================================" + echo "" + echo "Version: $($INSTALL_PATH --version)" + echo "Location: $INSTALL_PATH (persistent storage)" + echo "" + echo "Next steps:" + echo "1. Register the runner:" + echo " docker-compose exec gitea-runner /data/register.sh" + echo "" + echo "2. Restart the container:" + echo " docker-compose restart" + echo "" + echo "Note: act_runner is saved in persistent storage" + echo " and will survive container restarts." + echo "" + else + echo "" + echo "✗ Installation verification failed!" + rm -f "$INSTALL_PATH" "$SYSTEM_PATH" + exit 1 + fi +else + echo "" + echo "✗ Download failed!" + echo "Please check:" + echo " - Internet connection" + echo " - Version number is correct: $RUNNER_VERSION" + echo " - Architecture is correct: $RUNNER_ARCH" + echo " - URL is accessible: $DOWNLOAD_URL" + echo "" + echo "You can check available versions at:" + echo " https://dl.gitea.com/act_runner/" + exit 1 +fi diff --git a/docker-runner/standard/Dockerfile b/docker-runner/standard/Dockerfile new file mode 100644 index 0000000..62cf787 --- /dev/null +++ b/docker-runner/standard/Dockerfile @@ -0,0 +1,32 @@ +FROM ubuntu:22.04 + +# 设置环境变量避免交互式安装 +ENV DEBIAN_FRONTEND=noninteractive + +# RUN apt-get update && \ +# apt-get install -y ca-certificates && \ +# update-ca-certificates && \ +# rm -rf /var/lib/apt/lists/* + +# RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list && \ +# sed -i 's@//.*security.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list + +# 更新系统并安装必要软件 +RUN apt-get update && apt-get install -y \ + curl \ + git \ + jq \ + python3 \ + python3-yaml \ + supervisor \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# 创建必要目录 +RUN mkdir -p /data /etc/supervisor/conf.d /var/log/supervisor + +# 设置工作目录 +WORKDIR /data + +# 使用自定义入口点 +ENTRYPOINT ["/data/entrypoint.sh"] diff --git a/docker-runner/standard/docker-compose.yml b/docker-runner/standard/docker-compose.yml new file mode 100644 index 0000000..3532f57 --- /dev/null +++ b/docker-runner/standard/docker-compose.yml @@ -0,0 +1,30 @@ +services: + gitea-runner: + build: . + container_name: gitea-runner + restart: unless-stopped + volumes: + - ./runner-data:/data + - ./setup.sh:/data/setup.sh:ro + - ./register.sh:/data/register.sh:ro + - ./manage.sh:/data/manage.sh:ro + - ./entrypoint.sh:/data/entrypoint.sh:ro + - /var/run/docker.sock:/var/run/docker.sock + + environment: + - TZ=Asia/Shanghai + + # 如果需要使用代理,取消下面的注释并修改为你的代理地址 + # 注意:容器内访问宿主机需要使用 host.docker.internal 或宿主机IP + - http_proxy=http://host.docker.internal:20122 + - https_proxy=http://host.docker.internal:20122 + - HTTP_PROXY=http://host.docker.internal:20122 + - HTTPS_PROXY=http://host.docker.internal:20122 + # - no_proxy=localhost,127.0.0.1 + + # Linux 系统需要取消下面的注释以支持 host.docker.internal + # extra_hosts: + # - "host.docker.internal:host-gateway" + + # 如果需要使用主机网络,取消下面的注释 + # network_mode: host diff --git a/docker-runner/standard/entrypoint.sh b/docker-runner/standard/entrypoint.sh new file mode 100644 index 0000000..b7d1267 --- /dev/null +++ b/docker-runner/standard/entrypoint.sh @@ -0,0 +1,134 @@ +#!/bin/bash +set -e + +echo "===================================" +echo "Gitea Runner Container Starting..." +echo "===================================" + +# 定义路径 +PERSISTENT_BIN="/data/bin" +RUNNER_PATH="$PERSISTENT_BIN/act_runner" +SYSTEM_LINK="/usr/local/bin/act_runner" + +# 创建必要目录 +mkdir -p /data/runners +mkdir -p "$PERSISTENT_BIN" +mkdir -p /var/log/supervisor +mkdir -p /var/run + +# 创建主 supervisor 配置文件 +cat > /etc/supervisor/supervisord.conf < $RUNNER_PATH" + fi + + # 验证版本 + RUNNER_VERSION=$("$SYSTEM_LINK" --version 2>/dev/null || echo "unknown") + echo " Version: $RUNNER_VERSION" +elif [ -f "$SYSTEM_LINK" ]; then + # 旧版本可能在系统路径,迁移到持久化目录 + echo "⚠ Found act_runner in system path, migrating to persistent storage..." + cp "$SYSTEM_LINK" "$RUNNER_PATH" + chmod +x "$RUNNER_PATH" + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo " ✓ Migrated to: $RUNNER_PATH" +else + # 没有找到 act_runner + echo "⚠ act_runner not installed yet!" + echo "" + echo "Please run the setup script first:" + echo " docker-compose exec gitea-runner /data/setup.sh" + echo "" + echo "This will download and install the Gitea Runner to persistent storage." + echo "Container is waiting..." + + # 等待 act_runner 安装 + while [ ! -f "$RUNNER_PATH" ] && [ ! -f "$SYSTEM_LINK" ]; do + sleep 10 + done + + # 再次检查并创建链接 + if [ -f "$RUNNER_PATH" ]; then + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo "✓ act_runner detected and linked!" + elif [ -f "$SYSTEM_LINK" ]; then + cp "$SYSTEM_LINK" "$RUNNER_PATH" + ln -sf "$RUNNER_PATH" "$SYSTEM_LINK" + echo "✓ act_runner detected and migrated!" + fi +fi + +# 为每个已注册的 runner 创建 supervisor 配置 +echo "" +echo "Scanning for registered runners..." +RUNNER_COUNT=0 + +if [ -d "/data/runners" ]; then + for runner_dir in /data/runners/*/; do + if [ -d "$runner_dir" ]; then + runner_name=$(basename "$runner_dir") + + if [ -f "$runner_dir/.runner" ] && [ -f "$runner_dir/config.yaml" ]; then + echo "Found runner: $runner_name" + + # 创建该 runner 的 supervisor 配置 + cat > "/etc/supervisor/conf.d/runner-${runner_name}.conf" </dev/null)" ]; then + echo "No runners registered yet." + return + fi + + printf "%-20s %-15s %-30s\n" "Name" "Status" "Log File" + echo "-------------------------------------------" + + for runner_dir in /data/runners/*/; do + if [ -d "$runner_dir" ]; then + runner_name=$(basename "$runner_dir") + + if [ -f "$runner_dir/.runner" ]; then + # 获取状态 + status=$(supervisorctl status "runner-${runner_name}" 2>/dev/null | awk '{print $2}') + [ -z "$status" ] && status="NOT_LOADED" + + log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + printf "%-20s %-15s %-30s\n" "$runner_name" "$status" "$log_file" + fi + fi + done + echo "" +} + +# 函数:查看 runner 详细信息 +show_runner() { + local runner_name=$1 + local runner_dir="/data/runners/${runner_name}" + + if [ ! -d "$runner_dir" ]; then + echo "✗ Runner '$runner_name' not found!" + return 1 + fi + + echo "Runner Details: $runner_name" + echo "-------------------------------------------" + echo "Directory: $runner_dir" + + if [ -f "$runner_dir/config.yaml" ]; then + echo "" + echo "Configuration:" + cat "$runner_dir/config.yaml" + fi + + echo "" + echo "Status:" + supervisorctl status "runner-${runner_name}" 2>/dev/null | awk '{print $2}' + echo "" +} + +# 函数:删除 runner +delete_runner() { + local runner_name=$1 + local runner_dir="/data/runners/${runner_name}" + + if [ ! -d "$runner_dir" ]; then + echo "✗ Runner '$runner_name' not found!" + return 1 + fi + + echo "⚠ Warning: This will permanently delete runner '$runner_name'" + read -p "Are you sure? (y/N): " -n 1 -r + echo + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Deletion cancelled." + return 0 + fi + + echo "Stopping runner..." + supervisorctl stop "runner-${runner_name}" 2>/dev/null || true + + echo "Removing configuration..." + rm -f "/etc/supervisor/conf.d/runner-${runner_name}.conf" + + echo "Deleting runner directory..." + rm -rf "$runner_dir" + + echo "Updating supervisor..." + supervisorctl reread + supervisorctl update + + echo "" + echo "✓ Runner '$runner_name' deleted successfully!" +} + +# 函数:查看 runner 日志 +logs_runner() { + local runner_name=$1 + local lines=${2:-50} + + local log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + if [ ! -f "$log_file" ]; then + echo "✗ Log file not found for runner '$runner_name'" + return 1 + fi + + echo "Showing last $lines lines of '$runner_name' logs:" + echo "-------------------------------------------" + tail -n "$lines" "$log_file" +} + +# 函数:实时查看日志 +follow_logs() { + local runner_name=$1 + local log_file="/var/log/supervisor/runner-${runner_name}.out.log" + + if [ ! -f "$log_file" ]; then + echo "✗ Log file not found for runner '$runner_name'" + return 1 + fi + + echo "Following logs for '$runner_name' (Press Ctrl+C to exit):" + echo "-------------------------------------------" + tail -f "$log_file" +} + +# 函数:启动/停止/重启 runner +control_runner() { + local action=$1 + local runner_name=$2 + + case $action in + start|stop|restart) + echo "${action^}ing runner '$runner_name'..." + supervisorctl "$action" "runner-${runner_name}" + ;; + *) + echo "✗ Invalid action: $action" + return 1 + ;; + esac +} + +# 函数:显示所有 runner 状态 +status_all() { + echo "All Runners Status:" + echo "-------------------------------------------" + supervisorctl status | grep "^runner-" || echo "No runners running." + echo "" +} + +# 主菜单 +show_menu() { + echo "Choose an action:" + echo " 1) List all runners" + echo " 2) Show runner details" + echo " 3) Add new runner" + echo " 4) Delete runner" + echo " 5) Start runner" + echo " 6) Stop runner" + echo " 7) Restart runner" + echo " 8) View runner logs" + echo " 9) Follow runner logs (real-time)" + echo " 10) Show all runners status" + echo " 0) Exit" + echo "" +} + +# 主程序 +if [ $# -eq 0 ]; then + # 交互模式 + while true; do + show_menu + read -p "Enter your choice: " choice + echo "" + + case $choice in + 1) + list_runners + ;; + 2) + read -p "Enter runner name: " runner_name + show_runner "$runner_name" + ;; + 3) + echo "Starting registration process..." + /data/register.sh + ;; + 4) + read -p "Enter runner name to delete: " runner_name + delete_runner "$runner_name" + ;; + 5) + read -p "Enter runner name to start: " runner_name + control_runner start "$runner_name" + ;; + 6) + read -p "Enter runner name to stop: " runner_name + control_runner stop "$runner_name" + ;; + 7) + read -p "Enter runner name to restart: " runner_name + control_runner restart "$runner_name" + ;; + 8) + read -p "Enter runner name: " runner_name + read -p "Number of lines (default 50): " lines + logs_runner "$runner_name" "${lines:-50}" + ;; + 9) + read -p "Enter runner name: " runner_name + follow_logs "$runner_name" + ;; + 10) + status_all + ;; + 0) + echo "Goodbye!" + exit 0 + ;; + *) + echo "Invalid choice!" + ;; + esac + + echo "" + read -p "Press Enter to continue..." + clear + done +else + # 命令行模式 + case $1 in + list|ls) + list_runners + ;; + show|info) + show_runner "$2" + ;; + add|register) + /data/register.sh + ;; + delete|rm|remove) + delete_runner "$2" + ;; + start) + control_runner start "$2" + ;; + stop) + control_runner stop "$2" + ;; + restart) + control_runner restart "$2" + ;; + logs) + logs_runner "$2" "${3:-50}" + ;; + follow) + follow_logs "$2" + ;; + status) + status_all + ;; + *) + echo "Usage: $0 [command] [runner_name]" + echo "" + echo "Commands:" + echo " list - List all runners" + echo " show - Show runner details" + echo " add - Add new runner" + echo " delete - Delete a runner" + echo " start - Start a runner" + echo " stop - Stop a runner" + echo " restart - Restart a runner" + echo " logs [lines] - View runner logs" + echo " follow - Follow runner logs (real-time)" + echo " status - Show all runners status" + echo "" + echo "Or run without arguments for interactive mode." + exit 1 + ;; + esac +fi diff --git a/docker-runner/standard/register.sh b/docker-runner/standard/register.sh new file mode 100644 index 0000000..70ccdfb --- /dev/null +++ b/docker-runner/standard/register.sh @@ -0,0 +1,321 @@ +#!/bin/bash +set -e + +echo "==========================================" +echo " Gitea Runner Registration Script " +echo "==========================================" +echo "" + +# 检查 act_runner 是否安装 +if ! command -v act_runner &> /dev/null; then + echo "✗ act_runner is not installed!" + echo "" + echo "Please run the setup script first:" + echo " docker-compose exec gitea-runner /data/setup.sh" + exit 1 +fi + +echo "✓ act_runner found: $(act_runner --version)" +echo "" + +# 获取注册信息并验证 +while true; do + read -p "Enter Gitea instance URL (e.g., https://gitea.example.com): " GITEA_INSTANCE + + # 验证 URL 格式 + if [[ ! "$GITEA_INSTANCE" =~ ^https?:// ]]; then + echo "✗ Error: URL must start with http:// or https://" + echo "" + continue + fi + + # 移除末尾的斜杠 + GITEA_INSTANCE="${GITEA_INSTANCE%/}" + echo "✓ URL validated: $GITEA_INSTANCE" + break +done + +read -p "Enter registration token: " GITEA_TOKEN + +if [ -z "$GITEA_TOKEN" ]; then + echo "✗ Error: Token cannot be empty!" + exit 1 +fi + +read -p "Enter runner name (default: docker-runner): " RUNNER_NAME +RUNNER_NAME=${RUNNER_NAME:-docker-runner} + +# 多个 label(逗号分隔,无空格) +# ubuntu-22.04:host://ubuntu:22.04,ubuntu-20.04:host://ubuntu:20.04,node:docker://node:18 +read -p "Enter runner labels (default: ubuntu-22.04:docker://ubuntu:22.04): " RUNNER_LABELS +RUNNER_LABELS=${RUNNER_LABELS:-ubuntu-22.04:host://ubuntu:22.04} + +# 创建 runner 目录 +RUNNER_DIR="/data/runners/${RUNNER_NAME}" +mkdir -p "$RUNNER_DIR" +cd "$RUNNER_DIR" + +echo "" +echo "Registration Information:" +echo " Instance: $GITEA_INSTANCE" +echo " Name: $RUNNER_NAME" +echo " Labels: $RUNNER_LABELS" +echo " Directory: $RUNNER_DIR" +echo "" + +# 检查是否已经注册 +if [ -f ".runner" ] || [ -f "config.yaml" ]; then + echo "⚠ Runner already exists in this directory!" + read -p "Do you want to re-register? This will overwrite existing configuration. (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Registration cancelled." + exit 0 + fi + + echo "Cleaning up existing runner..." + + # 停止现有 runner + echo "Stopping existing runner..." + supervisorctl stop "runner-${RUNNER_NAME}" 2>/dev/null || true + + # 删除 supervisor 配置 + rm -f "/etc/supervisor/conf.d/runner-${RUNNER_NAME}.conf" + + # 重新加载 supervisor + supervisorctl reread 2>/dev/null || true + supervisorctl update 2>/dev/null || true + + # 删除日志 + rm -f "/var/log/supervisor/runner-${RUNNER_NAME}".*.log* + + # 删除旧配置和缓存 + rm -f .runner config.yaml + rm -rf cache +fi + +# 执行注册 +echo "" +echo "Registering runner..." +act_runner register \ + --instance "$GITEA_INSTANCE" \ + --token "$GITEA_TOKEN" \ + --name "$RUNNER_NAME" \ + --labels "$RUNNER_LABELS" \ + --no-interactive + +if [ ! -f ".runner" ]; then + echo "" + echo "✗ Registration failed! .runner file not created." + exit 1 +fi + +echo "✓ Registration successful!" + +# 生成配置文件 +echo "" +echo "Generating config.yaml..." +act_runner generate-config > config.yaml + +echo "✓ Configuration file generated!" + +# 创建缓存目录 +mkdir -p cache + +# 使用 Python 修改配置(最可靠的方法) +echo "" +echo "Configuring runner settings..." + +if command -v python3 &> /dev/null; then + python3 << PYEOF +import yaml +import sys + +try: + # 读取生成的配置 + with open('config.yaml', 'r') as f: + config = yaml.safe_load(f) + + # 读取 .runner 获取实际注册的 labels + import json + with open('.runner', 'r') as f: + runner_data = json.load(f) + + # 使用 .runner 中的 labels(这是实际注册的) + registered_labels = runner_data.get('labels', []) + + # 修改配置 + if 'runner' not in config: + config['runner'] = {} + + # 使用实际注册的 labels + config['runner']['labels'] = registered_labels + config['runner']['capacity'] = 2 + + # 启用缓存 + if 'cache' not in config: + config['cache'] = {} + config['cache']['enabled'] = True + config['cache']['dir'] = './cache' + + # 保存配置 + with open('config.yaml', 'w') as f: + yaml.dump(config, f, default_flow_style=False, sort_keys=False) + + print("✓ Configuration updated using Python") + print(f" - Labels: {registered_labels}") + print(f" - Capacity: 2") + print(f" - Cache enabled: ./cache") + sys.exit(0) + +except Exception as e: + print(f"✗ Python configuration failed: {e}", file=sys.stderr) + sys.exit(1) +PYEOF + + PYTHON_EXIT=$? + + if [ $PYTHON_EXIT -ne 0 ]; then + echo "" + echo "⚠ Python configuration failed, using basic sed..." + + # 基本的 sed 修改(只修改简单的值,不动 labels) + sed -i 's/capacity: 1/capacity: 2/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 + + echo "✓ Basic configuration applied" + echo " Note: Please manually verify labels in config.yaml match .runner" + fi +else + echo "⚠ Python3 not found, applying basic configuration..." + + # 基本的 sed 修改 + sed -i 's/capacity: 1/capacity: 2/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 + + echo "✓ Basic configuration applied" + echo " Note: Labels will use act_runner defaults" +fi + +# 验证配置文件 +echo "" +echo "Validating configuration..." + +# 检查 YAML 语法 +if command -v python3 &> /dev/null; then + python3 << PYEOF +import yaml +import sys +try: + with open('config.yaml', 'r') as f: + yaml.safe_load(f) + print("✓ config.yaml syntax is valid") + sys.exit(0) +except Exception as e: + print(f"✗ config.yaml syntax error: {e}", file=sys.stderr) + sys.exit(1) +PYEOF + + if [ $? -ne 0 ]; then + echo "" + echo "✗ Configuration file has syntax errors!" + echo " Backup available at: config.yaml.bak" + exit 1 + fi +fi + +# 显示配置摘要 +echo "" +echo "Configuration Summary:" +echo "-------------------------------------------" +echo ".runner labels:" +cat .runner | grep -A 10 '"labels"' | head -15 +echo "" +echo "config.yaml labels:" +grep -A 5 "^ labels:" config.yaml | head -10 + +# 创建 supervisor 配置 +echo "" +echo "Creating supervisor configuration..." +cat > "/etc/supervisor/conf.d/runner-${RUNNER_NAME}.conf" </dev/null || \ + supervisorctl start "runner-${RUNNER_NAME}" + +# 等待启动 +sleep 3 + +# 显示状态 +RUNNER_STATUS=$(supervisorctl status "runner-${RUNNER_NAME}" 2>/dev/null || echo "UNKNOWN") + +echo "" +echo "==========================================" +echo "✓ Runner registered and started!" +echo "==========================================" +echo "" +echo "Runner Information:" +echo " Name: $RUNNER_NAME" +echo " Directory: $RUNNER_DIR" +echo " Status: $RUNNER_STATUS" +echo "" +echo "Configuration files:" +echo " .runner: $(ls -lh .runner 2>/dev/null | awk '{print $5}' || echo 'N/A')" +echo " config.yaml: $(ls -lh config.yaml 2>/dev/null | awk '{print $5}' || echo 'N/A')" +echo "" +echo "Useful commands:" +echo " View logs: docker-compose exec gitea-runner /data/manage.sh logs ${RUNNER_NAME}" +echo " Follow logs: docker-compose exec gitea-runner /data/manage.sh follow ${RUNNER_NAME}" +echo " Check status: docker-compose exec gitea-runner /data/manage.sh status" +echo " Restart: docker-compose exec gitea-runner /data/manage.sh restart ${RUNNER_NAME}" +echo "" + +# 显示最近的日志 +if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.out.log" ]; then + echo "Recent logs:" + echo "-------------------------------------------" + tail -n 20 "/var/log/supervisor/runner-${RUNNER_NAME}.out.log" 2>/dev/null || echo "No logs yet" + echo "" +fi + +# 检查是否有错误 +if [ -f "/var/log/supervisor/runner-${RUNNER_NAME}.err.log" ]; then + # 只查找 error/fatal/panic 级别的日志 + ERR_CONTENT=$(grep -E 'level=(error|fatal|panic)' \ + "/var/log/supervisor/runner-${RUNNER_NAME}.err.log" | tail -n 5 2>/dev/null) + + if [ -n "$ERR_CONTENT" ]; then + echo "❌ Recent errors detected:" + echo "-------------------------------------------" + echo "$ERR_CONTENT" + echo "" + echo "Check full error log with:" + echo " docker-compose exec gitea-runner cat /var/log/supervisor/runner-${RUNNER_NAME}.err.log" + fi +fi diff --git a/docker-runner/standard/setup.sh b/docker-runner/standard/setup.sh new file mode 100644 index 0000000..e2180fc --- /dev/null +++ b/docker-runner/standard/setup.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +echo "==========================================" +echo " Gitea Runner Installation Script " +echo "==========================================" +echo "" + +# 持久化安装路径 +INSTALL_PATH="/data/bin/act_runner" +SYSTEM_PATH="/usr/local/bin/act_runner" + +# 创建目录 +mkdir -p /data/bin + +# 检查是否已安装 +if [ -f "$INSTALL_PATH" ]; then + CURRENT_VERSION=$($INSTALL_PATH --version 2>/dev/null | grep -oP 'version \K[0-9.]+' || echo "unknown") + echo "⚠ act_runner already installed (version: $CURRENT_VERSION)" + echo " Location: $INSTALL_PATH" + echo "" + read -p "Do you want to reinstall/upgrade? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Installation cancelled." + exit 0 + fi + rm -f "$INSTALL_PATH" "$SYSTEM_PATH" +fi + +# 获取要安装的版本 +echo "Available versions: https://dl.gitea.com/act_runner/" +echo "" +read -p "Enter version to install (default: 0.2.13): " RUNNER_VERSION +RUNNER_VERSION=${RUNNER_VERSION:-0.2.13} + +ARCH=$(uname -m) +case "$ARCH" in + x86_64) + RUNNER_ARCH="amd64" + ;; + aarch64|arm64) + RUNNER_ARCH="arm64" + ;; + armv7l) + RUNNER_ARCH="arm-7" + ;; + *) + echo "⚠ Unknown architecture: $ARCH" + RUNNER_ARCH="arm64" + ;; +esac + +# 确认架构 +echo "" +echo "Detected architecture: $ARCH -> $RUNNER_ARCH" +read -p "Is this correct? (Y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "" + echo "Available architectures:" + echo " 1. amd64 (x86_64)" + echo " 2. arm64 (aarch64)" + echo " 3. arm-7 (armv7l)" + read -p "Select architecture (1-3): " ARCH_CHOICE + case "$ARCH_CHOICE" in + 1) RUNNER_ARCH="amd64" ;; + 2) RUNNER_ARCH="arm64" ;; + 3) RUNNER_ARCH="arm-7" ;; + *) + echo "Invalid choice. Exiting." + exit 1 + ;; + esac +fi + +DOWNLOAD_URL="https://dl.gitea.com/act_runner/${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-${RUNNER_ARCH}" + +echo "" +echo "Download Configuration:" +echo " Version: $RUNNER_VERSION" +echo " Architecture: $RUNNER_ARCH" +echo " URL: $DOWNLOAD_URL" +echo " Install Location: $INSTALL_PATH (persistent)" +echo "" +read -p "Proceed with download? (Y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Nn]$ ]]; then + echo "Installation cancelled." + exit 0 +fi + +echo "" +echo "Downloading act_runner..." +echo "" + +# 下载到持久化目录 +if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then + chmod +x "$INSTALL_PATH" + + # 同时创建软链接到系统路径 + ln -sf "$INSTALL_PATH" "$SYSTEM_PATH" + + # 验证安装 + if $INSTALL_PATH --version; then + echo "" + echo "==========================================" + echo "✓ act_runner installed successfully!" + echo "==========================================" + echo "" + echo "Version: $($INSTALL_PATH --version)" + echo "Location: $INSTALL_PATH (persistent storage)" + echo "" + echo "Next steps:" + echo "1. Register the runner:" + echo " docker-compose exec gitea-runner /data/register.sh" + echo "" + echo "2. Restart the container:" + echo " docker-compose restart" + echo "" + echo "Note: act_runner is saved in persistent storage" + echo " and will survive container restarts." + echo "" + else + echo "" + echo "✗ Installation verification failed!" + rm -f "$INSTALL_PATH" "$SYSTEM_PATH" + exit 1 + fi +else + echo "" + echo "✗ Download failed!" + echo "Please check:" + echo " - Internet connection" + echo " - Version number is correct: $RUNNER_VERSION" + echo " - Architecture is correct: $RUNNER_ARCH" + echo " - URL is accessible: $DOWNLOAD_URL" + echo "" + echo "You can check available versions at:" + echo " https://dl.gitea.com/act_runner/" + exit 1 +fi