zhizhi/skyeye/scripts/self-healer.js

241 lines
7.1 KiB
JavaScript

#!/usr/bin/env node
/**
* skyeye/scripts/self-healer.js
* 天眼自愈引擎 — 自动修复损坏配置、清理过期数据
* Phase 4 of weekly scan: Self-Heal
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const SKYEYE_DIR = path.resolve(__dirname, '..');
const GUARDS_DIR = path.join(SKYEYE_DIR, 'guards');
const TEMPLATE_PATH = path.join(GUARDS_DIR, 'guard-template.json');
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
function getTimestamp() {
return new Date().toISOString();
}
function getDateStr() {
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
}
function loadJSON(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
return null;
}
}
function saveJSON(filePath, data) {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
}
function repairGuardConfigs() {
const results = { repaired: [], failed: [] };
const template = loadJSON(TEMPLATE_PATH);
if (!template) {
results.failed.push({ file: 'guard-template.json', reason: 'Template missing or invalid' });
return results;
}
const requiredFields = ['guard_id', 'status', 'mode', 'buffer_policy', 'quota_policy', 'trigger_policy', 'health_check'];
const files = fs.readdirSync(GUARDS_DIR)
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
for (const file of files) {
const filePath = path.join(GUARDS_DIR, file);
let guard = loadJSON(filePath);
if (!guard) {
// File is corrupted - rebuild from template
console.log(` [REPAIR] ${file}: Corrupted JSON, rebuilding from template`);
guard = { ...template };
guard.guard_id = `GUARD-${file.replace('-guard.json', '').toUpperCase()}`;
guard.status = 'repaired';
guard.last_updated_by = 'skyeye-self-healer';
guard.last_updated_at = getTimestamp();
saveJSON(filePath, guard);
results.repaired.push({ file, action: 'rebuilt_from_template' });
continue;
}
// Check for missing fields and fill from template
let modified = false;
for (const field of requiredFields) {
if (!(field in guard)) {
guard[field] = template[field];
modified = true;
console.log(` [REPAIR] ${file}: Added missing field '${field}'`);
}
}
if (modified) {
guard.last_updated_by = 'skyeye-self-healer';
guard.last_updated_at = getTimestamp();
saveJSON(filePath, guard);
results.repaired.push({ file, action: 'filled_missing_fields' });
}
}
return results;
}
function cleanExpiredBuffers() {
const results = { files_cleaned: 0, directories: [] };
const processedDir = path.join(ROOT, 'buffer/processed');
if (!fs.existsSync(processedDir)) return results;
const maxAgeDays = 7;
const cutoff = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
try {
const files = fs.readdirSync(processedDir);
for (const file of files) {
const filePath = path.join(processedDir, file);
const stat = fs.statSync(filePath);
if (stat.mtimeMs < cutoff) {
if (stat.isFile()) {
fs.unlinkSync(filePath);
results.files_cleaned++;
}
}
}
results.directories.push({ path: 'buffer/processed', cleaned: results.files_cleaned });
} catch (e) {
// Ignore errors during cleanup
}
return results;
}
function cleanExpiredLogs() {
const results = { files_cleaned: 0 };
const maxAgeDays = 30;
const cutoff = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
const logDirs = [
path.join(LOGS_DIR, 'daily'),
path.join(LOGS_DIR, 'weekly')
];
for (const dir of logDirs) {
if (!fs.existsSync(dir)) continue;
try {
const files = fs.readdirSync(dir);
for (const file of files) {
if (file === '.gitkeep') continue;
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.mtimeMs < cutoff && stat.isFile()) {
fs.unlinkSync(filePath);
results.files_cleaned++;
}
}
} catch (e) {
// Ignore errors during cleanup
}
}
return results;
}
function restartSuspendedGuards() {
const results = { restarted: [] };
const files = fs.readdirSync(GUARDS_DIR)
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
for (const file of files) {
const filePath = path.join(GUARDS_DIR, file);
const guard = loadJSON(filePath);
if (!guard) continue;
// Restart suspended guards with healthy checks
if (guard.mode === 'suspended' && guard.health_check) {
// Reset health check status for retry
guard.health_check.consecutive_failures = 0;
guard.health_check.last_status = 'pending_restart';
guard.mode = 'buffer';
guard.status = 'active';
guard.last_updated_by = 'skyeye-self-healer';
guard.last_updated_at = getTimestamp();
saveJSON(filePath, guard);
results.restarted.push({ file, guard_id: guard.guard_id });
console.log(` [RESTART] ${file}: Guard restarted`);
}
}
return results;
}
function ensureDirectories() {
const results = { created: [] };
const requiredDirs = [
'skyeye/scan-report',
'skyeye/logs/daily',
'skyeye/logs/weekly',
'buffer/inbox',
'buffer/staging',
'buffer/processed'
];
for (const dir of requiredDirs) {
const fullPath = path.join(ROOT, dir);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
results.created.push(dir);
console.log(` [CREATE] Directory: ${dir}`);
}
}
return results;
}
function run() {
console.log(`[SkyEye Self-Healer] Starting self-heal process`);
console.log(`[SkyEye Self-Healer] Timestamp: ${getTimestamp()}`);
const healResult = {
heal_id: `HEAL-${getDateStr()}`,
timestamp: getTimestamp(),
guard_repairs: repairGuardConfigs(),
buffer_cleanup: cleanExpiredBuffers(),
log_cleanup: cleanExpiredLogs(),
guard_restarts: restartSuspendedGuards(),
directory_fixes: ensureDirectories(),
summary: {}
};
healResult.summary = {
configs_repaired: healResult.guard_repairs.repaired.length,
files_cleaned: healResult.buffer_cleanup.files_cleaned + healResult.log_cleanup.files_cleaned,
guards_restarted: healResult.guard_restarts.restarted.length,
directories_created: healResult.directory_fixes.created.length
};
// Write log
const logDir = path.join(LOGS_DIR, 'weekly');
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
const logPath = path.join(logDir, `self-heal-${getDateStr()}.json`);
saveJSON(logPath, healResult);
console.log(`[SkyEye Self-Healer] Repairs: ${healResult.summary.configs_repaired}`);
console.log(`[SkyEye Self-Healer] Files cleaned: ${healResult.summary.files_cleaned}`);
console.log(`[SkyEye Self-Healer] Guards restarted: ${healResult.summary.guards_restarted}`);
console.log(`[SkyEye Self-Healer] Dirs created: ${healResult.summary.directories_created}`);
console.log(`[SkyEye Self-Healer] Log saved: ${logPath}`);
console.log('---HEAL_RESULT_JSON---');
console.log(JSON.stringify(healResult, null, 2));
return healResult;
}
run();