diff --git a/src/app.ts b/src/app.ts index df76729..0b0971a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -127,12 +127,12 @@ console.log(""); // 装载 终端界面UI import "./service/ui"; -['SIGINT', 'SIGQUIT'].forEach(function (sig) { - process.on(sig, function () { +["SIGTERM", "SIGINT", "SIGQUIT"].forEach(function(sig) { + process.on(sig, async function() { try { console.log("\n\n\n\n"); logger.warn(`${sig} close process signal detected.`); - InstanceSubsystem.exit(); + await InstanceSubsystem.exit(); logger.info("The data is saved, thanks for using, goodbye!"); logger.info("Closed."); } catch (err) { diff --git a/src/entity/commands/cmd.ts b/src/entity/commands/cmd.ts index 98ff73a..bf8a199 100644 --- a/src/entity/commands/cmd.ts +++ b/src/entity/commands/cmd.ts @@ -31,6 +31,6 @@ export default class SendCommand extends InstanceCommand { } async exec(instance: Instance) { - return await instance.execPreset("write", this.cmd); + return await instance.execPreset("command", this.cmd); } } diff --git a/src/entity/commands/dispatcher.ts b/src/entity/commands/dispatcher.ts index c2a4d50..e57fec7 100644 --- a/src/entity/commands/dispatcher.ts +++ b/src/entity/commands/dispatcher.ts @@ -31,6 +31,7 @@ import GeneralKillCommand from "./general/general _kill"; import GeneralSendCommand from "./general/general _command"; import GeneralRestartCommand from "./general/general _restart"; import DockerStartCommand from "./docker/docker _start"; +import GeneralInputCommand from "./general/general _input"; import TimeCheck from "./task/time"; import MinecraftBedrockGetPlayersCommand from "../minecraft/mc_getplayer_bedrock"; @@ -52,14 +53,16 @@ export default class FuntionDispatcher extends InstanceCommand { // 根据实例启动类型来进行基本操作方式的预设 if (!instance.config.processType || instance.config.processType === "general") { instance.setPreset("start", new GeneralStartCommand()); - instance.setPreset("write", new GeneralSendCommand()); + instance.setPreset("command", new GeneralSendCommand()); + instance.setPreset("input", new GeneralInputCommand()); instance.setPreset("stop", new GeneralStopCommand()); instance.setPreset("kill", new GeneralKillCommand()); instance.setPreset("restart", new GeneralRestartCommand()); } if (instance.config.processType === "docker") { instance.setPreset("start", new DockerStartCommand()); - instance.setPreset("write", new GeneralSendCommand()); + instance.setPreset("command", new GeneralSendCommand()); + instance.setPreset("input", new GeneralInputCommand()); instance.setPreset("stop", new GeneralStopCommand()); instance.setPreset("kill", new GeneralKillCommand()); instance.setPreset("restart", new GeneralRestartCommand()); diff --git a/src/entity/commands/docker/docker _start.ts b/src/entity/commands/docker/docker _start.ts index 040b741..ce98aa7 100644 --- a/src/entity/commands/docker/docker _start.ts +++ b/src/entity/commands/docker/docker _start.ts @@ -67,8 +67,8 @@ class DockerProcessAdapter extends EventEmitter implements IInstanceProcess { if (this.stream) this.stream.write(data); } - public kill(s?: string) { - this.container.kill(); + public async kill(s?: string) { + await this.container.kill(); return true; } diff --git a/src/entity/commands/general/general _input.ts b/src/entity/commands/general/general _input.ts new file mode 100644 index 0000000..28383c9 --- /dev/null +++ b/src/entity/commands/general/general _input.ts @@ -0,0 +1,39 @@ +/* + Copyright (C) 2022 RimuruChan + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + According to the AGPL, it is forbidden to delete all copyright notices, + and if you modify the source code, you must open source the + modified source code. + + 版权所有 (C) 2022 RimuruChan + + 该程序是免费软件,您可以重新分发和/或修改据 GNU Affero 通用公共许可证的条款, + 由自由软件基金会,许可证的第 3 版,或(由您选择)任何更高版本。 + + 根据 AGPL 与用户协议,您必须保留所有版权声明,如果修改源代码则必须开源修改后的源代码。 + 可以前往 https://mcsmanager.com/ 阅读用户协议,申请闭源开发授权等。 +*/ + +import Instance from "../../instance/instance"; +import { encode } from "iconv-lite"; +import InstanceCommand from "../base/command"; + +export default class GeneralInputCommand extends InstanceCommand { + constructor() { + super("SendInput"); + } + + async exec(instance: Instance, text?: string): Promise { + // 关服命令需要发送命令,但关服命令执行前会设置状态为关闭中状态。 + // 所以这里只能通过进程是否存在来执行命令 + if (!instance.process) { + instance.failure(new Error("命令执行失败,因为实例实际进程不存在.")); + } + instance.process.write(encode(text, instance.config.oe)); + } +} diff --git a/src/entity/commands/general/general _kill.ts b/src/entity/commands/general/general _kill.ts index a0f7ef7..c6b4315 100644 --- a/src/entity/commands/general/general _kill.ts +++ b/src/entity/commands/general/general _kill.ts @@ -29,7 +29,7 @@ export default class GeneralKillCommand extends InstanceCommand { async exec(instance: Instance) { if (instance.process) { - instance.process.kill("SIGKILL"); + await instance.process.kill("SIGKILL"); } instance.setLock(false); } diff --git a/src/entity/commands/input.ts b/src/entity/commands/input.ts new file mode 100644 index 0000000..3e48294 --- /dev/null +++ b/src/entity/commands/input.ts @@ -0,0 +1,36 @@ +/* + Copyright (C) 2022 RimuruChan + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + According to the AGPL, it is forbidden to delete all copyright notices, + and if you modify the source code, you must open source the + modified source code. + + 版权所有 (C) 2022 RimuruChan + + 该程序是免费软件,您可以重新分发和/或修改据 GNU Affero 通用公共许可证的条款, + 由自由软件基金会,许可证的第 3 版,或(由您选择)任何更高版本。 + + 根据 AGPL 与用户协议,您必须保留所有版权声明,如果修改源代码则必须开源修改后的源代码。 + 可以前往 https://mcsmanager.com/ 阅读用户协议,申请闭源开发授权等。 +*/ + +import Instance from "../instance/instance"; +import InstanceCommand from "./base/command"; + +export default class SendInput extends InstanceCommand { + public cmd: string; + + constructor(cmd: string) { + super("SendInput"); + this.cmd = cmd; + } + + async exec(instance: Instance) { + return await instance.execPreset("input", this.cmd); + } +} diff --git a/src/routers/stream_router.ts b/src/routers/stream_router.ts index efe0172..a3e0ae5 100644 --- a/src/routers/stream_router.ts +++ b/src/routers/stream_router.ts @@ -25,6 +25,7 @@ import { missionPassport } from "../service/mission_passport"; import InstanceSubsystem from "../service/system_instance"; import logger from "../service/log"; import SendCommand from "../entity/commands/cmd"; +import SendInput from "../entity/commands/input"; import ProcessInfo from "../entity/commands/process_info"; import ProcessInfoCommand from "../entity/commands/process_info"; @@ -98,7 +99,7 @@ routerApp.on("stream/detail", async (ctx) => { }); // 执行命令 -routerApp.on("stream/input", async (ctx, data) => { +routerApp.on("stream/command", async (ctx, data) => { try { const command = data.command; const instanceUuid = ctx.session.stream.instanceUuid; @@ -108,3 +109,15 @@ routerApp.on("stream/input", async (ctx, data) => { protocol.responseError(ctx, error); } }); + +// 处理终端输入 +routerApp.on("stream/input", async (ctx, data) => { + try { + const input = data.input; + const instanceUuid = ctx.session.stream.instanceUuid; + const instance = InstanceSubsystem.getInstance(instanceUuid); + await instance.exec(new SendInput(input)); + } catch (error) { + protocol.responseError(ctx, error); + } +}); diff --git a/src/service/system_instance.ts b/src/service/system_instance.ts index 53c2a13..baa36f9 100644 --- a/src/service/system_instance.ts +++ b/src/service/system_instance.ts @@ -78,8 +78,10 @@ class InstanceSubsystem extends EventEmitter { // 所有实例全部进行功能调度器 instance .forceExec(new FuntionDispatcher()) - .then((v) => {}) - .catch((v) => {}); + .then((v) => { + }) + .catch((v) => { + }); this.addInstance(instance); }); // 处理自动启动 @@ -159,7 +161,8 @@ class InstanceSubsystem extends EventEmitter { // 删除计划任务 InstanceControl.deleteInstanceAllTask(instanceUuid); // 异步删除文件 - if (deleteFile) fs.remove(instance.config.cwd, (err) => {}); + if (deleteFile) fs.remove(instance.config.cwd, (err) => { + }); return true; } throw new Error("Instance does not exist"); @@ -168,13 +171,15 @@ class InstanceSubsystem extends EventEmitter { forward(targetInstanceUuid: string, socket: Socket) { try { this.instanceStream.requestForward(socket, targetInstanceUuid); - } catch (err) {} + } catch (err) { + } } stopForward(targetInstanceUuid: string, socket: Socket) { try { this.instanceStream.cannelForward(socket, targetInstanceUuid); - } catch (err) {} + } catch (err) { + } } forEachForward(instanceUuid: string, callback: (socket: Socket) => void) { @@ -196,15 +201,18 @@ class InstanceSubsystem extends EventEmitter { } async exit() { + let promises = []; for (const iterator of this.instances) { const instance = iterator[1]; if (instance.status() != Instance.STATUS_STOP) { logger.info(`Instance ${instance.config.nickname} (${instance.instanceUuid}) is running or busy, and is being forced to end.`); - await instance.execCommand(new KillCommand()); + promises.push(instance.execCommand(new KillCommand()).then(() => { + StorageSubsystem.store("InstanceConfig", instance.instanceUuid, instance.config); + logger.info(`Instance ${instance.config.nickname} (${instance.instanceUuid}) saved successfully.`); + })); } - StorageSubsystem.store("InstanceConfig", instance.instanceUuid, instance.config); - logger.info(`Instance ${instance.config.nickname} (${instance.instanceUuid}) saved successfully.`); } + await Promise.all(promises); } } diff --git a/src/service/ui.ts b/src/service/ui.ts index 8acdff7..4675465 100644 --- a/src/service/ui.ts +++ b/src/service/ui.ts @@ -37,11 +37,11 @@ const rl = readline.createInterface({ console.log('[终端] 守护进程拥有基本的交互功能,请输入"help"查看更多信息'); function stdin() { - rl.question("> ", (answer) => { + rl.question("> ", async (answer) => { try { const cmds = answer.split(" "); logger.info(`[Terminal] ${answer}`); - const result = command(cmds[0], cmds[1], cmds[2], cmds[3]); + const result = await command(cmds[0], cmds[1], cmds[2], cmds[3]); if (result) console.log(result); else console.log(`Command ${answer} does not exist, type help to get help.`); } catch (err) { @@ -60,7 +60,7 @@ stdin(); * @param {String} cmd * @return {String} */ -function command(cmd: string, p1: string, p2: string, p3: string) { +async function command(cmd: string, p1: string, p2: string, p3: string) { if (cmd === "instance") { if (p1 === "start") { InstanceSubsystem.getInstance(p2).exec(new StartCommand("Terminal")); @@ -108,7 +108,7 @@ function command(cmd: string, p1: string, p2: string, p3: string) { if (cmd == "exit") { try { logger.info("Preparing to shut down the daemon..."); - InstanceSubsystem.exit(); + await InstanceSubsystem.exit(); // logger.info("Data saved, thanks for using, goodbye!"); logger.info("The data is saved, thanks for using, goodbye!"); logger.info("closed.");