diff options
Diffstat (limited to 'Components/CoreDaemon')
-rw-r--r-- | Components/CoreDaemon/index.js | 352 | ||||
-rw-r--r-- | Components/CoreDaemon/metadata.yml | 55 |
2 files changed, 407 insertions, 0 deletions
diff --git a/Components/CoreDaemon/index.js b/Components/CoreDaemon/index.js new file mode 100644 index 0000000..5e830b8 --- /dev/null +++ b/Components/CoreDaemon/index.js @@ -0,0 +1,352 @@ +let arguments = JSON.parse(process.argv[2]); +const chalk = require(__dirname + '/../../MistyCore/node_modules/chalk'); +const YAML = require(__dirname + '/../../MistyCore/node_modules/yaml'); +const si = require(__dirname + '/../../MistyCore/node_modules/systeminformation'); +const fs = require('fs'); +const net = require("net"); +let systemRoot = "/System/Library"; + +function size(bytes) { + if (bytes > 1024) { + if (bytes > 1024**2) { + if (bytes > 1024**3) { + if (bytes > 1024**4) { + return (bytes / 1024**4).toFixed(2) + "T"; + } else { + return (bytes / 1024**3).toFixed(2) + "G"; + } + } else { + return (bytes / 1024**2).toFixed(2) + "M"; + } + } else { + return (bytes / 1024).toFixed(2) + "K"; + } + } else { + return bytes + "B"; + } +} + +function waitForState(daemon, status) { + return new Promise((resolve) => { + let i = 0; + let me = setInterval(() => { + if (status === "running") { + if (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString().trim() !== "-1" && fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString().trim() !== "-3" && fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString().trim() !== "-2") { + clearInterval(me); + + if (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString().trim() !== "0") { + console.log(daemon + " is now running with PID " + fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString().trim()); + } else if (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString().trim() !== "-2") { + console.log(daemon + " has failed\nRun 'CoreDaemon -Status " + daemon + "' for details; 'CoreDaemon -ForceStop " + daemon + "' to force stop."); + process.exit(2); + } else { + console.log(daemon + " has now started"); + } + + resolve(); + } + } else if (status === "stopped") { + if (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString().trim() === "-1") { + clearInterval(me); + console.log(daemon + " is now stopped"); + resolve(); + } + if (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString().trim() === "-2") { + clearInterval(me); + console.log(daemon + " has failed\nRun 'CoreDaemon -Status " + daemon + "' for details; 'CoreDaemon -ForceStop " + daemon + "' to force stop."); + process.exit(2); + resolve(); + } + } else { + clearInterval(me); + resolve(); + } + + if (i >= 100) { + console.log("Timed out waiting for " + daemon + " after 10 seconds. Has the launch daemon failed?\nRun 'CoreDaemon -Status " + daemon + "' for details; 'CoreDaemon -ForceStop " + daemon + "' to force stop."); + process.exit(2); + } + + i++; + }, 100); + }); +} + +(async () => { + let processes = (await si.processes()).list; + + if (arguments['start'] && !arguments['stop'] && !arguments['restart'] && !arguments['status'] && !arguments['forcestop']) { + if (arguments['_finals'].length > 0) { + if (!fs.existsSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0])) { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("No such launch daemon: ") + arguments['_finals'][0]); + return; + } + + let state; switch (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString()) { case "0": state = "running"; break; case "-1": state = "stopped"; break; case "-2": state = "failed"; break; default: state = "running"; break } + + if (state === "running") { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("The launch daemon is not stopped or failed")); + return; + } + + const socket = net.createConnection(systemRoot + "/../Volumes/VM/MistyCore-Socket", () => { + console.log("Waiting for " + arguments['_finals'][0] + " to have started..."); + socket.write(JSON.stringify({ + action: "SERVICE", + payload: { + option: "start", + service: arguments['_finals'][0] + ".yml" + } + })); + waitForState(arguments['_finals'][0], "running"); + }); + } else { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("Missing operand")); + } + } else if (arguments['stop'] && !arguments['start'] && !arguments['restart'] && !arguments['status'] && !arguments['forcestop']) { + if (arguments['_finals'].length > 0) { + if (!fs.existsSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0])) { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("No such launch daemon: ") + arguments['_finals'][0]); + return; + } + + let state; switch (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString()) { case "0": state = "running"; break; case "-1": state = "stopped"; break; case "-2": state = "failed"; break; default: state = "running"; break } + + if (state !== "running") { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("The launch daemon is not running")); + return; + } + + const socket = net.createConnection(systemRoot + "/../Volumes/VM/MistyCore-Socket", async () => { + console.log("Waiting for " + arguments['_finals'][0] + " to have stopped..."); + socket.write(JSON.stringify({ + action: "SERVICE", + payload: { + option: "stop", + service: arguments['_finals'][0] + ".yml" + } + })); + await waitForState(arguments['_finals'][0], "stopped"); + }); + } else { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("Missing operand")); + } + } else if (arguments['forcestop'] && !arguments['start'] && !arguments['restart'] && !arguments['status'] && !arguments['stop']) { + if (arguments['_finals'].length > 0) { + if (!fs.existsSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0])) { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("No such launch daemon: ") + arguments['_finals'][0]); + return; + } + + let state; switch (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString()) { case "0": state = "running"; break; case "-1": state = "stopped"; break; case "-2": state = "failed"; break; default: state = "running"; break } + + if (state !== "running") { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("The launch daemon is not running")); + return; + } + + const socket = net.createConnection(systemRoot + "/../Volumes/VM/MistyCore-Socket", async () => { + console.log("Waiting for " + arguments['_finals'][0] + " to have force-stopped..."); + socket.write(JSON.stringify({ + action: "SERVICE", + payload: { + option: "forcestop", + service: arguments['_finals'][0] + ".yml" + } + })); + await waitForState(arguments['_finals'][0], "stopped"); + }); + } else { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("Missing operand")); + } + } else if (arguments['restart'] && !arguments['stop'] && !arguments['start'] && !arguments['status'] && !arguments['forcestop']) { + if (arguments['_finals'].length > 0) { + if (!fs.existsSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0])) { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("No such launch daemon: ") + arguments['_finals'][0]); + return; + } + + let state; switch (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString()) { case "0": state = "running"; break; case "-1": state = "stopped"; break; case "-2": state = "failed"; break; default: state = "running"; break } + + if (state !== "running") { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("The launch daemon is not running")); + return; + } + + const socket = net.createConnection(systemRoot + "/../Volumes/VM/MistyCore-Socket", async () => { + console.log("Waiting for " + arguments['_finals'][0] + " to have restarted..."); + socket.write(JSON.stringify({ + action: "SERVICE", + payload: { + option: "restart", + service: arguments['_finals'][0] + ".yml" + } + })); + await waitForState(arguments['_finals'][0], "running"); + }); + } else { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("Missing operand")); + } + } else if (arguments['status'] && !arguments['stop'] && !arguments['restart'] && !arguments['start'] && !arguments['forcestop']) { + if (arguments['_finals'].length > 0) { + if (!fs.existsSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0])) { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("No such launch daemon: ") + arguments['_finals'][0]); + return; + } + + let state; switch (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString()) { case "0": state = "running"; break; case "-1": state = "stopped"; break; case "-3": state = "starting"; break; case "-2": state = "failed"; break; default: state = "running"; break } + + let name = arguments['_finals'][0], displayName = arguments['_finals'][0]; + let service = YAML.parse(fs.readFileSync(systemRoot + "/LaunchDaemons/" + name + ".yml").toString()); + + let memoryUsage; + let pid; + let parent = "-"; + + switch (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString()) { + case "0": + case "-1": + case "-2": + case "-3": + memoryUsage = "-"; + pid = "-"; + break; + + default: + let _processInfo = processes.filter(i => i.pid === parseInt(fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString())); + + if (_processInfo.length > 0) { + pid = fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString() + " (" + _processInfo[0].name + ")"; + + let _parentInfo = processes.filter(i => i.pid === _processInfo[0].parentPid); + if (_parentInfo.length > 0) { + parent = _processInfo[0].parentPid + " (" + _parentInfo[0].name + ")"; + } else { + parent = _processInfo[0].parentPid; + } + + memoryUsage = size(_processInfo[0].memVsz); + } else { + pid = fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + arguments['_finals'][0]).toString(); + memoryUsage = "-"; + } + + break; + } + + let statusColor; + + switch (state) { + case "running": + statusColor = chalk.green("running"); + break; + + case "starting": + statusColor = chalk.yellow("starting"); + break; + + case "failed": + statusColor = chalk.cyan("failed"); + break; + + case "stopped": + statusColor = chalk.red("stopped"); + break; + + default: + statusColor = chalk.gray("unknown"); + break; + } + + let reachDate = "-"; + + if (fs.existsSync(systemRoot + "/../Volumes/VM/LaunchDaemonsTimes/" + arguments['_finals'][0])) { + reachDate = new Date(parseInt(fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemonsTimes/" + arguments['_finals'][0]).toString())).toString(); + } + + let autoStart; + if (service.metadata.target === false) { + autoStart = chalk.gray("manual"); + } else { + if (!isNaN(parseInt(service.metadata.target)) && parseInt(service.metadata.target) > -1 && parseInt(service.metadata.target) < 10) { + autoStart = "on target " + parseInt(service.metadata.target); + } else { + autoStart = chalk.red("invalid"); + } + } + + console.log(name + " - " + (service.metadata.description ?? "<no description>")); + console.log(chalk.cyan(" Loaded from:") + " " + systemRoot + "/LaunchDaemons/" + name + ".yml"); + console.log(chalk.cyan(" Auto-start:") + " " + autoStart); + console.log(chalk.cyan(" Status:") + " " + statusColor); + console.log(chalk.cyan(" Reached:") + " " + reachDate); + console.log(chalk.cyan(" Main PID:") + " " + pid); + console.log(chalk.cyan(" Initiator:") + " " + parent); + console.log(chalk.cyan(" Memory:") + " " + memoryUsage); + + if (service.commands.stop || service.commands.restart) { + console.log(chalk.cyan(" Type:") + " MistyCore:RunOnce"); + } else { + console.log(chalk.cyan(" Type:") + " MistyCore:Daemon"); + } + + console.log(""); + + switch (state) { + case "running": + console.log("Run 'Logger -Unit " + name + "' to view logs; 'CoreDaemon -Stop " + name + "' to stop."); + break; + + case "starting": + console.log("Run 'Logger -Unit " + name + "' to view logs; 'CoreDaemon -ForceStop " + name + "' to abort."); + break; + + case "failed": + console.log("Run 'Logger -Unit " + name + "' to view logs; 'CoreDaemon -Start " + name + "' to start."); + break; + + case "stopped": + console.log("Run 'Logger -Unit " + name + "' to view logs; 'CoreDaemon -Start " + name + "' to start."); + break; + + default: + console.log("Run 'Logger -Unit " + name + "' to view logs"); + break; + } + } else { + for (let name of fs.readdirSync(systemRoot + "/LaunchDaemons")) { + let daemon = name.substring(0, name.length - 4); + + if (fs.existsSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon)) { + let state; switch (fs.readFileSync(systemRoot + "/../Volumes/VM/LaunchDaemons/" + daemon).toString()) { case "0": state = "running"; break; case "-1": state = "stopped"; break; case "-3": state = "starting"; break; case "-2": state = "failed"; break; default: state = "running"; break } + + switch (state) { + case "running": + console.log(" [ " + chalk.green("+") + " ] " + daemon); + break; + + case "starting": + console.log(" [ " + chalk.yellow("W") + " ] " + daemon); + break; + + case "failed": + console.log(" [ " + chalk.cyan("x") + " ] " + daemon); + break; + + case "stopped": + console.log(" [ " + chalk.red("-") + " ] " + daemon); + break; + + default: + console.log(" [ " + chalk.gray("?") + " ] " + daemon); + break; + } + } else { + console.log(" [ " + chalk.gray("?") + " ] " + daemon); + } + } + } + } else { + console.log(chalk.bgYellow.white("<!>") + " " + chalk.yellow("Only exactly one operation can be done at a time")); + } +})();
\ No newline at end of file diff --git a/Components/CoreDaemon/metadata.yml b/Components/CoreDaemon/metadata.yml new file mode 100644 index 0000000..7cea718 --- /dev/null +++ b/Components/CoreDaemon/metadata.yml @@ -0,0 +1,55 @@ +description: Manages launch daemons running on the system +internal: false + +aliases: + - service + - systemctl + - rc-service + +manual: + summary: | + This command starts, stops, gets the status and restarts launch daemons on the system. + + parameters: + - name: Start + description: Start a launch daemon + required: false + + - name: Stop + description: Stop a launch daemon + required: false + + - name: ForceStop + description: Force-stop a launch daemon, having it exit immediately without finishing properly + required: false + + - name: Restart + description: Restart a launch daemon + required: false + + - name: Status + description: Show the status of a launch daemon + required: false + + final: + name: LaunchDaemon + description: The launch daemon to manage + required: false + multiple: false + command: false + path: false + daemon: true + + examples: + - command: CoreDaemon -Restart Something + description: Restarts the launch daemon named Something + + - command: CoreDaemon -ForceStop HangingThing + description: Forces the launch daemon named HangingThing to stop + + - command: CoreDaemon -Status + description: Shows the status of all launch daemons on the system + + compatibility: + mistyos: '>=1.2.0' + kernel: '>=5.10.0'
\ No newline at end of file |