From 7923aa8942b55884320ef2428417e3ee4b121613 Mon Sep 17 00:00:00 2001 From: Minteck Date: Mon, 28 Nov 2022 17:31:34 +0100 Subject: Initial commit --- Components/ChangeDir/index.js | 27 +++ Components/ChangeDir/metadata.yml | 33 ++++ Components/Clear/index.js | 3 + Components/Clear/metadata.yml | 20 ++ Components/CoreDaemon/index.js | 352 ++++++++++++++++++++++++++++++++++ Components/CoreDaemon/metadata.yml | 55 ++++++ Components/CurrentDir/index.js | 3 + Components/CurrentDir/metadata.yml | 20 ++ Components/DisplayFile/hex.js | 44 +++++ Components/DisplayFile/index.js | 43 +++++ Components/DisplayFile/metadata.yml | 46 +++++ Components/Documentation/index.js | 129 +++++++++++++ Components/Documentation/metadata.yml | 30 +++ Components/InternalShell/index.js | 14 ++ Components/InternalShell/metadata.yml | 17 ++ Components/ListDirectory/index.js | 85 ++++++++ Components/ListDirectory/metadata.yml | 40 ++++ Components/Restart/index.js | 11 ++ Components/Restart/metadata.yml | 26 +++ Components/ShowMisty/index.js | 30 +++ Components/ShowMisty/metadata.yml | 22 +++ Components/Shutdown/index.js | 11 ++ Components/Shutdown/metadata.yml | 27 +++ 23 files changed, 1088 insertions(+) create mode 100644 Components/ChangeDir/index.js create mode 100644 Components/ChangeDir/metadata.yml create mode 100644 Components/Clear/index.js create mode 100644 Components/Clear/metadata.yml create mode 100644 Components/CoreDaemon/index.js create mode 100644 Components/CoreDaemon/metadata.yml create mode 100644 Components/CurrentDir/index.js create mode 100644 Components/CurrentDir/metadata.yml create mode 100644 Components/DisplayFile/hex.js create mode 100644 Components/DisplayFile/index.js create mode 100644 Components/DisplayFile/metadata.yml create mode 100644 Components/Documentation/index.js create mode 100644 Components/Documentation/metadata.yml create mode 100644 Components/InternalShell/index.js create mode 100644 Components/InternalShell/metadata.yml create mode 100644 Components/ListDirectory/index.js create mode 100644 Components/ListDirectory/metadata.yml create mode 100644 Components/Restart/index.js create mode 100644 Components/Restart/metadata.yml create mode 100644 Components/ShowMisty/index.js create mode 100644 Components/ShowMisty/metadata.yml create mode 100644 Components/Shutdown/index.js create mode 100644 Components/Shutdown/metadata.yml (limited to 'Components') diff --git a/Components/ChangeDir/index.js b/Components/ChangeDir/index.js new file mode 100644 index 0000000..db9d82e --- /dev/null +++ b/Components/ChangeDir/index.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const path = require('path'); +const chalk = require(__dirname + '/../../MistyCore/node_modules/chalk'); + +module.exports = (arguments) => { + if (arguments._finals.length > 0) { + let newDirectory = path.resolve(arguments._finals[0]); + + if (fs.existsSync(newDirectory)) { + try { + fs.accessSync(newDirectory, fs.constants.R_OK); + + if (!fs.lstatSync(newDirectory).isDirectory()) { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Not a directory: ") + arguments['_finals'][0]); + } else { + process.chdir(newDirectory); + } + } catch (e) { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Permission denied: ") + arguments['_finals'][0]); + } + } else { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("No such file or directory: ") + arguments['_finals'][0]); + } + } else { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Missing operand")); + } +} \ No newline at end of file diff --git a/Components/ChangeDir/metadata.yml b/Components/ChangeDir/metadata.yml new file mode 100644 index 0000000..3818d75 --- /dev/null +++ b/Components/ChangeDir/metadata.yml @@ -0,0 +1,33 @@ +description: Changes the directory commands work from +internal: true + +aliases: + - cd + - chdir + - chwd + +manual: + summary: | + This command changes the current working directory (CWD) for a specified directory. + + parameters: [] + + final: + name: Dir + description: The directory to change to + required: true + multiple: false + command: false + path: true + daemon: false + + examples: + - command: ChangeDir / + description: Changes directory to go to the system's root directory + + - command: ChangeDir .. + description: Changes directory to go to the parent directory + + compatibility: + mistyos: '>=1.0.0' + kernel: '>=5.10.0' \ No newline at end of file diff --git a/Components/Clear/index.js b/Components/Clear/index.js new file mode 100644 index 0000000..68c98ce --- /dev/null +++ b/Components/Clear/index.js @@ -0,0 +1,3 @@ +module.exports = () => { + console.clear(); +} \ No newline at end of file diff --git a/Components/Clear/metadata.yml b/Components/Clear/metadata.yml new file mode 100644 index 0000000..fa28b6b --- /dev/null +++ b/Components/Clear/metadata.yml @@ -0,0 +1,20 @@ +description: Clears the current screen +internal: true + +aliases: + - cls + - clearscreen + +manual: + summary: | + This command clears the current screen. + + parameters: [] + + final: null + + examples: [] + + compatibility: + mistyos: '>=1.0.0' + kernel: '>=5.10.0' \ No newline at end of file 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 ?? "")); + 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 diff --git a/Components/CurrentDir/index.js b/Components/CurrentDir/index.js new file mode 100644 index 0000000..9d954fc --- /dev/null +++ b/Components/CurrentDir/index.js @@ -0,0 +1,3 @@ +module.exports = () => { + console.log(process.cwd()); +} \ No newline at end of file diff --git a/Components/CurrentDir/metadata.yml b/Components/CurrentDir/metadata.yml new file mode 100644 index 0000000..a8f5a32 --- /dev/null +++ b/Components/CurrentDir/metadata.yml @@ -0,0 +1,20 @@ +description: Returns the name of the current working directory +internal: true + +aliases: + - pwd + - cwd + +manual: + summary: | + This command returns the current working directory (CWD) commands run from. + + parameters: [] + + final: null + + examples: [] + + compatibility: + mistyos: '>=1.0.0' + kernel: '>=5.10.0' \ No newline at end of file diff --git a/Components/DisplayFile/hex.js b/Components/DisplayFile/hex.js new file mode 100644 index 0000000..f062608 --- /dev/null +++ b/Components/DisplayFile/hex.js @@ -0,0 +1,44 @@ +const chalk = require(__dirname + '/../../MistyCore/node_modules/chalk'); + +function hexEight(dec) { + let hex = Math.round(dec).toString(16); + let zero = "00000000"; + return zero.substring(0, 8 - hex.length) + hex; +} + +function fixLength(string, length) { + let end = " ".repeat(length); + if (string.length > length) return string.substring(0, length); + + return string + end.substring(0, length - string.length); +} + +module.exports = (buffer, color) => { + let ret = ""; + let lines = buffer.toString("hex").match(/.{1,32}/g).map(i => i.match(/.{1,2}/g).join(" ")); + + let byte = 0; + for (let line of lines) { + if (color) { + ret += chalk.gray(hexEight(byte)) + " " + fixLength(line, 47).split(" ").map((i, _) => { + if ((_ + 1) % 2 === 1) { + return chalk.red(i); + } else { + return chalk.magenta(i); + } + }).join(" ") + " " + chalk.blue(Buffer.from(line.replaceAll(" ", ""), "hex").toString().replace(/[\x00-\x1F]/gm, chalk.gray("."))) + "\n"; + } else { + ret += hexEight(byte) + " " + fixLength(line, 47) + " " + Buffer.from(line.replaceAll(" ", ""), "hex").toString().replace(/[\x00-\x1F]/gm, ".") + "\n"; + } + + byte += line.replaceAll(" ", "").length / 2; + } + + if (color) { + ret += chalk.gray(hexEight(byte)); + } else { + ret += hexEight(byte); + } + + return ret; +} \ No newline at end of file diff --git a/Components/DisplayFile/index.js b/Components/DisplayFile/index.js new file mode 100644 index 0000000..c6b3f25 --- /dev/null +++ b/Components/DisplayFile/index.js @@ -0,0 +1,43 @@ +let arguments = JSON.parse(process.argv[2]); +const chalk = require(__dirname + '/../../MistyCore/node_modules/chalk'); +const fs = require("fs"); +const child_process = require("child_process"); + +if (arguments._finals.length > 0) { + for (let file of arguments._finals) { + if (fs.existsSync(file)) { + try { + fs.accessSync(file, fs.constants.R_OK); + + if (arguments['hex']) { + fs.writeFileSync("/System/Volumes/VM/HexDump", require('./hex')(fs.readFileSync(file), !arguments['pagination'])); + file = "/System/Volumes/VM/HexDump"; + } + + if (fs.lstatSync(file).isDirectory()) { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Is a directory: ") + file); + } else { + if (arguments['pagination']) { + console.clear(); + child_process.execFileSync("/System/Binaries/busybox", [ "less", "-~", "-S", "--", file ], { stdio: "inherit" }); + console.clear(); + } else { + if (arguments['search']) { + console.log(chalk.white(fs.readFileSync(file).toString().replace(arguments['search'], chalk.bold.red(arguments['search'])))); + } else { + console.log(chalk.white(fs.readFileSync(file).toString())); + } + } + } + + if (fs.existsSync("/System/Volumes/VM/HexDump")) fs.unlinkSync("/System/Volumes/VM/HexDump"); + } catch (e) { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Permission denied: ") + file); + } + } else { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("No such file or directory: ") + file); + } + } +} else { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Missing operand")); +} \ No newline at end of file diff --git a/Components/DisplayFile/metadata.yml b/Components/DisplayFile/metadata.yml new file mode 100644 index 0000000..e05d233 --- /dev/null +++ b/Components/DisplayFile/metadata.yml @@ -0,0 +1,46 @@ +description: Displays one or multiple file(s) in the terminal +internal: false + +aliases: + - cat + - type + - more + - less + +manual: + summary: | + This command displays one or multiple file(s) in the terminal. + + parameters: + - name: Pagination + description: Enable pagination (scrolling) + required: false + + - name: Hex + description: Display an hexadecimal dump of the file instead of the contents of the file itself + required: false + + - name: Search + value: Query + description: Search for text in the file(s); does not work with -Pagination + required: false + + final: + name: File + description: The file(s) to display + required: true + multiple: true + command: false + path: true + daemon: false + + examples: + - command: DisplayFile /User/Document.txt + description: Display the "Document.txt" file + + - command: DisplayFile -Pagination /User/Essay.txt + description: Display the "Essay.txt" file with pagination + + compatibility: + mistyos: '>=1.0.0' + kernel: '>=5.10.0' \ No newline at end of file diff --git a/Components/Documentation/index.js b/Components/Documentation/index.js new file mode 100644 index 0000000..1b0e132 --- /dev/null +++ b/Components/Documentation/index.js @@ -0,0 +1,129 @@ +const fs = require('fs'); +const path = require('path'); +const chalk = require(__dirname + '/../../MistyCore/node_modules/chalk'); +const child_process = require("child_process"); +const semver = require(__dirname + '/../../MistyCore/node_modules/semver'); + +function prettyVersion(version) { + return version.replace(/^\*$/gm, "").replace(/^>=(| *)(\d+\.\d+\.\d+)$/gm, "$2 and later").replace(/^=(| *)(\d+\.\d+\.\d+)$/gm, "$2").replace(/^(\d+\.\d+\.\d+)(| +)-(| +)(\d+\.\d+\.\d+)$/gm, "$1 to $4"); +} + +module.exports = (arguments) => { + if (arguments['_finals'].length === 0) { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Missing operand")); + return; + } + + let input = arguments['_finals'][0]; + let command = null; + + let commands = global.commands; + let commandsMetadata = global.commandsMetadata; + + for (let cmd of commands) { + if (cmd.toLowerCase() === input.toLowerCase()) { + command = cmd; + break; + } + + let metadata = commandsMetadata[commands.indexOf(cmd)]; + if (metadata['aliases'].map(i => i.toLowerCase()).includes(input.toLowerCase())) { + command = cmd; + break; + } + } + + let metadata = commandsMetadata[commands.indexOf(command)]; + if (!command) { + console.log(chalk.bgRed.white("") + " " + chalk.red("Command not found: ") + input); + } else { + let out = chalk.reset("") + "\n"; + let pack = "*"; + + out += chalk.red(" ╔" + "═".repeat(command.length + 2) + "╗\n ║ " + command + " ║\n " + "╚" + "═".repeat(command.length + 2) + "╝") + "\n\n"; + + out += chalk.magenta(" Name\n ----") + "\n"; + out += chalk.whiteBright(" " + command + " - " + metadata.description) + "\n\n"; + + out += chalk.magenta(" Synopsis\n --------") + "\n"; + out += chalk.whiteBright(" " + chalk.green(command)); + + for (let param of metadata['manual']['parameters']) { + out += " "; + + if (!param.required) out += "["; + if (param.value) { + out += chalk.cyan("-" + param.name) + chalk.magenta(chalk.bgGray("=") + param.value); + } else { + out += chalk.magenta("-" + param.name); + } + if (!param.required) out += "]"; + } + + if (metadata['manual']['final']) { + out += " "; + + if (!metadata['manual']['final'].required) out += "["; + + if (metadata['manual']['final']['command']) { + out += chalk.yellow(metadata['manual']['final'].name); + } else { + out += metadata['manual']['final'].name; + } + + if (metadata['manual']['final'].multiple) out += " ..."; + if (!metadata['manual']['final'].required) out += "]"; + } + + out += "\n"; + + for (let alias of metadata.aliases) { + out += chalk.whiteBright(" " + chalk.green(alias) + chalk.gray(" (-> " + command + ")") + "\n"); + } + + if (metadata['manual']['summary'] || metadata['manual']['parameters'].length > 0) { + out += chalk.magenta("\n Description\n -----------") + "\n"; + if (metadata['manual']['summary']) { + out += " " + metadata['manual']['summary'].trim(); + for (let param of metadata['manual']['parameters']) { + out += "\n\n "; + + if (param.value) { + out += chalk.cyan("-" + param.name) + chalk.magenta(chalk.bgGray("=") + param.value); + } else { + out += chalk.magenta("-" + param.name); + } + + out += "\n " + param.description ?? ""; + } + + if (metadata['manual']['final']) { + out += "\n\n "; + + if (metadata['manual']['final']['command']) { + out += chalk.yellow(metadata['manual']['final'].name); + } else { + out += metadata['manual']['final'].name; + } + + if (metadata['manual']['final'].multiple) out += " ..."; + out += "\n " + metadata['manual']['final'].description ?? ""; + } + } + } + + if (metadata['manual']['examples']) { + out += chalk.magenta("\n\n Examples\n --------") + "\n"; + + for (let example of metadata['manual']['examples']) { + out += " " + example.description + "\n " + chalk.blue("> ") + example.command + "\n"; + } + } + + out += chalk.magenta("\n\n Compatibility\n -------------") + "\n"; + out += " MistyOS: " + prettyVersion(metadata['manual']['compatibility']['mistyos']) + "\n"; + out += " Kernel: " + prettyVersion(metadata['manual']['compatibility']['kernel']); + + console.log("\n" + out.trim() + "\n"); + } +} \ No newline at end of file diff --git a/Components/Documentation/metadata.yml b/Components/Documentation/metadata.yml new file mode 100644 index 0000000..799f509 --- /dev/null +++ b/Components/Documentation/metadata.yml @@ -0,0 +1,30 @@ +description: Shows documentation about a command +internal: true + +aliases: + - man + - help + - docs + +manual: + summary: | + This command shows documentation about another command, including how to use it. + + parameters: [] + + final: + name: Command + description: The command to get help for + required: true + multiple: false + command: true + path: false + daemon: false + + examples: + - command: Documentation ChangeDir + description: Shows documentation for the ChangeDir command + + compatibility: + mistyos: '>=1.0.0' + kernel: '>=5.10.0' \ No newline at end of file diff --git a/Components/InternalShell/index.js b/Components/InternalShell/index.js new file mode 100644 index 0000000..682da6e --- /dev/null +++ b/Components/InternalShell/index.js @@ -0,0 +1,14 @@ +const child_process = require("child_process"); +global.log = require('../../MistyCore/log'); + +module.exports = (arguments) => { + let args = []; + + if (arguments['_finals'].length > 0) { + args = [ "-c", arguments['_finals'].join(" ") ]; + } + + log("Shell-InternalShell", "Starting internal shell"); + child_process.execFileSync("/System/Binaries/sh", args, { stdio: "inherit" }); + log("Shell-InternalShell", "Stopped internal shell"); +} \ No newline at end of file diff --git a/Components/InternalShell/metadata.yml b/Components/InternalShell/metadata.yml new file mode 100644 index 0000000..d196032 --- /dev/null +++ b/Components/InternalShell/metadata.yml @@ -0,0 +1,17 @@ +description: Internal shell +internal: true + +aliases: [] + +manual: + summary: "" + + parameters: [] + + final: null + + examples: [] + + compatibility: + mistyos: '>=1.0.0' + kernel: '>=5.10.0' diff --git a/Components/ListDirectory/index.js b/Components/ListDirectory/index.js new file mode 100644 index 0000000..a7fa45b --- /dev/null +++ b/Components/ListDirectory/index.js @@ -0,0 +1,85 @@ +let arguments = JSON.parse(process.argv[2]); +const chalk = require(__dirname + '/../../MistyCore/node_modules/chalk'); +const fs = require("fs"); +const path = require('path'); + +let list = []; + +function processList(list, dir, arguments) { + if (!Object.keys(arguments).includes("all") && dir === "/") { + list = list.filter(i => i !== "dev" && i !== "etc" && i !== "bin" && i !== "lib" && i !== "lib64" && i !== "lost+found" && i !== "proc" && i !== "run" && i !== "sys" && i !== "MistyOSPrivate"); + } else if (!Object.keys(arguments).includes("All") &&dir === "/User") { + list = list.filter(i => i !== "Library"); + } + + if (Object.keys(arguments).includes("all")) list.unshift(".", ".."); + if (Object.keys(arguments).includes("search")) list = list.filter(i => i.includes(arguments["search"])); + + if (!Object.keys(arguments).includes("all")) list = list.filter(i => !i.startsWith(".")); + return list.map((i) => { + if (fs.existsSync(dir + "/" + i)) { + try { + fs.accessSync(dir + "/" + i, fs.constants.R_OK); + + if (fs.lstatSync(dir + "/" + i).isDirectory()) { + return i + chalk.gray("/"); + } else if (fs.lstatSync(dir + "/" + i).isSymbolicLink()) { + return i + chalk.gray(">"); + } else { + return i; + } + } catch (e) { + return i + chalk.gray("?"); + } + } else { + return i + chalk.gray("x"); + } + }); +} + +if (arguments._finals.length > 0) { + if (arguments._finals.length === 1) { + if (fs.existsSync(arguments._finals[0])) { + try { + fs.accessSync(arguments._finals[0], fs.constants.R_OK); + + if (!fs.lstatSync(arguments._finals[0]).isDirectory()) { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Not a directory: ") + arguments._finals[0]); + } else { + list = require('fs').readdirSync(arguments._finals[0]); + list = processList(list, path.resolve(arguments['_finals'][0]), arguments); + console.log(list.join(chalk.gray(", "))); + } + } catch (e) { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("Permission denied: ") + arguments._finals[0]); + } + } else { + console.log(chalk.bgYellow.white("") + " " + chalk.yellow("No such file or directory: ") + arguments._finals[0]); + } + } else { + for (let final of arguments._finals) { + console.log(final + ":"); + if (fs.existsSync(final)) { + try { + fs.accessSync(final, fs.constants.R_OK); + + if (!fs.lstatSync(final).isDirectory()) { + console.log(" " + chalk.bgYellow.white("") + " " + chalk.yellow("Not a directory: ") + final); + } else { + list = require('fs').readdirSync(final); + list = processList(list, path.resolve(final), arguments); + console.log(" " + list.join(chalk.gray(", "))); + } + } catch (e) { + console.log(" " + chalk.bgYellow.white("") + " " + chalk.yellow("Permission denied: ") + final); + } + } else { + console.log(" " + chalk.bgYellow.white("") + " " + chalk.yellow("No such file or directory: ") + final); + } + } + } +} else { + list = require('fs').readdirSync("."); + list = processList(list, path.resolve("."), arguments); + console.log(list.join(chalk.gray(", "))); +} \ No newline at end of file diff --git a/Components/ListDirectory/metadata.yml b/Components/ListDirectory/metadata.yml new file mode 100644 index 0000000..431c999 --- /dev/null +++ b/Components/ListDirectory/metadata.yml @@ -0,0 +1,40 @@ +description: Lists files in the current or a select directory +internal: false + +aliases: + - ls + - dir + +manual: + summary: | + This command lists all files that exist in the specific directory, or the current working directory if not specified. + + parameters: + - name: All + description: Show hidden files (starting with . or system files) + required: false + + - name: Search + value: Query + description: Search for a specific file containing the query + required: false + + final: + name: Dir + description: The directory or directories to list files of + required: false + multiple: true + command: false + path: true + daemon: false + + examples: + - command: ListDirectory -All + description: List all the files in the current directory, including hidden files + + - command: ListDirectory / + description: List all the files in the root directory + + compatibility: + mistyos: '>=1.0.0' + kernel: '>=5.10.0' \ No newline at end of file diff --git a/Components/Restart/index.js b/Components/Restart/index.js new file mode 100644 index 0000000..3846afe --- /dev/null +++ b/Components/Restart/index.js @@ -0,0 +1,11 @@ +let net = require('net'); +let systemRoot = "/System/Library"; +const socket = net.createConnection(systemRoot + "/../Volumes/VM/MistyCore-Socket", () => { + socket.write(JSON.stringify({ + action: "SERVICE", + payload: { + option: "restart", + service: "MistyOS.yml" + } + })); +}); \ No newline at end of file diff --git a/Components/Restart/metadata.yml b/Components/Restart/metadata.yml new file mode 100644 index 0000000..165b987 --- /dev/null +++ b/Components/Restart/metadata.yml @@ -0,0 +1,26 @@ +description: Restarts the system properly +internal: false + +aliases: + - reboot + - boot + - reset + +manual: + summary: | + This command restarts the system by passing a reboot command through MistyCore, causing the system to restart properly. + + parameters: [] + + final: null + + examples: + - command: Restart + description: Restarts the system normally + + - command: CoreDaemon -Restart MistyOS + description: Restarts the system by using MistyCore's frontend directly + + compatibility: + mistyos: '>=1.2.0' + kernel: '>=5.10.0' \ No newline at end of file diff --git a/Components/ShowMisty/index.js b/Components/ShowMisty/index.js new file mode 100644 index 0000000..2078a46 --- /dev/null +++ b/Components/ShowMisty/index.js @@ -0,0 +1,30 @@ +const chalk = require(__dirname + '/../../MistyCore/node_modules/chalk'); +const fs = require("fs"); + +module.exports = () => { + process.stdout.write("\n "); + process.stdout.write(chalk.blue("██")); + process.stdout.write(chalk.yellow("██")); + process.stdout.write(" \n "); + process.stdout.write(chalk.yellow("██")); + process.stdout.write(chalk.cyan("██")); + process.stdout.write(" \n "); + process.stdout.write(chalk.yellow("██")); + process.stdout.write(" "); + process.stdout.write(chalk.blue("██")); + process.stdout.write(chalk.cyan("██")); + process.stdout.write(chalk.cyan("██")); + process.stdout.write(" \n "); + process.stdout.write(chalk.blue("██")); + process.stdout.write(" "); + process.stdout.write(chalk.cyan("██")); + process.stdout.write(chalk.cyan("██")); + process.stdout.write(chalk.cyan("██")); + process.stdout.write(" \n "); + process.stdout.write(chalk.yellow("██")); + process.stdout.write(" "); + process.stdout.write(chalk.cyan("██")); + process.stdout.write(" "); + process.stdout.write(chalk.cyan("██")); + process.stdout.write(" \n\n"); +} \ No newline at end of file diff --git a/Components/ShowMisty/metadata.yml b/Components/ShowMisty/metadata.yml new file mode 100644 index 0000000..e4d4266 --- /dev/null +++ b/Components/ShowMisty/metadata.yml @@ -0,0 +1,22 @@ +description: Shows a Misty ASCII art +internal: true + +aliases: + - misty + - brightdawn + - mistybrightdawn + - mlp + +manual: + summary: | + This command shows a Misty ASCII art on the console. Yes, that's literally it. + + parameters: [] + + final: null + + examples: [] + + compatibility: + mistyos: '>=1.0.0' + kernel: '>=5.10.0' \ No newline at end of file diff --git a/Components/Shutdown/index.js b/Components/Shutdown/index.js new file mode 100644 index 0000000..cd26bd1 --- /dev/null +++ b/Components/Shutdown/index.js @@ -0,0 +1,11 @@ +let net = require('net'); +let systemRoot = "/System/Library"; +const socket = net.createConnection(systemRoot + "/../Volumes/VM/MistyCore-Socket", () => { + socket.write(JSON.stringify({ + action: "SERVICE", + payload: { + option: "stop", + service: "MistyOS.yml" + } + })); +}); \ No newline at end of file diff --git a/Components/Shutdown/metadata.yml b/Components/Shutdown/metadata.yml new file mode 100644 index 0000000..b335adb --- /dev/null +++ b/Components/Shutdown/metadata.yml @@ -0,0 +1,27 @@ +description: Shuts down the system properly +internal: false + +aliases: + - poweroff + - off + - turnoff + - halt + +manual: + summary: | + This command shuts down the system by passing a shutdown command through MistyCore, causing the system to power off properly. + + parameters: [] + + final: null + + examples: + - command: Shutdown + description: Shuts down the system normally + + - command: CoreDaemon -Stop MistyOS + description: Shuts down the system by using MistyCore's frontend directly + + compatibility: + mistyos: '>=1.2.0' + kernel: '>=5.10.0' \ No newline at end of file -- cgit