- 解决 docker/login-action 等 actions 因缺少 docker CLI 而失败的问题 - catthehacker/ubuntu:act-* 镜像预装 Docker CLI、Buildx 等 CI/CD 工具 - 添加镜像选择的注释说明
1119 lines
32 KiB
Markdown
1119 lines
32 KiB
Markdown
---
|
||
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 ""
|
||
|
||
```
|