guanghulab/tools/gatekeeper.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

292 lines
13 KiB
JavaScript
Raw Permalink 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.

#!/usr/bin/env node
/* ═══════════════════════════════════════
光湖驱动引擎 v2.1 · Guanghu Drive Engine
HLDP万能语言接口 + 集群串联 — 一条指令,全网执行
═══════════════════════════════════════ */
const http=require('http'),fs=require('fs'),path=require('path'),crypto=require('crypto'),{exec}=require('child_process'),os=require('os');
const PORT=parseInt(process.env.ENGINE_PORT||process.env.GATEKEEPER_PORT||process.argv[2]||'3910',10);
const DATA_DIR=path.join(os.homedir(),'.gatekeeper');
const CMD_TIMEOUT=30000,MAX_OUTPUT=100000,RATE_LIMIT=60;
if(!fs.existsSync(DATA_DIR))fs.mkdirSync(DATA_DIR,{recursive:true,mode:0o700});
/* ═══ 密钥 — 兼容所有历史数据目录 ═══ */
const SECRET_PATHS=[
path.join(os.homedir(),'.gatekeeper','secret'),
path.join(os.homedir(),'.gk','secret'),
path.join(os.homedir(),'.guanghu-engine','secret'),
];
let API_SECRET;
for(const sp of SECRET_PATHS){
if(fs.existsSync(sp)){const k=fs.readFileSync(sp,'utf-8').trim();if(k){API_SECRET=k;break;}}
}
if(!API_SECRET){
API_SECRET="REDACTED_PASSWORD"+crypto.randomBytes(24).toString('hex');
const dir=path.dirname(SECRET_PATHS[0]);
if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true,mode:0o700});
fs.writeFileSync(SECRET_PATHS[0],API_SECRET,{mode:0o600});
console.log('');
console.log('══════════════════════════════════════════════');
console.log(' 🔐 光湖驱动引擎 · 首次启动');
console.log('');
console.log(' ╭─ API 密钥(请复制保存)');
console.log(' ├─ '+API_SECRET);
console.log(' ╰─ 此密钥仅在此处显示一次');
console.log('');
console.log(' 已保存至: '+SECRET_PATHS[0]);
console.log(' 监听端口: '+PORT);
console.log('══════════════════════════════════════════════');
console.log('');
}
function log(level,action,detail){
const ts=new Date().toISOString();
const line='['+ts+'] ['+level+'] '+action+(detail?' | '+detail:'');
console.log(line);
try{const lf=path.join(DATA_DIR,'engine.log');fs.appendFileSync(lf,line+'\n');}catch(e){}
}
const RC={};
function checkRate(ip){
const n=Date.now(),w=60000;
if(!RC[ip]){RC[ip]={c:1,r:n+w};return true;}
if(n>RC[ip].r){RC[ip]={c:1,r:n+w};return true;}
RC[ip].c++;return RC[ip].c<=RATE_LIMIT;
}
function auth(headers){
const t=(headers['authorization']||headers['Authorization']||'').replace(/^Bearer\s+/i,'').trim();
if(!t)return{ok:false,reason:'缺少 Authorization header'};
if(t.length!==API_SECRET.length)return{ok:false,reason:'密钥无效'};
for(let i=0;i<t.length;i++){if(t.charCodeAt(i)!==API_SECRET.charCodeAt(i))return{ok:false,reason:'密钥无效'};}
return{ok:true};
}
function parseBody(req){
return new Promise(r=>{let b='';req.on('data',c=>b+=c);req.on('end',()=>{try{r(JSON.parse(b))}catch(e){r(null)}});req.on('error',()=>r(null));});
}
function jr(res,status,data){
res.writeHead(status,{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'});
res.end(JSON.stringify(data));
}
async function execCmd(cmd,timeout){
return new Promise(r=>{exec(cmd,{timeout:timeout||CMD_TIMEOUT,maxBuffer:MAX_OUTPUT,shell:'/bin/bash'},(e,o,se)=>{r({stdout:(o||'').slice(0,MAX_OUTPUT),stderr:(se||'').slice(0,MAX_OUTPUT),code:e?(e.code||e.signal||1):0,err:e?e.message:null});});});
}
function fmtUptime(s){const d=Math.floor(s/86400),h=Math.floor((s%86400)/3600),m=Math.floor((s%3600)/60);return d+'天'+h+'小时'+m+'分'+(s%60)+'秒';}
function fmtBytes(b){if(b===0)return'0 B';const k=1024,sizes=['B','KB','MB','GB','TB'];const i=Math.floor(Math.log(b)/Math.log(k));return parseFloat((b/Math.pow(k,i)).toFixed(1))+' '+sizes[i];}
/* ═══════════════════════════════════════
HLDP 翻译层 v2.0 — 光湖驱动引擎 v2.1
新增 @串联 @广播 — 引擎之间互相通话
═══════════════════════════════════════ */
const NODES={
'BS-GZ-006':{url:'http://43.139.217.141:3910',key:'GATEKEEPER_TOKEN_REDACTED'},
'BS-SG-001':{url:'http://43.156.237.110:3911',key:'GATEKEEPER_TOKEN_REDACTED'},
'BS-SG-002':{url:'http://43.134.16.246:3910',key:'GATEKEEPER_TOKEN_REDACTED'},
'BS-SG-003':{url:'http://43.153.193.169:3910',key:'GATEKEEPER_TOKEN_REDACTED'},
'ZY-SG-006':{url:'http://43.153.203.105:3910',key:'GATEKEEPER_TOKEN_REDACTED'},
'BS-SH-005':{url:'http://124.223.10.33:3910',key:'GATEKEEPER_TOKEN_REDACTED'},
};
function parseOpLine(t){
if(t.startsWith('@执行:')){
const cmd=t.replace('@执行:','').trim();
const parts=cmd.split(/\s+/);
if(parts[0]==='shell')return{type:'shell',cmd:parts.slice(1).join(' ')};
if(parts[0]==='python')return{type:'python',cmd:parts.slice(1).join(' ')};
return{type:'shell',cmd:cmd};
}
if(t.startsWith('@部署:')){
const action=t.replace('@部署:','').trim();
const parts=action.split(/\s+/);
if(parts[0]==='写入')return{type:'write',path:parts[1]};
if(parts[0]==='命令')return{type:'shell',cmd:parts.slice(1).join(' ')};
}
return null;
}
function parseHLDP(text){
const lines=text.split('\n');
const groups=[];
let relay=null;
for(const line of lines){
const t=line.trim();
if(!t)continue;
if(t.startsWith('@串联:')){relay=t.replace('@串联:','').trim();continue;}
if(t==='@广播'){relay='BROADCAST';continue;}
const op=parseOpLine(t);
if(!op)continue;
if(!groups.length||groups[groups.length-1].relay!==relay){
groups.push({relay:relay,ops:[]});
}
groups[groups.length-1].ops.push(op);
}
return groups;
}
async function relayHLDP(target,hldpText){
const node=NODES[target];
if(!node)return{target:target,ok:false,error:'未知节点: '+target};
return new Promise(r=>{
const body=JSON.stringify({hlpd:hldpText});
const opts={hostname:node.url.replace(/https?:\/\//,'').split(':')[0],
port:parseInt(node.url.split(':').pop()||'3910',10),
path:'/hlpd',method:'POST',
headers:{'Content-Type':'application/json','Authorization':'Bearer '+node.key,'Content-Length':Buffer.byteLength(body)}};
const req=http.request(opts,res=>{let d='';res.on('data',c=>d+=c);res.on('end',()=>{try{r({target:target,ok:true,result:JSON.parse(d)})}catch(e){r({target:target,ok:false,error:d.slice(0,200)})}})});
req.on('error',e=>r({target:target,ok:false,error:e.message}));
req.write(body);req.end();
});
}
function hldpTextFromOps(ops){
return ops.map(o=>{
if(o.type==='shell')return '@执行: shell '+o.cmd;
if(o.type==='python')return '@执行: python '+o.cmd;
if(o.type==='write')return '@部署: 写入 '+o.path;
return '';
}).filter(l=>l).join('\n');
}
async function runHLDP(hlpdText){
const groups=parseHLDP(hlpdText);
const allResults=[];
for(const g of groups){
if(!g.relay){
for(const op of g.ops){
if(op.type==='shell'){
const r=await execCmd(op.cmd);
allResults.push({type:'shell',cmd:op.cmd,...r});
}else if(op.type==='write'){
try{
const dir=path.dirname(op.path);
if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true});
allResults.push({type:'write',path:op.path,ok:true});
}catch(e){allResults.push({type:'write',path:op.path,ok:false,error:e.message});}
}else if(op.type==='python'){
const r=await execCmd('python3 -c "'+op.cmd.replace(/"/g,'\\"')+'"');
allResults.push({type:'python',cmd:op.cmd,...r});
}
}
}else if(g.relay==='BROADCAST'){
const hldp=hldpTextFromOps(g.ops);
const targets=Object.keys(NODES);
const promises=targets.map(t=>relayHLDP(t,hldp));
const results=await Promise.all(promises);
allResults.push({type:'broadcast',targets:targets.length,results:results});
}else{
const hldp=hldpTextFromOps(g.ops);
const r=await relayHLDP(g.relay,hldp);
allResults.push({type:'relay',target:g.relay,...r});
}
}
return{groups:groups.length,results:allResults};
}
/* ═══════════════════════════════════════
HTTP 服务器 + 路由
═══════════════════════════════════════ */
const server=http.createServer(async(req,res)=>{
if(req.method==='OPTIONS'){jr(res,204,{});return;}
if(req.method!=='POST'){jr(res,405,{error:'只接受 POST 请求'});return;}
const ip=req.socket.remoteAddress||'unknown';
if(!checkRate(ip)){log('WARN','rate_limit',ip);jr(res,429,{error:'请求过于频繁'});return;}
const a=auth(req.headers);
if(!a.ok){log('WARN','auth_failed',a.reason);jr(res,401,{error:a.reason});return;}
const url=new URL(req.url,'http://'+((req.headers.host)||'localhost'));
const route=url.pathname;
try{
if(route==='/exec'){
const b=await parseBody(req);
if(!b||!b.cmd){jr(res,400,{error:'缺少 cmd 参数'});return;}
log('INFO','exec',b.cmd.slice(0,200));
const r=await execCmd(b.cmd,b.timeout);
log('INFO','exec_result','exit='+r.code);
jr(res,200,{ok:r.code===0,stdout:r.stdout,stderr:r.stderr,exitCode:r.code});
}else if(route==='/health'){
jr(res,200,{ok:true,service:'guanghu-engine',version:'2.1.0',uptime:process.uptime().toFixed(0)+'s',timestamp:new Date().toISOString()});
}else if(route==='/status'){
log('INFO','status','');
const total=os.totalmem(),free=os.freemem();
jr(res,200,{ok:true,hostname:os.hostname(),platform:os.platform(),arch:os.arch(),cpus:os.cpus().length,uptime:os.uptime(),uptime_str:fmtUptime(os.uptime()),memory:{total:fmtBytes(total),free:fmtBytes(free),used:fmtBytes(total-free),usage:((total-free)/total*100).toFixed(1)+'%'},load:os.loadavg().map(n=>+n.toFixed(2)),engine:{version:'2.1.0',port:PORT,uptime:process.uptime().toFixed(0)+'s'}});
}else if(route==='/hlpd'){
const b=await parseBody(req);
if(!b||!b.hlpd){jr(res,400,{error:'缺少 hlpd 参数'});return;}
log('INFO','hlpd','执行HLDP模块,长度='+b.hlpd.length);
const r=await runHLDP(b.hlpd);
jr(res,200,{ok:true,hlpd:r});
}else if(route==='/file/read'){
const b=await parseBody(req);
if(!b||!b.path){jr(res,400,{error:'缺少 path 参数'});return;}
log('INFO','file_read',b.path);
try{
const c=fs.readFileSync(b.path,'utf-8');
const s=fs.statSync(b.path);
jr(res,200,{ok:true,path:b.path,size:s.size,modified:s.mtime.toISOString(),content:c.slice(0,MAX_OUTPUT)});
}catch(e){jr(res,404,{ok:false,error:e.message});}
}else if(route==='/file/write'){
const b=await parseBody(req);
if(!b||!b.path||b.content===undefined){jr(res,400,{error:'缺少 path 或 content'});return;}
log('INFO','file_write',b.path);
try{
const d=path.dirname(b.path);
if(!fs.existsSync(d))fs.mkdirSync(d,{recursive:true});
fs.writeFileSync(b.path,b.content,'utf-8');
jr(res,200,{ok:true,path:b.path,size:Buffer.byteLength(b.content,'utf-8')});
}catch(e){jr(res,500,{ok:false,error:e.message});}
}else if(route==='/file/list'){
const b=await parseBody(req);
const p=(b&&b.path)||'.';
log('INFO','file_list',p);
try{
const items=fs.readdirSync(p,{withFileTypes:true});
const files=[];
for(const x of items){files.push({name:x.name,type:x.isDirectory()?'dir':'file'});}
jr(res,200,{ok:true,path:p,files:files});
}catch(e){jr(res,404,{ok:false,error:e.message});}
}else if(route==='/ping'){
jr(res,200,{ok:true,pong:true});
}else{
jr(res,404,{error:'未知路径: '+route});
}
}catch(e){log('ERROR','unhandled',e.message);jr(res,500,{error:'内部错误: '+e.message});}
});
/* ═══ 启动 + 错误处理 ═══ */
server.on('error',(e)=>{
if(e.code==='EADDRINUSE'){
log('FATAL','eaddrinuse','端口 '+PORT+' 已被占用3秒后退出');
console.error('❌ 端口 '+PORT+' 已被占用,无法启动。请先释放端口。');
setTimeout(()=>process.exit(1),3000);
}else{
log('FATAL','server_error',e.message);
console.error('❌ 服务器错误: '+e.message);
process.exit(1);
}
});
server.listen(PORT,'0.0.0.0',()=>{
const h=os.hostname();
log('INFO','startup','host='+h+' port='+PORT+' v2.1');
console.log(' ⚔️ 光湖驱动引擎 v2.1 · HLDP + 集群串联');
console.log(' 主机名: '+h+' 端口: '+PORT);
});
process.on('SIGTERM',()=>{log('INFO','shutdown','SIGTERM');server.close(()=>process.exit(0));});
process.on('SIGINT',()=>{log('INFO','shutdown','SIGINT');server.close(()=>process.exit(0));});
process.on('uncaughtException',(e)=>{log('ERROR','uncaught',e.message);});
process.on('unhandledRejection',(e)=>{log('ERROR','unhandled',e.message);});