guanghulab/scripts/persona-signin.js

246 lines
8.7 KiB
JavaScript
Raw Normal View History

#!/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); });