pm2/lib/CLI.js

1728 lines
47 KiB
JavaScript

var commander = require('commander');
var fs = require('fs');
var path = require('path');
var async = require('async');
var debug = require('debug')('pm2:monit');
var util = require('util');
var chalk = require('chalk');
var exec = require('child_process').exec;
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 p = path;
var json5 = require('./tools/json5.js');
var Config = require('./tools/Config');
var Deploy = require('pm2-deploy');
var CLI = module.exports = {};
var exitCli = Common.exitCli;
var printError = Common.printError;
var printOut = Common.printOut;
var pm2 = require('..');
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) {}
}
};
/**
* Method to start a script
* @method startFile
* @param {string} script script name (will be resolved according to location)
* @return
*/
CLI.start = 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);
}
conf.script = script;
if ((appConf = verifyConfs(conf)) == null)
return exitCli(cst.ERROR_EXIT);
conf = appConf[0];
debug(conf);
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
fs.writeFileSync(dst_path, JSON.stringify(conf, null, 2));
}
/*
* Re start script name that is already launched
*/
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');
if (cb) return cb(null, list);
else return speedList();
});
return false;
}
else if (exec && !opts.force) {
printError(cst.PREFIX_MSG_ERR + 'Script already launched, add -f option to force re-execution');
if (cb) return cb({success:false});
else return exitCli(cst.ERROR_EXIT);
}
try {
var resolved_paths = resolvePaths(conf);
} catch(e) {
console.error(e.stack || e);
if (cb) return cb(e);
else return exitCli(cst.ERROR_EXIT);
}
Satan.executeRemote('prepare', resolved_paths, function(err, data) {
if (err) {
printError(cst.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err);
if (cb) return cb({msg : err});
else return speedList();
}
printOut(cst.PREFIX_MSG + 'Process %s launched', script);
if (cb) return cb(null, data);
else return speedList();
});
return false;
});
};
CLI.connect = function(cb) {
Satan.start(cb);
};
CLI.disconnect = function(cb) {
Satan.disconnectRPC(cb);
};
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 = json5.parse(fs.readFileSync(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 = json5.parse(data);
// 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 = proc.env ? proc.env : {};
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);
});
};
/**
* 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, deployConf = null;
if (jsonVia == 'pipe')
appConf = json5.parse(cmd);
else {
var data = fs.readFileSync(cmd);
appConf = json5.parse(data);
// 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 exitCli(cst.ERROR_EXIT);
async.eachLimit(appConf, cst.CONCURRENT_ACTIONS, function(app, next) {
mergeEnvironmentVariables(app, opts.env, deployConf);
var app_paths = null;
try {
app_paths = resolvePaths(app);
} catch (e) {
debug(e.stack || e);
return next();
}
// Set watch to true for app if argument passed to CLI
if (opts.watch)
app_paths.watch = true;
Satan.executeRemote('prepare', app_paths, function(err) {
printOut(cst.PREFIX_MSG + 'Process launched');
next();
});
}, function(err) {
return cb ? cb(err, appConf) : speedList();
});
};
/**
* 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 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(!!~['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);
fs.writeFileSync(scriptFile, script);
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;
printOut(cst.PREFIX_MSG + 'Making script booting at startup...');
switch (platform) {
case 'systemd':
cmd = [
'pm2 dump', //We need an empty dump so that the first resurrect works correctly
'pm2 kill',
'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;
default :
cmd = 'chmod +x ' + scriptFile + ' && update-rc.d ' + p.basename(scriptFile) + ' defaults';
break;
}
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);
});
};
/**
* 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);
}
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
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'));
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) {
fs.writeFileSync(cst.DUMP_FILE_PATH, json5.stringify(env_arr));
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;
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
CLI._reloadProcessName(process_name, 'softReloadProcessId', cb);
};
// CLI.gracefulStop = function(process_name, cb) {
// if (process_name == 'all')
// CLI._reloadAll('softReloadProcessId', cb);
// else
// CLI._reloadProcessName(process_name, 'softReloadProcessId', cb);
// };
CLI.reload = function(process_name, cb) {
if (process_name == 'all')
CLI._reloadAll('reloadProcessId', cb);
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) {
printError(err.stack);
return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
}
Satan.notifyGod('reload', proc.pm2_env.pm_id);
printOut(cst.PREFIX_MSG + 'Process %s succesfully reloaded', proc.pm2_env.name);
return next();
});
return false;
}, function(err) {
printOut(cst.PREFIX_MSG + 'All processes reloaded');
return cb ? cb(null, processes) : setTimeout(speedList, 500);
});
return false;
});
};
CLI.remote = function(command, opts, cb) {
var proc_name = opts.name;
Satan.lock({
name : proc_name,
meta : {
action : command
}
}, function(err, procs) {
if (err) return cb(err);
CLI[command](opts.name, function(err, ret) {
if (err) return cb(err);
console.log('Command %s finished', command);
// Throttle unlock
setTimeout(function() {
Satan.unlock({
name : proc_name,
meta : {
result : {success : true }
}
}, function(err) {
if (err) return cb(err);
console.log('Processes %s unlocked', proc_name);
return cb(null, ret);
});
}, 300);
});
});
};
/**
* Start or restart|reload|gracefulReload a JSON configuration file
* @param {string} action restart|reload
* @param {string} json_conf json 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 = json5.parse(data), 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 app name already exists
if (apps_name.indexOf(proc.name) != -1) {
// Do restart
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();
});
});
}
}
else
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;
});
};
CLI._operate = function(action_name, process_name, envs, cb) {
// Make sure all options exist
if(typeof envs == 'function'){
cb = envs;
envs = {};
}
function processIds(ids, cb) {
async.eachLimit(ids, cst.CONCURRENT_ACTIONS, function(id, next) {
var opts = id;
if (action_name == 'restartProcessId')
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(new Error('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(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 || 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);
}
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, 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';
fs.writeFileSync(path.join(process.env.PWD, f_name), dt);
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(list);
else
printOut(JSON.stringify(list));
exitCli(cst.SUCCESS_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);
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('%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 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 {} id
* @return
*/
function fallbackLogStream(id, lines) {
lines = lines || 20;
Satan.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
printError(err);
exitCli(cst.ERROR_EXIT);
}
Log.stream(cst.PM2_LOG_FILE_PATH, lines);
printOut('########### Starting streaming logs for [%s] process', id || 'all');
list.forEach(function(proc) {
if ((!id || (id && !isNaN(parseInt(id)) && proc.pm_id == id)) ||
(!id || (id && isNaN(parseInt(id)) && proc.pm2_env.name == id))) {
var app_name = (proc.pm2_env.name || p.basename(proc.pm2_env.pm_exec_path)) + '-' + proc.pm_id;
['', 'out', 'err'].some(function(n){
var pk = 'pm_' + (n ? n + '_':'') + 'log_path';
if(pk in proc.pm2_env){
Log.stream({
path: proc.pm2_env[pk],
type: !n ? 'entire' : n
}, app_name, lines);
return !n;
}
return false;
});
}
});
});
}
CLI.streamLogs = function(id, lines) {
fallbackLogStream(id, lines);
};
CLI.ilogs = function() {
try {
var logs = require('pm2-logs').init({
format: 'MMMM Do YYYY, h:mm:ss a'
});
} catch(e) {
printOut('pm2-logs module is not installed');
fallbackLogStream();
}
};
/**
* Description
* @method killDaemon
* @param {} cb
* @return
*/
CLI.killDaemon = function(cb) {
printOut(cst.PREFIX_MSG + 'Stopping PM2...');
CLI._operate('deleteProcessId', 'all', function(err, list) {
printOut(cst.PREFIX_MSG + 'All processes has 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.interact = function(secret_key, public_key, machine_name, cb) {
InteractorDaemonizer.launchAndInteract({
secret_key : secret_key || null,
public_key : public_key || null,
machine_name : machine_name || 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: 'restart'}, 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;
//
// 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;
});
}
var gl_retry = 0;
/**
* Description
* @method speedList
* @return
*/
function speedList() {
var self = this;
getInteractInfo(function(i_err, interact_infos) {
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);
exitCli(cst.ERROR_EXIT);
}
if (commander.miniList && !commander.silent)
UX.miniDisplay(list);
else if (!commander.silent) {
if (interact_infos) {
printOut(chalk.green.bold('●') + ' Agent online - public key: %s - machine name: %s - Web access: %s', interact_infos.public_key, interact_infos.machine_name, 'http://app.keymetrics.io/');
}
UX.dispAsTable(list, interact_infos);
printOut(' Use `pm2 info <id|name>` to get more details about an app');
}
if (Satan._noDaemonMode) {
printOut('--no-daemon option enabled = do not exit pm2 CLI');
printOut('PM2 dameon PID = %s', fs.readFileSync(cst.PM2_PID_FILE_PATH));
return Log.stream(cst.PM2_LOG_FILE_PATH);
}
else {
return exitCli(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;
}
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 (beta). 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 developped 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;
}
if (typeof(conf.instances) === 'string')
conf.instances = parseInt(conf.instances);
}
/**
* 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 (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';
}
}
/**
* 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);
}