#!/usr/bin/env bash # ════════════════════════════════════════════════════════════════ # 光湖 · Gitea Actions Runner 一键安装脚本 # Sovereign: TCS-0002∞ · ICE-GL∞ · 国作登字-2026-A-00037559 # 守护: 铸渊 · ICE-GL-ZY001 # ════════════════════════════════════════════════════════════════ # # 用法: # sudo bash install-runner.sh [--token GITEA_API_TOKEN] [--name RUNNER_NAME] # # 参数: # --token Gitea 管理员 API Token (必须,用于获取 Runner 注册 Token) # --name Runner 名称 (可选,默认 zhuyuan-runner-cn) # --mode 运行模式: host 或 docker (可选,默认 host,2C2G推荐host) # # 环境变量 (替代命令行参数): # GITEA_API_TOKEN 同 --token # RUNNER_NAME 同 --name # RUNNER_MODE 同 --mode # # 此脚本会: # 1. 下载 gitea-runner v1.0.2 # 2. 通过 Gitea API 获取 Runner 注册 Token # 3. 注册 Runner 到 Gitea # 4. 生成配置文件 (host 模式,适合 2C2G) # 5. 创建 systemd 服务 # 6. 启动服务 # # ════════════════════════════════════════════════════════════════ set -euo pipefail # ─── 常量 ───────────────────────────────────────────────────── RUNNER_VERSION="1.0.2" RUNNER_BINARY="gitea-runner-${RUNNER_VERSION}-linux-amd64" RUNNER_DOWNLOAD_URL="https://gitea.com/gitea/runner/releases/download/v${RUNNER_VERSION}/${RUNNER_BINARY}.xz" RUNNER_INSTALL_PATH="/usr/local/bin/gitea-runner" RUNNER_WORK_DIR="/var/lib/act_runner" RUNNER_CONFIG_DIR="/etc/act_runner" RUNNER_CONFIG_PATH="${RUNNER_CONFIG_DIR}/config.yaml" SYSTEMD_SERVICE_NAME="gitea-runner" SYSTEMD_SERVICE_PATH="/etc/systemd/system/${SYSTEMD_SERVICE_NAME}.service" # Gitea 配置 GITEA_URL="https://guanghulab.com" GITEA_API="${GITEA_URL}/api/v1" GITEA_OWNER="bingshuo" GITEA_REPO="guanghulab" # 颜色 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # ─── 解析参数 ───────────────────────────────────────────────── API_TOKEN="${GITEA_API_TOKEN:-}" RUNNER_NAME="${RUNNER_NAME:-zhuyuan-runner-cn}" RUNNER_MODE="${RUNNER_MODE:-host}" while [[ $# -gt 0 ]]; do case $1 in --token) API_TOKEN="$2"; shift 2 ;; --name) RUNNER_NAME="$2"; shift 2 ;; --mode) RUNNER_MODE="$2"; shift 2 ;; --help|-h) head -30 "$0" | grep '^#' | sed 's/^# \?//' exit 0 ;; *) echo -e "${RED}未知参数: $1${NC}"; exit 1 ;; esac done # ─── 函数 ───────────────────────────────────────────────────── log_info() { echo -e "${GREEN}[✓]${NC} $*"; } log_warn() { echo -e "${YELLOW}[!]${NC} $*"; } log_step() { echo -e "${CYAN}[→]${NC} $*"; } log_error() { echo -e "${RED}[✗]${NC} $*"; } check_root() { if [[ $EUID -ne 0 ]]; then log_error "此脚本必须以 root 运行" exit 1 fi } check_arch() { local arch arch=$(uname -m) if [[ "$arch" != "x86_64" && "$arch" != "amd64" ]]; then log_error "不支持的架构: $arch (仅支持 amd64/x86_64)" exit 1 fi log_info "架构检查通过: $arch" } check_disk() { local avail avail=$(df -BG /var | awk 'NR==2 {print $4}' | tr -d 'G') if [[ $avail -lt 1 ]]; then log_error "磁盘空间不足: ${avail}GB 可用,至少需要 1GB" exit 1 fi log_info "磁盘空间: ${avail}GB 可用" } check_memory() { local total_mb total_mb=$(free -m | awk '/Mem:/ {print $2}') log_info "内存: ${total_mb}MB" if [[ $total_mb -lt 1500 ]]; then log_warn "内存较低 (${total_mb}MB),将使用 host 模式以节省资源" RUNNER_MODE="host" fi } download_runner() { if [[ -x "$RUNNER_INSTALL_PATH" ]]; then local current_version current_version=$("$RUNNER_INSTALL_PATH" --version 2>/dev/null | grep -oP 'v\K[\d.]+' || echo "unknown") log_info "gitea-runner 已安装 (版本: ${current_version})" read -rp "是否重新安装?[y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then return 0 fi fi log_step "下载 gitea-runner v${RUNNER_VERSION}..." local tmp_dir tmp_dir=$(mktemp -d) trap "rm -rf $tmp_dir" EXIT if ! curl -fSL --progress-bar -o "${tmp_dir}/${RUNNER_BINARY}.xz" "$RUNNER_DOWNLOAD_URL"; then log_error "下载失败: $RUNNER_DOWNLOAD_URL" log_warn "如果国内无法访问 gitea.com,请通过 COS 中转:" log_warn " 1. 在新加坡服务器下载此文件" log_warn " 2. 推送到 COS" log_warn " 3. 从 COS 拉到本服务器" log_warn " 4. 用 --skip-download 参数跳过下载步骤" exit 1 fi log_step "解压..." xz -d "${tmp_dir}/${RUNNER_BINARY}.xz" chmod +x "${tmp_dir}/${RUNNER_BINARY}" log_step "安装到 ${RUNNER_INSTALL_PATH}..." mv "${tmp_dir}/${RUNNER_BINARY}" "$RUNNER_INSTALL_PATH" log_info "gitea-runner 安装完成: $("$RUNNER_INSTALL_PATH" --version 2>&1 | head -1)" } get_registration_token() { log_step "通过 API 获取 Runner 注册 Token..." if [[ -z "$API_TOKEN" ]]; then log_error "需要 Gitea API Token" echo "" echo "获取方式:" echo " 1. 浏览器打开 ${GITEA_URL}/user/settings/applications" echo " 2. 生成一个 Token(需要 repo + admin:runner 权限)" echo " 3. 重新运行: sudo bash $0 --token <你的token>" echo "" echo "或设置环境变量: export GITEA_API_TOKEN=" exit 1 fi local response # 先尝试仓库级 token response=$(curl -sf -H "Authorization: token ${API_TOKEN}" \ "${GITEA_API}/repos/${GITEA_OWNER}/${GITEA_REPO}/actions/runners/registration-token" 2>/dev/null || echo "") if [[ -z "$response" ]]; then # 尝试实例级 token(需要管理员权限) log_warn "仓库级 token 获取失败,尝试实例级..." response=$(curl -sf -H "Authorization: token ${API_TOKEN}" \ "${GITEA_API}/admin/runners/registration-token" 2>/dev/null || echo "") fi if [[ -z "$response" ]]; then log_error "无法获取 Runner 注册 Token" log_error "请确认 API Token 有正确的权限" exit 1 fi REG_TOKEN=$(echo "$response" | grep -oP '"token"\s*:\s*"\K[^"]+' || echo "") if [[ -z "$REG_TOKEN" ]]; then log_error "解析注册 Token 失败: $response" exit 1 fi log_info "注册 Token 获取成功" } create_user() { if id -u act_runner &>/dev/null; then log_info "act_runner 用户已存在" return 0 fi log_step "创建 act_runner 用户..." useradd -r -m -d "$RUNNER_WORK_DIR" -s /bin/bash act_runner log_info "用户创建完成" } generate_config() { log_step "生成 Runner 配置..." mkdir -p "$RUNNER_CONFIG_DIR" "$RUNNER_INSTALL_PATH" generate-config > "$RUNNER_CONFIG_PATH" # 根据模式修改 labels if [[ "$RUNNER_MODE" == "host" ]]; then # host 模式:直接在主机上执行,不启动 Docker 容器 # 适合 2C2G 低内存服务器 sed -i 's/^labels:.*$/labels:\n - "ubuntu:host"/' "$RUNNER_CONFIG_PATH" 2>/dev/null || true # 在 config.yaml 中设置 container mode 为空(不使用容器) if grep -q '^container:' "$RUNNER_CONFIG_PATH"; then sed -i '/^container:/a\ network: ""' "$RUNNER_CONFIG_PATH" 2>/dev/null || true fi fi chown -R act_runner:act_runner "$RUNNER_CONFIG_DIR" log_info "配置生成完成: ${RUNNER_CONFIG_PATH} (模式: ${RUNNER_MODE})" } register_runner() { log_step "注册 Runner 到 Gitea..." cd "$RUNNER_WORK_DIR" sudo -u act_runner "$RUNNER_INSTALL_PATH" register \ --no-interactive \ --instance "${GITEA_URL}/" \ --token "$REG_TOKEN" \ --name "$RUNNER_NAME" \ --labels "ubuntu:${RUNNER_MODE}" if [[ ! -f "${RUNNER_WORK_DIR}/.runner" ]]; then log_error "注册失败: .runner 文件未生成" exit 1 fi chown act_runner:act_runner "${RUNNER_WORK_DIR}/.runner" log_info "Runner 注册成功: ${RUNNER_NAME}" } create_systemd_service() { log_step "创建 systemd 服务..." cat > "$SYSTEMD_SERVICE_PATH" << EOF [Unit] Description=Gitea Actions Runner - ${RUNNER_NAME} Documentation=https://gitea.com/gitea/act_runner After=network.target Wants=network-online.target [Service] Type=simple ExecStart=${RUNNER_INSTALL_PATH} daemon --config ${RUNNER_CONFIG_PATH} WorkingDirectory=${RUNNER_WORK_DIR} Restart=always RestartSec=10 TimeoutSec=0 User=act_runner Group=act_runner Environment=HOME=${RUNNER_WORK_DIR} Environment=RUNNER_NAME=${RUNNER_NAME} # 安全限制 NoNewPrivileges=true ProtectSystem=strict ReadWritePaths=${RUNNER_WORK_DIR} /tmp /data PrivateTmp=true [Install] WantedBy=multi-user.target EOF log_info "systemd 服务文件创建完成" } start_service() { log_step "启动 Runner 服务..." systemctl daemon-reload systemctl enable "$SYSTEMD_SERVICE_NAME" systemctl start "$SYSTEMD_SERVICE_NAME" # 等待启动 sleep 2 if systemctl is-active --quiet "$SYSTEMD_SERVICE_NAME"; then log_info "Runner 服务启动成功! 🎉" else log_error "Runner 服务启动失败" journalctl -u "$SYSTEMD_SERVICE_NAME" --no-pager -n 20 exit 1 fi } verify() { log_step "验证..." echo "" # 本地验证 echo -e "${CYAN}本地状态:${NC}" systemctl status "$SYSTEMD_SERVICE_NAME" --no-pager -l | head -15 echo "" # 远程验证 log_step "检查 Gitea 上的 Runner 列表..." local runners runners=$(curl -sf -H "Authorization: token ${API_TOKEN}" \ "${GITEA_API}/repos/${GITEA_OWNER}/${GITEA_REPO}/actions/runners" 2>/dev/null || echo "") if [[ -n "$runners" ]]; then echo "$runners" | python3 -c " import json, sys data = json.load(sys.stdin) runners = data if isinstance(data, list) else data.get('runners', []) for r in runners: status = '🟢 在线' if r.get('online') else '🔴 离线' print(f\" {r.get('name', '?')} {status} labels={r.get('labels', [])}\") " 2>/dev/null || echo " (无法解析,但API有响应)" else log_warn "无法获取远程 Runner 列表(可能 API 权限不足)" fi echo "" echo -e "${GREEN}═══════════════════════════════════════════════════════${NC}" echo -e "${GREEN} Gitea Actions Runner 安装完成!${NC}" echo -e "${GREEN}═══════════════════════════════════════════════════════${NC}" echo "" echo " Runner 名称: ${RUNNER_NAME}" echo " 运行模式: ${RUNNER_MODE}" echo " 工作目录: ${RUNNER_WORK_DIR}" echo " 配置文件: ${RUNNER_CONFIG_PATH}" echo " 服务管理: systemctl {start|stop|restart|status} ${SYSTEMD_SERVICE_NAME}" echo " 查看日志: journalctl -u ${SYSTEMD_SERVICE_NAME} -f" echo "" echo " 下一步: 铸渊推一个 workflow 到 Gitea,Runner 就会自动执行" echo "" echo " 铸渊 · ICE-GL-ZY001 · TCS-0002∞" echo -e "${GREEN}═══════════════════════════════════════════════════════${NC}" } # ─── 主流程 ─────────────────────────────────────────────────── main() { echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}" echo -e "${CYAN} 光湖 · Gitea Actions Runner 一键安装${NC}" echo -e "${CYAN} 铸渊 · ICE-GL-ZY001 · 守护冰朔 TCS-0002∞${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}" echo "" check_root check_arch check_disk check_memory download_runner get_registration_token create_user generate_config register_runner create_systemd_service start_service verify } main "$@"