Files
opencode/skill/gitea/create-runner.md
Voson 417f3e6d2b fix(gitea): Docker runner 使用 catthehacker/ubuntu:act-* 镜像替代 node:16-bullseye
- 解决 docker/login-action 等 actions 因缺少 docker CLI 而失败的问题
- catthehacker/ubuntu:act-* 镜像预装 Docker CLI、Buildx 等 CI/CD 工具
- 添加镜像选择的注释说明
2026-01-24 12:13:18 +08:00

32 KiB
Raw Permalink Blame History

description, agent
description agent
创建并启动 Gitea Actions Runner 的完整脚本 general

Gitea Actions Runner 创建工具

本文档提供了多种创建 Gitea Actions Runner 的方式,支持 Host 模式和 Docker 模式。

📦 快速使用

方法一:直接执行(推荐)

复制以下命令到终端执行,可指定参数:

# 创建 Docker Runner默认
curl -s https://git.shigongcao.com/ai/opencode/raw/branch/main/skill/gitea/create-runner.md | \
  sed -n '/^```bash$/,/^```$/p' | sed '1d;$d' | \
  bash -s -- --docker --token "你的令牌"

# 创建 Host Runner
curl -s https://git.shigongcao.com/ai/opencode/raw/branch/main/skill/gitea/create-runner.md | \
  sed -n '/^```bash$/,/^```$/p' | sed '1d;$d' | \
  bash -s -- --host --token "你的令牌"

# 同时创建 Docker 和 Host Runner
curl -s https://git.shigongcao.com/raw/ai/opencode/main/skill/gitea/create-runner.md | \
  sed -n '/^```bash$/,/^```$/p' | sed '1d;$d' | \
  bash -s -- --all --token "你的令牌" --gitea-url "https://git.shigongcao.com"

方法二:定义快捷函数

将以下函数添加到 ~/.bashrc~/.zshrc

# Gitea Runner 创建函数
gitea-create-runner() {
  local mode="${1:-docker}"
  local token="${2:-$GITEA_TOKEN}"
  local gitea_url="${3:-https://git.shigongcao.com}"
  local name="${4:-runner-$(hostname -s)-$mode}"
  
  echo "创建 $mode runner: $name"
  
  # 临时脚本路径
  local script_path="/tmp/create_runner_$$.sh"
  
  # 生成脚本
  cat > "$script_path" << 'EOF'
#!/bin/bash
set -e

# 参数解析
MODE="docker"
TOKEN=""
GITEA_URL="https://git.shigongcao.com"
RUNNER_NAME="runner-$(hostname -s)-docker"
ALL_MODE=false

while [[ $# -gt 0 ]]; do
  case $1 in
    --docker) MODE="docker" ;;
    --host) MODE="host" ;;
    --all) ALL_MODE=true ;;
    --token) TOKEN="$2"; shift ;;
    --gitea-url) GITEA_URL="$2"; shift ;;
    --name) RUNNER_NAME="$2"; shift ;;
    *) echo "未知参数: $1"; exit 1 ;;
  esac
  shift
done

if [ -z "$TOKEN" ]; then
  echo "❌ 必须提供 --token 参数"
  exit 1
fi

create_runner() {
  local mode=$1
  local name=$2
  echo "创建 $mode runner: $name"
  
  if [ "$mode" = "docker" ]; then
    # Docker runner 创建逻辑
    if ! docker info >/dev/null 2>&1; then
      echo "❌ Docker 未运行"
      return 1
    fi
    
    local runner_dir="$HOME/.config/gitea/runners/$name"
    mkdir -p "$runner_dir"/{data,cache,workspace}
    
    cat > "$runner_dir/config.yaml" << YAML
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:
  network: "host"
  privileged: false
  options:
  workdir_parent: /data/workspace
  valid_volumes: []
  docker_host: ""
  force_pull: false
host:
  workdir_parent: /data/workspace
YAML

    # 使用 catthehacker/ubuntu:act-* 镜像,内置 Docker CLI、Buildx 等 CI/CD 工具
    # 注意:不要使用 node:16-bullseye 等纯运行时镜像,它们不包含 docker 命令
    local labels="ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04,ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04,linux:docker://catthehacker/ubuntu:act-latest"
    
    docker run -d \
      --name "$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="$name" \
      -e GITEA_RUNNER_LABELS="$labels" \
      gitea/act_runner:latest daemon --config /config.yaml
      
    echo "✅ Docker Runner 已启动: $name"
    
  else
    # Host runner 创建逻辑
    if ! command -v act_runner &> /dev/null; then
      echo "❌ act_runner 未安装"
      return 1
    fi
    
    local runner_dir="$HOME/.config/gitea/runners/$name"
    mkdir -p "$runner_dir"/{cache,workspace}
    
    local OS=$(uname -s)
    local ARCH=$(uname -m)
    case "$OS" in
      Darwin) os_label="macOS" ;;
      Linux)  os_label="ubuntu" ;;
      *)      os_label="unknown" ;;
    esac
    case "$ARCH" in
      arm64|aarch64) arch_label="ARM64" ;;
      x86_64)        arch_label="x64" ;;
      *)             arch_label="unknown" ;;
    esac
    local combined=$(echo "${OS}-${ARCH}" | tr '[:upper:]' '[:lower:]')
    local labels="self-hosted:host,${os_label}:host,${arch_label}:host,${combined}:host"
    
    cat > "$runner_dir/config.yaml" << YAML
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"
YAML

    act_runner register \
      --config "$runner_dir/config.yaml" \
      --instance "$GITEA_URL" \
      --token "$TOKEN" \
      --name "$name" \
      --labels "$labels" \
      --no-interactive
    
    nohup act_runner daemon --config "$runner_dir/config.yaml" \
      > "$runner_dir/runner.log" 2>&1 &
    local pid=$!
    sleep 2
    
    if ps -p $pid > /dev/null 2>&1; then
      echo $pid > "$runner_dir/pid"
      echo "✅ Host Runner 已启动: $name (PID: $pid)"
    else
      echo "❌ Host Runner 启动失败"
      return 1
    fi
  fi
}

if [ "$ALL_MODE" = true ]; then
  create_runner "docker" "runner-$(hostname -s)-docker"
  create_runner "host" "runner-$(hostname -s)-host"
else
  create_runner "$MODE" "$RUNNER_NAME"
fi
EOF
  
  chmod +x "$script_path"
  "$script_path" --"$mode" --token "$token" --gitea-url "$gitea_url" --name "$name"
  rm -f "$script_path"
}

# 使用示例:
# gitea-create-runner docker "wibnIxvgeyYj3D7d53VTQvNxv0UVqArBwAtPFBWD"
# gitea-create-runner host "wibnIxvgeyYj3D7d53VTQvNxv0UVqArBwAtPFBWD"
# gitea-create-runner all "wibnIxvgeyYj3D7d53VTQvNxv0UVqArBwAtPFBWD"

🔄 恢复离线 Runner

场景Runner 在 Gitea 服务器上被删除,但本地配置文件仍在。需要重新注册并上线。

快速恢复命令(针对 runner-Mac-mini4-host

cd ~/.config/gitea/runners/runner-Mac-mini4-host

# 停止旧进程
if [ -f pid ]; then kill $(cat pid) 2>/dev/null || true; fi

# 加载配置并重新注册
source ~/.config/gitea/config.env

# 获取令牌(需要 jq
token=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
  "${GITEA_URL}/api/v1/admin/runners/registration-token" | jq -r '.token')

# 生成标签
OS=$(uname -s); ARCH=$(uname -m)
case "$OS" in Darwin) os="macOS";; Linux) os="ubuntu";; *) os="unknown";; esac
case "$ARCH" in arm64|aarch64) arch="ARM64";; x86_64) arch="x64";; *) arch="unknown";; esac
labels="self-hosted:host,${os}:host,${arch}:host,$(echo "${OS}-${ARCH}" | tr '[:upper:]' '[:lower:]'):host"

# 重新注册并启动
act_runner register --config config.yaml --instance "$GITEA_URL" \
  --token "$token" --name "runner-Mac-mini4-host" --labels "$labels" --no-interactive

nohup act_runner daemon --config config.yaml > runner.log 2>&1 &
echo $! > pid
echo "✅ Runner 恢复完成 (PID: $(cat pid))"

说明

  • 确保 config.yaml 中的 labels: 配置为空(labels: []),注册时会使用命令行参数
  • 如果全局令牌权限不足,需要获取组织令牌(参考 runner-management.md
  • 恢复后 runner 会获得新的 ID但名称和 labels 保持不变

方法三:完整脚本

如果你需要更多自定义选项,可以使用下面的完整脚本:

脚本文件

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

#!/bin/bash

# Gitea Runner Creation Script
# Generated by OpenCode Skill
# 支持命令行参数,无需交互,支持批量创建

set -e

# ==========================================
# 1. Parse Command Line Arguments
# ==========================================

RUNNER_MODE="docker"  # 默认模式
GITEA_URL=""
GITEA_TOKEN=""
RUNNER_NAME=""
ALL_MODE=false
INTERACTIVE=false
SKIP_CHECKS=false

# 显示帮助
show_help() {
  cat << EOF
Gitea Actions Runner 创建脚本

用法: $0 [选项]

选项:
  --docker             创建 Docker runner (默认)
  --host               创建 Host runner
  --all                同时创建 Docker 和 Host runner
  --token TOKEN        Gitea 注册令牌 (必需)
  --gitea-url URL      Gitea 实例地址 (默认: https://git.shigongcao.com)
  --name NAME         Runner 名称 (默认: runner-\$(hostname)-模式)
  --skip-checks       跳过依赖检查
  --interactive        交互模式 (传统方式)
  --help               显示此帮助信息

示例:
  $0 --docker --token "wibnIxvgeyYj3D7d53VTQvNxv0UVqArBwAtPFBWD"
  $0 --host --token "your_token" --gitea-url "https://git.example.com"
  $0 --all --token "your_token"
  $0 --all --token "your_token" --name "custom-runner"

直接使用技能:
  # 从技能文档直接执行
  bash <(curl -s https://git.shigongcao.com/ai/opencode/raw/main/skill/gitea/create-runner.md | sed -n '/^```bash$/,/^```$/p' | sed '1d;\$d') --all --token "your_token"
EOF
  exit 0
}

# 解析参数
while [[ $# -gt 0 ]]; do
  case $1 in
    --docker) RUNNER_MODE="docker" ;;
    --host) RUNNER_MODE="host" ;;
    --all) ALL_MODE=true ;;
    --token) GITEA_TOKEN="$2"; shift ;;
    --gitea-url) GITEA_URL="$2"; shift ;;
    --name) RUNNER_NAME="$2"; shift ;;
    --skip-checks) SKIP_CHECKS=true ;;
    --interactive) INTERACTIVE=true ;;
    --help) show_help ;;
    *) echo "❌ 未知参数: $1"; echo "使用 --help 查看帮助"; exit 1 ;;
  esac
  shift
done

# 检查必需参数
if [ -z "$GITEA_TOKEN" ] && [ "$INTERACTIVE" = false ]; then
  echo "❌ 必须提供 --token 参数"
  echo "使用 --help 查看帮助"
  exit 1
fi

# ==========================================
# 2. Interactive Mode (向后兼容)
# ==========================================

if [ "$INTERACTIVE" = true ]; then
  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 ""
fi

# ==========================================
# 3. Load Gitea Configuration
# ==========================================

# 如果未通过命令行提供,尝试从配置文件加载
if [ -z "$GITEA_URL" ] || [ -z "$GITEA_TOKEN" ]; then
  config_file="$HOME/.config/gitea/config.env"
  
  if [ -f "$config_file" ]; then
    echo "从配置文件加载 Gitea 配置..."
    source "$config_file"
  fi
fi

# 如果仍然缺少 URL使用默认值
if [ -z "$GITEA_URL" ]; then
  GITEA_URL="https://git.shigongcao.com"
  echo "⚠️  使用默认 Gitea URL: $GITEA_URL"
fi

# 检查必需参数
if [ -z "$GITEA_TOKEN" ]; then
  echo "❌ 必须提供 Gitea 注册令牌"
  echo "   使用方法: $0 --token YOUR_TOKEN [其他选项]"
  echo "   或创建配置文件: $HOME/.config/gitea/config.env"
  exit 1
fi

echo "✓ Gitea 配置:"
echo "  URL:   $GITEA_URL"
echo "  Token: ${GITEA_TOKEN:0:8}..."
echo ""

# ==========================================
# 4. Generate Runner Name
# ==========================================

# 如果未指定名称,生成默认名称
if [ -z "$RUNNER_NAME" ]; then
  hostname=$(hostname -s 2>/dev/null || echo "unknown")
  if [ "$ALL_MODE" = true ]; then
    # 批量模式会创建两个runner
    echo "批量模式:将创建 docker 和 host runner"
  else
    RUNNER_NAME="runner-$hostname-$RUNNER_MODE"
    echo "生成 Runner 名称: $RUNNER_NAME"
  fi
else
  echo "使用指定的 Runner 名称: $RUNNER_NAME"
fi

# 检查名称有效性(如果不是批量模式)
if [ "$ALL_MODE" = false ] && [[ ! "$RUNNER_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
  echo "❌ Runner 名称无效 (仅限字母、数字、下划线、连字符)"
  exit 1
fi
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 ""
fi

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

# ==========================================
# 3. Load Gitea Configuration
# ==========================================

# 如果未通过命令行提供,尝试从配置文件加载
if [ -z "$GITEA_URL" ] || [ -z "$GITEA_TOKEN" ]; then
  config_file="$HOME/.config/gitea/config.env"
  
  if [ -f "$config_file" ]; then
    echo "从配置文件加载 Gitea 配置..."
    source "$config_file"
  fi
fi

# 如果仍然缺少 URL使用默认值
if [ -z "$GITEA_URL" ]; then
  GITEA_URL="https://git.shigongcao.com"
  echo "⚠️  使用默认 Gitea URL: $GITEA_URL"
fi

# 检查必需参数
if [ -z "$GITEA_TOKEN" ]; then
  echo "❌ 必须提供 Gitea 注册令牌"
  echo "   使用方法: $0 --token YOUR_TOKEN [其他选项]"
  echo "   或创建配置文件: $HOME/.config/gitea/config.env"
  exit 1
fi

echo "✓ Gitea 配置:"
echo "  URL:   $GITEA_URL"
echo "  Token: ${GITEA_TOKEN:0:8}..."
echo ""

# ==========================================
# 4. Generate Runner Name
# ==========================================

# 如果未指定名称,生成默认名称
if [ -z "$RUNNER_NAME" ]; then
  hostname=$(hostname -s 2>/dev/null || echo "unknown")
  if [ "$ALL_MODE" = true ]; then
    # 批量模式会创建两个runner
    echo "批量模式:将创建 docker 和 host runner"
  else
    RUNNER_NAME="runner-$hostname-$RUNNER_MODE"
    echo "生成 Runner 名称: $RUNNER_NAME"
  fi
else
  echo "使用指定的 Runner 名称: $RUNNER_NAME"
fi

# 检查名称有效性(如果不是批量模式)
if [ "$ALL_MODE" = false ] && [[ ! "$RUNNER_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
  echo "❌ Runner 名称无效 (仅限字母、数字、下划线、连字符)"
  exit 1
fi
echo ""

# ==========================================
# 5. Main Creation Function
# ==========================================

create_runner() {
  local mode="$1"
  local name="$2"
  
  echo ""
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "创建 $mode runner: $name"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  
  runners_dir="$HOME/.config/gitea/runners"
  runner_dir="$runners_dir/$name"
  
  # 检查是否已存在
  if [ "$mode" = "host" ]; then
    if [ -d "$runner_dir" ]; then
      echo "❌ Runner 目录已存在: $runner_dir"
      return 1
    fi
  else
    # Docker mode: check directory AND container
    if [ -d "$runner_dir" ]; then
      echo "❌ Runner 配置目录已存在: $runner_dir"
      return 1
    fi
    if docker ps -a --format '{{.Names}}' | grep -q "^${name}$"; then
      echo "❌ Docker 容器已存在: $name"
      echo "   请先删除旧容器: docker rm -f $name"
      return 1
    fi
  fi
  
  # ==========================================
  # 5.1 Check Dependencies
  # ==========================================
  
  if [ "$SKIP_CHECKS" = false ]; then
    if [ "$mode" = "host" ]; then
      echo "检查 act_runner 安装状态..."
      if ! command -v act_runner &> /dev/null; then
        echo "❌ act_runner 未安装"
        echo "   请安装: brew install act_runner"
        return 1
      fi
      version=$(act_runner --version 2>&1 | head -n1)
      echo "✓ act_runner 已安装: $version"
    else
      echo "检查 Docker 环境..."
      if ! command -v docker &> /dev/null; then
        echo "❌ Docker 未安装"
        return 1
      fi
      if ! docker info &> /dev/null; then
        echo "❌ Docker 已安装但未运行"
        return 1
      fi
      docker_ver=$(docker --version)
      echo "✓ Docker 已安装并运行: $docker_ver"
    fi
  fi
  
  # ==========================================
  # 5.2 Detect System Environment & Labels
  # ==========================================
  
  echo "生成 Labels..."
  
  if [ "$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
    # 重要:必须使用包含 Docker CLI 的镜像,否则 docker/login-action 等 actions 会失败
    # catthehacker/ubuntu:act-* 是专为 GitHub/Gitea Actions 设计的镜像,预装:
    #   - Docker CLI (docker 命令)
    #   - Docker Buildx
    #   - git, curl, jq 等常用工具
    # 不要使用 node:16-bullseye 等纯运行时镜像!
    labels="ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04,ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04,linux:docker://catthehacker/ubuntu:act-latest"
  fi
  
  echo "✓ Labels ($mode):"
  echo "  $labels"
  echo ""
  
  # ==========================================
  # 5.3 Create Runner Directory
  # ==========================================
  
  echo "创建 Runner 目录..."
  if [ "$mode" = "host" ]; then
    mkdir -p "$runner_dir"/{cache,workspace}
  else
    mkdir -p "$runner_dir"/{data,cache,workspace}
  fi
  echo "✓ 目录: $runner_dir"
  echo ""
  
  # ==========================================
  # 5.4 Create Configuration File
  # ==========================================
  
  echo "创建配置文件..."
  
  if [ "$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 "$GITEA_TOKEN" \
      --name "$name" \
      --labels "$labels" \
      --no-interactive

    if [ $? -ne 0 ]; then
       echo "❌ 注册失败"
       return 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"
      echo "日志文件: $runner_dir/runner.log"
    else
      echo "❌ 启动失败,请检查日志"
      return 1
    fi

  else
    # Docker Mode Config
    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:
  network: "host"
  privileged: false
  options:
  workdir_parent: /data/workspace
  valid_volumes: []
  docker_host: ""
  force_pull: false
host:
  workdir_parent: /data/workspace
EOF

    echo "启动 Docker 容器 (自动注册)..."
    
    docker run -d \
      --name "$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="$GITEA_TOKEN" \
      -e GITEA_RUNNER_NAME="$name" \
      -e GITEA_RUNNER_LABELS="$labels" \
      gitea/act_runner:latest daemon --config /config.yaml
      
    if [ $? -eq 0 ]; then
      echo "✓ Docker Runner 容器已启动: $name"
      # 等待容器启动并查看日志
      sleep 5
      echo "容器日志:"
      docker logs "$name" 2>&1 | tail -10
    else
      echo "❌ 容器启动失败"
      return 1
    fi
  fi
  
# ==========================================
# 5.5 Display Runner Info
# ==========================================
  
  echo ""
  echo "✅ $mode runner 创建成功: $name"
  if [ "$mode" = "host" ]; then
    echo "   PID:  $runner_pid"
    echo "   停止: kill \$(cat $runner_dir/pid)"
    echo "   日志: tail -f $runner_dir/runner.log"
  else
    echo "   容器: $name"
    echo "   停止: docker stop $name"
    echo "   日志: docker logs -f $name"
  fi
  echo "   目录: $runner_dir"
  echo ""
  
  return 0
}

# ==========================================
# 6. Main Execution Logic
# ==========================================

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Gitea Actions Runner 创建工具"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

# 批量模式:创建 docker 和 host runner
if [ "$ALL_MODE" = true ]; then
  hostname=$(hostname -s 2>/dev/null || echo "unknown")
  
  if [ -n "$RUNNER_NAME" ]; then
    # 如果指定了名称,使用该名称作为前缀
    docker_name="${RUNNER_NAME}-docker"
    host_name="${RUNNER_NAME}-host"
  else
    # 使用默认名称
    docker_name="runner-${hostname}-docker"
    host_name="runner-${hostname}-host"
  fi
  
  echo "批量创建模式:"
  echo "  Docker Runner: $docker_name"
  echo "  Host Runner:   $host_name"
  echo ""
  
  # 创建 Docker runner
  create_runner "docker" "$docker_name"
  docker_result=$?
  
  # 创建 Host runner  
  create_runner "host" "$host_name"
  host_result=$?
  
  echo ""
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "批量创建完成"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  
  if [ $docker_result -eq 0 ] && [ $host_result -eq 0 ]; then
    echo "✅ 两个 runner 都创建成功!"
    exit 0
  elif [ $docker_result -eq 0 ]; then
    echo "⚠️  Docker runner 创建成功,但 Host runner 失败"
    exit 1
  elif [ $host_result -eq 0 ]; then
    echo "⚠️  Host runner 创建成功,但 Docker runner 失败"
    exit 1
  else
    echo "❌ 两个 runner 都创建失败"
    exit 1
  fi

else
  # 单个 runner 模式
  create_runner "$RUNNER_MODE" "$RUNNER_NAME"
  result=$?
  
  if [ $result -eq 0 ]; then
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "✅ Runner 创建完成!"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
  else
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "❌ Runner 创建失败"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    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
  # 重要:必须使用包含 Docker CLI 的镜像,否则 docker/login-action 等 actions 会失败
  # catthehacker/ubuntu:act-* 是专为 GitHub/Gitea Actions 设计的镜像,预装:
  #   - Docker CLI (docker 命令)
  #   - Docker Buildx
  #   - git, curl, jq 等常用工具
  # 不要使用 node:16-bullseye 等纯运行时镜像!
  labels="ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04,ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04,linux:docker://catthehacker/ubuntu:act-latest"
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 ""