feat: 重构工作流体系,将命令模式迁移为技能文档

This commit is contained in:
2026-01-21 15:55:57 +08:00
parent 43e138b19e
commit dffa3fae12
18 changed files with 1101 additions and 2099 deletions

View File

@@ -7,6 +7,13 @@ description: Git workflow best practices with commit conventions, tagging, and c
You are an expert in Git version control and repository management.
## 功能文档
| 文档 | 说明 |
|------|------|
| [Commit Workflow](./commit-workflow.md) | 提交暂存文件,自动生成提交信息并创建版本标签 |
| [Push Workflow](./push-workflow.md) | 提交并推送到远程仓库的完整工作流 |
## Core Principles
1. **Default Main Branch**: Use `main` as the primary branch (not `master`)

View File

@@ -0,0 +1,163 @@
# Git Commit Workflow
提交暂存文件,自动生成提交信息并创建版本标签的完整工作流。
## 概述
此工作流用于:
- 自动分析暂存区变更
- 根据 Conventional Commits 规范生成提交信息
- 检测项目类型并更新版本号
- 创建语义化版本标签
## 执行步骤
### 步骤 1: 检查暂存区
```bash
git diff --cached --name-only
```
- 如果暂存区为空,通知用户并停止
- 如果有文件,继续下一步
### 步骤 2: 收集信息(并行执行)
```bash
# 并行执行以下命令
git status
git diff --cached
git log --oneline -10
git tag --list | sort -V | tail -5
```
同时检查 `AGENTS.md` 文件获取:
- 仓库类型polyrepo/monorepo
- 版本规则
- 项目结构信息
### 步骤 3: 检测仓库类型
**Polyrepo单仓库**
- Tag 格式:`<version>`(如 `1.2.0`
**Monorepo多项目**
- Tag 格式:`<subproject>-<version>`(如 `ios-1.2.0`
- 检测特征:`packages/``apps/``services/` 目录
### 步骤 4: 检测项目类型和版本
| 项目类型 | 版本文件 | 版本字段 |
|---------|---------|---------|
| iOS | `*.xcodeproj/project.pbxproj` | `MARKETING_VERSION` |
| Node.js | `package.json` | `version` |
| Android (Groovy) | `app/build.gradle` | `versionName` |
| Android (Kotlin) | `app/build.gradle.kts` | `versionName` |
| Go | Git tag only | - |
| Python | `pyproject.toml` / `setup.py` | `version` |
| Rust | `Cargo.toml` | `version` |
### 步骤 5: 生成提交信息
遵循 Conventional Commits 格式:
```
<type>(<scope>): <subject>
```
**提交类型**
| Type | 描述 | 版本影响 |
|------|------|---------|
| `feat` | 新功能 | minor +1 |
| `fix` | Bug 修复 | patch +1 |
| `perf` | 性能优化 | patch +1 |
| `BREAKING CHANGE` | 破坏性变更 | major +1 |
| `docs` | 文档更新 | 无 |
| `style` | 代码格式 | 无 |
| `refactor` | 重构 | 无 |
| `test` | 测试 | 无 |
| `chore` | 维护任务 | 无 |
| `ci` | CI/CD 变更 | 无 |
| `build` | 构建配置 | 无 |
**提交语言**
- macOS/Linux使用中文
- Windows使用英文避免编码问题
**Monorepo Scope**
- 单项目变更:`feat(ios): 添加上传功能`
- 多项目变更:`chore: 更新共享依赖`
### 步骤 6: 确定新版本号
根据提交类型计算新版本:
**版本递增规则**
- `feat`minor +1`1.2.0``1.3.0`
- `fix`/`perf`patch +1`1.2.3``1.2.4`
- Breaking changemajor +1`1.2.3``2.0.0`
**不更新版本**
- `docs``test``refactor``style``build``ci``chore`
### 步骤 7: 更新版本文件并暂存
如果需要更新版本(步骤 6 确定了新版本号):
1. **更新版本文件**:将版本号写入对应的版本文件
2. **添加到暂存区**
```bash
git add <version-file>
```
3. **验证暂存**
```bash
git diff --cached --name-only
```
确认版本文件已在暂存区
### 步骤 8: 执行提交
```bash
git commit -m "<commit-message>"
```
### 步骤 9: 创建版本标签
仅在版本更新时创建(除非用户指定 "skip tag"
**Polyrepo**
```bash
git tag -a "1.2.0" -m "<commit-message>"
```
**Monorepo**
```bash
git tag -a "ios-1.2.0" -m "<commit-message>"
```
## 选项
- `skip tag` / `skip`:跳过标签创建
## 输出格式
```
✓ 提交成功
提交信息:[commit message]
版本标签:[tag](如果创建了)
要推送到远程仓库,请运行:/git-push
```
## 注意事项
- 此命令**不会推送**到远程,使用 `/git-push` 推送
- 暂存区为空时会提示用户先 `git add`
- 标签注释使用与提交相同的消息内容
## 相关文档
- [Git Workflow Best Practices](./SKILL.md)
- [Push Workflow](./push-workflow.md)

107
skill/git/push-workflow.md Normal file
View File

@@ -0,0 +1,107 @@
# Git Push Workflow
提交暂存文件,创建版本标签并推送到远程仓库的完整工作流。
## 概述
此工作流是 **All-in-One** 命令,组合了:
- `/git-commit` 的所有功能
- 推送提交到远程
- 推送标签到远程
## 执行步骤
### 步骤 1-9: 与 Commit Workflow 相同
参考 [Commit Workflow](./commit-workflow.md)
1. 检查暂存区(不能为空)
2. 收集变更信息和仓库状态
3. 检测仓库类型polyrepo/monorepo
4. 检测项目类型和版本
5. 生成提交信息Conventional Commits中文
6. 确定新版本号
7. 更新版本文件并添加到暂存区
8. 执行提交
9. 创建版本标签
### 步骤 10: 推送提交到远程
```bash
git push origin $(git branch --show-current)
```
### 步骤 11: 推送标签到远程
仅在创建了标签时执行:
**Polyrepo**
```bash
git push origin <version>
```
**Monorepo**
```bash
git push origin <subproject>-<version>
```
## 选项
- `skip tag` / `skip`:跳过标签创建
## 输出格式
```
✓ 提交并推送成功
分支:[branch]
提交信息:[commit message]
版本标签:[tag](如果创建了)
已推送到远程仓库origin
- 提交:[commit hash]
- 标签:[tag]
```
## 错误处理
### 暂存区为空
```
暂存区为空,请先使用 `git add` 添加文件。
```
### 推送失败
```
❌ 推送失败:[error message]
可能的解决方案:
1. 先拉取远程变更git pull origin <branch>
2. 检查网络连接
3. 检查远程仓库权限
```
### 标签已存在
```
❌ 标签推送失败tag already exists
解决方案:
1. 删除本地标签git tag -d <tag>
2. 更新版本号后重新提交
```
## 使用场景
| 场景 | 推荐命令 |
|------|---------|
| 本地提交,稍后审查 | `/git-commit` |
| 提交并立即推送 | `/git-push` |
| 仅推送已有提交 | `git push origin <branch>` |
| 仅推送标签 | `git push origin <tag>` |
## 相关文档
- [Git Workflow Best Practices](./SKILL.md)
- [Commit Workflow](./commit-workflow.md)

View File

@@ -1,663 +0,0 @@
# Git Quick Reference
Quick reference guide for common Git operations.
## File Changes and Status
### View Changed Files
```bash
# Show working directory status
git status
# Show short status
git status -s
# List changed files only (unstaged)
git diff --name-only
# List changed files only (staged)
git diff --cached --name-only
# or
git diff --staged --name-only
# Show file change statistics
git diff --stat
git diff --cached --stat
```
### View Detailed Changes
```bash
# View unstaged changes
git diff
# View staged changes
git diff --cached
# or
git diff --staged
# View specific file changes
git diff <file-path>
git diff --cached <file-path>
# View changes between commits
git diff <commit1>..<commit2>
git diff HEAD~1..HEAD
# View changes between branches
git diff main..feature-branch
```
## Staging and Committing
### Add Files to Staging
```bash
# Add specific file
git add <file-path>
# Add all files in directory
git add .
# Add all files in repository
git add -A
# Add files interactively
git add -p
# Add only modified files (not new files)
git add -u
```
### Check Staging Area
```bash
# List files in staging area
git diff --cached --name-only
# Show detailed staged changes
git diff --cached
```
### Commit Changes
```bash
# Simple commit
git commit -m "feat: add user authentication"
# Multi-line commit (macOS/Linux)
git commit -m "$(cat <<'EOF'
feat: add user authentication
- Add OAuth2 support
- Implement JWT tokens
- Add login/logout endpoints
EOF
)"
# Multi-line commit (Windows)
git commit -m "feat: add user authentication" \
-m "" \
-m "- Add OAuth2 support" \
-m "- Implement JWT tokens" \
-m "- Add login/logout endpoints"
# Commit with automatic staging
git commit -am "fix: resolve issue"
# Amend last commit (before push only!)
git commit --amend -m "new message"
```
## Tag Management
### Create Tags
```bash
# Create annotated tag
git tag -a "1.2.0" -m "feat: add new feature"
# Create lightweight tag
git tag "1.2.0"
# Create tag with multi-line message
git tag -a "1.2.1" \
-m "fix: resolve connection issue" \
-m "" \
-m "- Increase timeout to 30s" \
-m "- Add retry mechanism"
# Create tag for specific commit
git tag -a "1.2.0" <commit-hash> -m "message"
# Monorepo tag
git tag -a "ios-1.2.0" -m "feat(ios): add feature"
```
### List Tags
```bash
# List all tags
git tag
# List tags with pattern
git tag -l "v1.*"
# List recent tags (sorted)
git tag --list | sort -V | tail -5
# Show tag details
git show <tag-name>
```
### Push Tags
```bash
# Push single tag
git push origin <tag-name>
# Push all tags
git push --tags
# or
git push origin --tags
# Push commit and tag together
git push origin main && git push origin 1.2.0
```
### Delete Tags
```bash
# Delete local tag
git tag -d <tag-name>
# Delete remote tag
git push origin --delete <tag-name>
# or
git push origin :refs/tags/<tag-name>
# Delete multiple tags
git tag -d tag1 tag2 tag3
```
## Branch Operations
### View Branches
```bash
# Show current branch
git branch --show-current
# List local branches
git branch
# List all branches (local + remote)
git branch -a
# List remote branches only
git branch -r
# Show branch with last commit
git branch -v
```
### Create and Switch Branches
```bash
# Create new branch
git branch <branch-name>
# Create and switch to new branch (old way)
git checkout -b <branch-name>
# Create and switch to new branch (modern)
git switch -c <branch-name>
# Switch to existing branch (old way)
git checkout <branch-name>
# Switch to existing branch (modern)
git switch <branch-name>
# Switch to previous branch
git switch -
```
### Delete Branches
```bash
# Delete local branch (safe)
git branch -d <branch-name>
# Delete local branch (force)
git branch -D <branch-name>
# Delete remote branch
git push origin --delete <branch-name>
# or
git push origin :<branch-name>
```
## Pushing and Pulling
### Push Changes
```bash
# Push current branch
git push
# Push to specific remote and branch
git push origin main
# Push current branch to remote
git push origin $(git branch --show-current)
# Push with upstream tracking
git push -u origin <branch-name>
# Push all branches
git push --all
# Push all tags
git push --tags
# Force push (dangerous!)
git push --force
# Better: force push with lease
git push --force-with-lease
```
### Pull Changes
```bash
# Pull from tracked remote
git pull
# Pull from specific remote and branch
git pull origin main
# Pull with rebase
git pull --rebase
# Pull and prune deleted remote branches
git pull --prune
```
### Fetch Changes
```bash
# Fetch from all remotes
git fetch
# Fetch from specific remote
git fetch origin
# Fetch and prune deleted remote branches
git fetch --prune
# Fetch all branches and tags
git fetch --all --tags
```
## History and Logs
### View Commit History
```bash
# View recent commits
git log
# View compact history
git log --oneline
# View recent 10 commits
git log --oneline -10
# View history with graph
git log --graph --oneline --all
# View history with stats
git log --stat
# View history with patches
git log -p
```
### Search History
```bash
# Search commits by message
git log --grep="feature"
# Search by author
git log --author="John"
# Search by date
git log --since="2024-01-01"
git log --after="2 weeks ago"
git log --before="yesterday"
# Search by file
git log -- <file-path>
# Search code changes
git log -S "function_name"
```
### View Commit Details
```bash
# Show specific commit
git show <commit-hash>
# Show specific tag
git show <tag-name>
# Show HEAD commit
git show HEAD
# Show previous commit
git show HEAD~1
git show HEAD^
```
## Undoing Changes
### Discard Changes
```bash
# Discard unstaged changes in file
git checkout -- <file-path>
# or (modern)
git restore <file-path>
# Discard all unstaged changes
git checkout -- .
# or (modern)
git restore .
# Unstage file (keep changes)
git reset HEAD <file-path>
# or (modern)
git restore --staged <file-path>
# Unstage all files
git reset HEAD
# or (modern)
git restore --staged .
```
### Reset Commits
```bash
# Undo last commit, keep changes staged
git reset --soft HEAD~1
# Undo last commit, keep changes unstaged
git reset HEAD~1
# or
git reset --mixed HEAD~1
# Undo last commit, discard changes (dangerous!)
git reset --hard HEAD~1
# Reset to specific commit
git reset --hard <commit-hash>
```
### Revert Commits
```bash
# Create new commit that undoes a commit
git revert <commit-hash>
# Revert without committing
git revert -n <commit-hash>
# Revert multiple commits
git revert <commit1>..<commit2>
```
## Stash Operations
### Save Changes
```bash
# Stash current changes
git stash
# Stash with message
git stash save "work in progress"
# Stash including untracked files
git stash -u
# Stash including untracked and ignored files
git stash -a
```
### Apply Stash
```bash
# Apply most recent stash
git stash apply
# Apply and remove from stash list
git stash pop
# Apply specific stash
git stash apply stash@{2}
```
### Manage Stash
```bash
# List all stashes
git stash list
# Show stash changes
git stash show
git stash show -p
# Drop specific stash
git stash drop stash@{1}
# Clear all stashes
git stash clear
```
## Remote Operations
### View Remotes
```bash
# List remotes
git remote
# List remotes with URLs
git remote -v
# Show remote details
git remote show origin
```
### Manage Remotes
```bash
# Add remote
git remote add <name> <url>
# Remove remote
git remote remove <name>
# Rename remote
git remote rename <old-name> <new-name>
# Change remote URL
git remote set-url <name> <new-url>
```
## Configuration
### View Configuration
```bash
# View all config
git config --list
# View global config
git config --global --list
# View local config
git config --local --list
# View specific config
git config user.name
git config user.email
```
### Set Configuration
```bash
# Set user name
git config --global user.name "Your Name"
# Set user email
git config --global user.email "your.email@example.com"
# Set default branch name
git config --global init.defaultBranch main
# Set default editor
git config --global core.editor "code --wait"
# Set credential helper
git config --global credential.helper store
```
## Workflow Examples
### Standard Commit and Tag Workflow
```bash
# 1. Check status
git status
git diff --cached --name-only
# 2. Stage changes
git add .
# 3. Commit
git commit -m "feat: add user authentication"
# 4. Create tag
git tag -a "1.2.0" -m "feat: add user authentication"
# 5. Push commit and tag
git push origin main
git push origin 1.2.0
```
### Complete Staging to Push Workflow
```bash
# Check what files changed
git status
# View changes
git diff
# Stage specific files
git add src/auth.js src/api.js
# Verify staging
git diff --cached --name-only
# Commit with message
git commit -m "feat: implement OAuth2 authentication"
# Push to remote
git push origin main
```
### Push All Tags Workflow
```bash
# List local tags
git tag
# View recent tags
git tag --list | sort -V | tail -5
# Push all tags to remote
git push --tags
# Verify tags on remote
git ls-remote --tags origin
```
### Quick Status Check
```bash
# Full status
git status
# Changed files only
git diff --name-only
git diff --cached --name-only
# Recent commits and tags
git log --oneline -5
git tag --list | sort -V | tail -5
# Current branch
git branch --show-current
```
## Tips and Tricks
### Aliases
Add to `~/.gitconfig`:
```ini
[alias]
st = status
co = checkout
br = branch
ci = commit
unstage = restore --staged
last = log -1 HEAD
lg = log --graph --oneline --all
tags = tag -l --sort=-v:refname
```
Usage:
```bash
git st
git co main
git lg
```
### Useful One-Liners
```bash
# Delete all merged branches
git branch --merged | grep -v "\*" | xargs -n 1 git branch -d
# View file in specific commit
git show <commit>:<file-path>
# Count commits by author
git shortlog -sn
# Find when a line was changed
git blame <file-path>
# Show what changed in each commit for a file
git log -p <file-path>
# List files in a commit
git diff-tree --no-commit-id --name-only -r <commit>
```

View File

@@ -58,6 +58,7 @@ C:\Users\YourUsername\.config\gitea\
|---------|------|------|
| 环境配置 | [setup-guide.md](./setup-guide.md) | 首次使用引导,配置 Gitea URL 和 Token |
| Runner 管理 | [runner-management.md](./runner-management.md) | 创建、注册、管理 Gitea Act Runner |
| 自动创建脚本 | [create-runner.md](./create-runner.md) | 包含完整的 Runner 创建 Bash 脚本 |
| Workflow 生成 | [workflow-generator.md](./workflow-generator.md) | 根据项目类型生成 CI/CD workflow |
| 仓库操作 | [repository-operations.md](./repository-operations.md) | 创建和配置 Gitea 仓库 |
| API 参考 | [api-reference.md](./api-reference.md) | Gitea API 常用接口 |

View File

@@ -0,0 +1,416 @@
---
description: 创建并启动 Gitea Actions Runner 的完整脚本
agent: general
---
# Create Runner Script
本文档包含用于创建 Gitea Actions Runner 的完整 Bash 脚本。该脚本支持 Host 模式和 Docker 模式。
## 脚本文件
你可以将以下内容保存为 `create_runner.sh` 并赋予执行权限 (`chmod +x create_runner.sh`)。
```bash
#!/bin/bash
# Gitea Runner Creation Script
# Generated by OpenCode Skill
set -e
# ==========================================
# 1. Choose Mode & Check Dependencies
# ==========================================
echo "请选择 Runner 运行模式:"
echo " 1. Host Mode (直接在宿主机运行,支持 macOS/iOS 原生构建)"
echo " 2. Docker Mode (在容器中运行,环境隔离,适合标准 Linux 构建)"
echo ""
read -p "请输入选项 [1/2] (默认 1): " mode_choice
if [ "$mode_choice" = "2" ]; then
RUNNER_MODE="docker"
echo "✅ 已选择: Docker Mode"
else
RUNNER_MODE="host"
echo "✅ 已选择: Host Mode"
fi
echo ""
# Check dependencies based on mode
if [ "$RUNNER_MODE" = "host" ]; then
echo "检查 act_runner 安装状态..."
if command -v act_runner &> /dev/null; then
version=$(act_runner --version 2>&1 | head -n1)
echo "✓ act_runner 已安装: $version"
else
echo "⚠️ act_runner 未安装"
echo "正在使用 Homebrew 安装..."
if ! command -v brew &> /dev/null; then
echo "❌ 需要先安装 Homebrew"
exit 1
fi
brew install act_runner
if [ $? -eq 0 ]; then
echo "✓ act_runner 安装成功"
else
echo "❌ act_runner 安装失败"
exit 1
fi
fi
else
# Docker mode checks
echo "检查 Docker 环境..."
if command -v docker &> /dev/null; then
if docker info &> /dev/null; then
docker_ver=$(docker --version)
echo "✓ Docker 已安装并运行: $docker_ver"
else
echo "❌ Docker 已安装但未运行"
echo " 请启动 Docker Desktop"
exit 1
fi
else
echo "❌ Docker 未安装"
echo " Docker 模式需要预先安装 Docker"
echo " 请访问 https://www.docker.com/products/docker-desktop/ 下载安装"
exit 1
fi
fi
echo ""
# ==========================================
# 2. Load Gitea Configuration
# ==========================================
config_file="$HOME/.config/gitea/config.env"
if [ ! -f "$config_file" ]; then
echo "❌ Gitea 配置不存在,请先运行 /gitea-reset"
exit 1
fi
source "$config_file"
if [ -z "$GITEA_URL" ] || [ -z "$GITEA_TOKEN" ]; then
echo "❌ Gitea 配置不完整 (缺少 URL 或 TOKEN)"
echo " 请运行 /gitea-reset 重新配置"
exit 1
fi
echo "✓ 已加载 Gitea 配置: $GITEA_URL"
echo ""
# ==========================================
# 3. Generate Runner Name
# ==========================================
if [ -n "$1" ]; then
runner_name="$1"
echo "使用指定的 Runner 名称: $runner_name"
else
hostname=$(hostname -s 2>/dev/null || echo "unknown")
runner_name="runner-$hostname-$RUNNER_MODE"
echo "生成 Runner 名称: $runner_name"
fi
if [[ ! "$runner_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "❌ Runner 名称无效 (仅限字母、数字、下划线、连字符)"
exit 1
fi
echo ""
# ==========================================
# 4. Check Runner Existence
# ==========================================
runners_dir="$HOME/.config/gitea/runners"
runner_dir="$runners_dir/$runner_name"
if [ "$RUNNER_MODE" = "host" ]; then
if [ -d "$runner_dir" ]; then
echo "❌ Runner 目录已存在: $runner_dir"
echo " 请使用 /gitea-delete-runner 删除旧 Runner 或指定新名称"
exit 1
fi
else
# Docker mode: check directory AND container
if [ -d "$runner_dir" ]; then
echo "❌ Runner 配置目录已存在: $runner_dir"
exit 1
fi
if docker ps -a --format '{{.Names}}' | grep -q "^${runner_name}$"; then
echo "❌ Docker 容器已存在: $runner_name"
echo " 请先删除旧容器: docker rm -f $runner_name"
exit 1
fi
fi
# ==========================================
# 5. Detect System Environment & Labels
# ==========================================
echo "生成 Labels..."
if [ "$RUNNER_MODE" = "host" ]; then
OS=$(uname -s)
case "$OS" in
Darwin) os_label="macOS" ;;
Linux) os_label="ubuntu" ;;
*) os_label="unknown" ;;
esac
ARCH=$(uname -m)
case "$ARCH" in
arm64|aarch64) arch_label="ARM64" ;;
x86_64) arch_label="x64" ;;
*) arch_label="unknown" ;;
esac
combined=$(echo "${OS}-${ARCH}" | tr '[:upper:]' '[:lower:]')
labels="self-hosted:host,${os_label}:host,${arch_label}:host,${combined}:host"
else
# Docker mode uses standard labels mapping to images
# Format: label:docker://image
labels="ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye,ubuntu-20.04:docker://node:16-buster,linux:docker://node:16-bullseye"
fi
echo "✓ Labels ($RUNNER_MODE):"
echo " $labels"
echo ""
# ==========================================
# 6. Create Runner Directory
# ==========================================
echo "创建 Runner 目录..."
mkdir -p "$runner_dir"/{cache,workspace}
if [ "$RUNNER_MODE" = "docker" ]; then
# Docker mode might strictly need data dir mapping
mkdir -p "$runner_dir/data"
fi
echo "✓ 目录: $runner_dir"
echo ""
# ==========================================
# 7. Get Registration Token
# ==========================================
echo "正在获取 Runner 注册 Token..."
# 默认尝试全局 Runner管理员权限
echo "尝试创建全局 Runner可用于所有组织和仓库..."
response=$(curl -s -w "\n%{http_code}" \
-H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/admin/runners/registration-token")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
# 如果全局 Runner 失败(权限不足),降级到组织 Runner
if [ "$http_code" != "200" ]; then
echo "⚠️ 全局 Runner 权限不足 (HTTP $http_code)"
echo " 全局 Runner 需要管理员 Token"
echo ""
echo "降级到组织 Runner..."
runner_level="organization"
if [ -n "$GITEA_DEFAULT_ORG" ]; then
org_name="$GITEA_DEFAULT_ORG"
else
read -p "请输入组织名称: " org_input
if [ -z "$org_input" ]; then
echo "❌ 必须指定组织名称"
exit 1
fi
org_name="$org_input"
fi
echo "使用组织: $org_name"
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/orgs/$org_name/actions/runners/registration-token")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
else
echo "✓ 使用全局 Runner"
runner_level="global"
fi
if [ "$http_code" != "200" ]; then
echo "❌ 获取注册 Token 失败 (HTTP $http_code)"
echo "$body"
exit 1
fi
# Need jq for parsing json
if ! command -v jq &> /dev/null; then
# Simple grep fallback if jq not available, but jq is better
registration_token=$(echo "$body" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
else
registration_token=$(echo "$body" | jq -r '.token')
fi
if [ -z "$registration_token" ]; then
echo "❌ 无法解析注册 Token"
exit 1
fi
echo "✓ 注册 Token 已获取"
echo ""
# ==========================================
# 8. Start Runner (Register & Run)
# ==========================================
echo "启动 Runner..."
if [ "$RUNNER_MODE" = "host" ]; then
# Host Mode Config
cat > "$runner_dir/config.yaml" << EOF
log:
level: info
runner:
file: .runner
capacity: 1
timeout: 3h
shutdown_timeout: 30s
insecure: false
fetch_timeout: 5s
fetch_interval: 2s
labels: []
cache:
enabled: true
dir: "$runner_dir/cache"
host: "127.0.0.1"
port: 0
host:
workdir_parent: "$runner_dir/workspace"
EOF
echo "注册 Host Runner..."
act_runner register \
--config "$runner_dir/config.yaml" \
--instance "$GITEA_URL" \
--token "$registration_token" \
--name "$runner_name" \
--labels "$labels" \
--no-interactive
if [ $? -ne 0 ]; then
echo "❌ 注册失败"
exit 1
fi
echo "启动后台进程..."
nohup act_runner daemon --config "$runner_dir/config.yaml" \
> "$runner_dir/runner.log" 2>&1 &
runner_pid=$!
sleep 3
if ps -p $runner_pid > /dev/null 2>&1; then
echo "✓ Host Runner 正在后台运行 (PID: $runner_pid)"
# Save PID
echo $runner_pid > "$runner_dir/pid"
else
echo "❌ 启动失败,请检查日志"
exit 1
fi
else
# Docker Mode
# Strategy: Use environment variables for auto-registration on startup
# This avoids the "Instance Address Empty" issue seen with 'act_runner register' inside containers
cat > "$runner_dir/config.yaml" << EOF
log:
level: info
runner:
file: /data/.runner
capacity: 2
timeout: 3h
shutdown_timeout: 30s
insecure: false
fetch_timeout: 5s
fetch_interval: 2s
labels: []
cache:
enabled: true
dir: "/data/cache"
host: "host.docker.internal"
port: 9040
container:
# 使用 host 网络模式,确保 runner 和 job 容器共处一个网络,以便 cache 能够正常访问
network: "host"
privileged: false
options:
workdir_parent: /data/workspace
valid_volumes: []
docker_host: ""
force_pull: false
host:
workdir_parent: /data/workspace
EOF
echo "启动 Docker 容器 (自动注册)..."
# 使用 host 网络模式,确保 runner 和 job 容器共处一个网络,以便 cache 能够正常访问
docker run -d \
--name "$runner_name" \
--restart always \
--network host \
-v "$runner_dir/config.yaml:/config.yaml" \
-v "$runner_dir/data:/data" \
-v "/var/run/docker.sock:/var/run/docker.sock" \
-e GITEA_INSTANCE_URL="$GITEA_URL" \
-e GITEA_RUNNER_REGISTRATION_TOKEN="$registration_token" \
-e GITEA_RUNNER_NAME="$runner_name" \
-e GITEA_RUNNER_LABELS="$labels" \
gitea/act_runner:latest daemon --config /config.yaml
if [ $? -eq 0 ]; then
echo "✓ Docker Runner 容器已启动: $runner_name"
# 等待容器启动并查看日志
sleep 5
echo "容器日志:"
docker logs "$runner_name" 2>&1 | tail -20
else
echo "❌ 容器启动失败"
exit 1
fi
fi
echo ""
# ==========================================
# 9. Display Summary
# ==========================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Runner 创建成功!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "基本信息:"
echo " 名称: $runner_name"
echo " 模式: $RUNNER_MODE"
echo " 状态: 🟢 运行中"
if [ "$RUNNER_MODE" = "host" ]; then
echo " PID: $runner_pid"
echo " 日志: $runner_dir/runner.log"
echo " 停止: kill \$(cat $runner_dir/pid)"
else
echo " 容器: $runner_name"
echo " 命令: docker logs -f $runner_name"
echo " docker stop $runner_name"
fi
echo ""
echo "配置文件: $runner_dir/config.yaml"
echo "Labels: $labels"
echo ""
```

View File

@@ -0,0 +1,303 @@
---
description: 交互式批量删除 Gitea Runners 脚本
agent: general
---
# Delete Runner Script
本文档包含用于交互式批量删除 Gitea Actions Runner 的完整 Bash 脚本。
## 功能特点
- **多选支持**:支持输入多个序号(如 `1,3``1 3`)或 `all` 进行批量删除。
- **双重清理**:同时从 Gitea 服务器注销 Runner 和删除本地配置/容器。
- **智能识别**:自动关联远程 Runner 状态与本地 Runner 目录。
- **安全检查**:删除前强制二次确认,防止误删。
## 脚本文件
你可以将以下内容保存为 `delete_runner.sh` 并赋予执行权限 (`chmod +x delete_runner.sh`)。
```bash
#!/bin/bash
# Gitea Runner Deletion Script
# Generated by OpenCode Skill
set -e
# ==========================================
# 1. Setup & Config
# ==========================================
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Config
CONFIG_FILE="$HOME/.config/gitea/config.env"
RUNNERS_BASE_DIR="$HOME/.config/gitea/runners"
if [ ! -f "$CONFIG_FILE" ]; then
echo -e "${RED}❌ 配置文件不存在: $CONFIG_FILE${NC}"
exit 1
fi
source "$CONFIG_FILE"
if [ -z "$GITEA_URL" ] || [ -z "$GITEA_TOKEN" ]; then
echo -e "${RED}❌ 配置无效: 缺少 URL 或 Token${NC}"
exit 1
fi
# Check requirements
if ! command -v jq &> /dev/null; then
echo -e "${RED}❌ 需要安装 jq 工具来解析 JSON${NC}"
exit 1
fi
echo "正在获取 Runner 列表..."
# ==========================================
# 2. Data Collection
# ==========================================
# Temporary files
REMOTE_LIST=$(mktemp)
LOCAL_MAP=$(mktemp)
FINAL_LIST=$(mktemp)
# 2.1 Fetch Remote Runners (Try Admin first, then Org)
# Note: Admin endpoint /api/v1/admin/runners lists all runners
HTTP_CODE=$(curl -s -w "%{http_code}" -o "$REMOTE_LIST" \
-H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/admin/runners?page=1&limit=100")
if [ "$HTTP_CODE" != "200" ]; then
# Fallback to Org level if defined
if [ -n "$GITEA_DEFAULT_ORG" ]; then
HTTP_CODE=$(curl -s -w "%{http_code}" -o "$REMOTE_LIST" \
-H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/orgs/${GITEA_DEFAULT_ORG}/actions/runners?page=1&limit=100")
fi
fi
if [ "$HTTP_CODE" != "200" ]; then
echo -e "${RED}❌ 无法获取 Runner 列表 (HTTP $HTTP_CODE)${NC}"
cat "$REMOTE_LIST"
rm "$REMOTE_LIST" "$LOCAL_MAP" "$FINAL_LIST"
exit 1
fi
# 2.2 Scan Local Directories to map UUID -> Path
# We need to find which local directory corresponds to which runner ID/UUID
echo "{}" > "$LOCAL_MAP"
if [ -d "$RUNNERS_BASE_DIR" ]; then
for d in "$RUNNERS_BASE_DIR"/*; do
if [ -d "$d" ]; then
# Check Host mode .runner
if [ -f "$d/.runner" ]; then
uuid=$(jq -r '.uuid' "$d/.runner" 2>/dev/null)
if [ -n "$uuid" ] && [ "$uuid" != "null" ]; then
# Add to JSON map
tmp=$(mktemp)
jq --arg uuid "$uuid" --arg path "$d" '.[$uuid] = $path' "$LOCAL_MAP" > "$tmp" && mv "$tmp" "$LOCAL_MAP"
fi
fi
# Check Docker mode data/.runner
if [ -f "$d/data/.runner" ]; then
uuid=$(jq -r '.uuid' "$d/data/.runner" 2>/dev/null)
if [ -n "$uuid" ] && [ "$uuid" != "null" ]; then
tmp=$(mktemp)
jq --arg uuid "$uuid" --arg path "$d" '.[$uuid] = $path' "$LOCAL_MAP" > "$tmp" && mv "$tmp" "$LOCAL_MAP"
fi
fi
fi
done
fi
# ==========================================
# 3. Display Interface
# ==========================================
# Combine Remote and Local info
# Output format: index | id | name | status | local_path
jq -r --slurpfile local "$LOCAL_MAP" '
.runners[] |
[.id, .uuid, .name, .status, ($local[0][.uuid] // "")] |
@tsv
' "$REMOTE_LIST" > "$FINAL_LIST"
count=$(wc -l < "$FINAL_LIST" | tr -d ' ')
if [ "$count" -eq 0 ]; then
echo "没有发现任何 Runners。"
rm "$REMOTE_LIST" "$LOCAL_MAP" "$FINAL_LIST"
exit 0
fi
echo ""
echo -e "${YELLOW}Gitea Runners 列表 (共 $count 个)${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
printf "%-4s | %-8s | %-20s | %-10s | %-30s\n" "序号" "ID" "名称" "状态" "本地目录"
echo "----------------------------------------------------------------------------"
i=1
declare -a runner_ids
declare -a runner_names
declare -a runner_paths
while IFS=$'\t' read -r id uuid name status local_path; do
status_icon="🔴"
if [ "$status" = "online" ] || [ "$status" = "idle" ] || [ "$status" = "active" ]; then
status_icon="🟢"
fi
local_mark=""
if [ -n "$local_path" ]; then
local_mark="$(basename "$local_path")"
else
local_mark="-"
fi
printf "%-4d | %-8s | %-20s | %s %-8s | %-30s\n" "$i" "$id" "${name:0:20}" "$status_icon" "$status" "$local_mark"
runner_ids[$i]=$id
runner_names[$i]=$name
runner_paths[$i]=$local_path
i=$((i+1))
done < "$FINAL_LIST"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# ==========================================
# 4. User Selection
# ==========================================
echo "请输入要删除的序号:"
echo " - 单个: 1"
echo " - 多选: 1,3,5 或 1 3 5"
echo " - 全部: all"
echo " - 退出: q"
echo ""
read -p "选择 > " selection
if [[ "$selection" =~ ^[qQ] ]]; then
echo "已取消。"
rm "$REMOTE_LIST" "$LOCAL_MAP" "$FINAL_LIST"
exit 0
fi
target_indices=()
if [ "$selection" = "all" ]; then
for ((j=1; j<=count; j++)); do
target_indices+=($j)
done
else
# Replace commas with spaces and iterate
for idx in ${selection//,/ }; do
# Validate number
if [[ "$idx" =~ ^[0-9]+$ ]] && [ "$idx" -ge 1 ] && [ "$idx" -le "$count" ]; then
target_indices+=($idx)
else
echo -e "${YELLOW}⚠️ 忽略无效序号: $idx${NC}"
fi
done
fi
if [ ${#target_indices[@]} -eq 0 ]; then
echo -e "${RED}未选择任何有效 Runner。${NC}"
rm "$REMOTE_LIST" "$LOCAL_MAP" "$FINAL_LIST"
exit 1
fi
# ==========================================
# 5. Confirmation
# ==========================================
echo ""
echo -e "${RED}⚠️ 警告: 即将删除以下 ${#target_indices[@]} 个 Runner:${NC}"
for idx in "${target_indices[@]}"; do
echo " - [${runner_ids[$idx]}] ${runner_names[$idx]}"
if [ -n "${runner_paths[$idx]}" ]; then
echo " └─ 本地目录: ${runner_paths[$idx]}"
fi
done
echo ""
echo "此操作将从服务器注销 Runner 并删除本地文件/容器。"
read -p "确认删除? (输入 yes 继续): " confirm
if [ "$confirm" != "yes" ]; then
echo "操作已取消。"
rm "$REMOTE_LIST" "$LOCAL_MAP" "$FINAL_LIST"
exit 0
fi
# ==========================================
# 6. Execution
# ==========================================
echo ""
echo "开始执行删除..."
for idx in "${target_indices[@]}"; do
r_id="${runner_ids[$idx]}"
r_name="${runner_names[$idx]}"
r_path="${runner_paths[$idx]}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "正在处理: $r_name (ID: $r_id)"
# 6.1 Delete from Server
echo -n " 1. 从服务器注销... "
del_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/admin/actions/runners/${r_id}")
if [ "$del_code" = "204" ] || [ "$del_code" = "404" ]; then
echo -e "${GREEN}成功${NC}"
else
echo -e "${RED}失败 (HTTP $del_code)${NC}"
# Continue cleanup anyway
fi
# 6.2 Cleanup Local
if [ -n "$r_path" ] && [ -d "$r_path" ]; then
dir_name=$(basename "$r_path")
# Stop Docker container if name matches folder name (common convention)
if docker ps -a --format '{{.Names}}' | grep -q "^${dir_name}$"; then
echo -n " 2. 停止并删除 Docker 容器 ($dir_name)... "
docker rm -f "$dir_name" >/dev/null 2>&1
echo -e "${GREEN}完成${NC}"
fi
# Stop Host process (if PID file exists)
if [ -f "$r_path/pid" ]; then
pid=$(cat "$r_path/pid")
echo -n " 2. 停止本地进程 (PID: $pid)... "
kill "$pid" >/dev/null 2>&1 || true
echo -e "${GREEN}完成${NC}"
fi
echo -n " 3. 删除本地目录... "
rm -rf "$r_path"
echo -e "${GREEN}完成${NC}"
else
echo " - 本地目录未找到或已清理"
fi
done
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "${GREEN}✅ 批量删除操作完成${NC}"
# Cleanup temps
rm "$REMOTE_LIST" "$LOCAL_MAP" "$FINAL_LIST"
```

View File

@@ -267,6 +267,8 @@ act_runner --version
7. 获取注册 token优先全局
8. 注册并启动 runner
> **提示**:你可以从 [create-runner.md](./create-runner.md) 获取完整的 Bash 脚本。
### 详细创建流程
当运行 `/gitea-create-runner` 命令时,会执行以下步骤:
@@ -640,23 +642,58 @@ log:
level: info
runner:
file: /path/to/.runner
file: /data/.runner # 容器内路径
capacity: 2
timeout: 3h
labels:
- "ubuntu-latest:docker://catthehacker/ubuntu:act-latest"
shutdown_timeout: 30s
insecure: false
fetch_timeout: 5s
fetch_interval: 2s
labels: [] # 通过环境变量设置
cache:
enabled: true
dir: "/path/to/cache"
host: "192.168.0.103" # 主机 IP非 127.0.0.1
port: 9000
dir: "/data/cache" # 容器内缓存目录
host: "host.docker.internal" # 宿主机地址Docker 特殊 DNS
port: 9040 # 缓存服务端口
container:
options: "--platform=linux/amd64" # 容器选项
network: "host" # 网络模式
# 使用 host 网络模式,确保 runner 和 job 容器共处一个网络,以便 cache 能够正常访问
network: "host"
privileged: false
options:
workdir_parent: /data/workspace
valid_volumes: []
docker_host: ""
force_pull: false
host:
workdir_parent: /data/workspace
```
**Docker 容器启动命令**
```bash
docker run -d \
--name "$runner_name" \
--restart always \
--network host \
-v "$runner_dir/config.yaml:/config.yaml" \
-v "$runner_dir/data:/data" \
-v "/var/run/docker.sock:/var/run/docker.sock" \
-e GITEA_INSTANCE_URL="$GITEA_URL" \
-e GITEA_RUNNER_REGISTRATION_TOKEN="$token" \
-e GITEA_RUNNER_NAME="$runner_name" \
-e GITEA_RUNNER_LABELS="ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye,linux:docker://node:16-bullseye" \
gitea/act_runner:latest daemon --config /config.yaml
```
**关键配置说明**
- `--network host`:使用 host 网络模式,确保 runner 和 job 容器共处一个网络,以便 cache 能够正常访问
- `host.docker.internal`Docker 内部 DNS指向宿主机
- 环境变量自动注册:容器启动时自动完成 Runner 注册
- `/var/run/docker.sock`:允许容器内创建兄弟容器执行 jobs
## 多 Runner 缓存共享
### 方案 A: Master-Slave 模式(推荐 2-3 个 runner
@@ -778,6 +815,64 @@ crontab -e
/gitea-list-runners
```
**API 调用详情**
1. **加载配置**
```bash
source ~/.config/gitea/config.env
```
2. **调用 API**
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/admin/actions/runners"
```
3. **响应结构**
```json
{
"runners": [...],
"total_count": 1
}
```
4. **解析每个 Runner**
- `id`: Runner ID
- `name`: Runner 名称
- `status`: 状态("online"/"offline"
- `busy`: 是否忙碌true/false
- `ephemeral`: 是否临时true/false
- `labels`: 标签数组
5. **状态图标**
- 🟢 在线 - `status: "online"`
- 🔴 离线 - `status: "offline"`
- ⚠️ 未知 - 无法确定
6. **输出格式**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Gitea 全局 Runners
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
服务器: [server_url]
总计: N 个全局 runner
[runner-name]
状态: 🟢/🔴 [在线/离线]
ID: [id]
忙碌: 是/否
临时: 是/否
标签: [comma-separated labels]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**注意**
- 需要管理员 API Token
- 正确的 API 端点是 `/api/v1/admin/actions/runners`(不是 `/api/v1/admin/runners`
- 使用 `jq` 解析 JSON 响应
### 启动 Runner
```bash