guanghulab/scripts/deploy-push-heartbeat.js
2026-05-26 02:45:38 +08:00

89 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 铸渊 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); });