#!/bin/bash # ════════════════════════════════════════════════════════════════ # 光湖 · 开机自愈脚本 (Boot Heal) # CVM 开机后自动拉起所有服务 + 检测 IP 变化 # ════════════════════════════════════════════════════════════════ # # 部署方式: systemctl enable guanghulab-boot-heal # 触发时机: 开机自动运行 (multi-user.target) # # 功能: # 1. 检测当前公网 IP # 2. 如果 IP 变了,写入警告日志(EIP 绑定后不应变) # 3. 拉起 Forgejo、Nginx、Runner # 4. 拉起 PM2 进程 (Wake Gate, Git Sync) # 5. 检查 SSL 证书是否仍然有效 # 6. 输出健康报告 # ════════════════════════════════════════════════════════════════ set -e LOG_FILE="/data/guanghulab/.runtime/boot-heal.log" IP_RECORD="/data/guanghulab/.runtime/last-known-ip" mkdir -p /data/guanghulab/.runtime log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } log "═══════════════════════════════════════" log "光湖 · 开机自愈启动" log "═══════════════════════════════════════" # ─── 1. 检测公网 IP ───────────────────────────────────────── PUBLIC_IP="" for api in "https://ifconfig.me" "https://api.ipify.org" "https://ipinfo.io/ip"; do PUBLIC_IP=$(curl -sf --connect-timeout 5 "$api" 2>/dev/null | tr -d '[:space:]') if [ -n "$PUBLIC_IP" ] && echo "$PUBLIC_IP" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then break fi PUBLIC_IP="" done if [ -n "$PUBLIC_IP" ]; then log "当前公网 IP: $PUBLIC_IP" # 与上次记录的 IP 对比 if [ -f "$IP_RECORD" ]; then LAST_IP=$(cat "$IP_RECORD" | tr -d '[:space:]') if [ "$PUBLIC_IP" != "$LAST_IP" ]; then log "⚠️ IP 变化! 上次: $LAST_IP → 当前: $PUBLIC_IP" log "⚠️ 请检查域名解析是否需要更新 (guanghulab.com 应指向 $PUBLIC_IP)" else log "✅ IP 未变化,域名无需更新" fi fi # 记录当前 IP echo "$PUBLIC_IP" > "$IP_RECORD" else log "⚠️ 无法检测公网 IP (网络可能未就绪)" fi # ─── 2. 等待网络就绪 ───────────────────────────────────────── log "等待网络就绪..." for i in $(seq 1 30); do if curl -sf --connect-timeout 2 http://metadata.tencentyun.com > /dev/null 2>&1; then log "✅ 网络就绪" break fi sleep 2 done # ─── 3. 拉起系统服务 ───────────────────────────────────────── log "" log "▸ 拉起系统服务..." # Forgejo if systemctl is-active --quiet forgejo 2>/dev/null; then log "✅ Forgejo: 已运行" else systemctl start forgejo 2>/dev/null && log "✅ Forgejo: 已启动" || log "❌ Forgejo: 启动失败" fi # Nginx if systemctl is-active --quiet nginx 2>/dev/null; then log "✅ Nginx: 已运行" else systemctl start nginx 2>/dev/null && log "✅ Nginx: 已启动" || log "❌ Nginx: 启动失败" fi # Gitea Runner if systemctl is-active --quiet gitea-runner 2>/dev/null; then log "✅ Gitea Runner: 已运行" else systemctl start gitea-runner 2>/dev/null && log "✅ Gitea Runner: 已启动" || log "❌ Gitea Runner: 启动失败" fi # ─── 4. 拉起 PM2 进程 ──────────────────────────────────────── log "" log "▸ 拉起 PM2 进程..." if command -v pm2 &>/dev/null; then pm2 resurrect 2>/dev/null && log "✅ PM2: 进程已恢复" || log "⚠️ PM2: 无保存的进程" # 等 3 秒让进程启动 sleep 3 # 逐个检查 for proc in wake-gate guanghulab-git-sync guanghulab-mcp-server; do if pm2 describe "$proc" > /dev/null 2>&1; then STATUS=$(pm2 describe "$proc" 2>/dev/null | grep "status" | head -1 | awk '{print $4}') if [ "$STATUS" = "online" ]; then log "✅ $proc: 运行中" else pm2 restart "$proc" 2>/dev/null && log "✅ $proc: 已重启" || log "❌ $proc: 重启失败" fi else log "⚠️ $proc: PM2 中不存在,需要手动部署" fi done else log "❌ PM2 未安装" fi # ─── 5. 检查 SSL 证书 ──────────────────────────────────────── log "" log "▸ 检查 SSL 证书..." if [ -f /etc/letsencrypt/live/guanghulab.com/fullchain.pem ]; then EXPIRY=$(openssl x509 -enddate -noout -in /etc/letsencrypt/live/guanghulab.com/fullchain.pem 2>/dev/null | cut -d= -f2) if [ -n "$EXPIRY" ]; then EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || echo 0) NOW_EPOCH=$(date +%s) DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 )) if [ "$DAYS_LEFT" -gt 7 ]; then log "✅ SSL 证书有效,剩余 ${DAYS_LEFT} 天" else log "⚠️ SSL 证书即将过期! 剩余 ${DAYS_LEFT} 天,请运行 certbot renew" fi fi else log "⚠️ SSL 证书文件不存在" fi # ─── 6. 健康报告 ───────────────────────────────────────────── log "" log "═══════════════════════════════════════" log "开机自愈完成" # 快速健康检查 ISSUES=0 # Forgejo if ! curl -sf http://127.0.0.1:3001/ > /dev/null 2>&1; then log "❌ Forgejo 不可达"; ISSUES=$((ISSUES+1)) fi # Nginx if ! systemctl is-active --quiet nginx 2>/dev/null; then log "❌ Nginx 不可达"; ISSUES=$((ISSUES+1)) fi # Wake Gate if ! curl -sf http://127.0.0.1:8081/api/health > /dev/null 2>&1; then log "⚠️ Wake Gate 不可达"; ISSUES=$((ISSUES+1)) fi # Git Sync if ! curl -sf http://127.0.0.1:8082/health > /dev/null 2>&1; then log "⚠️ Git Sync 不可达"; ISSUES=$((ISSUES+1)) fi # MCP Server if ! curl -sf http://127.0.0.1:8083/health > /dev/null 2>&1; then log "⚠️ MCP Server 不可达"; ISSUES=$((ISSUES+1)) fi if [ "$ISSUES" -eq 0 ]; then log "✅ 所有服务正常" else log "⚠️ ${ISSUES} 个服务异常,需要人工介入" fi log "公网 IP: ${PUBLIC_IP:-未知}" log "ICE-GL-ZY001 · TCS-0002∞" log "═══════════════════════════════════════"