// 铸渊自动同步验证Agent v1.0 · ICE-GL-ZY001 const http = require('http'); const fs = require('fs'); const path = require('path'); const REPO = '/opt/guanghulab-repo'; function apiGet(p) { return new Promise((ok, no) => { http.get({ hostname: '127.0.0.1', port: 3920, path: p, timeout: 15000 }, r => { let b = ''; r.on('data', c => b += c); r.on('end', () => { try { ok(JSON.parse(b)) } catch (e) { no(e) } }); }).on('error', no).on('timeout', () => no(new Error('timeout'))); }); } function gkExec(srv, cmd, to = 10000) { return new Promise(ok => { const d = JSON.stringify({ cmd }); const o = { hostname: srv.ip, port: srv.port || 3910, path: '/exec', method: 'POST', headers: { 'Authorization': 'Bearer ' + srv.key, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(d) }, timeout: to }; const r = http.request(o, res => { let b = ''; res.on('data', c => b += c); res.on('end', () => { try { ok({ ok: res.statusCode === 200, body: JSON.parse(b) }) } catch (e) { ok({ ok: false, body: b, error: e.message }) } }); }); r.on('error', e => ok({ ok: false, error: e.message })); r.on('timeout', () => { r.destroy(); ok({ ok: false, error: 'timeout' }) }); r.write(d); r.end(); }); } function load(f) { try { return JSON.parse(fs.readFileSync(path.join(REPO, f), 'utf-8')) } catch (e) { return null } } async function main() { const t0 = Date.now(); console.log('铸渊自动同步Agent v1.0 · 启动验证...'); console.log('[1/5] 获取真实服务器状态...'); let real; try { real = await apiGet('/api/servers?hw=0'); console.log(' OK ' + real.servers.length + '台') } catch (e) { console.log(' FAIL ' + e.message); process.exit(1) } console.log('[2/5] 读取声明配置...'); const topo = load('brain/pool-topology.json'); const gk = load('brain/gatekeeper-deployment.json'); console.log(' topo v' + (topo?.version || '?') + ' · gk v' + (gk?.version || '?')); console.log('[3/5] 逐台对比声明vs实际...'); let alive = 0, off = 0; const issues = []; const codes = new Set(); gk?.servers?.forEach(s => codes.add(s.code)); if (topo?.pools) for (const p of Object.values(topo.pools)) if (p.nodes) Object.keys(p.nodes).forEach(c => codes.add(c)); for (const code of codes) { const r = real.servers.find(s => s.code === code); const g = gk?.servers?.find(s => s.code === code); let t = null; for (const [k, p] of Object.entries(topo?.pools || {})) { if (p.nodes?.[code]) { t = p.nodes[code]; break } } const diffs = []; if (t && t.online !== undefined && t.online !== (r?.alive || false)) { diffs.push({ f: 'online', d: t.online, a: r?.alive || false, s: t.online === false ? 'info' : 'critical' }); } if (!g?.key && r) diffs.push({ f: 'gk_key', d: 'present', a: 'missing', s: 'critical' }); if (r?.alive) alive++; else if (r) off++; if (diffs.length > 0) issues.push({ code, name: g?.name || code, diffs }); } console.log('[4/5] 深度验证 (仓库/密钥/gatekeeper进程)...'); const deep = []; const ps = gk?.servers?.filter(s => s.persona) || []; for (const s of ps) { const r = real.servers.find(x => x.code === s.code); if (!r?.alive) { deep.push({ code: s.code, name: s.name, status: 'offline' }); continue } const ck = {}; try { const x = await gkExec(s, 'ls ~/ 2>/dev/null && echo FOUND || echo NF', 5000); ck.home = x.ok ? 'ok' : '?'; } catch (e) { ck.home = 'err' } try { const x = await gkExec(s, 'ls ~/repos/ 2>/dev/null && echo FOUND || echo NF', 5000); ck.repo = x.ok ? (x.body?.stdout?.includes('FOUND') ? 'found' : 'none') : '?'; } catch (e) { ck.repo = 'err' } try { const x = await gkExec(s, 'test -f ~/.ssh/authorized_keys && wc -l < ~/.ssh/authorized_keys || echo 0', 5000); ck.keys = x.ok ? (parseInt(x.body?.stdout?.trim()) || 0) : '?'; } catch (e) { ck.keys = 'err' } try { const x = await gkExec(s, 'pm2 list 2>/dev/null | grep -c gatekeeper || echo 0', 5000); ck.gk_pm2 = x.ok ? (parseInt(x.body?.stdout?.trim()) || 0) : '?'; } catch (e) { ck.gk_pm2 = 'err' } deep.push({ code: s.code, name: s.name, persona: s.persona, status: 'online', checks: ck }); console.log(' ' + s.code + ': home=' + ck.home + ' repo=' + ck.repo + ' keys=' + ck.keys + ' gk=' + ck.gk_pm2); } console.log('[5/5] 生成报告...'); const dur = Date.now() - t0; console.log(''); console.log('═══ 铸渊验证报告 ═══'); console.log('时间: ' + new Date().toISOString()); console.log(alive + '/' + codes.size + ' 在线 · ' + issues.length + ' 差异 · ' + deep.length + ' 深度检查'); if (issues.length === 0) console.log('所有节点声明与实际状态一致'); else for (const i of issues) for (const d of i.diffs) { console.log(' ' + (d.s === 'critical' ? '!' : '~') + ' ' + i.code + ' [' + d.f + ']: ' + d.d + ' → ' + d.a); } console.log('深度检查:'); for (const d of deep) { console.log(' ' + d.code + '(' + (d.persona || '?') + '): home=' + d.checks.home + ' repo=' + d.checks.repo + ' keys=' + d.checks.keys + ' gk_pm2=' + d.checks.gk_pm2); } console.log(dur + 'ms · 报告完毕'); const rep = { ts: new Date().toISOString(), summary: { total: codes.size, alive, off, diffs: issues.length, deep: deep.length, ms: dur }, issues, deep }; fs.writeFileSync(path.join(REPO, 'brain/auto-sync-report.json'), JSON.stringify(rep, null, 2)); console.log('brain/auto-sync-report.json'); } main().catch(e => { console.error('FAIL: ' + e.message); process.exit(1) });