188 lines
6.8 KiB
Bash
Raw Normal View History

#!/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 "═══════════════════════════════════════"