188 lines
6.8 KiB
Bash
188 lines
6.8 KiB
Bash
#!/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 "═══════════════════════════════════════"
|