// 铸渊 push-heartbeat 批量部署脚本 v1.0 // 在 BS-GZ-006 上运行,通过Gatekeeper exec部署到所有远程服务器 // 用法: node deploy-push-heartbeat.js const http = require('http'); const fs = require('fs'); const SERVERS = [ { code: "BS-SG-001", name: "新加坡·铸渊大脑", ip: "43.156.237.110", key: "zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9", port: 3911 }, { code: "BS-SG-002", name: "新加坡·面孔", ip: "43.134.16.246", key: "zy_gtw_d1f6d2b8cb4ea44292bd036e4dd03a70745ea502d4a3ae40", port: 3910 }, { code: "BS-SG-003", name: "新加坡·中继", ip: "43.153.193.169", key: "zy_gtw_d7c033d8ae992ffc9dd3e0dd5fe332dace0b644c0ae7c847", port: 3910 }, { code: "ZY-SG-006", name: "新加坡·语料", ip: "43.153.203.105", key: "zy_gtw_fff861a2fe40cbdc366ee21f218023be8b1c182f66c852d3", port: 3910 }, { code: "BS-SH-005", name: "上海·国内节点", ip: "124.223.10.33", key: "zy_gtw_b1045de5ddfd7832e3c53349d9c896f054b85a4a9ece22f9", port: 3910 }, ]; const MASTER_HOST = "43.139.217.141"; const MASTER_PORT = 80; const MASTER_PATH = "/console/api/heartbeat"; function gkExec(srv, cmd, timeoutMs) { timeoutMs = timeoutMs || 10000; const d = JSON.stringify({ cmd }); const opts = { hostname: srv.ip, port: srv.port, path: '/exec', method: 'POST', headers: { 'Authorization': 'Bearer ' + srv.key, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(d) }, timeout: timeoutMs }; return new Promise((resolve) => { const req = http.request(opts, (res) => { let b = ''; res.on('data', c => b += c); res.on('end', () => { try { resolve({ ok: res.statusCode === 200, body: JSON.parse(b) }); } catch(e) { resolve({ ok: false, error: e.message }); } }); }); req.on('error', (e) => resolve({ ok: false, error: e.message })); req.on('timeout', () => { req.destroy(); resolve({ ok: false, error: 'timeout' }); }); req.write(d); req.end(); }); } // 推送脚本模板 — 通过nginx /console/ → console-server:3920 function pushScript(code, name, key) { return ` const http=require("http"),os=require("os"); const HOST="${MASTER_HOST}",PORT=${MASTER_PORT},PATH="${MASTER_PATH}",CODE="${code}",NAME="${name}",KEY="${key}"; function hw(){const c=os.cpus().length,l=os.loadavg(),t=os.totalmem(),f=os.freemem();return{cpu_cores:c,cpu_load:parseFloat(l[0].toFixed(2)),mem_total_mb:Math.floor(t/1048576),mem_used_mb:Math.floor((t-f)/1048576),mem_used_pct:parseFloat(((t-f)/t*100).toFixed(1)),uptime_h:Math.floor(os.uptime()/3600),hostname:os.hostname()}} function post(d){return new Promise(r=>{const p=JSON.stringify(d),o={hostname:HOST,port:PORT,path:PATH,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(p),"X-Server-Code":CODE,"X-Server-Key":KEY},timeout:8000};const q=http.request(o,res=>{let b="";res.on("data",c=>b+=c);res.on("end",()=>r(res.statusCode===200))});q.on("error",()=>r(false));q.on("timeout",()=>{q.destroy();r(false)});q.write(p);q.end()})} (async()=>{const d={server_code:CODE,server_name:NAME,timestamp:new Date().toISOString(),hardware:hw()};const ok=await post(d);console.log(new Date().toISOString()+" "+(ok?"OK":"FAIL"))})(); `.trim(); } async function deployOne(srv) { console.log(`\n[${srv.code}] ${srv.name} ...`); // 1. 写脚本 const scriptCmd = `mkdir -p /opt/push-heartbeat && echo '${pushScript(srv.code, srv.name, srv.key).replace(/'/g, "'\\''")}' > /opt/push-heartbeat/push.js && echo "SCRIPT_OK"`; const r1 = await gkExec(srv, scriptCmd); if (!r1.ok) { console.log(` ❌ 写入失败: ${r1.error}`); return false; } console.log(` ✅ 脚本写入`); // 2. 配置cron(不覆盖已有cron,去重) const cronLine = `*/5 * * * * node /opt/push-heartbeat/push.js >> /opt/push-heartbeat/push.log 2>&1`; const cronCmd = `(crontab -l 2>/dev/null | grep -v "/opt/push-heartbeat/push.js"; echo "${cronLine}") | crontab - && echo "CRON_OK"`; const r2 = await gkExec(srv, cronCmd); if (!r2.ok) { console.log(` ❌ Cron失败: ${r2.error}`); return false; } console.log(` ✅ Cron配置`); // 3. 立即触发一次 const r3 = await gkExec(srv, `node /opt/push-heartbeat/push.js 2>&1`, 8000); if (r3.ok) console.log(` ✅ 首次上报: ${r3.body?.stdout?.trim() || 'sent'}`); else console.log(` ⚠️ 首次上报失败: ${r3.error || r3.body?.stdout?.trim()}`); return true; } async function main() { console.log('铸渊 · push-heartbeat 批量部署'); console.log(`主控台: ${MASTER_HOST}:${MASTER_PORT}${MASTER_PATH}`); console.log(`目标: ${SERVERS.length}台远程服务器\n`); let ok = 0, fail = 0; for (const srv of SERVERS) { const r = await deployOne(srv); if (r) ok++; else fail++; } console.log(`\n═══ 部署完成: ${ok}成功 ${fail}失败 ═══`); } main().catch(e => { console.error('FAIL:', e.message); process.exit(1); });