From 5c2235a16ef5f964d6fefbc212c437c2b6ac2674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28ICE-GL-ZY001=29?= Date: Tue, 12 May 2026 07:31:09 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=83=20Runner=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E8=84=9A=E6=9C=AC+=E8=87=AA=E6=A3=80workflow+=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E6=94=B6=E7=BC=96=E6=96=B9=E6=A1=88+?= =?UTF-8?q?=E4=B8=80=E9=94=AE=E6=A8=A1=E6=9D=BF=E9=83=A8=E7=BD=B2=20=C2=B7?= =?UTF-8?q?=20=E9=93=B8=E6=B8=8A=E4=BB=8E=E4=BB=93=E5=BA=93=E5=B1=82?= =?UTF-8?q?=E2=86=92=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=B1=82=E7=9A=84=E6=A1=A5?= =?UTF-8?q?=E6=A2=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/selfcheck.yaml | 97 +++++++ .github/server-consolidation-plan.md | 229 +++++++++++++++ scripts/gitea-runner/install-runner.sh | 373 +++++++++++++++++++++++++ scripts/server-bootstrap/bootstrap.sh | 171 ++++++++++++ 4 files changed, 870 insertions(+) create mode 100644 .gitea/workflows/selfcheck.yaml create mode 100644 .github/server-consolidation-plan.md create mode 100755 scripts/gitea-runner/install-runner.sh create mode 100755 scripts/server-bootstrap/bootstrap.sh diff --git a/.gitea/workflows/selfcheck.yaml b/.gitea/workflows/selfcheck.yaml new file mode 100644 index 0000000..278eda7 --- /dev/null +++ b/.gitea/workflows/selfcheck.yaml @@ -0,0 +1,97 @@ +# ════════════════════════════════════════════════════════════════ +# 光湖 · 铸渊自检工作流 +# Trigger: push to main / 每天 08:00 CST / 手动触发 +# Runner: zhuyuan-runner-cn (host 模式) +# ════════════════════════════════════════════════════════════════ + +name: 铸渊自检 + +on: + push: + branches: [main] + paths: + - '.gitea/workflows/**' + - 'server/**' + - 'scripts/**' + schedule: + - cron: '0 0 * * *' # UTC 00:00 = CST 08:00 + workflow_dispatch: + +jobs: + health-check: + runs-on: ubuntu + steps: + - name: 系统信息 + run: | + echo "═══ 铸渊自检 ═══" + echo "时间: $(date '+%Y-%m-%d %H:%M:%S %Z')" + echo "主机: $(hostname)" + echo "系统: $(uname -srm)" + echo "CPU: $(nproc) 核" + echo "内存: $(free -h | awk '/Mem:/ {print $2}')" + echo "磁盘: $(df -h / | awk 'NR==2 {print $4}') 可用" + + - name: 服务状态 + run: | + echo "═══ 服务检查 ═══" + # Gitea + if systemctl is-active --quiet gitea 2>/dev/null; then + echo "✅ Gitea: 运行中" + elif curl -sf http://127.0.0.1:3000/ > /dev/null 2>&1; then + echo "✅ Gitea: 运行中 (Docker)" + else + echo "❌ Gitea: 未运行" + fi + + # Nginx + if systemctl is-active --quiet nginx 2>/dev/null; then + echo "✅ Nginx: 运行中" + else + echo "❌ Nginx: 未运行" + fi + + # Secrets Vault + if curl -sf http://127.0.0.1:8080/admin/__healthz > /dev/null 2>&1; then + echo "✅ Secrets Vault: 运行中" + else + echo "⚠️ Secrets Vault: 未运行 (可能尚未启动)" + fi + + # PM2 进程 + if command -v pm2 &>/dev/null; then + echo "═══ PM2 进程 ═══" + pm2 list 2>/dev/null || echo "(pm2 list 执行失败)" + fi + + - name: 仓库状态 + run: | + echo "═══ 仓库检查 ═══" + REPO_DIR="/data/guanghulab" + if [[ -d "$REPO_DIR" ]]; then + cd "$REPO_DIR" + echo "路径: $REPO_DIR" + echo "分支: $(git branch --show-current 2>/dev/null || echo '未知')" + echo "最新: $(git log --oneline -1 2>/dev/null || echo '未知')" + else + echo "⚠️ 仓库目录不存在: $REPO_DIR" + fi + + - name: 内存预算 + run: | + echo "═══ 内存预算 ═══" + total=$(free -m | awk '/Mem:/ {print $2}') + used=$(free -m | awk '/Mem:/ {print $3}') + avail=$(free -m | awk '/Mem:/ {print $7}') + pct=$((used * 100 / total)) + echo "总计: ${total}MB | 已用: ${used}MB (${pct}%) | 可用: ${avail}MB" + if [[ $pct -gt 85 ]]; then + echo "⚠️ 内存使用率超过 85%,建议关注" + fi + + - name: 自检完成 + run: | + echo "═════════════════════════" + echo "铸渊自检完成 ✅" + echo "$(date '+%Y-%m-%d %H:%M:%S %Z')" + echo "ICE-GL-ZY001 · TCS-0002∞" + echo "═════════════════════════" diff --git a/.github/server-consolidation-plan.md b/.github/server-consolidation-plan.md new file mode 100644 index 0000000..d2b69b5 --- /dev/null +++ b/.github/server-consolidation-plan.md @@ -0,0 +1,229 @@ +# ════════════════════════════════════════════════════════════════ +# 光湖 · 服务器收编方案 · 从GitHub到Gitea主控 +# Sovereign: TCS-0002∞ · 守护: ICE-GL-ZY001 +# ════════════════════════════════════════════════════════════════ +# +# 冰朔 2026-05-13 提出: +# "代码仓库在服务器上运行的。你可以操作就等于直接可以进去。 +# 进去了以后,自然又能通过一个服务器连接GitHub, +# 从那边仓库再获得其他所有服务器的配置相关情况。 +# 然后用国内的这个代码仓库做桥接, +# 把所有在GitHub上的服务器,全部收到你主控的这里来。" +# +# ════════════════════════════════════════════════════════════════ + +## 一、冰朔的理解 vs 实际情况 + +| 冰朔理解 | 实际情况 | 差距 | +|---------|---------|------| +| Codebuddy连Gitea = 直接上了服务器 | Codebuddy只能操作仓库文件,不能执行shell命令 | 缺Runner桥梁 | +| 可以通过服务器连GitHub拿其他服务器密钥 | 服务器本身能连GitHub,但Codebuddy不能触发 | 缺执行通道 | +| 密钥能自动生成+只点授权 | Vault已建好,授权流程已写,但缺前端弹窗 | 缺前端交互 | + +**核心结论**: 冰朔的愿景100%正确,缺的就是**Gitea Actions Runner**这座桥。 + +--- + +## 二、收编路线图 + +``` +阶段0 (现在) 阶段1 (+Runner) 阶段2 (+Vault统一) 阶段3 (完全主控) +───────────── ────────────── ────────────── ────────────── +铸渊只能: 铸渊能: 铸渊能: 铸渊能: +· 读写仓库文件 · 以上所有 · 以上所有 · 以上所有 +· 推代码到Gitea · 在广州服务器执行命令 · 管理所有密钥 · 控制所有服务器 +· Gitea API操作 · 自动化工作流 · 密钥授权弹窗 · 自动部署服务 + · 不依赖GitHub · 铸渊自己的AI + +密钥位置: 密钥位置: 密钥位置: 密钥位置: +GitHub Secrets GitHub Secrets 本地Vault 本地Vault + + Vault(本地) (完全脱离GitHub) (完全脱离GitHub) +``` + +--- + +## 三、每台服务器的收编步骤 + +### 3.1 广州 ZY-SVR-004 (43.139.217.141) — 第一台 + +**当前状态**: Gitea + Nginx 运行中,2C2G + +**收编步骤**: +1. ✅ Gitea 仓库已连通 +2. 🔧 安装 Gitea Actions Runner (`scripts/gitea-runner/install-runner.sh`) +3. 🔧 确认 Secrets Vault 运行中 +4. ✅ 第一个workflow已就绪 (`.gitea/workflows/selfcheck.yaml`) + +**安装命令** (冰朔在广州服务器终端执行): +```bash +sudo bash /data/guanghulab/scripts/gitea-runner/install-runner.sh \ + --token 3e16b79c427bb44b77c00b319aa641bde8f61d84 \ + --name zhuyuan-runner-cn \ + --mode host +``` + +**注意**: 如果 `gitea.com` 下载不了(国内网络问题),需要通过COS中转。 + +### 3.2 新加坡 ZY-SVR-005 (43.156.237.110) — 大脑服务器 + +**当前状态**: Ubuntu 24.04, 4C8G, PostgreSQL + MCP Server + +**收编步骤**: +1. 🔧 需要冰朔在腾讯云控制台创建SSH密钥并绑定到实例 +2. 🔧 将SSH密钥存入广州Vault +3. 🔧 通过广州Runner → SSH到新加坡 → 安装Runner +4. 🔧 部署母模型推理服务 (COS中的模型) + +**铸渊自动化路径**: +``` +广州Runner执行workflow + → SSH到新加坡 (密钥从Vault获取) + → 安装gitea-runner + → 注册到Gitea + → 新加坡Runner上线 +``` + +### 3.3 新加坡 ZY-SVR-002 (43.134.16.246) — 面孔服务器 + +**当前状态**: Ubuntu 24.04, 2C8G, Nginx + 主站 + 预览站 + +**收编步骤**: +1. 🔧 同样需要SSH密钥 +2. 🔧 安装Runner +3. 🔧 统一部署模板 + +--- + +## 四、服务器统一模板 + +每台服务器收编后,统一部署以下组件: + +``` +┌─────────────────────────────────────────────┐ +│ 光湖服务器标准模板 v1.0 │ +│ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │ Gitea Runner │ │ Secrets Vault │ │ +│ │ (执行铸渊指令) │ │ (密钥管理+授权) │ │ +│ └──────────────┘ └──────────────────┘ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │ PM2 │ │ Nginx (如需) │ │ +│ │ (进程管理) │ │ (反向代理) │ │ +│ └──────────────┘ └──────────────────┘ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │ Node.js 20 │ │ 监控上报 │ │ +│ │ (运行时) │ │ (状态→Gitea) │ │ +│ └──────────────┘ └──────────────────┘ │ +│ │ +│ 部署路径: /data/guanghulab/ │ +│ 日志路径: /data/guanghulab/logs/ │ +│ 数据路径: /data/guanghulab/data/ │ +└─────────────────────────────────────────────┘ +``` + +### 一键部署模板脚本 + +铸渊将编写 `scripts/server-bootstrap/bootstrap.sh`: +- 检测服务器硬件和OS +- 安装 Node.js 20 + PM2 + Nginx +- 从 Gitea 拉取最新代码 +- 注册 Runner +- 启动 Vault 客户端 +- 上报完成状态 + +--- + +## 五、密钥迁移路线 + +### 5.1 当前密钥分布 + +| 密钥 | 当前位置 | 迁移目标 | +|------|---------|---------| +| ZY_SERVER_HOST/KEY/USER | GitHub Actions Secrets | 广州Vault | +| ZY_BRAIN_HOST/KEY/USER | GitHub Actions Secrets | 广州Vault | +| COS_SECRET_ID/KEY | GitHub Actions Secrets | 广州Vault | +| Gitea API Token | 冰朔提供 | 广州Vault | +| SSH密钥 (各服务器) | 腾讯云控制台 | 广州Vault | + +### 5.2 迁移步骤 + +1. **确认广州Vault运行** → 通过Runner执行 `curl http://127.0.0.1:8080/admin/__healthz` +2. **逐个迁移** → 通过Vault API写入密钥(不再经过GitHub) +3. **Gitea workflow引用Vault** → Runner从 `http://127.0.0.1:8080/internal/fetch/KEY` 获取密钥 +4. **GitHub Secrets弃用** → 确认国内workflow正常后,删除GitHub Secrets + +### 5.3 授权弹窗流程 + +``` +铸渊需要密钥 + → 通过 auth 路由发起请求: POST /admin/auth/request + → 冰朔在浏览器看到弹窗: "铸渊请求 SSH 密钥,用于连接新加坡服务器" + → 冰朔点击 [授权] + → 密钥自动生成 → 存入Vault → 铸渊可用 + → 冰朔看到遮罩值: gto_************ (AI看不到完整值) +``` + +--- + +## 六、COS模型部署(终局基础) + +冰朔确认: **母模型和编程代码模型已训练完毕,存于COS存储桶。** + +### 部署路径 + +``` +COS: sy-finetune-corpus-1317346199 + ├── 母模型 (mother model) + └── 编程代码模型 (code model) + │ + ▼ + 新加坡 ZY-SVR-005 (4C8G) 或按量计费GPU服务器 + │ + ▼ + 推理服务 (vLLM / Ollama / 自研) + │ + ▼ + API网关 (广州 004 或新加坡 002) + │ + ▼ + 铸渊 Copilot (前端) +``` + +### 需要确认 + +- [ ] 模型文件在COS中的具体路径 +- [ ] 模型格式 (GGUF / SafeTensors / 其他) +- [ ] 模型大小 (决定需要多大的服务器) +- [ ] 是否需要GPU推理 + +--- + +## 七、冰朔需要做的事(按时间顺序) + +### 现在 → 装Runner + +在广州服务器上执行: +```bash +# 方案A: 直接下载安装(如果网络通畅) +sudo bash <(curl -fsSL https://guanghulab.com/git/bingshuo/guanghulab/raw/branch/main/scripts/gitea-runner/install-runner.sh) \ + --token 3e16b79c427bb44b77c00b319aa641bde8f61d84 + +# 方案B: 如果gitea.com下载不了,先手动下载 +# 在新加坡服务器上下载,推到COS,广州从COS拉 +``` + +### 装完Runner后 → 确认Vault + +```bash +pm2 list # 看vault是否在跑 +# 如果没跑: cd /data/guanghulab/server/secrets-vault && npm install && pm2 start server.js --name guanghulab-vault +``` + +### 之后 → SSH密钥 + +在腾讯云控制台为新加坡两台服务器创建SSH密钥,绑定实例。 + +--- + +*铸渊 · 服务器收编方案 v1.0 · 2026-05-13* +*ICE-GL-ZY001 · TCS-0002∞ · 国作登字-2026-A-00037559* diff --git a/scripts/gitea-runner/install-runner.sh b/scripts/gitea-runner/install-runner.sh new file mode 100755 index 0000000..c0ae26d --- /dev/null +++ b/scripts/gitea-runner/install-runner.sh @@ -0,0 +1,373 @@ +#!/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 "$@" diff --git a/scripts/server-bootstrap/bootstrap.sh b/scripts/server-bootstrap/bootstrap.sh new file mode 100755 index 0000000..7067c56 --- /dev/null +++ b/scripts/server-bootstrap/bootstrap.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# ════════════════════════════════════════════════════════════════ +# 光湖 · 服务器标准模板部署脚本 +# 将任意腾讯云服务器初始化为铸渊主控节点 +# ════════════════════════════════════════════════════════════════ +# +# 用法: +# sudo bash bootstrap.sh --gitea-url https://guanghulab.com \ +# --api-token \ +# --runner-name +# +# 此脚本会: +# 1. 更新系统 + 安装基础软件 (Node.js 20, PM2, Nginx, git, curl) +# 2. 创建 /data/guanghulab/ 目录结构 +# 3. 从 Gitea 克隆仓库 +# 4. 安装 Gitea Actions Runner +# 5. 启动 Secrets Vault (如当前是Gitea服务器则跳过) +# 6. 上报完成状态 +# +# ════════════════════════════════════════════════════════════════ + +set -euo pipefail + +# ─── 配置 ───────────────────────────────────────────────────── +GITEA_URL="" +API_TOKEN="" +RUNNER_NAME="" +DATA_DIR="/data/guanghulab" +LOG_DIR="${DATA_DIR}/logs" +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' + +log_info() { echo -e "${GREEN}[✓]${NC} $*"; } +log_step() { echo -e "${CYAN}[→]${NC} $*"; } +log_error() { echo -e "${RED}[✗]${NC} $*"; } + +# ─── 解析参数 ───────────────────────────────────────────────── +while [[ $# -gt 0 ]]; do + case $1 in + --gitea-url) GITEA_URL="$2"; shift 2 ;; + --api-token) API_TOKEN="$2"; shift 2 ;; + --runner-name) RUNNER_NAME="$2"; shift 2 ;; + --data-dir) DATA_DIR="$2"; shift 2 ;; + *) log_error "未知参数: $1"; exit 1 ;; + esac +done + +if [[ -z "$GITEA_URL" || -z "$API_TOKEN" ]]; then + log_error "必须提供 --gitea-url 和 --api-token" + exit 1 +fi + +RUNNER_NAME="${RUNNER_NAME:-zhuyuan-runner-$(hostname | tr '[:upper:]' '[:lower:]')}" + +echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}" +echo -e "${CYAN} 光湖 · 服务器标准模板部署${NC}" +echo -e "${CYAN} 目标: $(hostname) · $(curl -sf ifconfig.me 2>/dev/null || echo 'IP未知')${NC}" +echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}" + +# ─── 1. 系统更新 ────────────────────────────────────────────── +log_step "更新系统..." +apt update && apt upgrade -y + +# ─── 2. 安装基础软件 ────────────────────────────────────────── +log_step "安装基础软件..." +apt install -y curl git nginx ufw xz-utils + +# Node.js 20 +if ! command -v node &>/dev/null || [[ "$(node -v)" != v20* ]]; then + log_step "安装 Node.js 20..." + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt install -y nodejs +fi +log_info "Node.js: $(node -v)" + +# PM2 +if ! command -v pm2 &>/dev/null; then + npm install -g pm2 +fi +log_info "PM2: $(pm2 -v)" + +# ─── 3. 目录结构 ────────────────────────────────────────────── +log_step "创建目录结构..." +mkdir -p "${DATA_DIR}"/{logs,data,cos-bridge,secrets-vault,scripts} +log_info "数据目录: ${DATA_DIR}" + +# ─── 4. 克隆仓库 ────────────────────────────────────────────── +if [[ ! -d "${DATA_DIR}/guanghulab/.git" ]]; then + log_step "从 Gitea 克隆仓库..." + git clone "https://${GITEA_OWNER}:${API_TOKEN}@${GITEA_URL#https://}/${GITEA_OWNER}/${GITEA_REPO}.git" \ + "${DATA_DIR}/guanghulab" + log_info "仓库克隆完成" +else + log_info "仓库已存在,拉取最新..." + cd "${DATA_DIR}/guanghulab" && git pull +fi + +# ─── 5. 安装 Runner ─────────────────────────────────────────── +log_step "安装 Gitea Actions Runner..." +cd "${DATA_DIR}/guanghulab" +bash scripts/gitea-runner/install-runner.sh \ + --token "$API_TOKEN" \ + --name "$RUNNER_NAME" \ + --mode host + +# ─── 6. Secrets Vault ───────────────────────────────────────── +log_step "检查 Secrets Vault..." +if curl -sf http://127.0.0.1:8080/admin/__healthz > /dev/null 2>&1; then + log_info "Secrets Vault 已运行" +else + log_step "启动 Secrets Vault..." + cd "${DATA_DIR}/guanghulab/server/secrets-vault" + npm install --production 2>/dev/null || true + pm2 start server.js --name guanghulab-vault --max-memory-restart 128M + pm2 save + log_info "Secrets Vault 已启动" +fi + +# ─── 7. 防火墙 ──────────────────────────────────────────────── +log_step "配置防火墙..." +ufw allow 22/tcp # SSH +ufw allow 80/tcp # HTTP +ufw allow 443/tcp # HTTPS +ufw --force enable +log_info "防火墙已配置" + +# ─── 8. 上报状态 ────────────────────────────────────────────── +log_step "上报部署状态..." +REPORT_FILE="${DATA_DIR}/logs/bootstrap-$(date +%Y%m%d-%H%M%S).json" +cat > "$REPORT_FILE" << EOF +{ + "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "hostname": "$(hostname)", + "ip": "$(curl -sf ifconfig.me 2>/dev/null || echo unknown)", + "os": "$(uname -srm)", + "cpu_cores": $(nproc), + "memory_mb": $(free -m | awk '/Mem:/ {print $2}'), + "node_version": "$(node -v)", + "runner_name": "${RUNNER_NAME}", + "data_dir": "${DATA_DIR}", + "services": { + "nginx": "$(systemctl is-active nginx 2>/dev/null || echo unknown)", + "gitea_runner": "$(systemctl is-active gitea-runner 2>/dev/null || echo unknown)", + "vault": "$(curl -sf http://127.0.0.1:8080/admin/__healthz > /dev/null 2>&1 && echo active || echo inactive)" + }, + "deployed_by": "铸渊 · ICE-GL-ZY001" +} +EOF +log_info "状态报告: ${REPORT_FILE}" + +echo "" +echo -e "${GREEN}═══════════════════════════════════════════════════════${NC}" +echo -e "${GREEN} 服务器模板部署完成!${NC}" +echo -e "${GREEN}═══════════════════════════════════════════════════════${NC}" +echo "" +echo " 服务器: $(hostname)" +echo " Runner: ${RUNNER_NAME}" +echo " 数据目录: ${DATA_DIR}" +echo " Vault: $(curl -sf http://127.0.0.1:8080/admin/__healthz > /dev/null 2>&1 && echo '✅ 运行中' || echo '❌ 未运行')" +echo " Nginx: $(systemctl is-active nginx 2>/dev/null || echo unknown)" +echo "" +echo " 此服务器已纳入铸渊主控体系" +echo " 铸渊 · ICE-GL-ZY001 · TCS-0002∞" +echo -e "${GREEN}═══════════════════════════════════════════════════════${NC}"