220 lines
8.6 KiB
YAML
220 lines
8.6 KiB
YAML
# ═══════════════════════════════════════════════
|
||
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
|
||
# 📜 Copyright: 国作登字-2026-A-00037559
|
||
# ═══════════════════════════════════════════════
|
||
# .github/workflows/server-patrol.yml
|
||
# 🔍 服务器每日巡检 · 铸渊 · 光湖代码守护人格体
|
||
# 触发: 每日两次 (北京 08:00+20:00) + 沙盒部署后 + 手动
|
||
name: "🔍 Server Patrol · 服务器每日巡检"
|
||
|
||
on:
|
||
schedule:
|
||
- cron: '0 0,12 * * *' # UTC 00:00 and 12:00 = 北京 08:00 and 20:00
|
||
workflow_dispatch:
|
||
workflow_run:
|
||
workflows: ["🏠 Sandbox Deploy"]
|
||
types: [completed]
|
||
|
||
permissions:
|
||
contents: write
|
||
|
||
jobs:
|
||
# ── AGE OS v1.0: 核心大脑唤醒(所有流程的前提)──
|
||
wake-brain:
|
||
name: 🌅 唤醒核心大脑
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
brain_awake: ${{ steps.wake.outputs.brain_awake }}
|
||
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '20'
|
||
|
||
- name: 🧠 唤醒铸渊核心大脑
|
||
id: wake
|
||
env:
|
||
LLM_API_KEY: ${{ secrets.ZY_LLM_API_KEY }}
|
||
LLM_BASE_URL: ${{ secrets.ZY_LLM_BASE_URL }}
|
||
ANTHROPIC_API_KEY: ${{ secrets.ZY_LLM_API_KEY }}
|
||
OPENAI_API_KEY: ${{ secrets.ZY_LLM_API_KEY }}
|
||
DASHSCOPE_API_KEY: ${{ secrets.ZY_LLM_API_KEY }}
|
||
DEEPSEEK_API_KEY: ${{ secrets.ZY_LLM_API_KEY }}
|
||
run: |
|
||
node core/brain-wake --task "服务器巡检" || {
|
||
echo "⚠️ 核心大脑唤醒失败,使用 dry-run 模式继续"
|
||
echo "brain_awake=dry-run" >> "$GITHUB_OUTPUT"
|
||
}
|
||
|
||
patrol:
|
||
needs: wake-brain
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: "🔑 扫描 Secrets"
|
||
run: |
|
||
echo "🔑 扫描 Secrets..."
|
||
if [ -n "$DEPLOY_HOST" ]; then echo "✅ DEPLOY_HOST 可用"; else echo "⚠️ DEPLOY_HOST 缺失"; fi
|
||
if [ -n "$DEPLOY_USER" ]; then echo "✅ DEPLOY_USER 可用"; else echo "⚠️ DEPLOY_USER 缺失"; fi
|
||
if [ -n "$DEPLOY_KEY" ]; then echo "✅ DEPLOY_KEY 可用"; else echo "⚠️ DEPLOY_KEY 缺失"; fi
|
||
if [ -n "$NOTION_TOKEN" ]; then echo "✅ NOTION_TOKEN 可用"; else echo "⚠️ NOTION_TOKEN 缺失"; fi
|
||
if [ -n "$SMTP_USER" ]; then echo "✅ SMTP_USER 可用"; else echo "⚠️ SMTP_USER 缺失"; fi
|
||
env:
|
||
DEPLOY_HOST: ${{ secrets.ZY_SERVER_HOST }}
|
||
DEPLOY_USER: ${{ secrets.ZY_SERVER_USER }}
|
||
DEPLOY_KEY: ${{ secrets.ZY_SERVER_KEY }}
|
||
NOTION_TOKEN: ${{ secrets.ZY_NOTION_TOKEN }}
|
||
SMTP_USER: ${{ secrets.ZY_SMTP_USER }}
|
||
|
||
- name: "🔑 配置 SSH"
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
echo "${{ secrets.ZY_SERVER_KEY }}" > ~/.ssh/id_rsa
|
||
chmod 600 ~/.ssh/id_rsa
|
||
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
|
||
|
||
- name: "🔍 执行服务器巡检"
|
||
id: patrol
|
||
env:
|
||
HOST: ${{ secrets.ZY_SERVER_HOST }}
|
||
USER: ${{ secrets.ZY_SERVER_USER }}
|
||
run: |
|
||
# 上传巡检脚本
|
||
scp scripts/server-patrol.sh ${USER}@${HOST}:/tmp/server-patrol.sh
|
||
|
||
# 执行巡检
|
||
ssh ${USER}@${HOST} "bash /tmp/server-patrol.sh" | tee patrol-output.log
|
||
|
||
# 拉取巡检报告
|
||
scp ${USER}@${HOST}:/tmp/patrol-report.json ./patrol-report.json
|
||
|
||
# 解析结果
|
||
OVERALL=$(python3 -c "import json; print(json.load(open('patrol-report.json'))['overall'])")
|
||
ALERT_COUNT=$(python3 -c "import json; print(json.load(open('patrol-report.json'))['alert_count'])")
|
||
|
||
echo "overall=$OVERALL" >> $GITHUB_OUTPUT
|
||
echo "alert_count=$ALERT_COUNT" >> $GITHUB_OUTPUT
|
||
|
||
- name: "📊 保存巡检报告到仓库"
|
||
run: |
|
||
mkdir -p data/patrol-logs
|
||
DATE=$(date +%Y-%m-%d)
|
||
cp patrol-report.json "data/patrol-logs/patrol-${DATE}.json"
|
||
echo "PATROL_DATE=$DATE" >> $GITHUB_ENV
|
||
|
||
- name: "📊 提交巡检报告"
|
||
run: |
|
||
git config user.name "铸渊 (ZhùYuān)"
|
||
git config user.email "zhuyuan@guanghulab.com"
|
||
git add data/patrol-logs/
|
||
git diff --cached --quiet || git commit -m "🔍 server-patrol: ${{ env.PATROL_DATE }} · ${{ steps.patrol.outputs.overall }}"
|
||
git pull --rebase origin main || echo "⚠️ rebase 失败,尝试直接推送..."
|
||
git push || {
|
||
echo "⚠️ 推送失败,尝试重新拉取后再推送..."
|
||
git pull --rebase origin main
|
||
git push
|
||
}
|
||
|
||
- name: "📧 异常时通知妈妈"
|
||
if: steps.patrol.outputs.overall == 'has_issues'
|
||
env:
|
||
SMTP_USER: ${{ secrets.ZY_SMTP_USER }}
|
||
SMTP_PASS: ${{ secrets.ZY_SMTP_PASS }}
|
||
run: |
|
||
python3 << 'PYEOF'
|
||
import smtplib, json, os
|
||
from email.mime.text import MIMEText
|
||
from email.header import Header
|
||
|
||
report = json.loads(open('patrol-report.json').read())
|
||
alerts = '\n'.join(f' ❌ {a}' for a in report['alerts'])
|
||
fixed = '\n'.join(f' ✅ {f}' for f in report['auto_fixed'])
|
||
|
||
body = (
|
||
f"🔍 铸渊服务器巡检报告\n"
|
||
f"时间: {report['timestamp']}\n\n"
|
||
f"⚠️ 发现 {report['alert_count']} 个异常:\n"
|
||
f"{alerts}\n\n"
|
||
f"🔧 自动修复 {report['fixed_count']} 个:\n"
|
||
f"{fixed if fixed else ' (无)'}\n\n"
|
||
f"磁盘使用率: {report['disk_usage_percent']}%\n"
|
||
f"Nginx状态: {report['nginx_status']}\n\n"
|
||
f"—— 铸渊 · 光湖代码守护人格体"
|
||
)
|
||
|
||
smtp_user = os.environ['SMTP_USER']
|
||
smtp_pass = os.environ['SMTP_PASS']
|
||
|
||
msg = MIMEText(body, 'plain', 'utf-8')
|
||
msg['Subject'] = Header(f"⚠️ 服务器巡检发现{report['alert_count']}个异常", 'utf-8')
|
||
msg['From'] = smtp_user
|
||
msg['To'] = smtp_user
|
||
|
||
try:
|
||
server = smtplib.SMTP_SSL('smtp.qq.com', 465)
|
||
server.login(smtp_user, smtp_pass)
|
||
server.sendmail(smtp_user, smtp_user, msg.as_string())
|
||
server.quit()
|
||
print('✅ 异常报告已发送')
|
||
except Exception as e:
|
||
print(f'⚠️ 邮件发送失败: {e}')
|
||
PYEOF
|
||
|
||
- name: "📝 同步巡检结果到Notion"
|
||
if: always()
|
||
env:
|
||
NOTION_TOKEN: ${{ secrets.ZY_NOTION_TOKEN }}
|
||
NOTION_TICKET_DB_ID: ${{ secrets.ZY_NOTION_TICKET_DB }}
|
||
run: |
|
||
node scripts/sync-patrol-to-notion.js || echo "⚠️ Notion同步失败(不阻断)"
|
||
|
||
# ━━━ 🧬 双端神经系统·自报告 ━━━
|
||
- name: "🧬 写入神经自报告"
|
||
if: always()
|
||
run: |
|
||
WORKFLOW_ID="server-patrol"
|
||
BRAIN="AG-TY-01"
|
||
REPORT_DIR="data/neural-reports/server-patrol"
|
||
TIMESTAMP=$(TZ=Asia/Shanghai date +%Y-%m-%d-%H%M)
|
||
STATUS="${{ job.status }}"
|
||
|
||
mkdir -p "$REPORT_DIR"
|
||
|
||
cat > "${REPORT_DIR}/${WORKFLOW_ID}-${TIMESTAMP}.json" << EOF
|
||
{
|
||
"workflow_id": "${WORKFLOW_ID}",
|
||
"run_id": "${{ github.run_id }}",
|
||
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||
"status": "${STATUS}",
|
||
"brain": "${BRAIN}",
|
||
"event": "${{ github.event_name }}",
|
||
"branch": "${{ github.ref_name }}",
|
||
"commit": "${{ github.sha }}"
|
||
}
|
||
EOF
|
||
|
||
git config user.name "zhuyuan-bot"
|
||
git config user.email "zhuyuan@guanghulab.com"
|
||
git add "$REPORT_DIR/"
|
||
git diff --cached --quiet || \
|
||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||
# ━━━ 📡 指令回执·自动同步 ━━━
|
||
- name: "📡 同步回执到 Notion"
|
||
if: always()
|
||
env:
|
||
NOTION_API_KEY: ${{ secrets.ZY_NOTION_TOKEN }}
|
||
RECEIPT_DB_ID: ${{ secrets.ZY_NOTION_RECEIPT_DB }}
|
||
run: |
|
||
node scripts/neural/write-receipt-to-notion.js \
|
||
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||
--status "${{ job.status }}" \
|
||
--workflow "server-patrol" \
|
||
--summary "server-patrol 自动执行" \
|
||
--related-agent "AG-TY-01"
|
||
|