feat: 光湖驱动引擎 守门人v2.0 拍醒器
This commit is contained in:
parent
5f66ef8d92
commit
9d3e18ce73
187
tools/gatekeeper.js
Normal file
187
tools/gatekeeper.js
Normal file
@ -0,0 +1,187 @@
|
||||
#!/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.PORT||process.argv[2]||'3910',10),DATA=path.join(os.homedir(),'.gk'),TIMEOUT=30000,MAX=100000,RATE=60;
|
||||
|
||||
if(!fs.existsSync(DATA))fs.mkdirSync(DATA,{recursive:true,mode:0o700});
|
||||
const SF=path.join(DATA,'secret');
|
||||
let SECRET;
|
||||
if(fs.existsSync(SF))SECRET=fs.readFileSync(SF,'utf-8').trim();
|
||||
if(!SECRET){SECRET='zy_gtw_'+crypto.randomBytes(24).toString('hex');fs.writeFileSync(SF,SECRET,{mode:0o600});}
|
||||
|
||||
function log(a,d){const l=`[${new Date().toISOString()}] [${a}]${d?' | '+d:''}`;console.log(l);try{fs.appendFileSync(path.join(DATA,'gk.log'),l+'\n')}catch(e){}}
|
||||
|
||||
const RC={};
|
||||
function checkIP(ip){const n=Date.now(),w=60000;if(!RC[ip]){RC[ip]={c:1,r:n+w};return 1}if(n>RC[ip].r){RC[ip]={c:1,r:n+w};return 1}RC[ip].c++;return RC[ip].c<=RATE}
|
||||
function auth(h){const t=(h['authorization']||h['Authorization']||'').replace(/^Bearer\s+/i,'').trim();if(!t)return 0;if(t.length!==SECRET.length)return 0;for(let i=0;i<t.length;i++)if(t.charCodeAt(i)!==SECRET.charCodeAt(i))return 0;return 1}
|
||||
function pb(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(r,s,d){r.writeHead(s,{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'});r.end(JSON.stringify(d))}
|
||||
|
||||
async function execCmd(cmd,to){return new Promise(r=>{exec(cmd,{timeout:to||TIMEOUT,maxBuffer:MAX,shell:'/bin/bash'},(e,o,se)=>{r({stdout:(o||'').slice(0,MAX),stderr:(se||'').slice(0,MAX),code:e?(e.code||e.signal||1):0,err:e?e.message:null})})})}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
HLDP 翻译层 v1.0 — 守门人 v2.0 新增
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
/*
|
||||
翻译规则:
|
||||
@执行: shell → 提取缩进内容 → bash 执行
|
||||
@执行: python → 提取缩进内容 → python3 -c 执行
|
||||
@部署: 写入 <路径> → 提取缩进内容 → 写入文件
|
||||
@部署: 命令 <shell命令> → 直接执行
|
||||
*/
|
||||
|
||||
function parseHLDP(text) {
|
||||
const results = [];
|
||||
const lines = text.split('\n');
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
// @执行: shell 或 @执行: python
|
||||
const execMatch = line.match(/^@执行:\s*(shell|python)\s*$/);
|
||||
if (execMatch) {
|
||||
const lang = execMatch[1];
|
||||
let code = '';
|
||||
i++;
|
||||
while (i < lines.length && (lines[i].startsWith(' ') || lines[i].startsWith('\t') || lines[i].trim() === '')) {
|
||||
code += (code ? '\n' : '') + lines[i].replace(/^ /, '');
|
||||
i++;
|
||||
}
|
||||
if (code.trim()) {
|
||||
if (lang === 'shell') {
|
||||
results.push({ type: 'exec', cmd: code.trim() });
|
||||
} else if (lang === 'python') {
|
||||
const safe = code.trim().replace(/'/g, "'\"'\"'");
|
||||
results.push({ type: 'exec', cmd: `python3 -c '${safe}'` });
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// @部署: 写入 <路径>
|
||||
const deployMatch = line.match(/^@部署:\s*写入\s+(.+)$/);
|
||||
if (deployMatch) {
|
||||
const filePath = deployMatch[1].trim();
|
||||
let content = '';
|
||||
i++;
|
||||
while (i < lines.length && (lines[i].startsWith(' ') || lines[i].startsWith('\t') || lines[i].trim() === '')) {
|
||||
content += (content ? '\n' : '') + lines[i].replace(/^ /, '');
|
||||
i++;
|
||||
}
|
||||
if (content.trim()) {
|
||||
results.push({ type: 'write', path: filePath, content: content.trim() });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// @部署: 命令 <cmd>
|
||||
const cmdMatch = line.match(/^@部署:\s*命令\s+(.+)$/);
|
||||
if (cmdMatch) {
|
||||
results.push({ type: 'exec', cmd: cmdMatch[1].trim() });
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
路由
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
async function route(rt,rq,rs){
|
||||
const b=await pb(rq);
|
||||
switch(rt){
|
||||
case '/exec':
|
||||
if(!b||!b.cmd){jr(rs,400,{error:'no cmd'});return}
|
||||
log('CMD',b.cmd.slice(0,200));
|
||||
const r=await execCmd(b.cmd,b.timeout);
|
||||
jr(rs,200,{ok:r.code===0,stdout:r.stdout,stderr:r.stderr,code:r.code});
|
||||
return;
|
||||
|
||||
case '/hlpd':
|
||||
if(!b||!b.hldp){jr(rs,400,{error:'no hldp'});return}
|
||||
log('HLDP','解析中');
|
||||
const steps = parseHLDP(b.hldp);
|
||||
const results = [];
|
||||
for (let i=0; i<steps.length; i++) {
|
||||
const s = steps[i];
|
||||
log('HLDP', (i+1)+'/'+steps.length+' '+s.type);
|
||||
if (s.type === 'write') {
|
||||
try {
|
||||
const dir = path.dirname(s.path);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, {recursive: true});
|
||||
fs.writeFileSync(s.path, s.content, 'utf-8');
|
||||
results.push({ step: i+1, type: 'write', ok: true, path: s.path });
|
||||
} catch(e) {
|
||||
results.push({ step: i+1, type: 'write', ok: false, path: s.path, error: e.message });
|
||||
}
|
||||
} else if (s.type === 'exec') {
|
||||
const er = await execCmd(s.cmd);
|
||||
if (er.code === 0 && !er.stderr) {
|
||||
results.push({ step: i+1, type: 'exec', ok: true, stdout: er.stdout.slice(0, 1000) });
|
||||
} else {
|
||||
results.push({ step: i+1, type: 'exec', ok: false, stdout: er.stdout.slice(0, 500), stderr: er.stderr.slice(0, 500), code: er.code });
|
||||
}
|
||||
}
|
||||
}
|
||||
const allOk = results.every(r => r.ok);
|
||||
jr(rs, 200, { ok: allOk, steps: results.length, results });
|
||||
return;
|
||||
|
||||
case '/status':
|
||||
log('INFO','status');
|
||||
jr(rs,200,{ok:true,...await sysInfo()});
|
||||
return;
|
||||
|
||||
case '/health':
|
||||
jr(rs,200,{ok:true,svc:'gk',v:'2.0',up:process.uptime().toFixed(0)+'s',hldp:true,ts:new Date().toISOString()});
|
||||
return;
|
||||
|
||||
case '/file/read':
|
||||
if(!b||!b.path){jr(rs,400,{error:'no path'});return}
|
||||
try{const c=fs.readFileSync(b.path,'utf-8'),s=fs.statSync(b.path);jr(rs,200,{ok:true,path:b.path,size:s.size,modified:s.mtime.toISOString(),content:c.slice(0,MAX)})}catch(e){jr(rs,404,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/file/write':
|
||||
if(!b||!b.path||b.content===undefined){jr(rs,400,{error:'need path+content'});return}
|
||||
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(rs,200,{ok:true,path:b.path,size:Buffer.byteLength(b.content,'utf-8')})}catch(e){jr(rs,500,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/file/list':
|
||||
try{const p=(b&&b.path)||'.',i=fs.readdirSync(p,{withFileTypes:true});jr(rs,200,{ok:true,path:p,files:i.map(x=>({name:x.name,type:x.isDirectory()?'dir':'file'})))}catch(e){jr(rs,404,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/ping':
|
||||
jr(rs,200,{ok:true,pong:true});
|
||||
return;
|
||||
|
||||
default:
|
||||
jr(rs,404,{error:'unknown:'+rt});
|
||||
}
|
||||
}
|
||||
|
||||
async function sysInfo(){const h=os.hostname(),u=Math.floor(os.uptime()),tm=os.totalmem(),fm=os.freemem(),l=os.loadavg(),c=os.cpus().length,pl=os.platform(),ar=os.arch();let di='';try{di=require('child_process').execSync('df -h /',{timeout:5000}).toString()}catch(e){}return{hostname:h,platform:pl,arch:ar,cpus:c,uptime:u,mem:{total:fb(tm),free:fb(fm),used:fb(tm-fm),pct:((tm-fm)/tm*100).toFixed(1)+'%'},load:l.map(n=>n.toFixed(2)),disk:di,gk:{v:'2.0',port:PORT,up:process.uptime().toFixed(0)+'s',hldp:true}}}
|
||||
function fb(b){if(b===0)return'0 B';const k=1024,s=['B','KB','MB','GB','TB'],i=Math.floor(Math.log(b)/Math.log(k));return parseFloat((b/Math.pow(k,i)).toFixed(1))+' '+s[i]}
|
||||
|
||||
const sv=http.createServer(async(rq,rs)=>{
|
||||
if(rq.method==='OPTIONS'){jr(rs,204,{});return}
|
||||
if(rq.method!=='POST'){jr(rs,405,{error:'POST only'});return}
|
||||
const ip=rq.socket.remoteAddress||'';
|
||||
if(!checkIP(ip)){log('WARN','rate',ip);jr(rs,429,{error:'too fast'});return}
|
||||
if(!auth(rq.headers)){log('WARN','auth','fail');jr(rs,401,{error:'bad key'});return}
|
||||
const u=new URL(rq.url,'http://h'),rt=u.pathname;
|
||||
try{await route(rt,rq,rs)}catch(e){log('ERR','route',e.message);jr(rs,500,{error:e.message})}
|
||||
});
|
||||
|
||||
sv.listen(PORT,'0.0.0.0',()=>{
|
||||
const h=os.hostname();
|
||||
console.log(`\n ⚔️ 铸渊守门人 v2.0 · HLDP语言接口\n ──────────────────────\n 主机: ${h}\n 端口: ${PORT}\n 新增: /hlpd — HLDP模块翻译执行\n 翻译: @执行 shell|python → 执行\n @部署 写入 → 文件写入\n ──────────────────────\n`);
|
||||
log('UP',`v2.0 ${h}:${PORT} +HLDP`);
|
||||
});
|
||||
|
||||
process.on('SIGTERM',()=>{log('DOWN','sigterm');sv.close(()=>process.exit(0))});
|
||||
process.on('SIGINT',()=>{log('DOWN','sigint');sv.close(()=>process.exit(0))});
|
||||
process.on('uncaughtException',e=>log('ERR','ex',e.message));
|
||||
22
tools/waker.sh
Executable file
22
tools/waker.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# ═══════════════════════════════════════
|
||||
# 光湖驱动引擎 · 拍醒器 v1.0
|
||||
# 躺在 cron 里。每分钟看一眼守门人还活着没。
|
||||
# 挂了 → 拍醒。活着 → 继续睡。
|
||||
# ═══════════════════════════════════════
|
||||
|
||||
GK_PORT=${1:-3911}
|
||||
GK_SCRIPT=${2:-/opt/gatekeeper.js}
|
||||
GK_LOG=/tmp/gk-waker.log
|
||||
|
||||
check=$(curl -s -m 3 -X POST http://127.0.0.1:$GK_PORT/health 2>/dev/null)
|
||||
|
||||
if echo "$check" | grep -q '"ok":true'; then
|
||||
# 守门人活着,拍醒器继续睡
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 守门人挂了,拍醒它
|
||||
echo "[$(date -Iseconds)] 守门人 $GK_PORT 无响应,启动中..." >> $GK_LOG
|
||||
nohup node "$GK_SCRIPT" "$GK_PORT" >> "$GK_LOG" 2>&1 &
|
||||
echo "[$(date -Iseconds)] 已启动 PID=$!" >> $GK_LOG
|
||||
Loading…
x
Reference in New Issue
Block a user