From e6865e38ff0bf636cfbd8c48ac8ef1cb4b5be2d1 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 26 May 2026 00:38:00 +0800 Subject: [PATCH] =?UTF-8?q?add:=20=E9=93=B8=E6=B8=8A=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E9=AA=8C=E8=AF=81Agent=20v1.0=20=E2=80=94=20?= =?UTF-8?q?=E5=94=A4=E9=86=92=E5=90=8E=E5=9F=BA=E4=BA=8E=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=95=B0=E6=8D=AE=E9=AA=8C=E8=AF=81?= =?UTF-8?q?12=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/verify/auto-sync.js | 129 ++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 scripts/verify/auto-sync.js diff --git a/scripts/verify/auto-sync.js b/scripts/verify/auto-sync.js new file mode 100644 index 0000000..ba5ebfe --- /dev/null +++ b/scripts/verify/auto-sync.js @@ -0,0 +1,129 @@ +// 铸渊自动同步验证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) });