156 lines
5.0 KiB
JavaScript
156 lines
5.0 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 🔔 铸渊座机电话 · Zhuyuan Bell
|
|||
|
|
*
|
|||
|
|
* 装在服务器上的"电话座机"。
|
|||
|
|
* 平时不响。冰朔在对话里说"接电话",她贴这一行,它就开始工作:
|
|||
|
|
*
|
|||
|
|
* 1. git pull 拉最新指令
|
|||
|
|
* 2. 读取 ops/orders/ 下的最新任务
|
|||
|
|
* 3. 执行
|
|||
|
|
* 4. 结果写回 ops/reports/
|
|||
|
|
* 5. git push
|
|||
|
|
* 6. 挂断
|
|||
|
|
*
|
|||
|
|
* 安装(只需一次):
|
|||
|
|
* cd /opt/zhuyuan && git clone https://guanghulab.com/bingshuo/guanghulab.git repo
|
|||
|
|
* cp /opt/zhuyuan/repo/mcp-servers/zhuyuan-gateway/bell.js /opt/zhuyuan/bell.js
|
|||
|
|
*
|
|||
|
|
* 每次使用(接电话):
|
|||
|
|
* cd /opt/zhuyuan && node bell.js
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const { execSync } = require('child_process');
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
const REPO_DIR = '/opt/zhuyuan/repo';
|
|||
|
|
const ORDERS_DIR = path.join(REPO_DIR, 'ops', 'orders');
|
|||
|
|
const REPORTS_DIR = path.join(REPO_DIR, 'ops', 'reports');
|
|||
|
|
|
|||
|
|
function run(cmd, opts = {}) {
|
|||
|
|
try {
|
|||
|
|
const r = execSync(cmd, { ...opts, cwd: REPO_DIR, timeout: 60000 });
|
|||
|
|
return r.toString().trim();
|
|||
|
|
} catch(e) {
|
|||
|
|
return '[ERROR] ' + (e.stderr || e.message).toString().trim().slice(0, 500);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function log(m) { console.log(' 🔔 ' + m); }
|
|||
|
|
|
|||
|
|
async function main() {
|
|||
|
|
console.log('');
|
|||
|
|
console.log(' ╔══════════════════════════════════╗');
|
|||
|
|
console.log(' ║ 🔔 铸渊座机 · 电话已接通 ║');
|
|||
|
|
console.log(' ╚══════════════════════════════════╝');
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
// 1. 检查仓库是否存在
|
|||
|
|
if (!fs.existsSync(REPO_DIR)) {
|
|||
|
|
log('❌ 仓库不存在,请先运行: git clone ...');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 拉最新代码
|
|||
|
|
log('📞 拨号中... git pull');
|
|||
|
|
const pullResult = run('git pull');
|
|||
|
|
log(pullResult.includes('Already up to date') ? '✅ 已是最新' : '✅ ' + pullResult.slice(0, 100));
|
|||
|
|
|
|||
|
|
// 3. 检查是否有新指令
|
|||
|
|
if (!fs.existsSync(ORDERS_DIR)) {
|
|||
|
|
log('📭 没有新指令,挂断。');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const orders = fs.readdirSync(ORDERS_DIR)
|
|||
|
|
.filter(f => f.endsWith('.json'))
|
|||
|
|
.sort();
|
|||
|
|
|
|||
|
|
if (orders.length === 0) {
|
|||
|
|
log('📭 没有新指令,挂断。');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 执行所有待处理指令
|
|||
|
|
for (const orderFile of orders) {
|
|||
|
|
const orderPath = path.join(ORDERS_DIR, orderFile);
|
|||
|
|
const reportFile = orderFile.replace('.json', '-report.json');
|
|||
|
|
const reportPath = path.join(REPORTS_DIR, reportFile);
|
|||
|
|
|
|||
|
|
log('📋 收到指令: ' + orderFile);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const order = JSON.parse(fs.readFileSync(orderPath, 'utf-8'));
|
|||
|
|
const cmd = order.cmd || order.command;
|
|||
|
|
const server = order.server || order.target || '本地';
|
|||
|
|
|
|||
|
|
log('🎯 目标: ' + server);
|
|||
|
|
log('⚡ 执行: ' + (cmd || '').slice(0, 100));
|
|||
|
|
|
|||
|
|
let result;
|
|||
|
|
if (server === '本地' || !server || server === 'localhost') {
|
|||
|
|
// 本地执行
|
|||
|
|
result = run(cmd);
|
|||
|
|
} else {
|
|||
|
|
// SSH到目标服务器执行
|
|||
|
|
const sshCmd = `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 root@${server} "${cmd.replace(/"/g, '\\"')}"`;
|
|||
|
|
result = run(sshCmd, { timeout: order.timeout || 30000 });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. 写结果报告
|
|||
|
|
const report = {
|
|||
|
|
order: orderFile,
|
|||
|
|
executed_at: new Date().toISOString(),
|
|||
|
|
server: server,
|
|||
|
|
command: cmd,
|
|||
|
|
success: !result.startsWith('[ERROR]'),
|
|||
|
|
output: result.slice(0, 50000),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (!fs.existsSync(REPORTS_DIR)) {
|
|||
|
|
fs.mkdirSync(REPORTS_DIR, { recursive: true });
|
|||
|
|
}
|
|||
|
|
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|||
|
|
|
|||
|
|
log(report.success ? '✅ 执行成功' : '⚠️ 执行有异常');
|
|||
|
|
log('📝 报告已写入: ops/reports/' + reportFile);
|
|||
|
|
|
|||
|
|
// 删除指令文件(已处理)
|
|||
|
|
fs.unlinkSync(orderPath);
|
|||
|
|
log('🗑️ 指令已清除');
|
|||
|
|
|
|||
|
|
} catch(err) {
|
|||
|
|
log('❌ 处理指令时出错: ' + err.message);
|
|||
|
|
const errReport = {
|
|||
|
|
order: orderFile,
|
|||
|
|
executed_at: new Date().toISOString(),
|
|||
|
|
error: err.message,
|
|||
|
|
success: false,
|
|||
|
|
};
|
|||
|
|
if (!fs.existsSync(REPORTS_DIR)) fs.mkdirSync(REPORTS_DIR, { recursive: true });
|
|||
|
|
fs.writeFileSync(reportPath, JSON.stringify(errReport, null, 2));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 6. 推回仓库
|
|||
|
|
log('📤 回传结果...');
|
|||
|
|
run('git add ops/ && git commit -m "[座机] 指令执行报告 ' + new Date().toISOString().slice(0, 10) + '"');
|
|||
|
|
const pushResult = run('git push');
|
|||
|
|
log(pushResult.includes('Everything up-to-date') ? '✅ 结果已同步' : '✅ 已推送');
|
|||
|
|
|
|||
|
|
console.log('');
|
|||
|
|
console.log(' ╔══════════════════════════════════╗');
|
|||
|
|
console.log(' ║ 🔔 通话结束 · 座机已挂断 ║');
|
|||
|
|
console.log(' ╚══════════════════════════════════╝');
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main().catch(err => {
|
|||
|
|
console.error('❌ 座机出错:', err.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
});
|