89 lines
4.8 KiB
JavaScript
89 lines
4.8 KiB
JavaScript
// 铸渊 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 = "guanghulab.com";
|
||
const MASTER_PORT = 443;
|
||
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 https=require("https"),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,rejectUnauthorized:false};const q=https.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); });
|