Files
opencode/command/gitea-delete-runner.md
Voson 5a05d5ab53 chore: 重构 OpenCode 命令和技能文档体系
- 新增:统一的 git 命令文档(add/commit/push/pull 等)
- 新增:整合的 Gitea 技能文档(API、运行器、工作流等)
- 新增:工作流模板(Android、Go、Node.js 等)
- 移除:已弃用的旧命令脚本和发布脚本
- 改进:.gitignore 添加敏感文件保护规则
- 改进:AGENTS.md 完善了开发规范和示例

此次重组统一了命令和技能的文档结构,便于后续维护和扩展。
2026-01-13 00:27:21 +08:00

22 KiB
Raw Blame History

description
description
Delete a Gitea runner configuration (interactive)

gitea-delete-runner

删除 Gitea Runner 配置(交互式选择)。

命令说明

此命令用于从 Gitea 服务器删除 runner 配置。支持交互式选择单个或所有 runners。

重要这是一个可执行命令AI 应该按照以下步骤执行操作,并与用户进行交互。

Features

  • 显示 Gitea 服务器上的全局 runners
  • 交互式选择要删除的 runner
  • 支持删除单个或所有 runners
  • 删除 runner 配置目录(包括 cache 和 workspace
  • 停止运行中的 runner 进程
  • 自动从 Gitea 服务器注销 runner
  • 安全确认机制(需要用户输入 'yes'

User Input Format

无需参数,运行后交互式选择。

/gitea-delete-runner

AI 执行指导

当用户调用此命令时AI 应该:

  1. 获取并展示 runner 列表:调用 Gitea API 获取服务器上的全局 runners并以清晰的格式展示给用户
  2. 等待用户选择:让用户选择要删除的 runner序号、'all' 或 'q'
  3. 确认操作:在删除前要求用户输入 'yes' 确认
  4. 执行删除:按照三步流程删除选中的 runners
  5. 显示结果:展示删除操作的详细结果

注意:此命令需要与用户进行多次交互,不要一次性执行所有步骤。

Implementation Steps

1. Load Configuration

config_file_main="$HOME/.config/gitea/config.env"

if [ ! -f "$config_file_main" ]; then
  echo "❌ 未找到 Gitea 配置文件"
  echo "   路径: $config_file_main"
  exit 1
fi

source "$config_file_main"

if [ -z "$GITEA_URL" ] || [ -z "$GITEA_TOKEN" ]; then
  echo "❌ Gitea 配置不完整"
  echo "   需要 GITEA_URL 和 GITEA_TOKEN"
  exit 1
fi

2. Fetch and Display Runners

AI 执行:运行以下命令获取并显示 runners 列表,然后等待用户选择。

source "$HOME/.config/gitea/config.env"

echo "正在从 Gitea 服务器获取全局 runners..."
echo ""

# Fetch global runners from Gitea API
api_endpoint="${GITEA_URL}/api/v1/admin/actions/runners"
response=$(curl -s -H "Authorization: token $GITEA_TOKEN" "$api_endpoint")

if [ $? -ne 0 ] || [ -z "$response" ]; then
  echo "❌ 无法连接到 Gitea 服务器"
  exit 1
fi

# Parse runners using jq
if ! command -v jq &> /dev/null; then
  echo "❌ 需要安装 jq 工具"
  echo "   安装: brew install jq"
  exit 1
fi

# Check if response is valid JSON
if ! echo "$response" | jq empty 2>/dev/null; then
  echo "❌ API 返回数据格式错误"
  echo "   请检查 Token 权限(需要 admin 权限)"
  exit 1
fi

# Extract runner information (注意API 返回格式是 {"runners": [...], "total_count": N})
runner_count=$(echo "$response" | jq '.runners | length')

if [ "$runner_count" -eq 0 ]; then
  echo "⚠️  服务器上没有全局 runners"
  exit 0
fi

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Gitea 全局 Runners (共 $runner_count 个)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

# Display runners with formatted output
echo "$response" | jq -r '.runners[] | [.id, .name, .status] | @tsv' | \
  awk 'BEGIN{i=1} {
    printf "%2d. %-30s [ID: %-5s] %s\n", 
    i++, $2, $1, ($3=="online"?"🟢 在线":"🔴 离线")
  }'

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "选择要删除的 Runner"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "  输入序号: 删除单个 runner"
echo "  输入 'all': 删除所有 runners"
echo "  输入 'q' 或 'quit': 取消"
echo ""

# Save runner data to temp file for later use
echo "$response" | jq -r '.runners[] | "\(.id)|\(.name)"' > /tmp/gitea_runners.txt

AI 指导:执行完上述命令后,等待用户输入选择(序号、'all' 或 'q')。

3. Process User Selection

AI 指导:根据用户的输入(从步骤 2 获取),执行相应的处理逻辑。

  • 如果用户输入 'q' 或 'quit':取消操作,清理临时文件
  • 如果用户输入 'all':准备删除所有 runners进入步骤 4 确认
  • 如果用户输入序号:验证并准备删除对应的 runner进入步骤 4 确认

选择处理脚本AI 根据用户输入执行相应部分):

# 假设用户选择存储在变量 $USER_SELECTION 中

if [ "$USER_SELECTION" = "q" ] || [ "$USER_SELECTION" = "quit" ]; then
  echo "已取消"
  rm -f /tmp/gitea_runners.txt
  exit 0
fi

# Read runner data from temp file
mapfile -t runner_data < /tmp/gitea_runners.txt
runner_count=${#runner_data[@]}

if [ "$USER_SELECTION" = "all" ]; then
  # Prepare to delete all runners
  echo ""
  echo "⚠️  将删除所有 $runner_count 个 runners"
  echo ""
  # 继续到步骤 4 显示警告并确认
else
  # Validate selection is a number
  if ! [[ "$USER_SELECTION" =~ ^[0-9]+$ ]]; then
    echo "❌ 无效的选择"
    rm -f /tmp/gitea_runners.txt
    exit 1
  fi
  
  # Validate selection is in range
  if [ "$USER_SELECTION" -lt 1 ] || [ "$USER_SELECTION" -gt "$runner_count" ]; then
    echo "❌ 选择超出范围 (1-$runner_count)"
    rm -f /tmp/gitea_runners.txt
    exit 1
  fi
  
  # Get selected runner info
  selected_data="${runner_data[$((USER_SELECTION-1))]}"
  IFS='|' read -r id name <<< "$selected_data"
  
  echo ""
  echo "已选择: $name (ID: $id)"
  echo ""
  # 继续到步骤 4 显示警告并确认
fi

4. Display Warning and Get Confirmation

AI 执行:显示删除警告,列出将要删除的 runners然后等待用户输入 'yes' 确认。

echo "⚠️  警告: 此操作将执行以下操作:"
echo "  - 从 Gitea 服务器注销 runner"
echo "  - 停止本地运行的 runner 进程"
echo "  - 删除 runner 配置文件"
echo "  - 删除 cache 和 workspace 目录"
echo "  - 删除所有相关数据"
echo ""

# List runners to be deleted
if [ "$USER_SELECTION" = "all" ]; then
  echo "将删除以下 runners:"
  cat /tmp/gitea_runners.txt | while IFS='|' read -r id name; do
    echo "  - $name (ID: $id)"
  done
else
  # Single runner already displayed in step 3
  :
fi

echo ""

AI 指导:显示完警告后,等待用户输入确认('yes' 继续,其他取消)。

5. Execute Deletion

AI 执行:如果用户确认(输入 'yes'),执行删除操作;否则取消。

# 假设用户确认输入存储在 $USER_CONFIRM 中
if [ "$USER_CONFIRM" != "yes" ]; then
  echo "已取消"
  rm -f /tmp/gitea_runners.txt
  exit 0
fi

source "$HOME/.config/gitea/config.env"

echo "开始删除..."
echo ""

success_count=0
fail_count=0
runners_dir="$HOME/.config/gitea/runners"

# Process all selected runners
while IFS='|' read -r runner_id runner_name; do
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "删除: $runner_name (ID: $runner_id)"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo ""
  
  # Find local runner directory by ID (处理 runners/* 不存在的情况)
  runner_dir=""
  if [ -d "$runners_dir" ]; then
    shopt -s nullglob  # 避免 * 无匹配时报错
    for dir in "$runners_dir"/*; do
      if [ -f "$dir/.runner" ]; then
        local_id=$(jq -r '.id // ""' "$dir/.runner" 2>/dev/null)
        if [ "$local_id" = "$runner_id" ]; then
          runner_dir="$dir"
          break
        fi
      fi
    done
    shopt -u nullglob
  fi
  
  # Step 1: Unregister from Gitea server
  echo "[1/3] 从 Gitea 服务器注销..."
  api_endpoint="${GITEA_URL}/api/v1/admin/actions/runners/${runner_id}"
  http_code=$(curl -s -w "%{http_code}" -o /dev/null \
    -X DELETE \
    -H "Authorization: token $GITEA_TOKEN" \
    "$api_endpoint")
  
  if [ "$http_code" = "204" ]; then
    echo "      ✓ 已从服务器注销"
  else
    echo "      ⚠️  注销失败 (HTTP $http_code)"
  fi
  
  # Step 2: Stop local process if running
  echo "[2/3] 检查本地进程..."
  if [ -n "$runner_dir" ] && [ -f "$runner_dir/config.yaml" ]; then
    config_file="$runner_dir/config.yaml"
    
    if pgrep -f "act_runner daemon --config $config_file" > /dev/null 2>&1; then
      pid=$(pgrep -f "act_runner daemon --config $config_file")
      
      # Check if runner is busy (executing jobs)
      # 从服务器获取 runner 的 busy 状态
      runner_info=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
        "${GITEA_URL}/api/v1/admin/actions/runners/${runner_id}")
      is_busy=$(echo "$runner_info" | jq -r '.busy // false')
      
      if [ "$is_busy" = "true" ]; then
        echo "      ⚠️  警告: Runner 正在执行 job"
        echo "      强制停止可能导致 job 中断和数据不一致"
        echo ""
        echo "      选项:"
        echo "        1. 等待 job 完成后再停止(推荐)"
        echo "        2. 强制立即停止"
        echo ""
        # AI 应在此处等待用户选择
        # 假设用户选择存储在 $STOP_CHOICE 中
        
        if [ "$STOP_CHOICE" = "1" ]; then
          echo "      等待 job 完成..."
          # 轮询检查 runner 是否仍然 busy
          max_wait=300  # 最多等待 5 分钟
          waited=0
          while [ $waited -lt $max_wait ]; do
            sleep 10
            waited=$((waited + 10))
            runner_info=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
              "${GITEA_URL}/api/v1/admin/actions/runners/${runner_id}")
            is_busy=$(echo "$runner_info" | jq -r '.busy // false')
            if [ "$is_busy" = "false" ]; then
              echo "      ✓ Job 已完成"
              break
            fi
            echo "      仍在执行... (已等待 ${waited}s)"
          done
          
          if [ $waited -ge $max_wait ]; then
            echo "      ⚠️  等待超时,将强制停止"
          fi
        else
          echo "      ⚠️  用户选择强制停止"
        fi
      fi
      
      # 优雅停止 runner
      echo "      正在停止进程 (PID: $pid)..."
      kill "$pid" 2>/dev/null
      sleep 2
      
      # 如果进程还在运行,强制杀死
      if pgrep -f "act_runner daemon --config $config_file" > /dev/null 2>&1; then
        echo "      进程未响应,强制终止..."
        kill -9 "$pid" 2>/dev/null
        sleep 1
      fi
      
      # 验证进程已停止
      if pgrep -f "act_runner daemon --config $config_file" > /dev/null 2>&1; then
        echo "      ❌ 无法停止进程"
      else
        echo "      ✓ 进程已停止"
      fi
    else
      echo "      ✓ 未运行"
    fi
  else
    echo "      ⓘ 未找到本地配置"
  fi
  
  # Step 3: Delete local directory
  echo "[3/3] 删除本地配置..."
  if [ -n "$runner_dir" ] && [ -d "$runner_dir" ]; then
    rm -rf "$runner_dir"
    
    if [ ! -d "$runner_dir" ]; then
      echo "      ✓ 本地配置已删除"
      ((success_count++))
    else
      echo "      ❌ 删除失败"
      ((fail_count++))
    fi
  else
    # Server runner deleted, but no local config
    ((success_count++))
    echo "      ⓘ 无本地配置需要删除"
  fi
  
  echo ""
done < /tmp/gitea_runners.txt

# Cleanup temp file
rm -f /tmp/gitea_runners.txt

6. Display Summary

AI 执行:显示删除操作的最终结果。

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "删除完成"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "成功: $success_count 个"
if [ $fail_count -gt 0 ]; then
  echo "失败: $fail_count 个"
fi
echo ""
echo "管理命令:"
echo "  查看剩余 runners: /gitea-list-runners"
echo "  创建新 runner:   /gitea-create-runner"
echo ""

AI 指导:删除完成后,可以选择性地验证服务器上的 runner 数量,确认删除成功。

Output Example

Example 1: Delete Single Runner

正在从 Gitea 服务器获取全局 runners...

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Gitea 全局 Runners (共 3 个)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 1. runner-mac-mini4              [ID: 42   ] 🟢 在线
    系统: darwin     架构: arm64      最后在线: 2026-01-12 10:30

 2. runner-macbook-pro            [ID: 43   ] 🔴 离线
    系统: darwin     架构: arm64      最后在线: 2026-01-10 18:45

 3. runner-linux-server           [ID: 44   ] 🟢 在线
    系统: linux      架构: amd64      最后在线: 2026-01-12 10:25

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
选择要删除的 Runner
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  输入序号: 删除单个 runner
  输入 'all': 删除所有 runners
  输入 'q' 或 'quit': 取消

请选择: 2

已选择: runner-macbook-pro (ID: 43)

⚠️  警告: 此操作将执行以下操作:
  - 从 Gitea 服务器注销 runner
  - 停止本地运行的 runner 进程
  - 删除 runner 配置文件
  - 删除 cache 和 workspace 目录
  - 删除所有相关数据

确认删除? 输入 'yes' 继续: yes

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
删除: runner-macbook-pro (ID: 43)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1/3] 从 Gitea 服务器注销...
      ✓ 已从服务器注销
[2/3] 检查本地进程...
      ✓ 未运行
[3/3] 删除本地配置...
      ✓ 本地配置已删除

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
删除完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

成功: 1 个

管理命令:
  查看剩余 runners: /gitea-list-runners
  创建新 runner:   /gitea-create-runner

Example 2: Delete All Runners

正在从 Gitea 服务器获取全局 runners...

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Gitea 全局 Runners (共 2 个)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 1. runner-mac-mini4              [ID: 42   ] 🟢 在线
    系统: darwin     架构: arm64      最后在线: 2026-01-12 10:30

 2. runner-linux-server           [ID: 44   ] 🟢 在线
    系统: linux      架构: amd64      最后在线: 2026-01-12 10:25

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
选择要删除的 Runner
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  输入序号: 删除单个 runner
  输入 'all': 删除所有 runners
  输入 'q' 或 'quit': 取消

请选择: all

⚠️  将删除所有 2 个 runners

⚠️  警告: 此操作将执行以下操作:
  - 从 Gitea 服务器注销 runner
  - 停止本地运行的 runner 进程
  - 删除 runner 配置文件
  - 删除 cache 和 workspace 目录
  - 删除所有相关数据

将删除 2 个 runners:
  - runner-mac-mini4 (ID: 42)
  - runner-linux-server (ID: 44)

确认删除? 输入 'yes' 继续: yes

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
删除: runner-mac-mini4 (ID: 42)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1/3] 从 Gitea 服务器注销...
      ✓ 已从服务器注销
[2/3] 检查本地进程...
      正在停止进程 (PID: 12345)...
      ✓ 进程已停止
[3/3] 删除本地配置...
      ✓ 本地配置已删除

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
删除: runner-linux-server (ID: 44)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1/3] 从 Gitea 服务器注销...
      ✓ 已从服务器注销
[2/3] 检查本地进程...
      ⓘ 未找到本地配置
[3/3] 删除本地配置...
      ⓘ 无本地配置需要删除

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
删除完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

成功: 2 个

管理命令:
  查看剩余 runners: /gitea-list-runners
  创建新 runner:   /gitea-create-runner

Safety Features

  • 服务器数据优先:直接从 Gitea 服务器获取 runner 列表,确保准确性
  • 交互式选择:支持选择单个或所有 runners避免误删
  • 双重确认:需要输入 'yes' 进行最终确认(防止误删)
  • Busy 状态检测:检查 runner 是否正在执行 job
    • 如果 runner 正在执行 job给用户选择
      • 等待 job 完成后停止(推荐)
      • 强制立即停止(有风险)
  • 优雅停止:先尝试 SIGTERM2 秒后才使用 SIGKILL
  • 服务器注销:自动从 Gitea 服务器注销 runner
  • 三步删除流程
    1. 从服务器注销
    2. 停止本地进程(检测 busy 状态)
    3. 删除本地配置
  • 清晰提示:显示每个步骤的执行状态和警告信息
  • 批量删除支持:可一次性删除所有 runners
  • 错误处理:各步骤独立执行,部分失败不影响其他操作
  • 超时保护:等待 job 完成最多 5 分钟,超时后强制停止

Technical Notes

  • 必需工具:需要安装 jq 工具(brew install jq
  • 权限要求:需要 Gitea admin 权限(全局 runner 管理)
  • 配置文件:读取 ~/.config/gitea/config.env 获取 API 配置
  • 本地匹配:通过 runner ID 匹配本地配置目录
  • 服务器同步:从服务器注销后再删除本地配置
  • 无本地配置:如果只存在服务器记录,仅注销服务器端
  • 取消操作:输入 'q' 或 'quit' 可随时取消
  • 临时文件:使用 /tmp/gitea_runners.txt 存储临时数据,操作完成后自动清理
  • API 格式Gitea API 返回格式为 {"runners": [...], "total_count": N},不是直接的数组
  • Busy 检测:通过 API 的 busy 字段判断 runner 是否正在执行 job
  • 停止信号
    • kill $pid (SIGTERM): 优雅停止,给进程清理资源的机会
    • kill -9 $pid (SIGKILL): 强制终止,无法被捕获或忽略
  • 等待策略:如果 runner busy最多等待 5 分钟300 秒),每 10 秒检查一次状态

AI 执行流程总结

当用户调用 /gitea-delete-runnerAI 应该按以下流程执行:

  1. 步骤 1:加载并验证 Gitea 配置
  2. 步骤 2:获取并展示 runners 列表 → 等待用户选择
  3. 步骤 3:处理用户选择,验证输入
  4. 步骤 4:显示删除警告和列表 → 等待用户确认
  5. 步骤 5:执行删除操作
    • 5.1: 从服务器注销
    • 5.2: 检查并停止本地进程
      • 如果 runner 正在执行 jobbusy等待用户选择(等待完成 or 强制停止)
    • 5.3: 删除本地配置
  6. 步骤 6:显示操作结果摘要

重要:这是一个交互式命令,可能需要在以下位置等待用户输入:

  • 步骤 2: 选择要删除的 runner
  • 步骤 4: 确认删除操作
  • 步骤 5.2: 如果 runner 正在执行 job选择等待或强制停止

不要一次性执行所有步骤。