130 lines
5.6 KiB
JavaScript
130 lines
5.6 KiB
JavaScript
|
|
// 铸渊自动同步验证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) });
|