fix(core): update
This commit is contained in:
@ -1,2 +1,26 @@
|
||||
const removeme = {};
|
||||
export { removeme };
|
||||
// node native scope
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
export {
|
||||
path
|
||||
};
|
||||
|
||||
// @pushrocks scope
|
||||
import * as smartshell from '@pushrocks/smartshell';
|
||||
import * as smartlog from '@pushrocks/smartlog';
|
||||
import * as smartlogDestinationLocal from '@pushrocks/smartlog-destination-local';
|
||||
|
||||
export {
|
||||
smartshell,
|
||||
smartlog,
|
||||
smartlogDestinationLocal
|
||||
};
|
||||
|
||||
// third party
|
||||
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
export {
|
||||
fs
|
||||
};
|
174
ts/smartdaemon.settings.ts
Normal file
174
ts/smartdaemon.settings.ts
Normal file
@ -0,0 +1,174 @@
|
||||
export const settingsReference = {
|
||||
name: {
|
||||
cli: {
|
||||
short: 'n',
|
||||
long: 'service',
|
||||
value: '[name]',
|
||||
description: 'Service name'
|
||||
},
|
||||
mandatory: true
|
||||
},
|
||||
description: {
|
||||
cli: {
|
||||
short: 'd',
|
||||
long: 'description',
|
||||
value: '[description]',
|
||||
description: 'Service description'
|
||||
},
|
||||
default: '{name} service'
|
||||
},
|
||||
author: {
|
||||
cli: {
|
||||
short: 'k',
|
||||
long: 'author',
|
||||
value: '[author]',
|
||||
description: 'Service author'
|
||||
}
|
||||
},
|
||||
user: {
|
||||
cli: {
|
||||
short: 'u',
|
||||
long: 'user',
|
||||
value: '[user]',
|
||||
description: 'User to run service as'
|
||||
}
|
||||
},
|
||||
group: {
|
||||
cli: {
|
||||
short: 'g',
|
||||
long: 'group',
|
||||
value: '[group]',
|
||||
description: 'Group to run service as'
|
||||
}
|
||||
},
|
||||
app: {
|
||||
cli: {
|
||||
short: 'A',
|
||||
long: 'app',
|
||||
value: '[app]',
|
||||
description: 'Application main file'
|
||||
},
|
||||
default: 'main.js'
|
||||
|
||||
},
|
||||
cwd: {
|
||||
cli: {
|
||||
short: 'c',
|
||||
long: 'cwd',
|
||||
value: '[path]',
|
||||
description: 'Application cwd'
|
||||
},
|
||||
mandatory: true,
|
||||
fs: true
|
||||
},
|
||||
'app.args': {
|
||||
cli: {
|
||||
short: 'p',
|
||||
long: 'app.args',
|
||||
value: '[args]',
|
||||
description: 'Application arguments'
|
||||
}
|
||||
},
|
||||
env: {
|
||||
cli: {
|
||||
short: 'e',
|
||||
long: 'env',
|
||||
value: '[NAME=VALUE]',
|
||||
description: 'Environment variables to set in systemd job',
|
||||
function: (v, vars) => {
|
||||
const _v = v.split('=')
|
||||
vars[_v[0]] = _v[1]; return vars
|
||||
},
|
||||
store: []
|
||||
}
|
||||
},
|
||||
pid: {
|
||||
cli: {
|
||||
short: 'P',
|
||||
long: 'pid',
|
||||
value: '[file]',
|
||||
description: 'Service pid file'
|
||||
},
|
||||
default: '/var/run/{name}.pid'
|
||||
},
|
||||
log: {
|
||||
cli: {
|
||||
short: 'L',
|
||||
long: 'log',
|
||||
value: '[log]',
|
||||
description: 'Service log file'
|
||||
},
|
||||
default: '/var/log/{name}/log'
|
||||
},
|
||||
error: {
|
||||
cli: {
|
||||
short: 'E',
|
||||
long: 'error',
|
||||
value: '[error]',
|
||||
description: 'Service error file'
|
||||
},
|
||||
default: '/var/log/{name}/error'
|
||||
},
|
||||
engine: {
|
||||
cli: {
|
||||
short: 'X',
|
||||
long: 'engine',
|
||||
value: '[node|forever|pm2]',
|
||||
description: 'Service engine (node|forever|pm2)'
|
||||
},
|
||||
default: 'node',
|
||||
mandatory: true
|
||||
},
|
||||
'engine.bin': {
|
||||
cli: {
|
||||
short: 'b',
|
||||
long: 'engine.bin',
|
||||
value: '[bin]',
|
||||
description: 'Service engine bin'
|
||||
},
|
||||
default: '/usr/bin/{engine}',
|
||||
fs: true
|
||||
},
|
||||
'engine.args': {
|
||||
cli: {
|
||||
short: 't',
|
||||
long: 'engine.args',
|
||||
value: '[eargs]',
|
||||
description: 'Service engine args'
|
||||
}
|
||||
},
|
||||
logrotate: {
|
||||
cli: {
|
||||
short: 'l',
|
||||
long: 'logrotate',
|
||||
description: 'Add logrotate config'
|
||||
}
|
||||
// default false
|
||||
},
|
||||
'logrotate.rotate': {
|
||||
cli: {
|
||||
short: 'R',
|
||||
long: 'logrotate.rotate',
|
||||
value: '[rotate]',
|
||||
description: 'Logrotate rotations'
|
||||
},
|
||||
default: 10
|
||||
|
||||
},
|
||||
'logrotate.frequency': {
|
||||
cli: {
|
||||
short: 'F',
|
||||
long: 'logrotate.frequency',
|
||||
value: '[frequency]',
|
||||
description: 'Logrotate frequency'
|
||||
},
|
||||
default: 'daily'
|
||||
},
|
||||
noroot: {
|
||||
cli: {
|
||||
short: 'w',
|
||||
long: 'noroot',
|
||||
description: 'Skip check for root permission'
|
||||
}
|
||||
}
|
||||
};
|
259
ts/smartdaemon.setup.ts
Normal file
259
ts/smartdaemon.setup.ts
Normal file
@ -0,0 +1,259 @@
|
||||
import * as plugins from './smartdaemon.plugins';
|
||||
|
||||
import { logger } from './smartdamon.logging';
|
||||
import { settingsReference } from './smartdaemon.settings';
|
||||
import { templateReference } from './smartdaemon.templates';
|
||||
|
||||
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||
executor: 'bash'
|
||||
});
|
||||
|
||||
/**
|
||||
* print success message
|
||||
* @method setup.success
|
||||
* @param {string} message
|
||||
*/
|
||||
export const setupSuccess = (message: string) => {
|
||||
logger.log('success', `service-systemd: ${message}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* print error message
|
||||
* @method setup.fail
|
||||
* @param {string} message
|
||||
*/
|
||||
export const setupFail = (message) => {
|
||||
logger.log('error', `service-systemd ${message}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* install the service
|
||||
* can also be used to update the service
|
||||
* @method setup.add
|
||||
* @param {object} settings
|
||||
* @return {string}
|
||||
*/
|
||||
export const setupAdd = async (settings) => {
|
||||
setupCheckSettings(settings);
|
||||
const contents = setupParse(settings);
|
||||
await setupCheckPaths(settings);
|
||||
await setupAddLog(settings);
|
||||
await setupAddScripts(settings, contents);
|
||||
await setupAddLogrotate(settings, contents);
|
||||
return `service ${settings.name} installed`;
|
||||
};
|
||||
|
||||
/**
|
||||
* remove the service
|
||||
* @method setup.remove
|
||||
* @param {string} service service name
|
||||
* @return {string}
|
||||
*/
|
||||
export const setupRemove = async (service) => {
|
||||
if (!service) {
|
||||
throw new Error('Missing argument: service name');
|
||||
}
|
||||
|
||||
const cmd = `systemctl disable ${service}.service`;
|
||||
logger.log('info', `service-systemd > ${cmd}`);
|
||||
await smartshellInstance.exec(cmd);
|
||||
|
||||
let file = plugins.path.join('/etc/systemd/system', `${service}.service`);
|
||||
logger.log('info', `service-systemd remove ${file}`);
|
||||
await plugins.fs.unlink(file);
|
||||
|
||||
file = plugins.path.join('/usr/local/bin', `systemd-${service}-start`);
|
||||
logger.log('info', `service-systemd remove ${file}`);
|
||||
await plugins.fs.unlink(file);
|
||||
|
||||
file = plugins.path.join('/etc/logrotate.d', service);
|
||||
logger.log('info', `service-systemd remove ${file}`);
|
||||
await plugins.fs.unlink(file);
|
||||
return `service ${service} uninstalled`;
|
||||
};
|
||||
|
||||
/**
|
||||
* check mandatories params and paths
|
||||
* @method setup.checkSettings
|
||||
* @param {object} settings
|
||||
*/
|
||||
export const setupCheckSettings = (settings) => {
|
||||
if (!settings.name) {
|
||||
settings.name = settings.service;
|
||||
}
|
||||
|
||||
delete settings.service;
|
||||
|
||||
const paths = [];
|
||||
for (const optionArg of Object.keys(settingsReference)) {
|
||||
const option = settingsReference[optionArg];
|
||||
if (option.mandatory && !settings[optionArg]) {
|
||||
throw new Error(`Missing ${optionArg} in settings file or arguments`);
|
||||
}
|
||||
if (option.fs) {
|
||||
settings[optionArg] = plugins.path.resolve(settings[optionArg]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* check mandatories params and paths
|
||||
* @method setup.checkPaths
|
||||
* @param {object} settings
|
||||
*/
|
||||
export const setupCheckPaths = async (settings) => {
|
||||
const exists = [];
|
||||
for (const optionArg of Object.keys(settingsReference)) {
|
||||
const option = settingsReference[optionArg];
|
||||
if (option.fs) {
|
||||
exists.push((async () => {
|
||||
const exists2 = await plugins.fs.pathExists(settings[optionArg]);
|
||||
if (!exists2) {
|
||||
throw Error(`path ${settings[optionArg]} (${optionArg}) does not exists`);
|
||||
}
|
||||
})());
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(exists);
|
||||
};
|
||||
|
||||
/**
|
||||
* merge settings with templates
|
||||
* create scripts contents
|
||||
* @method setup.parse
|
||||
* @param {object} settings
|
||||
*/
|
||||
export const setupParse = (settings) => {
|
||||
|
||||
if (settings.env) {
|
||||
settings.envs = '';
|
||||
for (const key of Object.keys(settings.env)) {
|
||||
settings.envs += 'Environment=' + key + '=' + settings.env[key] + '\n';
|
||||
}
|
||||
}
|
||||
settings.user = settings.user
|
||||
? `User=${settings.user}`
|
||||
: '';
|
||||
settings.group = settings.group
|
||||
? `Group=${settings.group}`
|
||||
: '';
|
||||
|
||||
settings.date = (new Date()).toString();
|
||||
|
||||
for (const key in settings) {
|
||||
if (typeof settings[key] === 'string') {
|
||||
settings[key] = plugins.string.template(settings[key], settings, true);
|
||||
}
|
||||
}
|
||||
|
||||
const _service = plugins.string.template(templateReference.engines[settings.engine].service, settings, true);
|
||||
const _start = plugins.string.template(templateReference.engines[settings.engine].start, settings, true);
|
||||
const _stop = plugins.string.template(templateReference.engines[settings.engine].stop, settings, true);
|
||||
const _logrotate = settings.logrotate ? plugins.string.template(templateReference.logrotate, settings, true) : '';
|
||||
|
||||
return {
|
||||
service: _service,
|
||||
start: _start,
|
||||
stop: _stop,
|
||||
logrotate: _logrotate
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* write scripts and run commands to install the service
|
||||
* @method setup.addScripts
|
||||
* @param {object} settings
|
||||
* @param {object} contents scripts contents
|
||||
* @param {string} contents.start
|
||||
* @param {string} contents.stop
|
||||
* @param {string} contents.service
|
||||
*/
|
||||
export const setupAddScripts = async (settings, contents) => {
|
||||
const service = plugins.path.join('/etc/systemd/system', `${settings.name}.service`);
|
||||
const tasks = [];
|
||||
|
||||
if (contents.start) {
|
||||
tasks.push(() => {
|
||||
const start = plugins.path.join('/usr/local/bin', `systemd-${settings.name}-start`);
|
||||
logger.log('info', `service-systemd: write file ${start}`);
|
||||
return plugins.fs.writeFile(start, contents.start, 'utf8');
|
||||
});
|
||||
}
|
||||
|
||||
if (contents.stop) {
|
||||
tasks.push(() => {
|
||||
const stop = plugins.path.join('/usr/local/bin', `systemd-${settings.name}-stop`);
|
||||
logger.log('info', `service-systemd: write file ${stop}`);
|
||||
return plugins.fs.writeFile(stop, contents.stop, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (contents.start || contents.stop) {
|
||||
tasks.push(() => {
|
||||
const cmd = `chmod a+x /usr/local/bin/systemd-${settings.name}*`;
|
||||
logger.log('info', `service-systemd > ${cmd}`);
|
||||
return smartshellInstance.exec(cmd);
|
||||
});
|
||||
}
|
||||
|
||||
tasks.push(() => {
|
||||
logger.log('info', `service-systemd write file ${service}`);
|
||||
return plugins.fs.writeFile(service, contents.service, 'utf8');
|
||||
});
|
||||
|
||||
tasks.push(() => {
|
||||
const cmd = `systemctl enable ${service};systemctl daemon-reload`;
|
||||
logger.log('info', `service-systemd > ${cmd}`);
|
||||
return smartshellInstance.exec(cmd);
|
||||
});
|
||||
|
||||
for (const task of tasks) {
|
||||
await task();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ensure dirs for log files
|
||||
* @method setup.addLog
|
||||
* @param {object} settings
|
||||
*/
|
||||
export const setupAddLog = async (settings) => {
|
||||
const tasks = [];
|
||||
let dirLog;
|
||||
let dirError;
|
||||
|
||||
if (settings.log) {
|
||||
dirLog = plugins.path.dirname(settings.log);
|
||||
logger.log('info', `service-systemd: ensure dir ${dirLog}`);
|
||||
tasks.push(plugins.fs.ensureDir(dirLog));
|
||||
}
|
||||
if (settings.error) {
|
||||
dirError = plugins.path.dirname(settings.error);
|
||||
if (dirError !== dirLog) {
|
||||
logger.log('info', `service-systemd ensure dir ${dirError}`);
|
||||
tasks.push(plugins.fs.ensureDir(dirError));
|
||||
}
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
};
|
||||
|
||||
/**
|
||||
* write logrotate conf script
|
||||
* @method setup.addLogrotate
|
||||
* @param {object} settings
|
||||
* @param {object} contents scripts contents
|
||||
* @param {string} contents.start
|
||||
* @param {string} contents.stop
|
||||
* @param {string} contents.service
|
||||
*/
|
||||
export const setupAddLogrotate = async (settings, contents) => {
|
||||
if (!settings.logrotate) {
|
||||
return;
|
||||
}
|
||||
const file = plugins.path.join('/etc/logrotate.d/', settings.name);
|
||||
logger.log('info', `service-systemd: write logrotate file ${file}`);
|
||||
await plugins.fs.writeFile(file, contents.logrotate, 'utf8');
|
||||
};
|
75
ts/smartdaemon.templates.ts
Normal file
75
ts/smartdaemon.templates.ts
Normal file
@ -0,0 +1,75 @@
|
||||
export const templateReference = {
|
||||
engines: {
|
||||
node: {
|
||||
service:
|
||||
'# Generated by service-systemd on {{date}}\n' +
|
||||
'[Unit]\n' +
|
||||
'Description={description}\n' +
|
||||
'Requires=network.target\n' +
|
||||
'After=network.target\n' +
|
||||
'\n' +
|
||||
'[Service]\n' +
|
||||
'Type=simple\n' +
|
||||
'ExecStart={engine.bin} {engine.args} {app} {app.args}\n' +
|
||||
'WorkingDirectory={cwd}\n' +
|
||||
'Restart=on-failure\n' +
|
||||
'{envs}\n' +
|
||||
'LimitNOFILE=infinity\n' +
|
||||
'LimitCORE=infinity\n' +
|
||||
'StandardInput=null\n' +
|
||||
'StandardOutput=syslog\n' +
|
||||
'StandardError=syslog\n' +
|
||||
'Restart=always\n' +
|
||||
'SyslogIdentifier={name}\n' +
|
||||
'PIDFile={pid}\n' +
|
||||
'{user}\n' +
|
||||
'{group}\n' +
|
||||
'[Install]\n' +
|
||||
'WantedBy=multi-user.target\n'
|
||||
},
|
||||
forever: {
|
||||
service:
|
||||
'# Generated by service-systemd on {date}\n' +
|
||||
'[Unit]\n' +
|
||||
'Description={description}\n' +
|
||||
'Requires=network.target\n' +
|
||||
'After=network.target\n' +
|
||||
'\n' +
|
||||
'[Service]\n' +
|
||||
'Type=forking\n' +
|
||||
'WorkingDirectory={cwd}\n' +
|
||||
'{envs}\n' +
|
||||
'ExecStart=/usr/local/bin/systemd-{name}-start\n' +
|
||||
'ExecStop=/usr/local/bin/systemd-{name}-stop\n' +
|
||||
'PIDFile={pid}\n' +
|
||||
'{user}\n' +
|
||||
'{group}\n' +
|
||||
'[Install]\n' +
|
||||
'WantedBy=multi-user.target\n',
|
||||
start:
|
||||
'#!/bin/bash\n' +
|
||||
'{engine.bin} start ' +
|
||||
'--pidFile {pid} ' +
|
||||
'--uid {name} ' +
|
||||
'--sourceDir {cwd} ' +
|
||||
'-l {log} ' +
|
||||
'-e {error} ' +
|
||||
'--append --minUptime 5000 --spinSleepTime 2000 ' +
|
||||
'{engine.args} ' +
|
||||
'{app} {app.args}\n' +
|
||||
'exit 0',
|
||||
stop: '#!/bin/bash\n' + '{engine.bin} stop {name}\n' + 'exit 0'
|
||||
}
|
||||
},
|
||||
logrotate:
|
||||
'{log}\n{error} {\n' +
|
||||
' {logrotate.frequency}\n' +
|
||||
' rotate {logrotate.rotate}\n' +
|
||||
' create\n' +
|
||||
' missingok\n' +
|
||||
' notifempty\n' +
|
||||
' compress\n' +
|
||||
' sharedscripts\n' +
|
||||
// ' postrotate\n{restart}\n' +
|
||||
' endscript\n}'
|
||||
};
|
15
ts/smartdamon.logging.ts
Normal file
15
ts/smartdamon.logging.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import * as plugins from './smartdaemon.plugins';
|
||||
|
||||
export const logger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
company: 'Some Company',
|
||||
companyunit: 'Some CompanyUnit',
|
||||
containerName: 'Some Containername',
|
||||
environment: 'local',
|
||||
runtime: 'node',
|
||||
zone: 'gitzone'
|
||||
},
|
||||
minimumLogLevel: 'silly'
|
||||
});
|
||||
|
||||
logger.addLogDestination(new plugins.smartlogDestinationLocal.DestinationLocal());
|
Reference in New Issue
Block a user