Compare commits
10 commits
739c52fc84
...
361a46daa6
Author | SHA1 | Date | |
---|---|---|---|
361a46daa6 | |||
c728314af2 | |||
bb1d8f3f50 | |||
fe57525ed9 | |||
8a9af168a7 | |||
2b30557a14 | |||
01875bc0b3 | |||
b42f8c5612 | |||
7dc504171b | |||
daeb9d004e |
14 changed files with 303 additions and 100 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1 +1,3 @@
|
||||||
node_modules/
|
config.json
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
17
|
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
|
@ -3,13 +3,16 @@
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"type": "pwa-node",
|
"type": "pwa-node",
|
||||||
"runtimeVersion": "16",
|
"runtimeVersion": "17",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "remex",
|
"name": "remex",
|
||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
"<node_internals>/**"
|
"<node_internals>/**"
|
||||||
],
|
],
|
||||||
"program": "${workspaceFolder}/remex.js"
|
"program": "${workspaceFolder}/remex.js",
|
||||||
|
"args": [
|
||||||
|
"${workspaceFolder}/example_config.json"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
20
LICENSE.md
Normal file
20
LICENSE.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# MIT License
|
||||||
|
**Copyright (c) 2022 Daniel Sommer \<daniel.sommer@velvettear.de\>**
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**
|
69
README.md
69
README.md
|
@ -1,3 +1,70 @@
|
||||||
# remex
|
# remex
|
||||||
|
|
||||||
execute local commands remotely via http requests
|
execute local commands remotely via http requests
|
||||||
|
|
||||||
|
## requirements
|
||||||
|
|
||||||
|
- node.js
|
||||||
|
- [nvm](https://github.com/nvm-sh/nvm)
|
||||||
|
|
||||||
|
## setup (as root)
|
||||||
|
|
||||||
|
- install nvm
|
||||||
|
|
||||||
|
- to load nvm restart your terminal or `source ~/.nvm/nvm.sh`
|
||||||
|
|
||||||
|
- clone the project (to '/opt/remex')
|
||||||
|
`git clone https://git.velvettear.de/velvettear/remex.git /opt/remex`
|
||||||
|
|
||||||
|
- install and switch to a supported node.js version (automatically done via .nvmrc file)
|
||||||
|
`nvm install`
|
||||||
|
|
||||||
|
- install the required modules
|
||||||
|
`npm install`
|
||||||
|
|
||||||
|
- switch back to your system's default node.js version
|
||||||
|
`nvm deactivate`
|
||||||
|
|
||||||
|
- execute remex
|
||||||
|
`nvm run remex.js`
|
||||||
|
|
||||||
|
## systemd
|
||||||
|
|
||||||
|
**for security reasons it is highly recommended to not run remex with root permissions!**
|
||||||
|
|
||||||
|
- create a new system user
|
||||||
|
`useradd -U -r -s /usr/bin/nologin node`
|
||||||
|
|
||||||
|
- make your install of nvm available to the new user
|
||||||
|
`cp -R ~/.nvm /opt/nvm`
|
||||||
|
`chown -R node /opt/nvm`
|
||||||
|
|
||||||
|
- symlink the provided systemd-service file and modify it according to your needs
|
||||||
|
`ln -s /opt/remex/remex.service /etc/systemd/system/remex.service`
|
||||||
|
|
||||||
|
- reload systemd-services
|
||||||
|
`systemctl daemon-reload`
|
||||||
|
|
||||||
|
- enable and start remex as a systemd-service
|
||||||
|
`systemctl enable --now remex`
|
||||||
|
|
||||||
|
## configuration
|
||||||
|
|
||||||
|
configuration is done entirely within the file `config.json`.
|
||||||
|
|
||||||
|
### server: [*object*]
|
||||||
|
- address: [*string*] server address to listen on (`0.0.0.0` to listen on all interfaces)
|
||||||
|
- port: [*number*] port to listen on
|
||||||
|
|
||||||
|
### log: [*object*]
|
||||||
|
- level: [*string*] verbosity of the log; either `debug`, `info`, `warning` or `error`
|
||||||
|
- timestamp: [*string*] format string for the timestamp; review [moment.js](https://momentjs.com/docs/#/displaying/format/) for further information
|
||||||
|
|
||||||
|
### api: [*object-array*]
|
||||||
|
- url: [*string*] url of the endpoint
|
||||||
|
- type: [*string*] http request method for the endpoint (either `get` or `post`)
|
||||||
|
- command: [*string*] command / path to script to execute
|
||||||
|
- args: [*string-array*] arguments to pass to the executed command
|
||||||
|
- timeout [*number*] max amount of time in milliseconds until the executed command times out and gets killed
|
||||||
|
- detach [*boolean*] detach from the executed command
|
||||||
|
- unique [*boolean* or *string*] if set to `true` the command can not be executed again until it has finished; if set to `restart` the command will be killed (if active) and started again; if set to `toggle` the command will either be killed if active or started if not active
|
||||||
|
|
28
config.json
28
config.json
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
"server": {
|
|
||||||
"listen": "0.0.0.0",
|
|
||||||
"port": 3000
|
|
||||||
},
|
|
||||||
"log": {
|
|
||||||
"level": "debug",
|
|
||||||
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
|
|
||||||
},
|
|
||||||
"api": [
|
|
||||||
{
|
|
||||||
"url": "/tail",
|
|
||||||
"method": "get",
|
|
||||||
"command": "tail",
|
|
||||||
"args": [
|
|
||||||
"-f", "/tmp/test"
|
|
||||||
],
|
|
||||||
"detach": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/uptime",
|
|
||||||
"method": "get",
|
|
||||||
"command": "uptime",
|
|
||||||
"args": [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
60
example_config.json
Normal file
60
example_config.json
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"port": 3000
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"level": "debug",
|
||||||
|
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
|
||||||
|
},
|
||||||
|
"api": [
|
||||||
|
{
|
||||||
|
"url": "/watch",
|
||||||
|
"method": "get",
|
||||||
|
"command": "watch",
|
||||||
|
"args": [
|
||||||
|
"-n",
|
||||||
|
"3",
|
||||||
|
"ls"
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"detach": true,
|
||||||
|
"unique": "restart",
|
||||||
|
"timeout": 10000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/uptime",
|
||||||
|
"method": "get",
|
||||||
|
"command": "uptime",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/systemctl/example",
|
||||||
|
"method": "get",
|
||||||
|
"command": "systemctl",
|
||||||
|
"args": [
|
||||||
|
"is-active",
|
||||||
|
"example"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/systemctl/example/start",
|
||||||
|
"method": "post",
|
||||||
|
"command": "systemctl",
|
||||||
|
"args": [
|
||||||
|
"start",
|
||||||
|
"example"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/systemctl/example/stop",
|
||||||
|
"method": "post",
|
||||||
|
"command": "systemctl",
|
||||||
|
"args": [
|
||||||
|
"stop",
|
||||||
|
"example"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
125
libs/commands.js
125
libs/commands.js
|
@ -1,89 +1,146 @@
|
||||||
const logger = require('./logger.js');
|
const logger = require('./logger.js');
|
||||||
const { spawn } = require('child_process')
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
|
const STATE_OK = 'ok';
|
||||||
|
const STATE_DETACHED = 'detached';
|
||||||
|
const STATE_REJECTED = 'rejected';
|
||||||
|
const STATE_KILLED = 'killed';
|
||||||
|
const STATE_ERROR = 'error';
|
||||||
|
|
||||||
const cmds = new Map();
|
const cmds = new Map();
|
||||||
|
let cmdId = -1;
|
||||||
|
|
||||||
async function execute(endpoint) {
|
async function execute(endpoint) {
|
||||||
if (endpoint === undefined) {
|
if (endpoint === undefined) {
|
||||||
return;
|
return createResult(STATE_REJECTED, undefined, 'endpoint is not defined');
|
||||||
}
|
}
|
||||||
if (isCommandActive(endpoint)) {
|
let unique = endpoint.options?.unique?.toString();
|
||||||
logger.info('not executing command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') because it is already active');
|
if (unique !== undefined && isCommandActive(endpoint)) {
|
||||||
throw new Error('command is already active');
|
unique = unique.toLowerCase();
|
||||||
|
switch (unique.toLowerCase()) {
|
||||||
|
case 'true':
|
||||||
|
logger.info('not executing unique command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') because it is already active');
|
||||||
|
return createResult(STATE_REJECTED, undefined, 'unique command is already active');
|
||||||
|
case 'restart':
|
||||||
|
logger.info('killing and restarting unique command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...');
|
||||||
|
await killCommand(endpoint);
|
||||||
|
break;
|
||||||
|
case 'toggle':
|
||||||
|
logger.info('stopping unique command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...');
|
||||||
|
await killCommand(endpoint);
|
||||||
|
return createResult(STATE_KILLED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
logger.info('executing command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...');
|
cmdId++;
|
||||||
var cmd = spawn(endpoint.command, endpoint.args);
|
logger.info('executing command #' + cmdId + ' \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...');
|
||||||
|
let cmd = spawn(endpoint.command, endpoint.args);
|
||||||
|
cmd.id = cmdId;
|
||||||
cmd.timestamp = new Date().getTime();
|
cmd.timestamp = new Date().getTime();
|
||||||
let result = '';
|
cmd.data = '';
|
||||||
let error = '';
|
cmd.error = '';
|
||||||
cmd.stdout.on('data', (data) => {
|
cmd.stdout.on('data', (data) => {
|
||||||
result += data;
|
cmd.data += data;
|
||||||
});
|
});
|
||||||
cmd.stderr.on('data', (data) => {
|
cmd.stderr.on('data', (err) => {
|
||||||
error += data;
|
if (err.toString().toLowerCase().contains('warning')) {
|
||||||
|
cmd.data += err;
|
||||||
|
}
|
||||||
|
cmd.error += err;
|
||||||
});
|
});
|
||||||
cmd.on('spawn', () => {
|
cmd.on('spawn', () => {
|
||||||
logger.info('spawned command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')');
|
logger.info('spawned command #' + cmd.id + ' \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...');
|
||||||
addCommand(cmd, endpoint);
|
addCommand(cmd, endpoint);
|
||||||
if (endpoint.detach === true) {
|
if (endpoint.options?.timeout && !isNaN(endpoint.options?.timeout)) {
|
||||||
resolve();
|
setTimeout(async () => {
|
||||||
|
if (!cmds.has(endpoint)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.warn('killing timed out command #' + cmd.id + ' \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') after ' + endpoint.options.timeout + 'ms...');
|
||||||
|
await killCommand(endpoint);
|
||||||
|
}, endpoint.options.timeout);
|
||||||
|
}
|
||||||
|
if (endpoint.options?.detach) {
|
||||||
|
return resolve(createResult(STATE_DETACHED, cmd.data, cmd.error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cmd.on('error', (err) => {
|
cmd.on('error', (err) => {
|
||||||
error += err;
|
cmd.error += err;
|
||||||
removeCommand(endpoint);
|
removeCommand(endpoint);
|
||||||
if (endpoint.detach === true) {
|
if (endpoint.options?.detach) {
|
||||||
reject();
|
reject(createResult(STATE_ERROR, cmd.data, cmd.error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cmd.on('close', (code) => {
|
cmd.on('exit', (code) => {
|
||||||
|
if (code === null) {
|
||||||
|
code = 0;
|
||||||
|
}
|
||||||
removeCommand(endpoint);
|
removeCommand(endpoint);
|
||||||
let fn = logger.info;
|
let fn = logger.info;
|
||||||
let msg = 'command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') finished with exit code ' + code + ' after ' + (new Date().getTime() - cmd.timestamp) + 'ms';
|
let msg = 'command #' + cmd.id + ' \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') finished with exit code ' + code + ' after ' + (new Date().getTime() - cmd.timestamp) + 'ms';
|
||||||
if (error !== undefined && error.length > 0) {
|
if (cmd.error !== undefined && cmd.error.length > 0) {
|
||||||
error = error.trim();
|
cmd.error = error.trim();
|
||||||
msg += ' > error: ' + error;
|
msg += ' > error: ' + cmd.error;
|
||||||
fn = logger.error;
|
fn = logger.error;
|
||||||
reject(error);
|
reject(createResult(STATE_ERROR, cmd.data, cmd.error));
|
||||||
}
|
}
|
||||||
if (result !== undefined && result.length > 0) {
|
if (cmd.data !== undefined && cmd.data.length > 0) {
|
||||||
result = result.trim();
|
cmd.data = cmd.data.trim();
|
||||||
msg += ' > data: ' + result;
|
msg += ' > data: ' + cmd.data;
|
||||||
}
|
}
|
||||||
fn(msg);
|
fn(msg);
|
||||||
resolve(result);
|
resolve(createResult(STATE_OK, cmd.data, cmd.error));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createResult(state, data, error) {
|
||||||
|
if (state === undefined ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
data,
|
||||||
|
error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function addCommand(command, endpoint) {
|
function addCommand(command, endpoint) {
|
||||||
if (command === undefined || endpoint === undefined) {
|
if (command === undefined || endpoint === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cmds.set(JSON.stringify(endpoint), command);
|
cmds.set(endpoint, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCommand(endpoint) {
|
function removeCommand(endpoint) {
|
||||||
if (endpoint === undefined) {
|
if (endpoint === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cmds.delete(JSON.stringify(endpoint));
|
cmds.delete(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCommandActive(endpoint) {
|
function isCommandActive(endpoint) {
|
||||||
return endpoint !== undefined && cmds.has(JSON.stringify(endpoint));
|
return endpoint !== undefined && cmds.has(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
function killCommand(endpoint) {
|
async function killCommand(endpoint) {
|
||||||
if (endpoint === undefined) {
|
if (endpoint === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const command = cmds.get(JSON.stringify(endpoint));
|
const command = cmds.get(endpoint);
|
||||||
if (command === undefined) {
|
if (command === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
process.kill(command.pid);
|
process.kill(command.pid, 'SIGINT');
|
||||||
|
while (isCommandActive(endpoint)) {
|
||||||
|
await sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleep(milliseconds) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(resolve, milliseconds);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const logger = require('./logger.js');
|
const logger = require('./logger.js');
|
||||||
const commands = require('./commands.js');
|
const commands = require('./commands.js');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const { config } = require('process');
|
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
let api;
|
let api;
|
||||||
|
@ -16,7 +15,7 @@ async function start() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
server.listen(port, listen)
|
server.listen(port, listen)
|
||||||
.on('listening', function () {
|
.on('listening', function () {
|
||||||
logger.info('server listening on ' + global.config.server.listen + ':' + global.config.server.port + '...');
|
logger.info('server listening on ' + listen + ':' + port + '...');
|
||||||
handleRequests();
|
handleRequests();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
@ -35,33 +34,24 @@ async function respond(request, response) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let endpoint = api[request.method]?.[request.url];
|
let endpoint = api[request.method]?.[request.url];
|
||||||
if (endpoint === undefined) {
|
let result = await commands.execute(endpoint);
|
||||||
return finishRequest(request, response, { error: 'endpoint not defined' }, 501);
|
result.command = endpoint.command;
|
||||||
}
|
result.args = endpoint.args;
|
||||||
try {
|
return finishRequest(request, response, result);
|
||||||
endpoint.result = await commands.execute(endpoint);
|
|
||||||
} catch (err) {
|
|
||||||
endpoint.error = err.toString();
|
|
||||||
return finishRequest(request, response, endpoint, 501);
|
|
||||||
}
|
|
||||||
return finishRequest(request, response, endpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishRequest(request, response, data, code) {
|
function finishRequest(request, response, result) {
|
||||||
if (response === undefined) {
|
if (response === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (code === undefined) {
|
result.time = (new Date().getTime() - request.timestamp) + 'ms';
|
||||||
code = 200;
|
let code = result.code || 200;
|
||||||
|
if (result.error !== undefined && result.error.length > 0) {
|
||||||
|
code = 501;
|
||||||
}
|
}
|
||||||
if (code === 200) {
|
delete result.code;
|
||||||
data.status = 'ok';
|
|
||||||
} else {
|
|
||||||
data.status = 'error';
|
|
||||||
}
|
|
||||||
data.time = (new Date().getTime() - request.timestamp) + 'ms';
|
|
||||||
response.writeHead(code);
|
response.writeHead(code);
|
||||||
const json = JSON.stringify(data);
|
const json = JSON.stringify(result);
|
||||||
response.end(json);
|
response.end(json);
|
||||||
logger.http({ request: request, code: code, data: json });
|
logger.http({ request: request, code: code, data: json });
|
||||||
}
|
}
|
||||||
|
|
28
package-lock.json
generated
Normal file
28
package-lock.json
generated
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "remex",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "remex",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.29.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"moment": {
|
||||||
|
"version": "2.29.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,7 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "execute local commands remotely via http requests",
|
"description": "execute local commands remotely via http requests",
|
||||||
"main": "remex.js",
|
"main": "remex.js",
|
||||||
"scripts": {
|
"scripts": {},
|
||||||
},
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"scripts",
|
"scripts",
|
||||||
"commands",
|
"commands",
|
||||||
|
@ -20,4 +19,4 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"moment": "^2.29.1"
|
"moment": "^2.29.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
remex.js
2
remex.js
|
@ -8,7 +8,7 @@ const INTERRUPTS = ['beforeExit', 'SIGINT', 'SIGTERM'];
|
||||||
main();
|
main();
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
let configPath = path.resolve('./config.json');
|
let configPath = path.resolve(process.argv[2] || __dirname + '/config.json');
|
||||||
try {
|
try {
|
||||||
global.config = require(configPath);
|
global.config = require(configPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
12
remex.service
Normal file
12
remex.service
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=remex
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
WorkingDirectory=/opt/remex
|
||||||
|
ExecStart=/opt/nvm/nvm-exec node remex.js
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -1,8 +0,0 @@
|
||||||
# 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==
|
|
Loading…
Reference in a new issue