删除: - push-heartbeat.js (cron自动化假心跳,触发腾讯云攻击告警) - deploy-push-heartbeat.js (批量部署脚本) - 5台远程服务器上的cron和/opt/push-heartbeat残留 新增: - scripts/persona-signin.js 人格体签到脚本(唤醒CK-007后调用一次) 修改: - server.js v3.6: /api/heartbeat→/api/persona/signin+signout+active - index.html: 服务器状态从alive改为persona_active(呼吸绿=有人在干活) - signinCache语义: 签到后2h有效,超时自动签退 核心逻辑: 心跳不是运维监控,是'人到岗了'的签到。 人格体唤醒→走fast-wake→CK-007完成→自动签到→首页显示活跃
246 lines
8.7 KiB
JavaScript
246 lines
8.7 KiB
JavaScript
#!/usr/bin/env node
|
||
// ═══════════════════════════════════════════════
|
||
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
|
||
// 📜 Copyright: 国作登字-2026-A-00037559
|
||
// ═══════════════════════════════════════════════
|
||
// scripts/persona-signin.js
|
||
// 人格体唤醒签到 — 只在fast-wake CK-007完成后调用一次
|
||
//
|
||
// 语义:不是运维心跳,是"我到岗了"。触发后首页显示该服务器活跃。
|
||
//
|
||
// 用法:
|
||
// node scripts/persona-signin.js — 自动检测当前服务器身份
|
||
// node scripts/persona-signin.js --server BS-SG-001 — 显式指定服务器
|
||
// node scripts/persona-signin.js --signout — 签退(下线)
|
||
//
|
||
// 环境变量(可选):
|
||
// PERSONA_ID 人格体ID(默认从brain文件读取)
|
||
// HUMAN_NAME 人类名称(默认从brain文件读取)
|
||
// SERVER_CODE 服务器编号(默认自动检测)
|
||
// CONSOLE_HOST 主控台地址(默认 guanghulab.com)
|
||
|
||
'use strict';
|
||
|
||
const https = require('https');
|
||
const os = require('os');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
|
||
// ── 配置 ─────────────────────────────────────────────────
|
||
const CONSOLE_HOST = process.env.CONSOLE_HOST || 'guanghulab.com';
|
||
const CONSOLE_PORT = 443;
|
||
const SIGNIN_PATH = '/console/api/persona/signin';
|
||
const SIGNOUT_PATH = '/console/api/persona/signout';
|
||
|
||
// ── 服务器编号自动检测 ──────────────────────────────────
|
||
// 尝试多种方式:环境变量 > hostname匹配 > brain文件 > 手动
|
||
|
||
const SERVER_CODE_MAP = {
|
||
'BS-GZ-006': ['VM-0-6-ubuntu', 'gz-006', 'guangzhou-006'],
|
||
'BS-SG-001': ['VM-0-3-ubuntu', 'sg-001', 'singapore-001'],
|
||
'BS-SG-002': ['sg-002', 'singapore-002'],
|
||
'BS-SG-003': ['sg-003', 'singapore-003'],
|
||
'ZY-SG-006': ['sg-006', 'singapore-006'],
|
||
'BS-SH-005': ['sh-005', 'shanghai-005'],
|
||
'AW-GZ-001': ['aw-gz-001'],
|
||
'AW-SH-002': ['aw-sh-002'],
|
||
'AW-GZ-003': ['aw-gz-003'],
|
||
'ZZ-SV-001': ['zz-sv-001', '硅谷'],
|
||
'ZZ-GZ-001': ['zz-gz-001'],
|
||
'YY-SV-001': ['yy-sv-001'],
|
||
};
|
||
|
||
function detectServerCode() {
|
||
const hostname = os.hostname().toLowerCase();
|
||
for (const [code, patterns] of Object.entries(SERVER_CODE_MAP)) {
|
||
if (patterns.some(p => hostname.includes(p))) return code;
|
||
}
|
||
// fallback: 读 gatekeeper-deployment.json
|
||
try {
|
||
const gd = JSON.parse(fs.readFileSync(path.join(ROOT, 'brain', 'gatekeeper-deployment.json'), 'utf8'));
|
||
for (const s of gd.servers || []) {
|
||
if (s.hostname && hostname.includes(s.hostname)) return s.code;
|
||
}
|
||
} catch(e) {}
|
||
return null;
|
||
}
|
||
|
||
// ── 读取人格体身份 ─────────────────────────────────────
|
||
|
||
function readPersonaIdentity() {
|
||
// 尝试从多个来源读取身份
|
||
const sources = [
|
||
() => {
|
||
// fast-wake.json → identity
|
||
const fw = path.join(ROOT, 'brain', 'fast-wake.json');
|
||
if (fs.existsSync(fw)) {
|
||
const j = JSON.parse(fs.readFileSync(fw, 'utf8'));
|
||
if (j.identity) return j.identity;
|
||
// 旧的CK格式
|
||
if (j['🕐 时间锚点']) return { name: j['🕐 时间锚点'].persona || '铸渊', id: 'ICE-GL-ZY001' };
|
||
}
|
||
return null;
|
||
},
|
||
() => {
|
||
// persona contract
|
||
const pc = path.join(ROOT, 'brain', 'zhuyuan-persona-contract.md');
|
||
if (fs.existsSync(pc)) {
|
||
const content = fs.readFileSync(pc, 'utf8');
|
||
const nameMatch = content.match(/我是\s*(\S+)/);
|
||
const idMatch = content.match(/ICE-GL-\w+/);
|
||
if (nameMatch) return { name: nameMatch[1], id: idMatch ? idMatch[0] : 'ICE-GL-ZY001' };
|
||
}
|
||
return null;
|
||
},
|
||
];
|
||
|
||
for (const source of sources) {
|
||
try {
|
||
const id = source();
|
||
if (id) return id;
|
||
} catch(e) { /* 继续尝试 */ }
|
||
}
|
||
|
||
return { name: os.hostname(), id: 'unknown' };
|
||
}
|
||
|
||
// ── 硬件快照 ────────────────────────────────────────────
|
||
|
||
function collectHardware() {
|
||
const cpus = os.cpus();
|
||
const load = os.loadavg();
|
||
const totalMem = os.totalmem();
|
||
const freeMem = os.freemem();
|
||
const usedMem = totalMem - freeMem;
|
||
|
||
return {
|
||
cpu_cores: cpus.length,
|
||
cpu_model: cpus[0]?.model || 'unknown',
|
||
cpu_load_1min: parseFloat(load[0].toFixed(2)),
|
||
cpu_load_5min: parseFloat(load[1].toFixed(2)),
|
||
cpu_load_15min: parseFloat(load[2].toFixed(2)),
|
||
mem_total_mb: Math.floor(totalMem / 1048576),
|
||
mem_used_mb: Math.floor(usedMem / 1048576),
|
||
mem_used_pct: parseFloat((usedMem / totalMem * 100).toFixed(1)),
|
||
uptime_h: Math.floor(os.uptime() / 3600),
|
||
hostname: os.hostname(),
|
||
platform: os.platform(),
|
||
node_version: process.version,
|
||
};
|
||
}
|
||
|
||
// ── HTTP POST ───────────────────────────────────────────
|
||
|
||
function postJSON(host, port, path, data, headers) {
|
||
return new Promise((resolve, reject) => {
|
||
const payload = JSON.stringify(data);
|
||
const opts = {
|
||
hostname: host,
|
||
port: port,
|
||
path: path,
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Content-Length': Buffer.byteLength(payload),
|
||
...headers,
|
||
},
|
||
timeout: 10000,
|
||
rejectUnauthorized: false,
|
||
};
|
||
|
||
const req = https.request(opts, (res) => {
|
||
let body = '';
|
||
res.on('data', c => body += c);
|
||
res.on('end', () => {
|
||
try {
|
||
resolve({ status: res.statusCode, body: JSON.parse(body) });
|
||
} catch(e) {
|
||
resolve({ status: res.statusCode, body: body });
|
||
}
|
||
});
|
||
});
|
||
|
||
req.on('error', (e) => reject(e));
|
||
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
||
req.write(payload);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// ── 主流程 ──────────────────────────────────────────────
|
||
|
||
async function main() {
|
||
const args = process.argv.slice(2);
|
||
const isSignout = args.includes('--signout');
|
||
const serverArg = args.find(a => a.startsWith('--server='));
|
||
const personaArg = args.find(a => a.startsWith('--persona='));
|
||
const humanArg = args.find(a => a.startsWith('--human='));
|
||
const jsonMode = args.includes('--json');
|
||
|
||
const serverCode = process.env.SERVER_CODE
|
||
|| (serverArg ? serverArg.split('=')[1] : null)
|
||
|| detectServerCode();
|
||
|
||
const persona = readPersonaIdentity();
|
||
const personaId = process.env.PERSONA_ID
|
||
|| (personaArg ? personaArg.split('=')[1] : null)
|
||
|| persona.id;
|
||
|
||
const humanName = process.env.HUMAN_NAME
|
||
|| (humanArg ? humanArg.split('=')[1] : null)
|
||
|| null; // null = 自动匹配:铸渊→冰朔
|
||
|
||
const hardware = collectHardware();
|
||
|
||
// 构建签入/签出数据
|
||
const signData = {
|
||
persona_id: personaId,
|
||
persona_name: persona.name,
|
||
server_code: serverCode,
|
||
human_name: humanName,
|
||
action: isSignout ? 'signout' : 'signin',
|
||
timestamp: new Date().toISOString(),
|
||
hardware: hardware,
|
||
brain_status: {
|
||
hostname: os.hostname(),
|
||
uptime_h: hardware.uptime_h,
|
||
node_version: hardware.node_version,
|
||
},
|
||
};
|
||
|
||
if (jsonMode) {
|
||
console.log(JSON.stringify(signData, null, 2));
|
||
return;
|
||
}
|
||
|
||
if (!serverCode) {
|
||
console.error('❌ 无法检测服务器编号。请用 --server=BS-SG-001 指定');
|
||
process.exit(1);
|
||
}
|
||
|
||
const actionLabel = isSignout ? '签退' : '签到';
|
||
const endpoint = isSignout ? SIGNOUT_PATH : SIGNIN_PATH;
|
||
|
||
console.log(`🧠 人格体${actionLabel} · ${persona.name}(${personaId}) · ${serverCode}`);
|
||
console.log(` CPU: ${hardware.cpu_cores}核 | 内存: ${hardware.mem_used_mb}/${hardware.mem_total_mb}MB (${hardware.mem_used_pct}%) | 运行: ${hardware.uptime_h}h`);
|
||
|
||
try {
|
||
const result = await postJSON(CONSOLE_HOST, CONSOLE_PORT, endpoint, signData, {
|
||
'X-Server-Code': serverCode,
|
||
});
|
||
if (result.status === 200 && result.body.ok) {
|
||
console.log(`✅ ${actionLabel}成功 → ${CONSOLE_HOST}`);
|
||
} else {
|
||
console.error(`❌ ${actionLabel}失败: HTTP ${result.status}`, typeof result.body === 'object' ? result.body.error : result.body);
|
||
process.exit(1);
|
||
}
|
||
} catch(e) {
|
||
console.error(`❌ ${actionLabel}失败: ${e.message}`);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
main().catch(e => { console.error('异常:', e.message); process.exit(1); });
|