304 lines
9.2 KiB
Markdown
304 lines
9.2 KiB
Markdown
---
|
|
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"
|
|
```
|