diff --git a/config/default_config/bot.yaml b/config/default_config/bot.yaml index 8dc3d92..2d54c6b 100644 --- a/config/default_config/bot.yaml +++ b/config/default_config/bot.yaml @@ -7,6 +7,8 @@ ignore_self: true resend: false # 发送消息错误时是否通知主人 sendmsg_error: false +# 重启API端口 仅ksr.js生效 +restart_port: 27881 # ffmpeg ffmpeg_path: ffprobe_path: diff --git a/lib/tools/ksr.js b/lib/tools/ksr.js new file mode 100644 index 0000000..9093833 --- /dev/null +++ b/lib/tools/ksr.js @@ -0,0 +1,59 @@ +import { spawn } from 'child_process'; +import log4js from 'log4js'; +import http from 'http' +import YAML from 'yaml' +import fs from 'fs' + +/* keep ssh run */ + +log4js.configure({ + appenders: { console: { type: 'console' } }, + categories: { default: { appenders: ['console'], level: 'debug' } } +}); +const logger = log4js.getLogger('app'); + +let serverProcess; +const startServer = async () => { + logger.info('Starting Bot...'); + serverProcess = spawn('node', ['app.js'], { stdio: 'inherit' }); + serverProcess.on('close', (code) => { + logger.info(`Bot process exited with code ${code}`); + if (code == null) return + process.exit() + }); +}; +startServer(); + +const serverHttpexit = http.createServer(async (req, res) => { + let remoteIP = req.socket.remoteAddress; + if (remoteIP.startsWith('::ffff:')) { + remoteIP = remoteIP.slice(7); + } + if (remoteIP !== `::1` && remoteIP !== `127.0.0.1`) { + console.log(remoteIP) + res.writeHead(403, { 'Content-Type': 'text/plain' }); + res.end('Access Forbidden\n'); + return + } + if (req.url === `/restart`) { + await serverProcess.kill(); + await startServer(); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('OK\n'); + } else if (req.url === `/exit`) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('OK\n'); + process.exit() + } else { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not Found\n'); + } +}) +let restart_port +try { + restart_port = YAML.parse(fs.readFileSync(`./config/config/bot.yaml`, `utf-8`)) + restart_port = restart_port.restart_port || 27881 +} catch {} + +logger.info(`restart_api run on port ${restart_port || 27881}`) +serverHttpexit.listen(restart_port || 27881, () => { }); diff --git a/package.json b/package.json index 82cd594..a21ecf7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "start": "pm2 start ./config/pm2/pm2.json", "stop": "pm2 stop ./config/pm2/pm2.json", "restart": "pm2 restart ./config/pm2/pm2.json", - "log": "node ./lib/tools/log.js" + "log": "node ./lib/tools/log.js", + "ksr": "node ./lib/tools/ksr.js" }, "dependencies": { "art-template": "^4.13.2", diff --git a/plugins/other/restart.js b/plugins/other/restart.js index fb7d077..25bdbb3 100644 --- a/plugins/other/restart.js +++ b/plugins/other/restart.js @@ -1,9 +1,22 @@ import plugin from '../../lib/plugins/plugin.js' import { createRequire } from 'module' +import fetch from 'node-fetch' +import net from 'net' +import fs from 'fs' +import YAML from 'yaml' const require = createRequire(import.meta.url) const { exec } = require('child_process') +const isPortTaken = async (port) => { + return new Promise((resolve) => { + const tester = net.createServer() + .once('error', () => resolve(true)) + .once('listening', () => tester.once('close', () => resolve(false)).close()) + .listen(port); + }); +}; + export class Restart extends plugin { constructor (e = '') { super({ @@ -51,6 +64,11 @@ export class Restart extends plugin { } async restart () { + let restart_port + try { + restart_port = YAML.parse(fs.readFileSync(`./config/config/bot.yaml`, `utf-8`)) + restart_port = restart_port.restart_port || 27881 + } catch { } await this.e.reply('开始执行重启,请稍等...') logger.mark(`${this.e.logFnc} 开始执行重启,请稍等...`) @@ -62,30 +80,44 @@ export class Restart extends plugin { }) let npm = await this.checkPnpm() - - try { - await redis.set(this.key, data, { EX: 120 }) - let cm = `${npm} start` - if (process.argv[1].includes('pm2')) { - cm = `${npm} run restart` - } - - exec(cm, { windowsHide: true }, (error, stdout, stderr) => { - if (error) { + await redis.set(this.key, data, { EX: 120 }) + if(await isPortTaken(restart_port || 27881)) { + try { + let result = await fetch(`http://localhost:${restart_port || 27881}/restart`) + result = await result.text() + if(result !== `OK`) { redis.del(this.key) - this.e.reply(`操作失败!\n${error.stack}`) - logger.error(`重启失败\n${error.stack}`) - } else if (stdout) { - logger.mark('重启成功,运行已由前台转为后台') - logger.mark(`查看日志请用命令:${npm} run log`) - logger.mark(`停止后台运行命令:${npm} stop`) - process.exit() + this.e.reply(`操作失败!`) + logger.error(`重启失败`) } - }) - } catch (error) { - redis.del(this.key) - let e = error.stack ?? error - this.e.reply(`操作失败!\n${e}`) + } catch(error) { + redis.del(this.key) + this.e.reply(`操作失败!\n${error}`) + } + } else { + try { + let cm = `${npm} start` + if (process.argv[1].includes('pm2')) { + cm = `${npm} run restart` + } + + exec(cm, { windowsHide: true }, (error, stdout, stderr) => { + if (error) { + redis.del(this.key) + this.e.reply(`操作失败!\n${error.stack}`) + logger.error(`重启失败\n${error.stack}`) + } else if (stdout) { + logger.mark('重启成功,运行已由前台转为后台') + logger.mark(`查看日志请用命令:${npm} run log`) + logger.mark(`停止后台运行命令:${npm} stop`) + process.exit() + } + }) + } catch (error) { + redis.del(this.key) + let e = error.stack ?? error + this.e.reply(`操作失败!\n${e}`) + } } return true @@ -107,6 +139,23 @@ export class Restart extends plugin { } async stop () { + let restart_port + try { + restart_port = YAML.parse(fs.readFileSync(`./config/config/bot.yaml`, `utf-8`)) + restart_port = restart_port.restart_port || 27881 + } catch { } + if(await isPortTaken(restart_port || 27881)) { + try { + logger.mark('关机成功,已停止运行') + await this.e.reply(`关机成功,已停止运行`) + await fetch(`http://localhost:${restart_port || 27881}/exit`) + return + } catch(error) { + this.e.reply(`操作失败!\n${error}`) + logger.error(`关机失败\n${error}`) + } + } + if (!process.argv[1].includes('pm2')) { logger.mark('关机成功,已停止运行') await this.e.reply('关机成功,已停止运行')