Files
opencode/skill/gitea/delete-runner.md

9.2 KiB

description, agent
description agent
交互式批量删除 Gitea Runners 脚本 general

Delete Runner Script

本文档包含用于交互式批量删除 Gitea Actions Runner 的完整 Bash 脚本。

功能特点

  • 多选支持:支持输入多个序号(如 1,31 3)或 all 进行批量删除。
  • 双重清理:同时从 Gitea 服务器注销 Runner 和删除本地配置/容器。
  • 智能识别:自动关联远程 Runner 状态与本地 Runner 目录。
  • 安全检查:删除前强制二次确认,防止误删。

脚本文件

你可以将以下内容保存为 delete_runner.sh 并赋予执行权限 (chmod +x delete_runner.sh)。

#!/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"