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