diff --git a/scripts/deploy-log-collector.js b/scripts/deploy-log-collector.js index ece64c0d..8f2b0d5f 100644 --- a/scripts/deploy-log-collector.js +++ b/scripts/deploy-log-collector.js @@ -50,6 +50,16 @@ function ensureDir(dir) { } } +// 智能截断日志:保留头部和尾部,确保错误信息不丢失 +function truncateLog(content, maxLen) { + if (!content || content.length <= maxLen) return content; + const headSize = Math.floor(maxLen * 0.3); // 30%给头部 + const tailSize = maxLen - headSize - 100; // 70%给尾部(错误通常在末尾) + const head = content.slice(0, headSize); + const tail = content.slice(-tailSize); + return `${head}\n\n... [截断 ${content.length - headSize - tailSize} 字符] ...\n\n${tail}`; +} + // ── GitHub API 请求 ──────────────────────────── function githubApi(endpoint) { const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN; @@ -225,7 +235,7 @@ async function collectLogs() { conclusion: s.conclusion, number: s.number })), - log_content: logContent.slice(0, 10000) // 限制日志大小 + log_content: truncateLog(logContent, 10000) // 智能截断:保留头部和尾部 }); console.log(` ✅ 日志采集成功 (${logContent.length} 字符)`); } catch (err) { @@ -330,6 +340,7 @@ async function collectLogs() { // ── 简单错误分析 (模式匹配) ──────────────────── function simpleAnalysis(logContent) { + // 基础错误模式 + 动态加载经验库中的错误模式 const patterns = [ { regex: /EADDRINUSE/i, name: '端口被占用', fix: '重启PM2进程释放端口', severity: 'medium' }, { regex: /ECONNREFUSED/i, name: '服务连接被拒绝', fix: '检查PM2进程是否运行', severity: 'high' }, @@ -349,6 +360,27 @@ function simpleAnalysis(logContent) { { regex: /exit code [1-9]/i, name: '命令执行失败', fix: '查看上方具体错误信息', severity: 'medium' } ]; + // 尝试从经验数据库动态加载错误模式 + try { + const errPatternsDb = JSON.parse(fs.readFileSync(ERROR_PATTERNS_DB, 'utf8')); + if (errPatternsDb.patterns) { + for (const p of errPatternsDb.patterns) { + // 提取经验库中的关键词作为匹配模式 + const keywords = p.pattern.split(/[·\s+]/g).filter(k => k.length > 3); + if (keywords.length > 0) { + patterns.push({ + regex: new RegExp(keywords.join('|'), 'i'), + name: `[经验库] ${p.pattern}`, + fix: (p.prevention || []).join(' → ') || '参考经验库', + severity: p.severity || 'medium' + }); + } + } + } + } catch { + // 经验库加载失败不影响基础分析 + } + const matched = []; for (const p of patterns) { if (p.regex.test(logContent)) { diff --git a/scripts/deputy-auto-repair.js b/scripts/deputy-auto-repair.js index 56ae8966..911ee411 100644 --- a/scripts/deputy-auto-repair.js +++ b/scripts/deputy-auto-repair.js @@ -99,7 +99,7 @@ const REPAIR_STRATEGIES = [ 'sudo find /tmp -type f -mtime +1 -delete 2>/dev/null || true', 'df -h /' ], - verify: 'AVAIL=$(df / | tail -1 | awk \'{print $5}\' | tr -d \'%\'); [ "$AVAIL" -lt 90 ] || exit 1', + verify: 'USED=$(df / | tail -1 | awk \'{print $5}\' | tr -d \'%\'); [ "$USED" -lt 90 ] || exit 1', severity: 'high', success_rate: '中 · 取决于可清理的文件量' }, @@ -132,7 +132,7 @@ function sshExec(command) { } try { - const sshCmd = `ssh -i ~/.ssh/zy_key -o ConnectTimeout=10 -o StrictHostKeyChecking=no ${user}@${host} '${command.replace(/'/g, "'\\''")}'`; + const sshCmd = `ssh -i ~/.ssh/zy_key -o ConnectTimeout=10 ${user}@${host} '${command.replace(/'/g, "'\\''")}'`; const output = execSync(sshCmd, { timeout: 60000, // 60秒超时 encoding: 'utf8', @@ -463,7 +463,7 @@ async function repair() { // ── 危险命令检测 ──────────────────────────── function isDangerousCommand(cmd) { const dangerous = [ - /rm\s+-rf\s+\//, // rm -rf / + /rm\s+-r[fF]*\s+(\/|~|\$HOME)/, // rm -rf / or ~ or $HOME /mkfs/, // 格式化 /dd\s+if=/, // 磁盘写入 /shutdown/, // 关机 @@ -474,6 +474,7 @@ function isDangerousCommand(cmd) { /chmod\s+-R\s+777\s+\//, // 全局777 /userdel/, // 删除用户 /passwd/, // 修改密码 + /rm\s+-r[fF]*\s+\/\w/, // rm -rf /home etc ]; return dangerous.some(regex => regex.test(cmd)); }