guanghulab/.github/archived-workflows/pm2-server-diagnose.yml
2026-05-10 13:12:44 +08:00

320 lines
15 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ═══════════════════════════════════════════════
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
# 📜 Copyright: 国作登字-2026-A-00037559
# ═══════════════════════════════════════════════
name: "🔧 铸渊 · PM2 服务诊断与健康检查"
# ━━━ PM2 服务诊断 + 全系统健康检查 + Notion 报告 ━━━
# 手动触发:对服务器 PM2 进程进行全面排查,
# 自动处理 errored 进程,并将诊断报告写入 Notion 工单队列。
#
# 必需 GitHub Secrets:
# DEPLOY_HOST — 服务器 IP
# DEPLOY_USER — SSH 用户名
# DEPLOY_KEY — SSH 私钥(完整 PEM 内容)
# DEPLOY_PATH — 网站根目录(如 /var/www/guanghulab
#
# 可选 Secrets有则自动写 Notion 工单):
# NOTION_API_TOKEN — Notion 集成 token
# NOTION_TICKET_DB_ID — 工单队列数据库 ID默认 84edd0640bf146a9a5a7840107013e8c
on:
workflow_dispatch:
inputs:
auto_cleanup:
description: '是否自动清理 errored 进程(删除启动路径不存在的进程)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
permissions:
contents: read
jobs:
diagnose:
name: "🔍 PM2 诊断 + 系统健康检查"
runs-on: ubuntu-latest
steps:
- name: 📥 检出代码
uses: actions/checkout@v4
- name: 🟢 配置 Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: 🔑 配置 SSH 连接
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_SERVER_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "${{ secrets.ZY_SERVER_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null || {
echo "⚠️ ssh-keyscan 失败,使用 StrictHostKeyChecking=no 后备"
}
# ━━━ 阶段一PM2 服务诊断 ━━━
- name: "🔧 阶段一 · PM2 errored 服务排查"
id: pm2_diagnose
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 阶段一 · PM2 errored 服务排查"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
PM2_REPORT=$(ssh -i ~/.ssh/deploy_key \
-o StrictHostKeyChecking=no \
"${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}" '
echo "══════════════════════════════════════"
echo " PM2 进程状态概览"
echo "══════════════════════════════════════"
pm2 status 2>/dev/null || echo "❌ pm2 未安装或不可用"
echo ""
echo "══════════════════════════════════════"
echo " errored 进程详细日志"
echo "══════════════════════════════════════"
# 遍历所有 errored 进程
ERRORED_LIST=$(pm2 jlist 2>/dev/null | python3 -c "
import sys, json
try:
procs = json.load(sys.stdin)
for p in procs:
if p.get(\"pm2_env\", {}).get(\"status\") == \"errored\":
name = p.get(\"name\", \"unknown\")
pm_id = p.get(\"pm_id\", \"?\")
script = p.get(\"pm2_env\", {}).get(\"pm_exec_path\", \"unknown\")
restarts = p.get(\"pm2_env\", {}).get(\"restart_time\", 0)
print(f\"{pm_id}|{name}|{script}|{restarts}\")
except:
pass
" 2>/dev/null)
if [ -z "$ERRORED_LIST" ]; then
echo "✅ 没有 errored 进程"
else
echo "$ERRORED_LIST" | while IFS="|" read -r pm_id name script restarts; do
echo ""
echo "━━━ 进程: $name (PM2 ID: $pm_id) ━━━"
echo " 启动脚本: $script"
echo " 重启次数: $restarts"
# 检查脚本文件是否存在
if [ -f "$script" ]; then
echo " 脚本文件: ✅ 存在"
else
echo " 脚本文件: ❌ 不存在"
fi
# 查看最近日志
echo " 最近错误日志 (最后 20 行):"
pm2 logs "$name" --lines 20 --nostream --err 2>/dev/null || echo " (无法读取日志)"
echo ""
done
fi
echo ""
echo "══════════════════════════════════════"
echo " 所有进程详细信息"
echo "══════════════════════════════════════"
pm2 jlist 2>/dev/null | python3 -c "
import sys, json
try:
procs = json.load(sys.stdin)
for p in procs:
env = p.get(\"pm2_env\", {})
print(f\" [{p.get(\"pm_id\")}] {p.get(\"name\")} | status={env.get(\"status\")} | script={env.get(\"pm_exec_path\",\"?\")} | restarts={env.get(\"restart_time\",0)} | memory={p.get(\"monit\",{}).get(\"memory\",0)}\")
except Exception as e:
print(f\" 解析失败: {e}\")
" 2>/dev/null
echo ""
echo "══════════════════════════════════════"
echo " 端口占用情况"
echo "══════════════════════════════════════"
netstat -tlnp 2>/dev/null | grep -E "3000|3001|3002|3721|8080" || \
ss -tlnp 2>/dev/null | grep -E "3000|3001|3002|3721|8080" || \
echo " (netstat/ss 不可用)"
')
echo "$PM2_REPORT"
# 保存到文件供后续步骤使用
echo "$PM2_REPORT" > /tmp/pm2-report.txt
# 设置输出
{
echo "pm2_report<<REPORT_EOF"
echo "$PM2_REPORT"
echo "REPORT_EOF"
} >> "$GITHUB_OUTPUT"
# ━━━ 阶段一 · 自动清理 ━━━
- name: "🧹 阶段一 · 自动清理 errored 进程"
if: ${{ inputs.auto_cleanup == 'true' }}
id: pm2_cleanup
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 自动清理 errored 进程"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
CLEANUP_REPORT=$(ssh -i ~/.ssh/deploy_key \
-o StrictHostKeyChecking=no \
"${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}" '
ERRORED_LIST=$(pm2 jlist 2>/dev/null | python3 -c "
import sys, json
try:
procs = json.load(sys.stdin)
for p in procs:
if p.get(\"pm2_env\", {}).get(\"status\") == \"errored\":
name = p.get(\"name\", \"unknown\")
script = p.get(\"pm2_env\", {}).get(\"pm_exec_path\", \"unknown\")
print(f\"{name}|{script}\")
except:
pass
" 2>/dev/null)
CLEANED=""
SKIPPED=""
if [ -z "$ERRORED_LIST" ]; then
echo "✅ 没有需要清理的 errored 进程"
else
echo "$ERRORED_LIST" | while IFS="|" read -r name script; do
if [ ! -f "$script" ]; then
echo "🗑️ 删除 $name脚本 $script 不存在)"
pm2 delete "$name" 2>/dev/null && echo " ✅ 已删除" || echo " ❌ 删除失败"
else
echo "⚠️ 保留 $name脚本 $script 存在,可能是运行时错误)"
echo " 尝试重启..."
pm2 restart "$name" 2>/dev/null && echo " ✅ 重启成功" || echo " ❌ 重启失败"
fi
done
echo ""
echo "📊 清理后 PM2 状态:"
pm2 status 2>/dev/null
pm2 save 2>/dev/null || echo "⚠️ pm2 save 失败"
fi
')
echo "$CLEANUP_REPORT"
echo "$CLEANUP_REPORT" > /tmp/cleanup-report.txt
{
echo "cleanup_report<<CLEANUP_EOF"
echo "$CLEANUP_REPORT"
echo "CLEANUP_EOF"
} >> "$GITHUB_OUTPUT"
# ━━━ 阶段二:全系统健康检查 ━━━
- name: "🏥 阶段二 · 全系统健康检查"
id: health_check
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 阶段二 · 全系统健康检查"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
HEALTH_REPORT=$(ssh -i ~/.ssh/deploy_key \
-o StrictHostKeyChecking=no \
"${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}" '
echo "══════════════════════════════════════"
echo " Nginx 状态"
echo "══════════════════════════════════════"
nginx -t 2>&1 || echo "❌ Nginx 配置检测失败"
systemctl status nginx --no-pager -l 2>/dev/null | head -15 || echo "⚠️ systemctl 不可用"
echo ""
echo "══════════════════════════════════════"
echo " SSL 证书有效期"
echo "══════════════════════════════════════"
if [ -f "/etc/letsencrypt/live/guanghulab.com/fullchain.pem" ]; then
openssl x509 -in /etc/letsencrypt/live/guanghulab.com/fullchain.pem -noout -dates 2>/dev/null || echo "⚠️ 无法读取 SSL 证书"
else
echo "⚠️ SSL 证书文件不存在于默认路径"
# 尝试查找其他证书路径
find /etc/letsencrypt/live/ -name "fullchain.pem" 2>/dev/null | head -3 || echo " 未找到 Let'\''s Encrypt 证书"
find /etc/nginx/ -name "*.pem" -o -name "*.crt" 2>/dev/null | head -3 || echo " 未找到 Nginx SSL 文件"
fi
echo ""
echo "══════════════════════════════════════"
echo " 磁盘空间"
echo "══════════════════════════════════════"
df -h 2>/dev/null || echo "❌ df 命令不可用"
echo ""
echo "══════════════════════════════════════"
echo " 内存使用"
echo "══════════════════════════════════════"
free -h 2>/dev/null || echo "❌ free 命令不可用"
echo ""
echo "══════════════════════════════════════"
echo " PM2 最终状态"
echo "══════════════════════════════════════"
pm2 status 2>/dev/null || echo "❌ pm2 不可用"
echo ""
echo "══════════════════════════════════════"
echo " Node.js 版本"
echo "══════════════════════════════════════"
node -v 2>/dev/null || echo "❌ Node.js 未安装"
npm -v 2>/dev/null || echo "❌ npm 未安装"
echo ""
echo "══════════════════════════════════════"
echo " 网站可访问性检查"
echo "══════════════════════════════════════"
curl -sI https://guanghulab.com 2>/dev/null | head -5 || echo "⚠️ HTTPS 访问失败"
echo "---"
curl -s http://127.0.0.1:3000/health 2>/dev/null | head -c 200 || echo "⚠️ 端口 3000 无响应"
echo ""
echo "---"
curl -s http://127.0.0.1:3001/api/v1/ 2>/dev/null | head -c 200 || echo "⚠️ 端口 3001 无响应"
echo ""
echo "---"
curl -s http://127.0.0.1:3002/api/ps/ 2>/dev/null | head -c 200 || echo "⚠️ 端口 3002 无响应"
echo ""
echo ""
echo "══════════════════════════════════════"
echo " 系统运行时间"
echo "══════════════════════════════════════"
uptime 2>/dev/null || echo "❌ uptime 不可用"
')
echo "$HEALTH_REPORT"
echo "$HEALTH_REPORT" > /tmp/health-report.txt
{
echo "health_report<<HEALTH_EOF"
echo "$HEALTH_REPORT"
echo "HEALTH_EOF"
} >> "$GITHUB_OUTPUT"
# ━━━ 阶段三:生成 Notion 诊断报告 ━━━
- name: "📋 阶段三 · 写入 Notion 工单"
env:
NOTION_TOKEN: ${{ secrets.ZY_NOTION_TOKEN }}
NOTION_TICKET_DB_ID: ${{ secrets.ZY_NOTION_TICKET_DB }}
PM2_REPORT: ${{ steps.pm2_diagnose.outputs.pm2_report }}
CLEANUP_REPORT: ${{ steps.pm2_cleanup.outputs.cleanup_report }}
HEALTH_REPORT: ${{ steps.health_check.outputs.health_report }}
run: |
if [ -z "$NOTION_TOKEN" ]; then
echo "⚠️ Notion credentials 未配置,跳过工单创建"
echo "诊断报告仅输出到 Actions 日志"
exit 0
fi
node scripts/server-diagnose-report.js
- name: "✅ 诊断完成"
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ✅ PM2 诊断完毕 · $(date '+%Y-%m-%d %H:%M:%S')"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"