mirror of
https://github.com/Unitech/pm2.git
synced 2025-12-08 20:35:53 +00:00
2537 lines
70 KiB
JavaScript
2537 lines
70 KiB
JavaScript
|
|
var CLI = module.exports = {};
|
|
|
|
var commander = require('commander');
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var async = require('async');
|
|
var debug = require('debug')('pm2:monit');
|
|
var semver = require('semver');
|
|
var util = require('util');
|
|
var vm = require('vm');
|
|
var chalk = require('chalk');
|
|
var exec = require('child_process').exec;
|
|
|
|
var p = path;
|
|
|
|
var Monit = require('./Monit');
|
|
var UX = require('./CliUx');
|
|
var Log = require('./Log');
|
|
var Satan = require('./Satan');
|
|
var Common = require('./Common');
|
|
var cst = require('../constants.js');
|
|
var extItps = require('./interpreter.json');
|
|
var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer');
|
|
var json5 = require('./tools/json5.js');
|
|
var Config = require('./tools/Config');
|
|
|
|
var Modularizer = require('./Modularizer.js');
|
|
var Configuration = require('../lib/Configuration.js');
|
|
|
|
var Deploy = require('pm2-deploy');
|
|
|
|
var exitCli = Common.exitCli;
|
|
var printError = Common.printError;
|
|
var printOut = Common.printOut;
|
|
|
|
CLI.pm2Init = function() {
|
|
if (!fs.existsSync(cst.PM2_ROOT_PATH)) {
|
|
fs.mkdirSync(cst.PM2_ROOT_PATH);
|
|
fs.mkdirSync(cst.DEFAULT_LOG_PATH);
|
|
fs.mkdirSync(cst.DEFAULT_PID_PATH);
|
|
}
|
|
if (!fs.existsSync(cst.PM2_CONF_FILE)) {
|
|
fs
|
|
.createReadStream(path.join(__dirname, cst.SAMPLE_CONF_FILE))
|
|
.pipe(fs.createWriteStream(cst.PM2_CONF_FILE));
|
|
}
|
|
if (cst.PM2_HOME && !fs.existsSync(cst.PM2_HOME)) {
|
|
try {
|
|
fs.mkdirSync(cst.PM2_HOME);
|
|
fs.mkdirSync(cst.DEFAULT_LOG_PATH);
|
|
fs.mkdirSync(cst.DEFAULT_PID_PATH);
|
|
} catch(e) {}
|
|
}
|
|
if (!fs.existsSync(cst.PM2_MODULE_CONF_FILE)) {
|
|
try {
|
|
fs.writeFileSync(cst.PM2_MODULE_CONF_FILE, "{}");
|
|
} catch (e) {
|
|
console.error(e.stack || e);
|
|
}
|
|
}
|
|
if (process.stdout._handle
|
|
&& process.stdout._handle.setBlocking)
|
|
process.stdout._handle.setBlocking(true);
|
|
};
|
|
|
|
/**
|
|
* API Methods
|
|
*/
|
|
CLI.connect = Satan.start;
|
|
|
|
CLI.launchBus = Satan.launchBus;
|
|
|
|
CLI.disconnectBus = Satan.disconnectBus;
|
|
|
|
CLI.disconnect = function(cb) {
|
|
if (!cb) cb = function() {};
|
|
Satan.disconnectRPC(cb);
|
|
};
|
|
|
|
/**
|
|
* Entry point to start an app / json file
|
|
*/
|
|
CLI.start = function(cmd, opts, cb) {
|
|
if (typeof(opts) == "function") {
|
|
cb = opts;
|
|
opts = {};
|
|
}
|
|
|
|
if ((typeof(cmd) === 'string' && cmd.indexOf('.json') != -1) || typeof(cmd) === 'object')
|
|
CLI._startJson(cmd, opts, 'file', cb);
|
|
else
|
|
CLI._startScript(cmd, opts, cb);
|
|
};
|
|
|
|
/**
|
|
* Method to START / RESTART a script
|
|
* @method startFile
|
|
* @param {string} script script name (will be resolved according to location)
|
|
* @return
|
|
*/
|
|
CLI._startScript = function(script, opts, cb) {
|
|
if (typeof opts == "function") {
|
|
cb = opts;
|
|
opts = {};
|
|
}
|
|
|
|
var conf = Config.transCMDToConf(opts);
|
|
var appConf = {};
|
|
|
|
if (!!opts.executeCommand)
|
|
conf.exec_mode = 'fork';
|
|
else if (opts.instances !== undefined)
|
|
conf.exec_mode = 'cluster';
|
|
else
|
|
conf.exec_mode = 'fork';
|
|
|
|
if (typeof conf.name == 'function'){
|
|
delete conf.name;
|
|
}
|
|
|
|
delete conf.args;
|
|
|
|
var argsIndex;
|
|
|
|
if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0) {
|
|
conf.args = opts.rawArgs.slice(argsIndex + 1);
|
|
}
|
|
else if (opts.scriptArgs) {
|
|
conf.args = opts.scriptArgs;
|
|
}
|
|
|
|
conf.script = script;
|
|
|
|
if ((appConf = verifyConfs(conf)) == null)
|
|
return exitCli(cst.ERROR_EXIT);
|
|
|
|
conf = appConf[0];
|
|
|
|
/**
|
|
* If -w option, write configuration to configuration.json file
|
|
*/
|
|
if (appConf.write) {
|
|
var dst_path = path.join(process.env.PWD, conf.name + '-pm2.json');
|
|
printOut(cst.PREFIX_MSG + 'Writing configuration to', chalk.blue(dst_path));
|
|
// pretty JSON
|
|
try {
|
|
fs.writeFileSync(dst_path, JSON.stringify(conf, null, 2));
|
|
} catch (e) {
|
|
console.error(e.stack || e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If start <app_name> start/restart application
|
|
*/
|
|
function restartExistingProcessName(cb) {
|
|
if (!isNaN(parseInt(script)) ||
|
|
(typeof script === 'string' && script.indexOf('/') != -1) ||
|
|
(typeof script === 'string' && path.extname(script) != ''))
|
|
return cb(null);
|
|
|
|
if (script !== 'all') {
|
|
Common.getProcessIdByName(script, function(err, ids) {
|
|
if (ids.length > 0) {
|
|
CLI._restart(script, function(err, list) {
|
|
if (err) return cb(err);
|
|
printOut(cst.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
}
|
|
else return cb(null);
|
|
});
|
|
}
|
|
else {
|
|
CLI._restart('all', function(err, list) {
|
|
if (err) return cb(err);
|
|
printOut(cst.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
}
|
|
};
|
|
|
|
function restartExistingProcessId(cb) {
|
|
if (isNaN(parseInt(script))) return cb(null);
|
|
|
|
CLI._restart(script, function(err, list) {
|
|
if (err) return cb(err);
|
|
printOut(cst.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
};
|
|
|
|
function restartExistingProcessPath(cb) {
|
|
Satan.executeRemote('findByFullPath', path.resolve(process.cwd(), script), function(err, exec) {
|
|
if (exec && (exec[0].pm2_env.status == cst.STOPPED_STATUS ||
|
|
exec[0].pm2_env.status == cst.STOPPING_STATUS ||
|
|
exec[0].pm2_env.status == cst.ERRORED_STATUS)) {
|
|
var app_name = exec[0].pm2_env.name;
|
|
|
|
CLI._restart(app_name, function(err, list) {
|
|
printOut(cst.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
return false;
|
|
}
|
|
else if (exec && !opts.force) {
|
|
printError(cst.PREFIX_MSG_ERR + 'Script already launched, add -f option to force re-execution');
|
|
return cb(new Error('Script already launched'));
|
|
}
|
|
|
|
try {
|
|
var resolved_paths = resolvePaths(conf);
|
|
} catch(e) {
|
|
return cb(e);
|
|
}
|
|
|
|
printOut(cst.PREFIX_MSG + 'Starting %s in %s (%d instance' + (resolved_paths.instances > 1 ? 's' : '') + ')',
|
|
script, resolved_paths.exec_mode, resolved_paths.instances);
|
|
|
|
Satan.executeRemote('prepare', resolved_paths, function(err, data) {
|
|
if (err) {
|
|
printError(cst.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err);
|
|
return cb({msg : err});
|
|
}
|
|
|
|
printOut(cst.PREFIX_MSG + 'Done.');
|
|
return cb(true, data);
|
|
});
|
|
return false;
|
|
});
|
|
};
|
|
|
|
async.series([
|
|
restartExistingProcessName,
|
|
restartExistingProcessId,
|
|
restartExistingProcessPath
|
|
], function(err, data) {
|
|
|
|
if (err instanceof Error) {
|
|
return cb ? cb(err) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
var ret = {};
|
|
data.forEach(function(_dt) {
|
|
if (_dt !== undefined)
|
|
ret = _dt;
|
|
});
|
|
|
|
return cb ? cb(null, ret) : speedList();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Process and start a JSON file
|
|
* @method startJson
|
|
* @param {string} cmd
|
|
* @param {object} opts
|
|
* @param {string} jsonVia
|
|
* @param {function} cb
|
|
*/
|
|
CLI._startJson = function(cmd, opts, jsonVia, cb) {
|
|
var appConf;
|
|
var deployConf = null;
|
|
var apps_info = [];
|
|
|
|
var action = 'restart';
|
|
|
|
if (typeof(cb) === 'undefined' && typeof(jsonVia) === 'function')
|
|
cb = jsonVia;
|
|
if (typeof(cmd) === 'object')
|
|
appConf = cmd;
|
|
else if (jsonVia == 'pipe')
|
|
appConf = json5.parse(cmd);
|
|
else {
|
|
var data = null;
|
|
try {
|
|
data = fs.readFileSync(cmd);
|
|
} catch(e) {
|
|
printError(cst.PREFIX_MSG_ERR + 'JSON ' + cmd +' not found');
|
|
return cb ? cb(e) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
appConf = parseConfig(data, cmd);
|
|
}
|
|
|
|
// v2 JSON declaration
|
|
if (appConf.deploy) deployConf = appConf.deploy;
|
|
if (appConf.apps) appConf = appConf.apps;
|
|
|
|
if (!Array.isArray(appConf)) appConf = [appConf]; //convert to array
|
|
|
|
if ((appConf = verifyConfs(appConf)) == null)
|
|
return cb ? cb({success:false}) : exitCli(cst.ERROR_EXIT);
|
|
|
|
// Get App list
|
|
var apps_name = [];
|
|
|
|
appConf.forEach(function(app) {
|
|
apps_name.push(app.name);
|
|
});
|
|
|
|
function startApps(app_name_to_start, cb) {
|
|
var apps_to_start = [];
|
|
|
|
appConf.forEach(function(app, i) {
|
|
if (app_name_to_start.indexOf(app.name) != -1) {
|
|
apps_to_start.push(appConf[i]);
|
|
}
|
|
});
|
|
|
|
async.eachLimit(apps_to_start, cst.CONCURRENT_ACTIONS, function(app, next) {
|
|
|
|
if (opts.cwd)
|
|
app.cwd = opts.cwd;
|
|
if (opts.force_name)
|
|
app.name = opts.force_name;
|
|
if (opts.started_as_module)
|
|
app.pmx_module = true;
|
|
if (opts.additional_env) {
|
|
if (!app.env) app.env = {};
|
|
util._extend(app.env, opts.additional_env);
|
|
}
|
|
|
|
mergeEnvironmentVariables(app, opts.env, deployConf);
|
|
var resolved_paths = null;
|
|
try {
|
|
resolved_paths = resolvePaths(app);
|
|
} catch (e) {
|
|
printError(e);
|
|
return cb ? cb({msg : e.message || e}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
// Set watch to true for app if argument passed to CLI
|
|
if (opts.watch)
|
|
resolved_paths.watch = true;
|
|
|
|
Satan.executeRemote('prepare', resolved_paths, function(err, data) {
|
|
printOut(cst.PREFIX_MSG + 'Process launched');
|
|
apps_info = apps_info.concat(data);
|
|
next();
|
|
});
|
|
|
|
}, function(err) {
|
|
return cb ? cb(err || null, apps_info) : speedList();
|
|
});
|
|
return false;
|
|
}
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
async.eachLimit(list, cst.CONCURRENT_ACTIONS, function(proc, next) {
|
|
// If app name already exists
|
|
if (apps_name.indexOf(proc.name) == -1)
|
|
return next();
|
|
|
|
if (action == 'reload') {
|
|
CLI._reloadProcessName(proc.pm2_env.name, 'reloadProcessId', function(err, ret) {
|
|
if (err) printError(err);
|
|
|
|
Satan.notifyGod('reload', proc.pm2_env.pm_id);
|
|
|
|
// And Remove from array to spy
|
|
apps_name.splice(apps_name.indexOf(proc.name), 1);
|
|
return next();
|
|
});
|
|
} else if (action == 'gracefulReload') {
|
|
CLI._reloadProcessName(proc.pm2_env.name, 'softReloadProcessId', function(err, ret) {
|
|
if (err) printError(err);
|
|
// And Remove from array to spy
|
|
|
|
Satan.notifyGod('graceful reload', proc.pm2_env.pm_id);
|
|
|
|
apps_name.splice(apps_name.indexOf(proc.name), 1);
|
|
return next();
|
|
});
|
|
} else {
|
|
// Get `env` from appConf by name
|
|
async.filter(appConf, function(app, callback){
|
|
callback(app.name == proc.name);
|
|
}, function(apps){
|
|
var envs = apps.map(function(app){
|
|
// Binds env_diff to env and returns it.
|
|
return mergeEnvironmentVariables(app, opts.env, deployConf);
|
|
});
|
|
// Assigns own enumerable properties of all
|
|
// Notice: if people use the same name in different apps,
|
|
// duplicated envs will be overrode by the last one
|
|
var env = envs.reduce(function(e1, e2){
|
|
return util._extend(e1, e2);
|
|
});
|
|
|
|
// Pass `env` option
|
|
CLI._restart(proc.pm2_env.name, env, function(err, ret) {
|
|
if (err) printError(err);
|
|
|
|
Satan.notifyGod('restart', proc.pm2_env.pm_id);
|
|
// And Remove from array to spy
|
|
apps_name.splice(apps_name.indexOf(proc.name), 1);
|
|
return next();
|
|
});
|
|
});
|
|
}
|
|
}, function(err) {
|
|
if (err) return cb ? cb(new Error(err)) : exitCli(cst.ERROR_EXIT);
|
|
// Start missing apps
|
|
return startApps(apps_name, function(err, apps) {
|
|
return cb ? cb(err, apps) : speedList(err ? 1 : 0);
|
|
});
|
|
});
|
|
return false;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Deploy command
|
|
*/
|
|
CLI.deploy = function(file, commands, cb) {
|
|
if (file == 'help')
|
|
return deployHelp();
|
|
|
|
var args = commands.rawArgs;
|
|
var env;
|
|
|
|
args.splice(0, args.indexOf('deploy') + 1);
|
|
|
|
// Find ecosystem file by default
|
|
if (file.indexOf('.json') == -1) {
|
|
env = args[0];
|
|
file = 'ecosystem.json';
|
|
}
|
|
else
|
|
env = args[1];
|
|
|
|
try {
|
|
var json_conf = parseConfig(fs.readFileSync(file), file);
|
|
} catch (e) {
|
|
printError(e);
|
|
return cb ? cb(e) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
if (!env)
|
|
return deployHelp();
|
|
|
|
if (!json_conf.deploy || !json_conf.deploy[env]) {
|
|
printError('%s environment is not defined in %s file', env, file);
|
|
return cb ? cb('%s environment is not defined in %s file') : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
if (!json_conf.deploy[env]['post-deploy']) {
|
|
json_conf.deploy[env]['post-deploy'] = 'pm2 startOrRestart ' + file + ' --env ' + env;
|
|
}
|
|
|
|
Deploy.deployForEnv(json_conf.deploy, env, args, function(err, data) {
|
|
if (err) {
|
|
printError('Deploy failed');
|
|
return cb ? cb(err) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
printOut('--> Success');
|
|
return exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get version of the daemonized PM2
|
|
* @method getVersion
|
|
* @callback cb
|
|
*/
|
|
CLI.getVersion = function(cb) {
|
|
Satan.executeRemote('getVersion', {}, function(err, version) {
|
|
return cb ? cb.apply(null, arguments) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Apply a RPC method on the json file
|
|
* @method actionFromJson
|
|
* @param {string} action RPC Method
|
|
* @param {string} JSON file
|
|
* @param {string} jsonVia action type
|
|
*/
|
|
CLI.actionFromJson = function(action, file, jsonVia, cb) {
|
|
var appConf;
|
|
|
|
if (jsonVia == 'pipe')
|
|
appConf = json5.parse(file);
|
|
else {
|
|
var data = fs.readFileSync(file);
|
|
appConf = parseConfig(data, file);
|
|
// v2 JSON declaration
|
|
if (appConf.apps) appConf = appConf.apps;
|
|
}
|
|
|
|
if (!Array.isArray(appConf)) appConf = [appConf]; //convert to array
|
|
|
|
if ((appConf = verifyConfs(appConf)) == null)
|
|
return exitCli(cst.ERROR_EXIT);
|
|
|
|
async.eachLimit(appConf, cst.CONCURRENT_ACTIONS, function(proc, next1) {
|
|
var name = '';
|
|
var new_env = mergeEnvironmentVariables(proc);
|
|
|
|
if (!proc.name)
|
|
name = p.basename(proc.script);
|
|
else
|
|
name = proc.name;
|
|
|
|
Common.getProcessIdByName(name, function(err, ids) {
|
|
if (err) {
|
|
printError(err);
|
|
return next1();
|
|
}
|
|
if (!ids) return next1();
|
|
|
|
async.eachLimit(ids, cst.CONCURRENT_ACTIONS, function(id, next2) {
|
|
var opts;
|
|
|
|
if (action == 'restartProcessId')
|
|
opts = { id : id, env : new_env };
|
|
else
|
|
opts = id;
|
|
|
|
Satan.executeRemote(action, opts, function(err, res) {
|
|
if (err) {
|
|
printError(err);
|
|
return next2();
|
|
}
|
|
|
|
if (action == 'restartProcessId') {
|
|
Satan.notifyGod('restart', id);
|
|
} else if (action == 'deleteProcessId') {
|
|
Satan.notifyGod('delete', id);
|
|
} else if (action == 'stopProcessId') {
|
|
Satan.notifyGod('stop', id);
|
|
}
|
|
|
|
printOut(cst.PREFIX_MSG + 'Process ' + id + ' restarted');
|
|
return next2();
|
|
});
|
|
}, function(err) {
|
|
return next1(null, {success:true});
|
|
});
|
|
});
|
|
}, function(err) {
|
|
if (cb) return cb(null, {success:true});
|
|
else return setTimeout(speedList, 100);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Startup script generation
|
|
* @method startup
|
|
* @param {string} platform type (centos|redhat|amazon|gentoo|systemd)
|
|
*/
|
|
CLI.startup = function(platform, opts, cb) {
|
|
if (process.getuid() != 0) {
|
|
return exec('whoami', function(err, stdout, stderr) {
|
|
console.error(cst.PREFIX_MSG + 'You have to run this command as root. Execute the following command:\n' +
|
|
chalk.grey(' sudo su -c "env PATH=$PATH:' + p.dirname(process.execPath) + ' pm2 startup ' + platform + ' -u ' + stdout.trim() +'"'));
|
|
cb ? cb({msg: 'You have to run this with elevated rights'}) : exitCli(cst.ERROR_EXIT);
|
|
});
|
|
}
|
|
|
|
var scriptFile = '/etc/init.d/pm2-init.sh',
|
|
script = cst.UBUNTU_STARTUP_SCRIPT;
|
|
|
|
if(platform == 'redhat'){
|
|
platform = 'centos';
|
|
}else if (platform == 'systemd') {
|
|
scriptFile = '/etc/systemd/system/pm2.service';
|
|
}else if (platform == 'darwin') {
|
|
scriptFile = path.join(process.env.HOME, 'Library/LaunchAgents/io.keymetrics.PM2.plist');
|
|
if(!fs.existsSync(path.dirname(scriptFile))) {
|
|
fs.mkdirSync(path.dirname(scriptFile));
|
|
}
|
|
}else if (platform == 'freebsd') {
|
|
scriptFile = '/etc/rc.d/pm2';
|
|
}
|
|
|
|
if(!!~['freebsd', 'systemd', 'centos', 'amazon', 'gentoo', 'darwin'].indexOf(platform)){
|
|
script = cst[platform.toUpperCase() + '_STARTUP_SCRIPT'];
|
|
}
|
|
|
|
script = fs.readFileSync(path.join(__dirname, script), {encoding: 'utf8'});
|
|
|
|
var user = opts.user || 'root';
|
|
|
|
script = script.replace(/%PM2_PATH%/g, process.mainModule.filename)
|
|
.replace(/%HOME_PATH%/g, cst.PM2_ROOT_PATH)
|
|
.replace(/%NODE_PATH%/g, platform != 'darwin' ? p.dirname(process.execPath) : process.env.PATH)
|
|
.replace(/%USER%/g, user);
|
|
|
|
printOut(cst.PREFIX_MSG + 'Generating system init script in ' + scriptFile);
|
|
|
|
try {
|
|
fs.writeFileSync(scriptFile, script);
|
|
} catch (e) {
|
|
console.error(e.stack || e);
|
|
}
|
|
|
|
if (!fs.existsSync(scriptFile)) {
|
|
printOut(script);
|
|
printOut(cst.PREFIX_MSG_ERR + ' There is a problem when trying to write file : ' + scriptFile);
|
|
return cb ? cb({msg:'Problem with ' + scriptFile}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
var cmd;
|
|
var cmdAsUser;
|
|
|
|
printOut(cst.PREFIX_MSG + 'Making script booting at startup...');
|
|
|
|
switch (platform) {
|
|
case 'systemd':
|
|
cmdAsUser = [
|
|
'pm2 dump', //We need an empty dump so that the first resurrect works correctly
|
|
'pm2 kill',
|
|
].join(' && ');
|
|
cmd = [
|
|
'systemctl daemon-reload',
|
|
'systemctl enable pm2',
|
|
'systemctl start pm2'
|
|
].join(' && ');
|
|
break;
|
|
case 'centos':
|
|
case 'amazon':
|
|
cmd = 'chmod +x ' + scriptFile + '; chkconfig --add ' + p.basename(scriptFile);
|
|
fs.closeSync(fs.openSync('/var/lock/subsys/pm2-init.sh', 'w'));
|
|
printOut(cst.PREFIX_MSG + '/var/lock/subsys/pm2-init.sh lockfile has been added');
|
|
break;
|
|
case 'gentoo':
|
|
cmd = 'chmod +x ' + scriptFile + '; rc-update add ' + p.basename(scriptFile) + ' default';
|
|
break;
|
|
case 'freebsd':
|
|
cmd = 'chmod +x ' + scriptFile;
|
|
break;
|
|
default :
|
|
cmd = 'chmod +x ' + scriptFile + ' && update-rc.d ' + p.basename(scriptFile) + ' defaults';
|
|
break;
|
|
}
|
|
|
|
if (platform == 'systemd') {
|
|
cmd = 'su ' + user + ' -c "' + cmdAsUser + '" && su root -c "' + cmd + '"';
|
|
}else if (platform == 'freebsd') {
|
|
cmd = 'su root -c "' + cmd + '"';
|
|
}else if (platform != 'darwin') {
|
|
cmd = 'su -c "' + cmd + '"';
|
|
}else {
|
|
cmd = 'pm2 dump';
|
|
}
|
|
|
|
printOut(cst.PREFIX_MSG + '-' + platform + '- Using the command:\n %s', chalk.grey(cmd));
|
|
|
|
exec(cmd, function(err, stdo, stde) {
|
|
if (err) {
|
|
printError(err);
|
|
printError('----- Are you sure you use the right platform command line option ? centos / redhat, amazon, ubuntu, gentoo, systemd or darwin?');
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
printOut(stdo.toString().replace(/[\r\n]$/, ''));
|
|
printOut(cst.PREFIX_MSG + 'Done.');
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
CLI.logrotate = function(opts, cb) {
|
|
if (process.getuid() != 0) {
|
|
return exec('whoami', function(err, stdout, stderr) {
|
|
printError(cst.PREFIX_MSG + 'You have to run this command as root. Execute the following command:\n' +
|
|
chalk.grey(' sudo env PATH=$PATH:' + p.dirname(process.execPath) + ' pm2 logrotate -u ' + stdout.trim()));
|
|
cb ? cb({msg: 'You have to run this with elevated rights'}) : exitCli(cst.ERROR_EXIT);
|
|
});
|
|
}
|
|
|
|
if(!fs.existsSync('/etc/logrotate.d')) {
|
|
printError(cst.PREFIX_MSG + '/etc/logrotate.d does not exist we can not copy the default configuration.');
|
|
return cb ? cb({msg: '/etc/logrotate.d does not exist'}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
var script = fs.readFileSync(path.join(__dirname, cst.LOGROTATE_SCRIPT), {encoding: 'utf8'});
|
|
|
|
var user = opts.user || 'root';
|
|
|
|
script = script.replace(/%HOME_PATH%/g, cst.PM2_ROOT_PATH)
|
|
.replace(/%USER%/g, user);
|
|
|
|
try {
|
|
fs.writeFileSync('/etc/logrotate.d/pm2-'+user, script);
|
|
} catch (e) {
|
|
console.error(e.stack || e);
|
|
}
|
|
|
|
printOut(cst.PREFIX_MSG + 'Logrotate configuration added to /etc/logrotate.d/pm2');
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
}
|
|
|
|
/**
|
|
* Ping daemon - if PM2 daemon not launched, it will launch it
|
|
* @method ping
|
|
*/
|
|
CLI.ping = function(cb) {
|
|
Satan.executeRemote('ping', {}, function(err, res) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
printOut(res);
|
|
return cb ? cb(null, res) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Reset meta data
|
|
* @method resetMetaProcess
|
|
*/
|
|
CLI.reset = function(process_name, cb) {
|
|
function processIds(ids, cb) {
|
|
async.eachLimit(ids, cst.CONCURRENT_ACTIONS, function(id, next) {
|
|
Satan.executeRemote('resetMetaProcessId', id, function(err, res) {
|
|
if (err) console.error(err);
|
|
printOut(cst.PREFIX_MSG + 'Reseting meta for process id %d', id);
|
|
return next();
|
|
});
|
|
}, function(err) {
|
|
if (err) return cb(new Error(err));
|
|
return cb ? cb(null, {success:true}) : speedList();
|
|
});
|
|
};
|
|
|
|
if (process_name == 'all') {
|
|
Common.getAllProcessId(function(err, ids) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
return processIds(ids, cb);
|
|
});
|
|
}
|
|
else if (isNaN(parseInt(process_name))) {
|
|
Common.getProcessIdByName(process_name, function(err, ids) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
if (ids.length === 0) {
|
|
printError('Unknow process name');
|
|
return cb ? cb({msg:'Unknow process name'}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
return processIds(ids, cb);
|
|
});
|
|
} else {
|
|
processIds([process_name], cb);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Resurrect processes
|
|
* @method resurrect
|
|
* @param {} cb
|
|
* @return
|
|
*/
|
|
CLI.resurrect = function(cb) {
|
|
try {
|
|
var apps = fs.readFileSync(cst.DUMP_FILE_PATH);
|
|
} catch(e) {
|
|
console.error(cst.PREFIX_MSG + 'No processes saved; DUMP file doesn\'t exist');
|
|
if (cb) return cb(e);
|
|
else return exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
(function ex(apps) {
|
|
if (!apps[0]) return cb ? cb(null, apps) : speedList();
|
|
Satan.executeRemote('prepare', apps[0], function(err, dt) {
|
|
if (err)
|
|
printError('Process %s not launched - (script missing)', apps[0].pm_exec_path);
|
|
else
|
|
printOut('Process %s launched', apps[0].pm_exec_path);
|
|
|
|
Satan.notifyGod('resurrect', dt[0].pm2_env.pm_id);
|
|
|
|
apps.shift();
|
|
return ex(apps);
|
|
});
|
|
return false;
|
|
})(json5.parse(apps));
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method updatePM2
|
|
* @param {} cb
|
|
* @return
|
|
*/
|
|
|
|
CLI.updatePM2 = function(cb) {
|
|
printOut('Be sure to have the latest version by doing `npm install pm2@latest -g` before doing this procedure.');
|
|
|
|
// Dump PM2 processes
|
|
Satan.executeRemote('notifyKillPM2', {}, function() {});
|
|
CLI.dump(function(err) {
|
|
debug('Dumping successfull', err);
|
|
CLI.killDaemon(function() {
|
|
debug('------------------ Everything killed', arguments);
|
|
Satan.launchDaemon(function(err, child) {
|
|
Satan.launchRPC(function() {
|
|
CLI.resurrect(function() {
|
|
printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated'));
|
|
require('./Modularizer.js').launchAll(function() {
|
|
return cb ? cb(null, {success:true}) : speedList();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Dump current processes managed by pm2 into DUMP_FILE_PATH file
|
|
* @method dump
|
|
* @param {} cb
|
|
* @return
|
|
*/
|
|
CLI.dump = function(cb) {
|
|
var env_arr = [];
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError('Error retrieving process list: ' + err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
/**
|
|
* Description
|
|
* @method fin
|
|
* @param {} err
|
|
* @return
|
|
*/
|
|
function fin(err) {
|
|
try {
|
|
fs.writeFileSync(cst.DUMP_FILE_PATH, json5.stringify(env_arr));
|
|
} catch (e) {
|
|
console.error(e.stack || e);
|
|
}
|
|
if (cb) return cb(null, {success:true});
|
|
else return exitCli(cst.SUCCESS_EXIT);
|
|
}
|
|
|
|
(function ex(apps) {
|
|
if (!apps[0]) return fin(null);
|
|
delete apps[0].pm2_env.instances;
|
|
delete apps[0].pm2_env.pm_id;
|
|
if (!apps[0].pm2_env.pmx_module)
|
|
env_arr.push(apps[0].pm2_env);
|
|
apps.shift();
|
|
return ex(apps);
|
|
})(list);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Launch API interface
|
|
* @method web
|
|
* @return
|
|
*/
|
|
CLI.web = function(cb) {
|
|
var filepath = p.resolve(p.dirname(module.filename), 'HttpInterface.js');
|
|
|
|
CLI.start(filepath, {
|
|
name : 'pm2-http-interface',
|
|
execMode : 'fork_mode'
|
|
}, function(err, proc) {
|
|
if (err) {
|
|
printError(cst.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err);
|
|
return cb ? cb({msg:err}) : speedList();
|
|
}
|
|
printOut(cst.PREFIX_MSG + 'Process launched');
|
|
return cb ? cb(null, proc) : speedList();
|
|
});
|
|
};
|
|
|
|
CLI.gracefulReload = function(process_name, cb) {
|
|
if (process_name == 'all')
|
|
CLI._reloadAll('softReloadProcessId', cb);
|
|
else if (process_name.indexOf('.json') > 0)
|
|
CLI._jsonStartOrAction('gracefulReload', process_name, commander);
|
|
else
|
|
CLI._reloadProcessName(process_name, 'softReloadProcessId', cb);
|
|
};
|
|
|
|
CLI.reload = function(process_name, cb) {
|
|
if (process_name == 'all')
|
|
CLI._reloadAll('reloadProcessId', cb);
|
|
else if (process_name.indexOf('.json') > 0)
|
|
CLI._jsonStartOrAction('reload', process_name, commander);
|
|
else
|
|
CLI._reloadProcessName(process_name, 'reloadProcessId', cb);
|
|
};
|
|
|
|
/**
|
|
* CLI method for reloading
|
|
* @method reload
|
|
* @param {string} reload_method RPC method to hit (can be reloadProcessId or softReloadProcessId)
|
|
* @return
|
|
*/
|
|
CLI._reloadAll = function (reload_method, cb) {
|
|
Common.getAllProcess(function(err, procs) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
async.eachLimit(procs, cst.CONCURRENT_ACTIONS, function(proc, next) {
|
|
if ((proc.pm2_env.status == cst.STOPPED_STATUS ||
|
|
proc.pm2_env.status == cst.STOPPING_STATUS ||
|
|
proc.pm2_env.status == cst.ERRORED_STATUS)) {
|
|
return next();
|
|
}
|
|
|
|
if (proc.pm2_env.exec_mode != 'cluster_mode') {
|
|
console.log(cst.PREFIX_MSG_WARNING + '%s app can\'t be reloaded - restarting it', proc.pm2_env.name);
|
|
return CLI._restart(proc.pm2_env.name, next);
|
|
}
|
|
|
|
Satan.executeRemote(reload_method, proc.pm2_env.pm_id, function(err, list) {
|
|
printOut(cst.PREFIX_MSG + 'Process %s succesfully reloaded', proc.pm2_env.name);
|
|
Satan.notifyGod('reload', proc.pm2_env.pm_id);
|
|
return next();
|
|
});
|
|
return false;
|
|
}, function(err) {
|
|
return cb ? cb(null, procs) : speedList();
|
|
});
|
|
return false;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* CLI method for reloading
|
|
* @method reloadProcessName
|
|
* @param {string} process_name name of processes to reload
|
|
* @param {string} reload_method RPC method to hit (can be reloadProcessId or softReloadProcessId)
|
|
* @return
|
|
*/
|
|
CLI._reloadProcessName = function(process_name, reload_method, cb) {
|
|
printOut(cst.PREFIX_MSG + 'Reloading process by name %s', process_name);
|
|
|
|
Common.getProcessByName(process_name, function(err, processes) {
|
|
|
|
if (err) {
|
|
return cb ? cb({msg : err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
if (processes.length === 0) {
|
|
printError('No processes with this name: %s', process_name);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
async.eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) {
|
|
// if (proc.state == cst.STOPPED_STATUS ||
|
|
// proc.state == cst.STOPPING_STATUS ||
|
|
// proc.state == cst.ERRORED_STATUS) {
|
|
// return next();
|
|
// }
|
|
|
|
if (proc.pm2_env.exec_mode != 'cluster_mode') {
|
|
console.log(cst.PREFIX_MSG_WARNING + '%s app can\'t be reloaded - restarting it', process_name);
|
|
|
|
Satan.notifyGod('restart', proc.pm2_env.pm_id);
|
|
|
|
return CLI.restart(process_name, next);
|
|
}
|
|
|
|
Satan.executeRemote(reload_method, proc.pm2_env.pm_id, function(err, res) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
Satan.notifyGod('reload', proc.pm2_env.pm_id);
|
|
|
|
printOut(cst.PREFIX_MSG + 'Process %s succesfully reloaded', proc.pm2_env.name);
|
|
return next();
|
|
});
|
|
}, function(err) {
|
|
if (err) {
|
|
printError(err.stack);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
printOut(cst.PREFIX_MSG + 'All processes reloaded');
|
|
return cb ? cb(null, processes) : setTimeout(speedList, 300);
|
|
});
|
|
return false;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Execute command with locking system
|
|
*/
|
|
CLI.remote = function(command, opts, cb) {
|
|
var proc_name = opts.name;
|
|
|
|
Satan.lock({
|
|
name : proc_name,
|
|
meta : {
|
|
action : command
|
|
}
|
|
}, function(err) {
|
|
if (err) return cb(err + '');
|
|
|
|
console.log('Processes %s locked', proc_name);
|
|
CLI[command](opts.name, function(err_cmd, ret) {
|
|
Satan.unlock({
|
|
name : proc_name,
|
|
meta : {
|
|
result : {success : true}
|
|
}
|
|
}, function(err) {
|
|
console.log('Command %s finished', command);
|
|
if (err)
|
|
console.log('Processes %s could not be unlocked', proc_name);
|
|
else
|
|
console.log('Processes %s unlocked', proc_name);
|
|
return cb(err_cmd, ret);
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* This remote method allows to pass multiple arguments
|
|
* to PM2
|
|
* It is used for the new scoped PM2 action system
|
|
*/
|
|
CLI.remoteV2 = function(command, opts, cb) {
|
|
if (CLI[command].length == 1)
|
|
return CLI[command](cb);
|
|
|
|
opts.args.push(cb);
|
|
return CLI[command].apply(this, opts.args);
|
|
};
|
|
|
|
/**
|
|
* Start or restart|reload|gracefulReload a JSON configuration file
|
|
* @param {string} action restart|reload
|
|
* @param {string} json_conf JS file path
|
|
* @param {string} opts option like environment type and co
|
|
* @callback cb optional
|
|
* @param cb
|
|
*/
|
|
CLI._jsonStartOrAction = function(action, json_conf, opts, cb) {
|
|
try {
|
|
var data = fs.readFileSync(json_conf);
|
|
} catch(e) {
|
|
printError('Configuration file %s is missing. Action canceled.', json_conf);
|
|
return cb ? cb(e) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
var appConf = parseConfig(data, json_conf), deployConf = null;
|
|
// v2 JSON declaration
|
|
if (appConf.deploy) deployConf = appConf.deploy;
|
|
if (appConf.apps) appConf = appConf.apps;
|
|
|
|
if ((appConf = verifyConfs(appConf)) == null)
|
|
return exitCli(cst.ERROR_EXIT);
|
|
|
|
var apps_name = [];
|
|
|
|
appConf.forEach(function(app) {
|
|
apps_name.push(app.name);
|
|
});
|
|
|
|
function startApps(app_name_to_start, cb) {
|
|
var apps_to_start = [];
|
|
|
|
appConf.forEach(function(app, i) {
|
|
if (app_name_to_start.indexOf(app.name) != -1) {
|
|
apps_to_start.push(appConf[i]);
|
|
}
|
|
});
|
|
|
|
async.eachLimit(apps_to_start, cst.CONCURRENT_ACTIONS, function(app, next) {
|
|
mergeEnvironmentVariables(app, opts.env, deployConf);
|
|
var resolved_paths = null;
|
|
try {
|
|
resolved_paths = resolvePaths(app);
|
|
} catch (e) {
|
|
printError(e);
|
|
return cb ? cb({msg : e.message || e}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
Satan.executeRemote('prepare', resolved_paths, function(err, data) {
|
|
return next();
|
|
});
|
|
|
|
}, function(err) {
|
|
return cb(null, {success:true});
|
|
});
|
|
return false;
|
|
}
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
async.eachLimit(list, cst.CONCURRENT_ACTIONS, function(proc, next) {
|
|
if (apps_name.indexOf(proc.name) == -1)
|
|
return next();
|
|
|
|
if (action == 'reload') {
|
|
CLI._reloadProcessName(proc.pm2_env.name, 'reloadProcessId', function(err, ret) {
|
|
if (err) printError(err);
|
|
|
|
Satan.notifyGod('reload', proc.pm2_env.pm_id);
|
|
|
|
// And Remove from array to spy
|
|
apps_name.splice(apps_name.indexOf(proc.name), 1);
|
|
return next();
|
|
});
|
|
} else if (action == 'gracefulReload') {
|
|
CLI._reloadProcessName(proc.pm2_env.name, 'softReloadProcessId', function(err, ret) {
|
|
if (err) printError(err);
|
|
// And Remove from array to spy
|
|
|
|
Satan.notifyGod('graceful reload', proc.pm2_env.pm_id);
|
|
|
|
apps_name.splice(apps_name.indexOf(proc.name), 1);
|
|
return next();
|
|
});
|
|
} else {
|
|
// Get `env` from appConf by name
|
|
async.filter(appConf, function(app, callback){ callback(app.name == proc.name); }, function(apps){
|
|
var envs = apps.map(function(app){
|
|
// Binds env_diff to env and returns it.
|
|
return mergeEnvironmentVariables(app, opts.env, deployConf);
|
|
});
|
|
// Assigns own enumerable properties of all
|
|
// Notice: if people use the same name in different apps,
|
|
// duplicated envs will be overrode by the last one
|
|
var env = envs.reduce(function(e1, e2){
|
|
return util._extend(e1, e2);
|
|
});
|
|
|
|
// Pass `env` option
|
|
CLI._restart(proc.pm2_env.name, env, function(err, ret) {
|
|
if (err) printError(err);
|
|
|
|
Satan.notifyGod('restart', proc.pm2_env.pm_id);
|
|
// And Remove from array to spy
|
|
apps_name.splice(apps_name.indexOf(proc.name), 1);
|
|
return next();
|
|
});
|
|
});
|
|
}
|
|
return false;
|
|
}, function(err) {
|
|
if (err) return cb ? cb(new Error(err)) : exitCli(cst.ERROR_EXIT);
|
|
// Start missing apps
|
|
return startApps(apps_name, function() {
|
|
return cb ? cb(null, {success:true}) : speedList();
|
|
});
|
|
});
|
|
return false;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* This methods is used for stop, delete and restart
|
|
* Module cannot be stopped or deleted but can be restarted
|
|
*/
|
|
CLI._operate = function(action_name, process_name, envs, cb) {
|
|
// Make sure all options exist
|
|
|
|
if (typeof(envs) == 'function'){
|
|
cb = envs;
|
|
envs = {};
|
|
}
|
|
|
|
/**
|
|
* Operate action on specific process id
|
|
*/
|
|
function processIds(ids, cb) {
|
|
async.eachLimit(ids, cst.CONCURRENT_ACTIONS, function(id, next) {
|
|
var opts = id;
|
|
if (action_name == 'restartProcessId') {
|
|
// I dont get why we did this
|
|
// env : process.env.PM2_PROGRAMMATIC === 'true' ? {} : util._extend(process.env, envs)
|
|
opts = {
|
|
id : id,
|
|
env : util._extend(process.env, envs)
|
|
};
|
|
}
|
|
|
|
Satan.executeRemote(action_name, opts, function(err, res) {
|
|
if (err) {
|
|
printError(cst.PREFIX_MSG_ERR + 'Process %s not found', id);
|
|
return next('Process not found');
|
|
}
|
|
|
|
if (action_name == 'restartProcessId') {
|
|
Satan.notifyGod('restart', id);
|
|
} else if (action_name == 'deleteProcessId') {
|
|
Satan.notifyGod('delete', id);
|
|
} else if (action_name == 'stopProcessId') {
|
|
Satan.notifyGod('stop', id);
|
|
}
|
|
|
|
printOut(cst.PREFIX_MSG + action_name + ' process id %d', id);
|
|
return next();
|
|
});
|
|
}, function(err) {
|
|
if (err) return cb ? cb(new Error(err)) : exitCli(cst.ERROR_EXIT);
|
|
return cb ? cb(null, {success:true}) : speedList();
|
|
});
|
|
};
|
|
|
|
if (process_name == 'all') {
|
|
Common.getAllProcessId(function(err, ids) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
if (!ids || ids.length === 0) {
|
|
printError(cst.PREFIX_MSG_WARNING + 'No process found');
|
|
return cb ? cb({ success : false, msg : 'process name not found'}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
return processIds(ids, cb);
|
|
});
|
|
}
|
|
else if (isNaN(process_name)) {
|
|
|
|
/**
|
|
* We can not stop or delete a module but we can restart it
|
|
* to refresh configuration variable
|
|
*/
|
|
var allow_module_restart = action_name == 'restartProcessId' ? true : false;
|
|
|
|
Common.getProcessIdByName(process_name, allow_module_restart, function(err, ids, full_detail) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
if (!ids || ids.length === 0) {
|
|
printError(cst.PREFIX_MSG_ERR + 'Process %s not found', process_name);
|
|
return cb ? cb({ success : false, msg : 'process name not found'}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
/**
|
|
* Determine if the process to restart is a module
|
|
* if yes load configuration variables and merge with the current environment
|
|
*/
|
|
if (full_detail && typeof(ids[0]) !== 'undefined' && full_detail[ids[0]] &&
|
|
full_detail[ids[0]].pm2_env && full_detail[ids[0]].pm2_env.pmx_module === true) {
|
|
|
|
var additional_env = Modularizer.getAdditionalConf(process_name);
|
|
util._extend(envs, additional_env);
|
|
}
|
|
|
|
|
|
return processIds(ids, cb);
|
|
});
|
|
} else {
|
|
processIds([process_name], cb);
|
|
}
|
|
};
|
|
|
|
|
|
CLI.restart = function(process_name, cb) {
|
|
if (typeof(process_name) === 'number')
|
|
process_name = process_name.toString();
|
|
|
|
if (process_name == "-") {
|
|
process.stdin.resume();
|
|
process.stdin.setEncoding('utf8');
|
|
process.stdin.on('data', function (param) {
|
|
process.stdin.pause();
|
|
CLI.actionFromJson('restartProcessId', param, 'pipe', cb);
|
|
});
|
|
}
|
|
else if (process_name.indexOf('.json') > 0)
|
|
CLI.actionFromJson('restartProcessId', process_name, 'file', cb);
|
|
else
|
|
CLI._restart(process_name, process.env, cb);
|
|
};
|
|
|
|
CLI._restart = function(process_name, envs, cb) {
|
|
CLI._operate('restartProcessId', process_name, envs, cb);
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method deleteProcess
|
|
* @param {} process_name
|
|
* @param {} jsonVia
|
|
* @return
|
|
*/
|
|
CLI.delete = function(process_name, jsonVia, cb) {
|
|
if (typeof(jsonVia) === "function") {
|
|
cb = jsonVia;
|
|
jsonVia = null;
|
|
}
|
|
if (typeof(process_name) === "number") {
|
|
process_name = process_name.toString();
|
|
}
|
|
|
|
printOut(cst.PREFIX_MSG + 'Deleting %s process', process_name);
|
|
|
|
if (jsonVia == 'pipe')
|
|
return CLI.actionFromJson('deleteProcessId', process_name, 'pipe', cb);
|
|
if (process_name.indexOf('.json') > 0)
|
|
return CLI.actionFromJson('deleteProcessId', process_name, 'file', cb);
|
|
else {
|
|
CLI._delete(process_name, cb);
|
|
}
|
|
};
|
|
|
|
CLI._delete = function(process_name, cb) {
|
|
CLI._operate('deleteProcessId', process_name, cb);
|
|
};
|
|
|
|
CLI.stop = function(process_name, cb) {
|
|
if (typeof(process_name) === 'number')
|
|
process_name = process_name.toString();
|
|
|
|
printOut(cst.PREFIX_MSG + 'Stopping ' + process_name);
|
|
|
|
if (process_name == "-") {
|
|
process.stdin.resume();
|
|
process.stdin.setEncoding('utf8');
|
|
process.stdin.on('data', function (param) {
|
|
process.stdin.pause();
|
|
CLI.actionFromJson('stopProcessId', param, 'pipe', cb);
|
|
});
|
|
}
|
|
else if (process_name.indexOf('.json') > 0)
|
|
CLI.actionFromJson('stopProcessId', process_name, 'file', cb);
|
|
else {
|
|
CLI._stop(process_name, cb);
|
|
}
|
|
};
|
|
|
|
CLI._stop = function(process_name, cb) {
|
|
CLI._operate('stopProcessId', process_name, cb);
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method generateSample
|
|
* @param {} name
|
|
* @return
|
|
*/
|
|
CLI.generateSample = function() {
|
|
var sample = fs.readFileSync(path.join(__dirname, cst.SAMPLE_FILE_PATH));
|
|
var dt = sample.toString();
|
|
var f_name = 'ecosystem.json5';
|
|
|
|
try {
|
|
fs.writeFileSync(path.join(process.env.PWD, f_name), dt);
|
|
} catch (e) {
|
|
console.error(e.stack || e);
|
|
}
|
|
printOut('File %s generated', path.join(process.env.PWD, f_name));
|
|
exitCli(cst.SUCCESS_EXIT);
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method list
|
|
* @return
|
|
*/
|
|
CLI.list = function(cb) {
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
return cb ? cb(null, list) : speedList();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method jlist
|
|
* @param {} debug
|
|
* @return
|
|
*/
|
|
CLI.jlist = function(debug) {
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError(err);
|
|
exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
if (debug) {
|
|
printOut(util.inspect(list, false, null, false));
|
|
}
|
|
else {
|
|
printOut(JSON.stringify(list));
|
|
}
|
|
|
|
exitCli(cst.SUCCESS_EXIT);
|
|
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Scale up/down a process
|
|
* @method scale
|
|
*/
|
|
CLI.scale = function(app_name, number, cb) {
|
|
|
|
function addProcs(proc, value, cb) {
|
|
(function ex(proc, number) {
|
|
if (number-- === 0) return cb();
|
|
printOut(cst.PREFIX_MSG + 'Scaling up application');
|
|
Satan.executeRemote('duplicateProcessId', proc.pm2_env.pm_id, ex.bind(this, proc, number));
|
|
})(proc, number);
|
|
}
|
|
|
|
function rmProcs(procs, value, cb) {
|
|
var i = 0;
|
|
|
|
(function ex(procs, number) {
|
|
if (number++ === 0) return cb();
|
|
CLI._operate('deleteProcessId', procs[i++].pm2_env.pm_id, ex.bind(this, procs, number));
|
|
})(procs, number);
|
|
}
|
|
|
|
function end() {
|
|
return cb ? cb(null, {success:true}) : speedList();
|
|
}
|
|
|
|
Common.getProcessByName(app_name, function(err, procs) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
if (!procs || procs.length === 0) {
|
|
printError(cst.PREFIX_MSG_ERR + 'Application %s not found', app_name);
|
|
return cb ? cb({msg: 'App not found'}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
if (procs[0].pm2_env.exec_mode !== 'cluster_mode') {
|
|
printError(cst.PREFIX_MSG_ERR + 'Application %s is not in cluster mode', app_name);
|
|
return cb ? cb({msg: 'App not in cluster mode'}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
var proc_number = procs.length;
|
|
|
|
if (typeof(number) === 'string' && number.indexOf('+') >= 0) {
|
|
number = parseInt(number, 10);
|
|
return addProcs(procs[0], number, end);
|
|
}
|
|
else if (typeof(number) === 'string' && number.indexOf('-') >= 0) {
|
|
number = parseInt(number, 10);
|
|
return rmProcs(procs[0], number, end);
|
|
}
|
|
else {
|
|
number = parseInt(number, 10);
|
|
number = number - proc_number;
|
|
|
|
if (number < 0)
|
|
return rmProcs(procs, number, end);
|
|
else if (number > 0)
|
|
return addProcs(procs[0], number, end);
|
|
else {
|
|
printError(cst.PREFIX_MSG_ERR + 'Nothing to do');
|
|
return cb ? cb({msg: 'Same process number'}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method flush
|
|
* @return
|
|
*/
|
|
CLI.flush = function(cb) {
|
|
printOut(cst.PREFIX_MSG + 'Flushing ' + cst.PM2_LOG_FILE_PATH);
|
|
fs.closeSync(fs.openSync(cst.PM2_LOG_FILE_PATH, 'w'));
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
list.forEach(function(l) {
|
|
printOut(cst.PREFIX_MSG + 'Flushing');
|
|
printOut(cst.PREFIX_MSG + l.pm2_env.pm_out_log_path);
|
|
printOut(cst.PREFIX_MSG + l.pm2_env.pm_err_log_path);
|
|
|
|
if (l.pm2_env.pm_log_path) {
|
|
printOut(cst.PREFIX_MSG + l.pm2_env.pm_log_path);
|
|
fs.closeSync(fs.openSync(l.pm2_env.pm_log_path, 'w'));
|
|
}
|
|
|
|
fs.closeSync(fs.openSync(l.pm2_env.pm_out_log_path, 'w'));
|
|
fs.closeSync(fs.openSync(l.pm2_env.pm_err_log_path, 'w'));
|
|
});
|
|
printOut(cst.PREFIX_MSG + 'Logs flushed');
|
|
return cb ? cb(null, list) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method describeProcess
|
|
* @param {} pm2_id
|
|
* @return
|
|
*/
|
|
CLI.describe = function(pm2_id, cb) {
|
|
var found_proc = [];
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError('Error retrieving process list: ' + err);
|
|
exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
list.forEach(function(proc) {
|
|
if ((!isNaN(parseInt(pm2_id)) && proc.pm_id == pm2_id) ||
|
|
(typeof(pm2_id) === 'string' && proc.name == pm2_id)) {
|
|
found_proc.push(proc);
|
|
}
|
|
});
|
|
|
|
if (found_proc.length === 0) {
|
|
printError(cst.PREFIX_MSG_WARNING + '%s doesn\'t exist', pm2_id);
|
|
return cb ? cb(null, []) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
if (!cb) {
|
|
found_proc.forEach(function(proc) {
|
|
UX.describeTable(proc);
|
|
});
|
|
}
|
|
|
|
return cb ? cb(null, found_proc) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
|
|
/**
|
|
* Description
|
|
* @method IProbe
|
|
* @param {} pm2_id
|
|
* @return
|
|
*/
|
|
CLI.IProbe = function(pm2_id) {
|
|
|
|
var getProbeData = function(pm2_id, cb) {
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError('Error retrieving process list: ' + err);
|
|
exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
var found_proc = [];
|
|
|
|
list.forEach(function(proc) {
|
|
if ((!isNaN(parseInt(pm2_id)) && proc.pm_id == pm2_id)
|
|
|| (typeof(pm2_id) === 'string' && proc.name == pm2_id)
|
|
|| pm2_id === 'ALL') {
|
|
found_proc.push(proc);
|
|
}
|
|
});
|
|
|
|
if (found_proc.length === 0) {
|
|
return cb(['{bold}No probes to display{/bold}']);
|
|
}
|
|
|
|
var content = [];
|
|
|
|
found_proc.forEach(function(proc) {
|
|
content.push('{bold}' + proc.pm2_env.name + ' (id:' + proc.pm2_env.pm_id + ')' + '{/bold}');
|
|
|
|
var monit = proc.pm2_env.axm_monitor;
|
|
for (var k in monit)
|
|
content.push(k + ' : ' + monit[k].value);
|
|
|
|
content.push('\n');
|
|
});
|
|
|
|
return cb(content);
|
|
});
|
|
};
|
|
|
|
var iprobe = new require('./IProbe.js')();
|
|
|
|
setInterval(function() {
|
|
getProbeData(pm2_id, function(content) {
|
|
iprobe.refresh(content);
|
|
});
|
|
}, 300);
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method reloadLogs
|
|
* @return
|
|
*/
|
|
CLI.reloadLogs = function(cb) {
|
|
printOut('Reloading all logs...');
|
|
Satan.executeRemote('reloadLogs', {}, function(err, logs) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
printOut('All logs reloaded');
|
|
return cb ? cb(null, logs) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method sendSignalToProcessName
|
|
* @param {} signal
|
|
* @param {} process_name
|
|
* @return
|
|
*/
|
|
CLI.sendSignalToProcessName = function(signal, process_name, cb) { Satan.executeRemote('sendSignalToProcessName', {
|
|
signal : signal,
|
|
process_name : process_name
|
|
}, function(err, list) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
printOut('Succesfully sent signal %s to process name %s', signal, process_name);
|
|
return cb ? cb(null, list) : speedList();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method sendSignalToProcessId
|
|
* @param {} signal
|
|
* @param {} process_id
|
|
* @return
|
|
*/
|
|
CLI.sendSignalToProcessId = function(signal, process_id, cb) {
|
|
Satan.executeRemote('sendSignalToProcessId', {
|
|
signal : signal,
|
|
process_id : process_id
|
|
}, function(err, list) {
|
|
if (err) {
|
|
printError(err);
|
|
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
printOut('Succesfully sent signal %s to process id %s', signal, process_id);
|
|
return cb ? cb(null, list) : speedList();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method monit
|
|
* @return
|
|
*/
|
|
CLI.monit = function(cb) {
|
|
if (cb) return cb({msg: 'Monit cant be called programmatically'});
|
|
Monit.init();
|
|
|
|
function launchMonitor() {
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
debug('CLI.monit - getMonitorData', err);
|
|
|
|
if (err) {
|
|
console.error('Error retrieving process list: ' + err);
|
|
exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
Monit.refresh(list);
|
|
|
|
setTimeout(function() {
|
|
launchMonitor();
|
|
}, 400);
|
|
});
|
|
}
|
|
|
|
launchMonitor();
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method streamLogs
|
|
* @param {String} id
|
|
* @param {Number} lines
|
|
* @param {Boolean} raw
|
|
* @return
|
|
*/
|
|
|
|
CLI.streamLogs = function(id, lines, raw, timestamp, exclusive) {
|
|
var files_list = [];
|
|
|
|
// If no argument is given, we stream logs for all running apps
|
|
id = id || 'all';
|
|
lines = lines !== undefined ? lines : 20;
|
|
lines = lines < 0 ? -(lines) : lines;
|
|
|
|
// Avoid duplicates and check if path is different from '/dev/null'
|
|
var pushIfUnique = function(entry) {
|
|
var exists = false;
|
|
|
|
if (entry.path.toLowerCase
|
|
&& entry.path.toLowerCase() !== '/dev/null') {
|
|
|
|
files_list.some(function(file) {
|
|
if (file.path === entry.path)
|
|
exists = true;
|
|
return exists;
|
|
});
|
|
|
|
if (exists)
|
|
return;
|
|
|
|
files_list.push(entry);
|
|
}
|
|
};
|
|
|
|
// Get the list of all running apps
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
printError(err);
|
|
exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
if (lines === 0)
|
|
return Log.stream(id, raw, timestamp, exclusive);
|
|
if (!raw)
|
|
printOut(chalk['inverse'](util.format.call(this, '[PM2] Tailing last %d lines for [%s] process%s', lines, id, id === 'all' ? 'es' : '', '\n')));
|
|
|
|
// Populate the array `files_list` with the paths of all files we need to tail
|
|
list.forEach(function(proc) {
|
|
if (proc.pm2_env
|
|
&& (id === 'all'
|
|
|| proc.pm2_env.name == id
|
|
|| proc.pm2_env.pm_id == id)) {
|
|
if (proc.pm2_env.pm_out_log_path && exclusive !== 'err')
|
|
pushIfUnique({
|
|
path : proc.pm2_env.pm_out_log_path,
|
|
app_name : proc.pm2_env.name + '-' + proc.pm2_env.pm_id,
|
|
type : 'out'});
|
|
if (proc.pm2_env.pm_err_log_path && exclusive !== 'out')
|
|
pushIfUnique({
|
|
path : proc.pm2_env.pm_err_log_path,
|
|
app_name : proc.pm2_env.name + '-' + proc.pm2_env.pm_id,
|
|
type : 'err'
|
|
});
|
|
}
|
|
});
|
|
|
|
if (!raw && (id === 'all' || id === 'PM2') && exclusive === false) {
|
|
Log.tail([{
|
|
path : cst.PM2_LOG_FILE_PATH,
|
|
app_name : 'PM2',
|
|
type : 'PM2'
|
|
}], lines, raw, function() {
|
|
Log.tail(files_list, lines, raw, function() {
|
|
Log.stream(id, raw, timestamp, exclusive);
|
|
});
|
|
});
|
|
}
|
|
else {
|
|
Log.tail(files_list, lines, raw, function() {
|
|
Log.stream(id, raw, timestamp, exclusive);
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method killDaemon
|
|
* @param {} cb
|
|
* @return
|
|
*/
|
|
CLI.killDaemon = CLI.kill = function(cb) {
|
|
printOut(cst.PREFIX_MSG + 'Stopping PM2...');
|
|
|
|
Satan.executeRemote('notifyKillPM2', {}, function() {});
|
|
CLI.killAllModules(function() {
|
|
CLI._operate('deleteProcessId', 'all', function(err, list) {
|
|
printOut(cst.PREFIX_MSG + 'All processes have been stopped and deleted');
|
|
|
|
InteractorDaemonizer.killDaemon(function(err, data) {
|
|
Satan.killDaemon(function(err, res) {
|
|
if (err) printError(err);
|
|
printOut(cst.PREFIX_MSG + 'PM2 stopped');
|
|
return cb ? cb(err, res) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Launch interactor
|
|
* @method interact
|
|
* @param {string} secret_key
|
|
* @param {string} public_key
|
|
* @param {string} machine_name
|
|
*/
|
|
CLI.install = function(module_name, cb) {
|
|
Modularizer.install(module_name, function(err, data) {
|
|
if (err)
|
|
return cb ? cb(err) : speedList(cst.ERROR_EXIT);
|
|
return cb ? cb(null, data) : speedList(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Launch interactor
|
|
* @method interact
|
|
* @param {string} secret_key
|
|
* @param {string} public_key
|
|
* @param {string} machine_name
|
|
*/
|
|
CLI.uninstall = function(module_name, cb) {
|
|
Modularizer.uninstall(module_name, function(err, data) {
|
|
if (err)
|
|
return cb ? cb(err) : speedList(cst.ERROR_EXIT);
|
|
return cb ? cb(null, data) : speedList(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
CLI.publish = function(module_name, cb) {
|
|
Modularizer.publish(function(err, data) {
|
|
if (err)
|
|
return cb ? cb(err) : speedList(cst.ERROR_EXIT);
|
|
return cb ? cb(null, data) : speedList(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
CLI.killAllModules = function(cb) {
|
|
Common.getAllModulesId(function(err, modules_id) {
|
|
async.forEachLimit(modules_id, 1, function(id, next) {
|
|
CLI._operate('deleteProcessId', id, next);
|
|
}, function() {
|
|
return cb ? cb() : false;
|
|
});
|
|
});
|
|
};
|
|
|
|
CLI.deleteModule = function(module_name, cb) {
|
|
var found_proc = [];
|
|
|
|
Common.getAllProcess(function(err, procs) {
|
|
if (err) {
|
|
Common.printError('Error retrieving process list: ' + err);
|
|
return cb(err);
|
|
}
|
|
|
|
procs.forEach(function(proc) {
|
|
if (proc.pm2_env.name == module_name && proc.pm2_env.pmx_module) {
|
|
found_proc.push(proc.pm_id);
|
|
}
|
|
});
|
|
|
|
if (found_proc.length == 0)
|
|
return cb();
|
|
|
|
CLI._operate('deleteProcessId', found_proc[0], function(err) {
|
|
if (err) return cb(err);
|
|
Common.printOut('In memory process deleted');
|
|
return cb();
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Configuration
|
|
*/
|
|
function displayConf(target_app, cb) {
|
|
if (typeof(target_app) == 'function') {
|
|
cb = target_app;
|
|
target_app = null;
|
|
}
|
|
|
|
Configuration.getAll(function(err, data) {
|
|
UX.dispKeys(data, target_app);
|
|
return cb();
|
|
});
|
|
};
|
|
|
|
CLI.get = function(key, cb) {
|
|
if (!key || key == 'all') {
|
|
displayConf(function(err, data) {
|
|
if (err)
|
|
return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
return false;
|
|
}
|
|
Configuration.get(key, function(err, data) {
|
|
Common.printOut(data);
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
var Password = require('./Interactor/Password.js');
|
|
|
|
CLI.set = function(key, value, cb) {
|
|
|
|
/**
|
|
* Specific when setting pm2 password
|
|
* Used for restricted remote actions
|
|
* Also alert Interactor that password has been set
|
|
*/
|
|
if (key.indexOf('pm2:passwd') > -1) {
|
|
value = Password.generate(value);
|
|
Configuration.set(key, value, function(err) {
|
|
if (err)
|
|
return cb ? cb({success:false, err:err }) : exitCli(cst.ERROR_EXIT);
|
|
InteractorDaemonizer.launchRPC(function(err) {
|
|
if (err) {
|
|
displayConf('pm2', function() {
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
return false;
|
|
}
|
|
InteractorDaemonizer.rpc.passwordSet(function() {
|
|
InteractorDaemonizer.disconnectRPC(function() {
|
|
displayConf('pm2', function() {
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
});
|
|
});
|
|
return false;
|
|
});
|
|
});
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Set value
|
|
*/
|
|
Configuration.set(key, value, function(err) {
|
|
if (err)
|
|
return cb ? cb({success:false, err:err }) : exitCli(cst.ERROR_EXIT);
|
|
|
|
var values = [];
|
|
|
|
if (key.indexOf('.') > -1)
|
|
values = key.split('.');
|
|
|
|
if (key.indexOf(':') > -1)
|
|
values = key.split(':');
|
|
|
|
if (values && values.length > 1) {
|
|
// The first element is the app name (module_conf.json)
|
|
var app_name = values[0];
|
|
|
|
process.env.PM2_PROGRAMMATIC = 'true';
|
|
CLI.restart(app_name, function(err, data) {
|
|
process.env.PM2_PROGRAMMATIC = 'false';
|
|
if (!err)
|
|
Common.printOut(cst.PREFIX_MSG + 'Module %s restarted', app_name);
|
|
displayConf(app_name, function() {
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
});
|
|
return false;
|
|
}
|
|
displayConf(app_name, function() {
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
});
|
|
};
|
|
|
|
CLI.unset = function(key, cb) {
|
|
Configuration.unset(key, function(err) {
|
|
if (err) {
|
|
return cb ? cb({success:false, err:err }) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
|
|
displayConf(function() {
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
});
|
|
};
|
|
|
|
CLI.conf = function(key, value, cb) {
|
|
if (typeof(value) === 'function') {
|
|
cb = value;
|
|
value = null;
|
|
};
|
|
|
|
// If key + value = set
|
|
if (key && value) {
|
|
CLI.set(key, value, function(err) {
|
|
if (err)
|
|
return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
}
|
|
// If only key = get
|
|
else if (key) {
|
|
CLI.get(key, function(err, data) {
|
|
if (err)
|
|
return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
}
|
|
else {
|
|
displayConf(function(err, data) {
|
|
if (err)
|
|
return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Launch interactor
|
|
* @method interact
|
|
* @param {string} secret_key
|
|
* @param {string} public_key
|
|
* @param {string} machine_name
|
|
*/
|
|
CLI.interact = function(secret_key, public_key, machine_name, recycle, cb) {
|
|
if (typeof(recycle) === 'function') {
|
|
cb = recycle;
|
|
recycle = null;
|
|
}
|
|
if (typeof(recycle) !== 'boolean') {
|
|
recycle = false;
|
|
}
|
|
|
|
InteractorDaemonizer.launchAndInteract({
|
|
secret_key : secret_key || null,
|
|
public_key : public_key || null,
|
|
machine_name : machine_name || null,
|
|
recycle : recycle || null
|
|
}, function(err, dt) {
|
|
if (err)
|
|
return cb ? cb(err) : exitCli(cst.ERROR_EXIT);
|
|
return cb ? cb(null, dt) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Kill interactor
|
|
* @method killInteract
|
|
*/
|
|
CLI.killInteract = function(cb) {
|
|
InteractorDaemonizer.killDaemon(function(err) {
|
|
return cb ? cb({msg:'Interactor not launched'}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get information about interactor connection
|
|
* @method infoInteract
|
|
*/
|
|
CLI.infoInteract = function(cb) {
|
|
getInteractInfo(function(err, data) {
|
|
if (err) {
|
|
printError('Interactor not launched');
|
|
return cb ? cb({msg:'Interactor not launched'}) : exitCli(cst.ERROR_EXIT);
|
|
}
|
|
printOut(data);
|
|
return cb ? cb(null, data) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
var Version = require('./tools/VersionManagement.js');
|
|
|
|
/**
|
|
* CLI method for updating a repository
|
|
* @method pullAndRestart
|
|
* @param {string} process_name name of processes to pull
|
|
* @return
|
|
*/
|
|
CLI.pullAndRestart = function (process_name, cb) {
|
|
Version._pull({process_name: process_name, action: 'reload'}, cb);
|
|
};
|
|
|
|
/**
|
|
* CLI method for updating a repository
|
|
* @method pullAndReload
|
|
* @param {string} process_name name of processes to pull
|
|
* @return
|
|
*/
|
|
CLI.pullAndReload = function (process_name, cb) {
|
|
Version._pull({process_name: process_name, action: 'reload'}, cb);
|
|
};
|
|
|
|
/**
|
|
* CLI method for updating a repository
|
|
* @method pullAndGracefulReload
|
|
* @param {string} process_name name of processes to pull
|
|
* @return
|
|
*/
|
|
CLI.pullAndGracefulReload = function (process_name, cb) {
|
|
Version._pull({process_name: process_name, action: 'gracefulReload'}, cb);
|
|
};
|
|
|
|
/**
|
|
* CLI method for updating a repository to a specific commit id
|
|
* @method pullCommitId
|
|
* @param {object} opts
|
|
* @return
|
|
*/
|
|
CLI.pullCommitId = function (opts, cb) {
|
|
Version.pullCommitId(opts.pm2_name, opts.commit_id);
|
|
};
|
|
|
|
/**
|
|
* CLI method for downgrading a repository to the previous commit (older)
|
|
* @method backward
|
|
* @param {string} process_name
|
|
* @return
|
|
*/
|
|
CLI.backward = Version.backward;
|
|
|
|
/**
|
|
* CLI method for updating a repository to the next commit (more recent)
|
|
* @method forward
|
|
* @param {string} process_name
|
|
* @return
|
|
*/
|
|
CLI.forward = Version.forward;
|
|
|
|
|
|
/**
|
|
* CLI method for triggering garbage collection manually
|
|
* @method forcegc
|
|
* @return
|
|
*/
|
|
CLI.forceGc = CLI.gc = function(cb) {
|
|
Satan.executeRemote('forceGc', {}, function(err, data) {
|
|
if (data && data.success === false) {
|
|
printError(cst.PREFIX_MSG_ERR + 'Garbage collection failed');
|
|
return cb ? cb({success:false}) : exitCli(cst.ERROR_EXIT);
|
|
} else {
|
|
printOut(cst.PREFIX_MSG + 'Garbage collection manually triggered');
|
|
return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
/**
|
|
* CLI method to perform a deep update of PM2
|
|
* @method deepUpdate
|
|
*/
|
|
CLI.deepUpdate = function(cb) {
|
|
printOut(cst.PREFIX_MSG + 'Updating PM2...');
|
|
|
|
var exec = require('shelljs').exec;
|
|
var child = exec("npm i -g pm2@latest; pm2 update", {async : true});
|
|
|
|
child.stdout.on('end', function() {
|
|
printOut(cst.PREFIX_MSG + 'PM2 successfully updated');
|
|
cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
|
|
});
|
|
};
|
|
|
|
//
|
|
// Private methods
|
|
//
|
|
|
|
/**
|
|
* Description
|
|
* @method getInteractInfo
|
|
* @param {} cb
|
|
* @return
|
|
*/
|
|
function getInteractInfo(cb) {
|
|
debug('Getting interaction info');
|
|
InteractorDaemonizer.ping(function(online) {
|
|
if (!online) {
|
|
return cb({msg : 'offline'});
|
|
}
|
|
InteractorDaemonizer.launchRPC(function() {
|
|
InteractorDaemonizer.rpc.getInfos(function(err, infos) {
|
|
if (err) {
|
|
return cb(err);
|
|
}
|
|
InteractorDaemonizer.disconnectRPC(function() {
|
|
return cb(null, infos);
|
|
});
|
|
return false;
|
|
});
|
|
});
|
|
return false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Asynchronous interactor checking
|
|
*/
|
|
var gl_interact_infos = null;
|
|
|
|
getInteractInfo(function(i_err, interact) {
|
|
if (i_err) {
|
|
gl_interact_infos = null;
|
|
return;
|
|
}
|
|
gl_interact_infos = interact;
|
|
});
|
|
|
|
|
|
|
|
var gl_retry = 0;
|
|
|
|
/**
|
|
* Description
|
|
* @method speedList
|
|
* @return
|
|
*/
|
|
function speedList(code) {
|
|
var self = this;
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
if (gl_retry == 0) {
|
|
gl_retry += 1;
|
|
return setTimeout(speedList, 1400);
|
|
}
|
|
console.error('Error retrieving process list: %s.\nA process seems to be on infinite loop, retry in 5 seconds',err);
|
|
return exitCli(cst.ERROR_EXIT);
|
|
}
|
|
if (commander.miniList && !commander.silent)
|
|
UX.miniDisplay(list);
|
|
else if (!commander.silent) {
|
|
if (gl_interact_infos) {
|
|
printOut(chalk.green.bold('●') + ' Agent online - public key: %s - machine name: %s - Web access: https://app.keymetrics.io/', gl_interact_infos.public_key, gl_interact_infos.machine_name);
|
|
}
|
|
UX.dispAsTable(list, gl_interact_infos);
|
|
printOut(chalk.white.italic(' Use `pm2 show <id|name>` to get more details about an app'));
|
|
}
|
|
|
|
if (Satan._noDaemonMode) {
|
|
printOut('--no-daemon option enabled = do not exit pm2 CLI');
|
|
printOut('PM2 daemon PID = %s', fs.readFileSync(cst.PM2_PID_FILE_PATH));
|
|
return CLI.streamLogs('all', 0, false, 'HH:mm:ss', false);
|
|
}
|
|
else {
|
|
return exitCli(code ? code : cst.SUCCESS_EXIT);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Extend the app.env object of with the properties taken from the app.env_[envName] and deploy configuration.
|
|
* @param {Object} app The app object.
|
|
* @param {string} envName The given environment name.
|
|
* @param {Object} deployConf Deployment configuration object (from JSON file or whatever).
|
|
* @returns {Object} The app.env variables object.
|
|
*/
|
|
function mergeEnvironmentVariables(app, envName, deployConf) {
|
|
if (!envName) {
|
|
// The environment name was not set with either `pm2 deploy envName` or `--env envName`
|
|
app.env = app.env || {};
|
|
} else {
|
|
var finalEnv = {};
|
|
|
|
// First merge variables from deploy.production.env object as least priority.
|
|
if (deployConf && deployConf[envName] && deployConf[envName]['env']) {
|
|
util._extend(finalEnv, deployConf[envName]['env']);
|
|
}
|
|
|
|
// Then merge app.env object.
|
|
if (app.env) {
|
|
util._extend(finalEnv, app.env);
|
|
}
|
|
|
|
// Then, last and highest priority, merge the app.env_production object.
|
|
if ('env_' + envName in app) {
|
|
util._extend(finalEnv, app['env_' + envName]);
|
|
}
|
|
|
|
app.env = finalEnv;
|
|
}
|
|
|
|
// JSON.stringify non-string entries
|
|
var stringifiedEnv = {};
|
|
for (var key in app.env) {
|
|
if (app.env.hasOwnProperty(key)) {
|
|
var value = app.env[key];
|
|
stringifiedEnv[key] = (typeof value === 'string') ? value : JSON.stringify(value);
|
|
}
|
|
}
|
|
app.env = stringifiedEnv;
|
|
|
|
return app.env;
|
|
}
|
|
|
|
/**
|
|
* Description
|
|
* @method resolvePaths
|
|
* @param {object} appConf
|
|
* @return app
|
|
*/
|
|
function resolvePaths(appConf) {
|
|
var cwd = null;
|
|
|
|
if (appConf.cwd) {
|
|
cwd = p.resolve(appConf.cwd);
|
|
process.env.PWD = appConf.cwd;
|
|
}
|
|
|
|
var app = Common.prepareAppConf(appConf, cwd, console.log);
|
|
if (app instanceof Error) {
|
|
printError(cst.PREFIX_MSG_ERR + app.message);
|
|
throw new Error(app.message);
|
|
}
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Verify configurations.
|
|
* @param {Array} appConfs
|
|
* @returns {Array}
|
|
*/
|
|
function verifyConfs(appConfs){
|
|
if (!appConfs || appConfs.length == 0){
|
|
return [];
|
|
}
|
|
|
|
// Make sure it is an Array.
|
|
appConfs = [].concat(appConfs);
|
|
|
|
var verifiedConf = [];
|
|
|
|
for (var i = 0; i < appConfs.length; i++) {
|
|
var app = appConfs[i];
|
|
|
|
// Warn deprecates.
|
|
checkDeprecates(app);
|
|
|
|
// Check Exec mode
|
|
checkExecMode(app);
|
|
|
|
// Render an app name if not existing.
|
|
prepareAppName(app);
|
|
|
|
// Check execute interpreter.
|
|
prepareInterpreter(app);
|
|
|
|
debug('Before processing', app);
|
|
// Verify JSON.
|
|
var ret = Config.verifyJSON(app);
|
|
debug('After processing', ret);
|
|
|
|
// Show errors if existing.
|
|
if (ret.errors && ret.errors.length > 0){
|
|
ret.errors.forEach(function(err){
|
|
warn(err);
|
|
});
|
|
// Return null == error
|
|
return null;
|
|
}
|
|
verifiedConf.push(ret.config);
|
|
}
|
|
|
|
|
|
// if (verifiedConf.length == 0) {
|
|
// warn('No verified configuration yet, peaceful exit.');
|
|
// return exitCli(cst.SUCCESS_EXIT);
|
|
// }
|
|
|
|
return verifiedConf;
|
|
}
|
|
|
|
/**
|
|
* Check if right Node.js version for cluster mode
|
|
* @param {Object} conf
|
|
*/
|
|
function checkExecMode(conf) {
|
|
|
|
if (conf.exec_mode === 'cluster' ||
|
|
conf.exec_mode === 'cluster_mode' ||
|
|
conf.instances && conf.exec_mode === undefined)
|
|
conf.exec_mode = 'cluster_mode';
|
|
else
|
|
conf.exec_mode = 'fork_mode';
|
|
|
|
// -x -i 4
|
|
|
|
if (!isNaN(conf.instances) && /^fork(_mode)?$/i.test(conf.exec_mode)) {
|
|
|
|
warn('You are starting ' +
|
|
chalk.blue(conf.instances) +
|
|
' processes in ' +
|
|
chalk.blue(conf.exec_mode) +
|
|
' without load balancing. To enable it remove -x option.');
|
|
}
|
|
|
|
if (conf.instances && conf.exec_mode === undefined)
|
|
conf.exec_mode = 'cluster_mode';
|
|
|
|
// Tell user about unstability of cluster module + Roadmap
|
|
if (/^cluster(_mode)?$/i.test(conf.exec_mode) &&
|
|
process.version.match(/0.10/) &&
|
|
!process.env.TRAVIS) {
|
|
warn('You should not use the cluster_mode (-i) in production, it\'s still a beta feature. A front HTTP load balancer or interaction with NGINX will be developed in the future.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check deprecates and show warnings.
|
|
* @param {Object} conf
|
|
*/
|
|
function checkDeprecates(conf){
|
|
if (conf.instances == 'max'){
|
|
warn('Deprecated, we recommend using ' + chalk.blue(0) + ' instead of ' + chalk.blue('max') + ' to indicate maximum of instances.');
|
|
conf.instances = 0;
|
|
}
|
|
|
|
// Sanity check, default to number of cores if value can't be parsed
|
|
if (typeof(conf.instances) === 'string')
|
|
conf.instances = parseInt(conf.instances) || 0;
|
|
|
|
// Ensure instance param is not a negative value
|
|
// if (conf.instances < 0) {
|
|
// warn('You passed a negative value to indicate the number of instances... Setting this to maximum instances.');
|
|
// conf.instances = 0;
|
|
// }
|
|
}
|
|
|
|
/**
|
|
* Render an app name if not existing.
|
|
* @param {Object} conf
|
|
*/
|
|
function prepareAppName(conf){
|
|
if(!conf.name){
|
|
conf.name = p.basename(conf.script);
|
|
var lastDot = conf.name.lastIndexOf('.');
|
|
if(lastDot > 0){
|
|
conf.name = conf.name.slice(0, lastDot);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check execute interpreter.
|
|
* @param {Object} conf
|
|
*/
|
|
function prepareInterpreter(conf){
|
|
var betterInterpreter = extItps[path.extname(conf.script)];
|
|
|
|
if (p.extname(conf.script).indexOf('.es') > -1) {
|
|
conf.exec_interpreter = cst.BABEL_EXEC_PATH;
|
|
}
|
|
|
|
if (conf.exec_interpreter && betterInterpreter){
|
|
if (betterInterpreter != conf.exec_interpreter){
|
|
warn('We\'ve notice that you are running the ' + chalk.blue(betterInterpreter) + ' script, but currently using a ' +
|
|
chalk.blue(conf.exec_interpreter) + ' interpreter, may be you need inspect the ' + chalk.blue('--interpreter') + ' option.');
|
|
}
|
|
} else if (!conf.exec_interpreter) {
|
|
conf.exec_interpreter = betterInterpreter || 'none';
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Parses a config file like ecosystem.json. Supported formats: JS, JSON, JSON5.
|
|
* @param {string} confString contents of the config file
|
|
* @param {string} filename path to the config file
|
|
* @return {Object} config object
|
|
*/
|
|
function parseConfig(confString, filename) {
|
|
var code = '(' + confString + ')';
|
|
var sandbox = {};
|
|
if (semver.satisfies(process.version, '>= 0.12.0')) {
|
|
return vm.runInNewContext(code, sandbox, {
|
|
filename: path.resolve(filename),
|
|
displayErrors: false,
|
|
timeout: 1000,
|
|
});
|
|
} else {
|
|
// Use the Node 0.10 API
|
|
return vm.runInNewContext(code, sandbox, filename);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show warnings
|
|
* @param {String} warning
|
|
*/
|
|
function warn(warning){
|
|
printOut(cst.PREFIX_MSG_WARNING + warning);
|
|
}
|
|
|
|
function deployHelp() {
|
|
console.log('');
|
|
console.log('-----> Helper: Deployment with PM2');
|
|
console.log('');
|
|
console.log(' Generate a sample ecosystem.json with the command');
|
|
console.log(' $ pm2 ecosystem');
|
|
console.log(' Then edit the file depending on your needs');
|
|
console.log('');
|
|
console.log(' Commands:');
|
|
console.log(' setup run remote setup commands');
|
|
console.log(' update update deploy to the latest release');
|
|
console.log(' revert [n] revert to [n]th last deployment or 1');
|
|
console.log(' curr[ent] output current release commit');
|
|
console.log(' prev[ious] output previous release commit');
|
|
console.log(' exec|run <cmd> execute the given <cmd>');
|
|
console.log(' list list previous deploy commits');
|
|
console.log(' [ref] deploy to [ref], the "ref" setting, or latest tag');
|
|
console.log('');
|
|
console.log('');
|
|
console.log(' Basic Examples:');
|
|
console.log('');
|
|
console.log(' First initialize remote production host:');
|
|
console.log(' $ pm2 deploy ecosystem.json production setup');
|
|
console.log('');
|
|
console.log(' Then deploy new code:');
|
|
console.log(' $ pm2 deploy ecosystem.json production');
|
|
console.log('');
|
|
console.log(' If I want to revert to the previous commit:');
|
|
console.log(' $ pm2 deploy ecosystem.json production revert 1');
|
|
console.log('');
|
|
console.log(' Execute a command on remote server:');
|
|
console.log(' $ pm2 deploy ecosystem.json production exec "pm2 restart all"');
|
|
console.log('');
|
|
console.log(' PM2 will look by default to the ecosystem.json file so you dont need to give the file name:');
|
|
console.log(' $ pm2 deploy production');
|
|
console.log(' Else you have to tell PM2 the name of your ecosystem file');
|
|
console.log('');
|
|
console.log(' More examples in https://github.com/Unitech/pm2');
|
|
console.log('');
|
|
exitCli(cst.SUCCESS_EXIT);
|
|
}
|