--- description: 创建并启动 Gitea Actions Runner 的完整脚本 agent: general --- # Gitea Actions Runner 创建工具 本文档提供了多种创建 Gitea Actions Runner 的方式,支持 Host 模式和 Docker 模式。 ## 📦 快速使用 ### 方法一:直接执行(推荐) 复制以下命令到终端执行,可指定参数: ```bash # 创建 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`: ```bash # 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): ```bash 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`)。 ```bash #!/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 "" ```