commit 2479abb1b1d5a26fd45b6ee93d27e5c33e8960fe Author: velvettear Date: Wed Feb 2 17:07:16 2022 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..65f5196 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/remex.js" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/config.json b/config.json new file mode 100644 index 0000000..61d2413 --- /dev/null +++ b/config.json @@ -0,0 +1,22 @@ +{ + "server": { + "listen": "0.0.0.0", + "port": 3000, + "timestamp": "DD.MM.YYYY HH:mm:ss:SS" + }, + "log": { + "level": "debug" + }, + "api": [ + { + "url": "/uptime", + "type": "get", + "command": "uptime123", + "args": [ + "-V", + "-p" + ], + "passargs": true + } + ] +} \ No newline at end of file diff --git a/libs/commands.js b/libs/commands.js new file mode 100644 index 0000000..2631a9c --- /dev/null +++ b/libs/commands.js @@ -0,0 +1,27 @@ +const logger = require('../libs/logger.js'); +const { spawn } = require('child_process') + +function execute(endpoint) { + if (!endpoint || !endpoint.command) { + logger.warn('no command defined'); + return; + } + logger.debug('executing command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\'...'); + var cmd = spawn(endpoint.command, endpoint.args); + cmd.stdout.on('data', function(data) { + logger.debug(data); + }); + cmd.stderr.on('data', function(data) { + logger.error(data); + }); + cmd.on('close', function(code) { + logger.debug('command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\' finished with exit code ' + code); + }); + cmd.on('error', function(err) { + logger.error('command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\' encountered an error >>> ' + err); + }); +} + +module.exports = { + execute +} \ No newline at end of file diff --git a/libs/logger.js b/libs/logger.js new file mode 100644 index 0000000..bdbdcfa --- /dev/null +++ b/libs/logger.js @@ -0,0 +1,110 @@ +const config = require("../config.json"); +const moment = require("moment"); + +// constants +const LOG_PREFIX_DEBUG = "debug"; +const LOG_PREFIX_INFO = "info"; +const LOG_PREFIX_WARNING = "warning"; +const LOG_PREFIX_ERROR = "error"; +const LOGLEVEL_DEBUG = 0; +const LOGLEVEL_INFO = 1; +const LOGLEVEL_WARNING = 2; +const LOGLEVEL_ERROR = 3; + +// set loglevel on "require" +const loglevel = function() { + switch (config.log.level) { + case LOG_PREFIX_DEBUG: + case LOGLEVEL_DEBUG: + return LOGLEVEL_DEBUG; + case LOG_PREFIX_INFO: + case LOGLEVEL_INFO: + return LOGLEVEL_INFO; + case LOG_PREFIX_WARNING: + case LOGLEVEL_WARNING: + return LOGLEVEL_WARNING; + case LOG_PREFIX_ERROR: + case LOGLEVEL_ERROR: + return LOGLEVEL_ERROR; + default: + return LOGLEVEL_INFO; + } +}(); + +// log a http request +function request(request) { + let message = "[" + request.method + "] url: \"" + request.url + "\""; + let counter = 1; + for (let param in request.body) { + message += ", parameter " + counter + ": \"" + param + "=" + request.body[param] + "\""; + counter++; + } + debug(message.trim()); +} + +// prefix log with "info" +function info(message) { + if (loglevel > LOGLEVEL_INFO) { + return; + } + trace(message); +} + +// prefix log with "info" +function warn(message) { + if (loglevel > LOGLEVEL_WARNING) { + return; + } + trace(message, "warning"); +} + +// prefix log with "debug" +function debug(message) { + if (loglevel > LOGLEVEL_DEBUG) { + return; + } + trace(message, "debug"); +} + +// prefix log with "error" +function error(message) { + if (loglevel > LOGLEVEL_ERROR) { + return; + } + trace(message, "error"); +} + +// default logging function +function trace(message, prefix) { + if (message === undefined || message === null || message.length === 0) { + return; + } + if (prefix === undefined || prefix === null || prefix.length === 0) { + prefix = "info"; + } + let print; + switch (prefix) { + case "error": + print = console.error; + break; + case "debug": + print = console.debug; + break; + case "warning": + print = console.warn; + break; + default: + print = console.log; + } + message = moment().format(config.server.timestamp) + " | " + prefix + " > " + message; + print(message); +} + +// exports +module.exports = { + info, + warn, + debug, + error, + request +}; \ No newline at end of file diff --git a/libs/server.js b/libs/server.js new file mode 100644 index 0000000..c3606c3 --- /dev/null +++ b/libs/server.js @@ -0,0 +1,55 @@ +const config = require('../config.json'); +const logger = require('../libs/logger.js'); +const commands = require('../libs/commands.js'); +const http = require('http'); + +var server; +var api; + +function start(callback) { + buildAPI(); + if (!server) { + server = http.createServer(); + } + server.listen(config.server.port, config.server.listen) + .on('listening', function () { + logger.debug('server listening on ' + config.server.listen + ':' + config.server.port + '...'); + handleRequests(); + }); +} + +function handleRequests() { + server.on('request', function (request, response) { + logger.request(request); + var endpoint = api.get(request.url); + if (!endpoint) { + response.writeHead(501); + response.end('endpoint not defined\n'); + return; + } + if (request.method.toLowerCase() != (endpoint.type.toLowerCase())) { + response.writeHead(405); + response.end('endpoint does not support ' + request.method + ' requests\n'); + return; + } + commands.execute(endpoint); + response.end(); + }); +} + +function buildAPI(callback) { + if (!config.api) { + logger.warn('no api defined'); + } + api = new Map(); + config.api.forEach(function(endpoint) { + var url = endpoint.url; + var tmp = endpoint; + delete tmp.url; + api.set(url, tmp); + }); +} + +module.exports = { + start +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..19f692f --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "remex", + "version": "0.0.1", + "description": "execute local commands remotely via http requests", + "main": "remex.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "scripts", + "commands", + "remote", + "api" + ], + "author": "Daniel Sommer ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://git.velvettear.de/velvettear/remex.git" + }, + "dependencies": { + "moment": "^2.29.1" + } +} \ No newline at end of file diff --git a/remex.js b/remex.js new file mode 100644 index 0000000..946acb1 --- /dev/null +++ b/remex.js @@ -0,0 +1,11 @@ +const logger = require('./libs/logger.js'); +const packageJSON = require('./package.json'); +const server = require('./libs/server.js') + +logger.info("launching " + packageJSON.name + " " + packageJSON.version) +server.start(function(err) { + if (err) { + logger.error(err); + process.exit(1); + } +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..30de4ea --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +moment@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==