#!/usr/bin/env node /* ═══════════════════════════════════════ 铸渊守门人 v2.0 · HLDP万能语言接口 新增 /hlpd 端点 — 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.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_FILE=path.join(DATA_DIR,'secret'); let API_SECRET; if(fs.existsSync(SECRET_FILE)){API_SECRET=fs.readFileSync(SECRET_FILE,'utf-8').trim();} if(!API_SECRET){ API_SECRET='zy_gtw_'+crypto.randomBytes(24).toString('hex'); fs.writeFileSync(SECRET_FILE,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_FILE); 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,'gatekeeper.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{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 翻译层 v1.0 — 守门人 v2.0 新增 ═══════════════════════════════════════ */ function parseHLDP(text){ const lines=text.split('\n').filter(l=>l.trim()); const ops=[]; for(const line of lines){ const t=line.trim(); if(t.startsWith('@执行:')){ const cmd=t.replace('@执行:','').trim(); const parts=cmd.split(/\s+/); if(parts[0]==='shell'){ops.push({type:'shell',cmd:parts.slice(1).join(' ')});} else if(parts[0]==='python'){ops.push({type:'python',cmd:parts.slice(1).join(' ')});} else{ops.push({type:'shell',cmd:cmd});} }else if(t.startsWith('@部署:')){ const action=t.replace('@部署:','').trim(); const parts=action.split(/\s+/); if(parts[0]==='写入'){ const filepath=parts[1]; ops.push({type:'write',path:filepath}); }else if(parts[0]==='命令'){ ops.push({type:'shell',cmd:parts.slice(1).join(' ')}); } } } return ops; } async function runHLDP(hlpdText){ const ops=parseHLDP(hlpdText); const results=[]; for(const op of ops){ if(op.type==='shell'){ const r=await execCmd(op.cmd); results.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}); results.push({type:'write',path:op.path,ok:true}); }catch(e){results.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,'\\"')+'"'); results.push({type:'python',cmd:op.cmd,...r}); } } return{parsed:ops.length,results:results}; } /* ═══════════════════════════════════════ 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:'zhuyuan-gatekeeper',version:'2.0.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)),gatekeeper:{version:'2.0.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.0'); console.log(' ⚔️ 铸渊守门人 v2.0 · 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);});