feat: 重构工作流体系,将命令模式迁移为技能文档
This commit is contained in:
303
skill/gitea/delete-runner.md
Normal file
303
skill/gitea/delete-runner.md
Normal 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"
|
||||
```
|
||||
Reference in New Issue
Block a user