1928 lines
59 KiB
JavaScript
1928 lines
59 KiB
JavaScript
/**
|
|
* Copyright 2013-2022 the PM2 project authors. All rights reserved.
|
|
* Use of this source code is governed by a license that
|
|
* can be found in the LICENSE file.
|
|
*/
|
|
'use strict';
|
|
|
|
const commander = require('commander');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const eachLimit = require('async/eachLimit');
|
|
const series = require('async/series');
|
|
const debug = require('debug')('pm2:cli');
|
|
const util = require('util');
|
|
const chalk = require('chalk');
|
|
const fclone = require('fclone');
|
|
|
|
var DockerMgmt = require('./API/ExtraMgmt/Docker.js')
|
|
var conf = require('../constants.js');
|
|
var Client = require('./Client');
|
|
var Common = require('./Common');
|
|
var KMDaemon = require('@pm2/agent/src/InteractorClient');
|
|
var Config = require('./tools/Config');
|
|
var Modularizer = require('./API/Modules/Modularizer.js');
|
|
var path_structure = require('../paths.js');
|
|
var UX = require('./API/UX');
|
|
var pkg = require('../package.json');
|
|
var hf = require('./API/Modules/flagExt.js');
|
|
var Configuration = require('./Configuration.js');
|
|
const sexec = require('./tools/sexec.js')
|
|
|
|
var IMMUTABLE_MSG = chalk.bold.blue('Use --update-env to update environment variables');
|
|
|
|
/**
|
|
* Main Function to be imported
|
|
* can be aliased to PM2
|
|
*
|
|
* To use it when PM2 is installed as a module:
|
|
*
|
|
* var PM2 = require('pm2');
|
|
*
|
|
* var pm2 = PM2(<opts>);
|
|
*
|
|
*
|
|
* @param {Object} opts
|
|
* @param {String} [opts.cwd=<current>] override pm2 cwd for starting scripts
|
|
* @param {String} [opts.pm2_home=[<paths.js>]] pm2 directory for log, pids, socket files
|
|
* @param {Boolean} [opts.independent=false] unique PM2 instance (random pm2_home)
|
|
* @param {Boolean} [opts.daemon_mode=true] should be called in the same process or not
|
|
* @param {String} [opts.public_key=null] pm2 plus bucket public key
|
|
* @param {String} [opts.secret_key=null] pm2 plus bucket secret key
|
|
* @param {String} [opts.machine_name=null] pm2 plus instance name
|
|
*/
|
|
class API {
|
|
|
|
constructor (opts) {
|
|
if (!opts) opts = {};
|
|
var that = this;
|
|
|
|
this.daemon_mode = typeof(opts.daemon_mode) == 'undefined' ? true : opts.daemon_mode;
|
|
this.pm2_home = conf.PM2_ROOT_PATH;
|
|
this.public_key = conf.PUBLIC_KEY || opts.public_key || null;
|
|
this.secret_key = conf.SECRET_KEY || opts.secret_key || null;
|
|
this.machine_name = conf.MACHINE_NAME || opts.machine_name || null
|
|
|
|
/**
|
|
* CWD resolution
|
|
*/
|
|
this.cwd = process.cwd();
|
|
if (opts.cwd) {
|
|
this.cwd = path.resolve(opts.cwd);
|
|
}
|
|
|
|
/**
|
|
* PM2 HOME resolution
|
|
*/
|
|
if (opts.pm2_home && opts.independent == true)
|
|
throw new Error('You cannot set a pm2_home and independent instance in same time');
|
|
|
|
if (opts.pm2_home) {
|
|
// Override default conf file
|
|
this.pm2_home = opts.pm2_home;
|
|
conf = Object.assign(conf, path_structure(this.pm2_home));
|
|
}
|
|
else if (opts.independent == true && conf.IS_WINDOWS === false) {
|
|
// Create an unique pm2 instance
|
|
const crypto = require('crypto');
|
|
var random_file = crypto.randomBytes(8).toString('hex');
|
|
this.pm2_home = path.join('/tmp', random_file);
|
|
|
|
// If we dont explicitly tell to have a daemon
|
|
// It will go as in proc
|
|
if (typeof(opts.daemon_mode) == 'undefined')
|
|
this.daemon_mode = false;
|
|
conf = Object.assign(conf, path_structure(this.pm2_home));
|
|
}
|
|
|
|
this._conf = conf;
|
|
|
|
if (conf.IS_WINDOWS) {
|
|
// Weird fix, may need to be dropped
|
|
// @todo windows connoisseur double check
|
|
if (process.stdout._handle && process.stdout._handle.setBlocking)
|
|
process.stdout._handle.setBlocking(true);
|
|
}
|
|
|
|
this.Client = new Client({
|
|
pm2_home: that.pm2_home,
|
|
conf: this._conf,
|
|
secret_key: this.secret_key,
|
|
public_key: this.public_key,
|
|
daemon_mode: this.daemon_mode,
|
|
machine_name: this.machine_name
|
|
});
|
|
|
|
this.pm2_configuration = Configuration.getSync('pm2') || {}
|
|
|
|
this.gl_interact_infos = null;
|
|
this.gl_is_km_linked = false;
|
|
|
|
try {
|
|
var pid = fs.readFileSync(conf.INTERACTOR_PID_PATH);
|
|
pid = parseInt(pid.toString().trim());
|
|
process.kill(pid, 0);
|
|
that.gl_is_km_linked = true;
|
|
} catch (e) {
|
|
that.gl_is_km_linked = false;
|
|
}
|
|
|
|
// For testing purposes
|
|
if (this.secret_key && process.env.NODE_ENV == 'local_test')
|
|
that.gl_is_km_linked = true;
|
|
|
|
KMDaemon.ping(this._conf, function(err, result) {
|
|
if (!err && result === true) {
|
|
fs.readFile(conf.INTERACTION_CONF, (err, _conf) => {
|
|
if (!err) {
|
|
try {
|
|
that.gl_interact_infos = JSON.parse(_conf.toString())
|
|
} catch(e) {
|
|
var json5 = require('./tools/json5.js')
|
|
try {
|
|
that.gl_interact_infos = json5.parse(_conf.toString())
|
|
} catch(e) {
|
|
console.error(e)
|
|
that.gl_interact_infos = null
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
this.gl_retry = 0;
|
|
}
|
|
|
|
/**
|
|
* Connect to PM2
|
|
* Calling this command is now optional
|
|
*
|
|
* @param {Function} cb callback once pm2 is ready for commands
|
|
*/
|
|
connect (noDaemon, cb) {
|
|
var that = this;
|
|
this.start_timer = new Date();
|
|
|
|
if (typeof(cb) == 'undefined') {
|
|
cb = noDaemon;
|
|
noDaemon = false;
|
|
} else if (noDaemon === true) {
|
|
// Backward compatibility with PM2 1.x
|
|
this.Client.daemon_mode = false;
|
|
this.daemon_mode = false;
|
|
}
|
|
|
|
this.Client.start(function(err, meta) {
|
|
if (err)
|
|
return cb(err);
|
|
|
|
if (meta.new_pm2_instance == false && that.daemon_mode === true)
|
|
return cb(err, meta);
|
|
|
|
that.launchSysMonitoring(() => {})
|
|
// If new pm2 instance has been popped
|
|
// Lauch all modules
|
|
that.launchAll(that, function(err_mod) {
|
|
return cb(err, meta);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Usefull when custom PM2 created with independent flag set to true
|
|
* This will cleanup the newly created instance
|
|
* by removing folder, killing PM2 and so on
|
|
*
|
|
* @param {Function} cb callback once cleanup is successfull
|
|
*/
|
|
destroy (cb) {
|
|
var that = this;
|
|
|
|
debug('Killing and deleting current deamon');
|
|
|
|
this.killDaemon(function() {
|
|
var cmd = 'rm -rf ' + that.pm2_home;
|
|
var test_path = path.join(that.pm2_home, 'module_conf.json');
|
|
var test_path_2 = path.join(that.pm2_home, 'pm2.pid');
|
|
|
|
if (that.pm2_home.indexOf('.pm2') > -1)
|
|
return cb(new Error('Destroy is not a allowed method on .pm2'));
|
|
|
|
fs.access(test_path, fs.R_OK, function(err) {
|
|
if (err) return cb(err);
|
|
debug('Deleting temporary folder %s', that.pm2_home);
|
|
sexec(cmd, cb);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Disconnect from PM2 instance
|
|
* This will allow your software to exit by itself
|
|
*
|
|
* @param {Function} [cb] optional callback once connection closed
|
|
*/
|
|
disconnect (cb) {
|
|
var that = this;
|
|
|
|
if (!cb) cb = function() {};
|
|
|
|
this.Client.close(function(err, data) {
|
|
debug('The session lasted %ds', (new Date() - that.start_timer) / 1000);
|
|
return cb(err, data);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Alias on disconnect
|
|
* @param cb
|
|
*/
|
|
close (cb) {
|
|
this.disconnect(cb);
|
|
}
|
|
|
|
/**
|
|
* Launch modules
|
|
*
|
|
* @param {Function} cb callback once pm2 has launched modules
|
|
*/
|
|
launchModules (cb) {
|
|
this.launchAll(this, cb);
|
|
}
|
|
|
|
/**
|
|
* Enable bus allowing to retrieve various process event
|
|
* like logs, restarts, reloads
|
|
*
|
|
* @param {Function} cb callback called with 1st param err and 2nb param the bus
|
|
*/
|
|
launchBus (cb) {
|
|
this.Client.launchBus(cb);
|
|
}
|
|
|
|
/**
|
|
* Exit methods for API
|
|
* @param {Integer} code exit code for terminal
|
|
*/
|
|
exitCli (code) {
|
|
var that = this;
|
|
|
|
// Do nothing if PM2 called programmatically (also in speedlist)
|
|
if (conf.PM2_PROGRAMMATIC && process.env.PM2_USAGE != 'CLI') return false;
|
|
|
|
KMDaemon.disconnectRPC(function() {
|
|
that.Client.close(function() {
|
|
code = code || 0;
|
|
// Safe exits process after all streams are drained.
|
|
// file descriptor flag.
|
|
var fds = 0;
|
|
// exits process when stdout (1) and sdterr(2) are both drained.
|
|
function tryToExit() {
|
|
if ((fds & 1) && (fds & 2)) {
|
|
debug('This command took %ds to execute', (new Date() - that.start_timer) / 1000);
|
|
process.exit(code);
|
|
}
|
|
}
|
|
|
|
[process.stdout, process.stderr].forEach(function(std) {
|
|
var fd = std.fd;
|
|
if (!std.bufferSize) {
|
|
// bufferSize equals 0 means current stream is drained.
|
|
fds = fds | fd;
|
|
} else {
|
|
// Appends nothing to the std queue, but will trigger `tryToExit` event on `drain`.
|
|
std.write && std.write('', function() {
|
|
fds = fds | fd;
|
|
tryToExit();
|
|
});
|
|
}
|
|
// Does not write anything more.
|
|
delete std.write;
|
|
});
|
|
tryToExit();
|
|
});
|
|
});
|
|
}
|
|
|
|
////////////////////////////
|
|
// Application management //
|
|
////////////////////////////
|
|
|
|
/**
|
|
* Start a file or json with configuration
|
|
* @param {Object||String} cmd script to start or json
|
|
* @param {Function} cb called when application has been started
|
|
*/
|
|
start (cmd, opts, cb) {
|
|
if (typeof(opts) == "function") {
|
|
cb = opts;
|
|
opts = {};
|
|
}
|
|
if (!opts) opts = {};
|
|
|
|
var that = this;
|
|
if (util.isArray(opts.watch) && opts.watch.length === 0)
|
|
opts.watch = (opts.rawArgs ? !!~opts.rawArgs.indexOf('--watch') : !!~process.argv.indexOf('--watch')) || false;
|
|
|
|
if (Common.isConfigFile(cmd) || (typeof(cmd) === 'object')) {
|
|
that._startJson(cmd, opts, 'restartProcessId', (err, procs) => {
|
|
return cb ? cb(err, procs) : this.speedList()
|
|
})
|
|
}
|
|
else {
|
|
that._startScript(cmd, opts, (err, procs) => {
|
|
return cb ? cb(err, procs) : this.speedList(0)
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset process counters
|
|
*
|
|
* @method resetMetaProcess
|
|
*/
|
|
reset (process_name, cb) {
|
|
var that = this;
|
|
|
|
function processIds(ids, cb) {
|
|
eachLimit(ids, conf.CONCURRENT_ACTIONS, function(id, next) {
|
|
that.Client.executeRemote('resetMetaProcessId', id, function(err, res) {
|
|
if (err) console.error(err);
|
|
Common.printOut(conf.PREFIX_MSG + 'Resetting meta for process id %d', id);
|
|
return next();
|
|
});
|
|
}, function(err) {
|
|
if (err) return cb(Common.retErr(err));
|
|
return cb ? cb(null, {success:true}) : that.speedList();
|
|
});
|
|
}
|
|
|
|
if (process_name == 'all') {
|
|
that.Client.getAllProcessId(function(err, ids) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
return processIds(ids, cb);
|
|
});
|
|
}
|
|
else if (isNaN(process_name)) {
|
|
that.Client.getProcessIdByName(process_name, function(err, ids) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
if (ids.length === 0) {
|
|
Common.printError('Unknown process name');
|
|
return cb ? cb(new Error('Unknown process name')) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
return processIds(ids, cb);
|
|
});
|
|
} else {
|
|
processIds([process_name], cb);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update daemonized PM2 Daemon
|
|
*
|
|
* @param {Function} cb callback when pm2 has been upgraded
|
|
*/
|
|
update (cb) {
|
|
var that = this;
|
|
|
|
Common.printOut('Be sure to have the latest version by doing `npm install pm2@latest -g` before doing this procedure.');
|
|
|
|
// Dump PM2 processes
|
|
that.Client.executeRemote('notifyKillPM2', {}, function() {});
|
|
|
|
that.getVersion(function(err, new_version) {
|
|
// If not linked to PM2 plus, and update PM2 to latest, display motd.update
|
|
if (!that.gl_is_km_linked && !err && (pkg.version != new_version)) {
|
|
var dt = fs.readFileSync(path.join(__dirname, that._conf.PM2_UPDATE));
|
|
console.log(dt.toString());
|
|
}
|
|
|
|
that.dump(function(err) {
|
|
that.killDaemon(function() {
|
|
that.Client.launchDaemon({interactor:false}, function(err, child) {
|
|
that.Client.launchRPC(function() {
|
|
that.resurrect(function() {
|
|
Common.printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated'));
|
|
that.launchSysMonitoring(() => {})
|
|
that.launchAll(that, function() {
|
|
KMDaemon.launchAndInteract(that._conf, {
|
|
pm2_version: pkg.version
|
|
}, function(err, data, interactor_proc) {
|
|
})
|
|
setTimeout(() => {
|
|
return cb ? cb(null, {success:true}) : that.speedList();
|
|
}, 250)
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Reload an application
|
|
*
|
|
* @param {String} process_name Application Name or All
|
|
* @param {Object} opts Options
|
|
* @param {Function} cb Callback
|
|
*/
|
|
reload (process_name, opts, cb) {
|
|
var that = this;
|
|
|
|
if (typeof(opts) == "function") {
|
|
cb = opts;
|
|
opts = {};
|
|
}
|
|
|
|
var delay = Common.lockReload();
|
|
if (delay > 0 && opts.force != true) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'Reload already in progress, please try again in ' + Math.floor((conf.RELOAD_LOCK_TIMEOUT - delay) / 1000) + ' seconds or use --force');
|
|
return cb ? cb(new Error('Reload in progress')) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
if (Common.isConfigFile(process_name))
|
|
that._startJson(process_name, opts, 'reloadProcessId', function(err, apps) {
|
|
Common.unlockReload();
|
|
if (err)
|
|
return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
|
|
return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT);
|
|
});
|
|
else {
|
|
if (opts && opts.env) {
|
|
var err = 'Using --env [env] without passing the ecosystem.config.js does not work'
|
|
Common.err(err);
|
|
Common.unlockReload();
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
if (opts && !opts.updateEnv)
|
|
Common.printOut(IMMUTABLE_MSG);
|
|
|
|
that._operate('reloadProcessId', process_name, opts, function(err, apps) {
|
|
Common.unlockReload();
|
|
|
|
if (err)
|
|
return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
|
|
return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restart process
|
|
*
|
|
* @param {String} cmd Application Name / Process id / JSON application file / 'all'
|
|
* @param {Object} opts Extra options to be updated
|
|
* @param {Function} cb Callback
|
|
*/
|
|
restart (cmd, opts, cb) {
|
|
if (typeof(opts) == "function") {
|
|
cb = opts;
|
|
opts = {};
|
|
}
|
|
var that = this;
|
|
|
|
if (typeof(cmd) === 'number')
|
|
cmd = cmd.toString();
|
|
|
|
if (cmd == "-") {
|
|
// Restart from PIPED JSON
|
|
process.stdin.resume();
|
|
process.stdin.setEncoding('utf8');
|
|
process.stdin.on('data', function (param) {
|
|
process.stdin.pause();
|
|
that.actionFromJson('restartProcessId', param, opts, 'pipe', cb);
|
|
});
|
|
}
|
|
else if (Common.isConfigFile(cmd) || typeof(cmd) === 'object')
|
|
that._startJson(cmd, opts, 'restartProcessId', cb);
|
|
else {
|
|
if (opts && opts.env) {
|
|
var err = 'Using --env [env] without passing the ecosystem.config.js does not work'
|
|
Common.err(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
if (opts && !opts.updateEnv)
|
|
Common.printOut(IMMUTABLE_MSG);
|
|
that._operate('restartProcessId', cmd, opts, cb);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete process
|
|
*
|
|
* @param {String} process_name Application Name / Process id / Application file / 'all'
|
|
* @param {Function} cb Callback
|
|
*/
|
|
delete (process_name, jsonVia, cb) {
|
|
var that = this;
|
|
|
|
if (typeof(jsonVia) === "function") {
|
|
cb = jsonVia;
|
|
jsonVia = null;
|
|
}
|
|
|
|
if (typeof(process_name) === "number") {
|
|
process_name = process_name.toString();
|
|
}
|
|
|
|
if (jsonVia == 'pipe')
|
|
return that.actionFromJson('deleteProcessId', process_name, commander, 'pipe', (err, procs) => {
|
|
return cb ? cb(err, procs) : this.speedList()
|
|
});
|
|
if (Common.isConfigFile(process_name))
|
|
return that.actionFromJson('deleteProcessId', process_name, commander, 'file', (err, procs) => {
|
|
return cb ? cb(err, procs) : this.speedList()
|
|
});
|
|
else {
|
|
that._operate('deleteProcessId', process_name, (err, procs) => {
|
|
return cb ? cb(err, procs) : this.speedList()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop process
|
|
*
|
|
* @param {String} process_name Application Name / Process id / Application file / 'all'
|
|
* @param {Function} cb Callback
|
|
*/
|
|
stop (process_name, cb) {
|
|
var that = this;
|
|
|
|
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();
|
|
that.actionFromJson('stopProcessId', param, commander, 'pipe', (err, procs) => {
|
|
return cb ? cb(err, procs) : this.speedList()
|
|
})
|
|
});
|
|
}
|
|
else if (Common.isConfigFile(process_name))
|
|
that.actionFromJson('stopProcessId', process_name, commander, 'file', (err, procs) => {
|
|
return cb ? cb(err, procs) : this.speedList()
|
|
});
|
|
else
|
|
that._operate('stopProcessId', process_name, (err, procs) => {
|
|
return cb ? cb(err, procs) : this.speedList()
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get list of all processes managed
|
|
*
|
|
* @param {Function} cb Callback
|
|
*/
|
|
list (opts, cb) {
|
|
var that = this;
|
|
|
|
if (typeof(opts) == 'function') {
|
|
cb = opts;
|
|
opts = null;
|
|
}
|
|
|
|
that.Client.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
if (opts && opts.rawArgs && opts.rawArgs.indexOf('--watch') > -1) {
|
|
var dayjs = require('dayjs');
|
|
function show() {
|
|
process.stdout.write('\x1b[2J');
|
|
process.stdout.write('\x1b[0f');
|
|
console.log('Last refresh: ', dayjs().format());
|
|
that.Client.executeRemote('getMonitorData', {}, function(err, list) {
|
|
UX.list(list, null);
|
|
});
|
|
}
|
|
|
|
show();
|
|
setInterval(show, 900);
|
|
return false;
|
|
}
|
|
|
|
return cb ? cb(null, list) : that.speedList(null);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Kill Daemon
|
|
*
|
|
* @param {Function} cb Callback
|
|
*/
|
|
killDaemon (cb) {
|
|
process.env.PM2_STATUS = 'stopping'
|
|
|
|
var that = this;
|
|
|
|
that.Client.executeRemote('notifyKillPM2', {}, function() {});
|
|
|
|
that._operate('deleteProcessId', 'all', function(err, list) {
|
|
Common.printOut(conf.PREFIX_MSG + '[v] All Applications Stopped');
|
|
process.env.PM2_SILENT = 'false';
|
|
|
|
that.killAgent(function(err, data) {
|
|
if (!err) {
|
|
Common.printOut(conf.PREFIX_MSG + '[v] Agent Stopped');
|
|
}
|
|
|
|
that.Client.killDaemon(function(err, res) {
|
|
if (err) Common.printError(err);
|
|
Common.printOut(conf.PREFIX_MSG + '[v] PM2 Daemon Stopped');
|
|
return cb ? cb(err, res) : that.exitCli(conf.SUCCESS_EXIT);
|
|
});
|
|
|
|
});
|
|
})
|
|
}
|
|
|
|
kill (cb) {
|
|
this.killDaemon(cb);
|
|
}
|
|
|
|
/////////////////////
|
|
// Private methods //
|
|
/////////////////////
|
|
|
|
/**
|
|
* Method to START / RESTART a script
|
|
*
|
|
* @private
|
|
* @param {string} script script name (will be resolved according to location)
|
|
*/
|
|
_startScript (script, opts, cb) {
|
|
if (typeof opts == "function") {
|
|
cb = opts;
|
|
opts = {};
|
|
}
|
|
var that = this;
|
|
|
|
/**
|
|
* Commander.js tricks
|
|
*/
|
|
var app_conf = Config.filterOptions(opts);
|
|
var appConf = {};
|
|
|
|
if (typeof app_conf.name == 'function')
|
|
delete app_conf.name;
|
|
|
|
delete app_conf.args;
|
|
|
|
// Retrieve arguments via -- <args>
|
|
var argsIndex;
|
|
|
|
if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0)
|
|
app_conf.args = opts.rawArgs.slice(argsIndex + 1);
|
|
else if (opts.scriptArgs)
|
|
app_conf.args = opts.scriptArgs;
|
|
|
|
app_conf.script = script;
|
|
if(!app_conf.namespace)
|
|
app_conf.namespace = 'default';
|
|
|
|
if ((appConf = Common.verifyConfs(app_conf)) instanceof Error) {
|
|
Common.err(appConf)
|
|
return cb ? cb(Common.retErr(appConf)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
app_conf = appConf[0];
|
|
|
|
if (opts.watchDelay) {
|
|
if (typeof opts.watchDelay === "string" && opts.watchDelay.indexOf("ms") !== -1)
|
|
app_conf.watch_delay = parseInt(opts.watchDelay);
|
|
else {
|
|
app_conf.watch_delay = parseFloat(opts.watchDelay) * 1000;
|
|
}
|
|
}
|
|
|
|
var mas = [];
|
|
if(typeof opts.ext != 'undefined')
|
|
hf.make_available_extension(opts, mas); // for -e flag
|
|
mas.length > 0 ? app_conf.ignore_watch = mas : 0;
|
|
|
|
/**
|
|
* If -w option, write configuration to configuration.json file
|
|
*/
|
|
if (app_conf.write) {
|
|
var dst_path = path.join(process.env.PWD || process.cwd(), app_conf.name + '-pm2.json');
|
|
Common.printOut(conf.PREFIX_MSG + 'Writing configuration to', chalk.blue(dst_path));
|
|
// pretty JSON
|
|
try {
|
|
fs.writeFileSync(dst_path, JSON.stringify(app_conf, null, 2));
|
|
} catch (e) {
|
|
console.error(e.stack || e);
|
|
}
|
|
}
|
|
|
|
series([
|
|
restartExistingProcessName,
|
|
restartExistingNameSpace,
|
|
restartExistingProcessId,
|
|
restartExistingProcessPathOrStartNew
|
|
], function(err, data) {
|
|
if (err instanceof Error)
|
|
return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
|
|
|
|
var ret = {};
|
|
|
|
data.forEach(function(_dt) {
|
|
if (_dt !== undefined)
|
|
ret = _dt;
|
|
});
|
|
|
|
return cb ? cb(null, ret) : that.speedList();
|
|
});
|
|
|
|
/**
|
|
* If start <app_name> start/restart application
|
|
*/
|
|
function restartExistingProcessName(cb) {
|
|
if (!isNaN(script) ||
|
|
(typeof script === 'string' && script.indexOf('/') != -1) ||
|
|
(typeof script === 'string' && path.extname(script) !== ''))
|
|
return cb(null);
|
|
|
|
that.Client.getProcessIdByName(script, function(err, ids) {
|
|
if (err && cb) return cb(err);
|
|
if (ids.length > 0) {
|
|
that._operate('restartProcessId', script, opts, function(err, list) {
|
|
if (err) return cb(err);
|
|
Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
}
|
|
else return cb(null);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* If start <namespace> start/restart namespace
|
|
*/
|
|
function restartExistingNameSpace(cb) {
|
|
if (!isNaN(script) ||
|
|
(typeof script === 'string' && script.indexOf('/') != -1) ||
|
|
(typeof script === 'string' && path.extname(script) !== ''))
|
|
return cb(null);
|
|
|
|
if (script !== 'all') {
|
|
that.Client.getProcessIdsByNamespace(script, function (err, ids) {
|
|
if (err && cb) return cb(err);
|
|
if (ids.length > 0) {
|
|
that._operate('restartProcessId', script, opts, function (err, list) {
|
|
if (err) return cb(err);
|
|
Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
}
|
|
else return cb(null);
|
|
});
|
|
}
|
|
else {
|
|
that._operate('restartProcessId', 'all', function(err, list) {
|
|
if (err) return cb(err);
|
|
Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
}
|
|
}
|
|
|
|
function restartExistingProcessId(cb) {
|
|
if (isNaN(script)) return cb(null);
|
|
|
|
that._operate('restartProcessId', script, opts, function(err, list) {
|
|
if (err) return cb(err);
|
|
Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Restart a process with the same full path
|
|
* Or start it
|
|
*/
|
|
function restartExistingProcessPathOrStartNew(cb) {
|
|
that.Client.executeRemote('getMonitorData', {}, function(err, procs) {
|
|
if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
|
|
var full_path = path.resolve(that.cwd, script);
|
|
var managed_script = null;
|
|
|
|
procs.forEach(function(proc) {
|
|
if (proc.pm2_env.pm_exec_path == full_path &&
|
|
proc.pm2_env.name == app_conf.name)
|
|
managed_script = proc;
|
|
});
|
|
|
|
if (managed_script &&
|
|
(managed_script.pm2_env.status == conf.STOPPED_STATUS ||
|
|
managed_script.pm2_env.status == conf.STOPPING_STATUS ||
|
|
managed_script.pm2_env.status == conf.ERRORED_STATUS)) {
|
|
// Restart process if stopped
|
|
var app_name = managed_script.pm2_env.name;
|
|
|
|
that._operate('restartProcessId', app_name, opts, function(err, list) {
|
|
if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
|
|
return cb(true, list);
|
|
});
|
|
return false;
|
|
}
|
|
else if (managed_script && !opts.force) {
|
|
Common.err('Script already launched, add -f option to force re-execution');
|
|
return cb(new Error('Script already launched'));
|
|
}
|
|
|
|
var resolved_paths = null;
|
|
|
|
try {
|
|
resolved_paths = Common.resolveAppAttributes({
|
|
cwd : that.cwd,
|
|
pm2_home : that.pm2_home
|
|
}, app_conf);
|
|
} catch(e) {
|
|
Common.err(e.message);
|
|
return cb(Common.retErr(e));
|
|
}
|
|
|
|
Common.printOut(conf.PREFIX_MSG + 'Starting %s in %s (%d instance' + (resolved_paths.instances > 1 ? 's' : '') + ')',
|
|
resolved_paths.pm_exec_path, resolved_paths.exec_mode, resolved_paths.instances);
|
|
|
|
if (!resolved_paths.env) resolved_paths.env = {};
|
|
|
|
// Set PM2 HOME in case of child process using PM2 API
|
|
resolved_paths.env['PM2_HOME'] = that.pm2_home;
|
|
|
|
var additional_env = Modularizer.getAdditionalConf(resolved_paths.name);
|
|
Object.assign(resolved_paths.env, additional_env);
|
|
|
|
// Is KM linked?
|
|
resolved_paths.km_link = that.gl_is_km_linked;
|
|
|
|
that.Client.executeRemote('prepare', resolved_paths, function(err, data) {
|
|
if (err) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err);
|
|
return cb(Common.retErr(err));
|
|
}
|
|
|
|
Common.printOut(conf.PREFIX_MSG + 'Done.');
|
|
return cb(true, data);
|
|
});
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to start/restart/reload processes from a JSON file
|
|
* It will start app not started
|
|
* Can receive only option to skip applications
|
|
*
|
|
* @private
|
|
*/
|
|
_startJson (file, opts, action, pipe, cb) {
|
|
var config = {};
|
|
var appConf = {};
|
|
var staticConf = [];
|
|
var deployConf = {};
|
|
var apps_info = [];
|
|
var that = this;
|
|
|
|
/**
|
|
* Get File configuration
|
|
*/
|
|
if (typeof(cb) === 'undefined' && typeof(pipe) === 'function') {
|
|
cb = pipe;
|
|
}
|
|
if (typeof(file) === 'object') {
|
|
config = file;
|
|
} else if (pipe === 'pipe') {
|
|
config = Common.parseConfig(file, 'pipe');
|
|
} else {
|
|
var data = null;
|
|
|
|
var isAbsolute = path.isAbsolute(file)
|
|
var file_path = isAbsolute ? file : path.join(that.cwd, file);
|
|
|
|
debug('Resolved filepath %s', file_path);
|
|
|
|
try {
|
|
data = fs.readFileSync(file_path);
|
|
} catch(e) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file +' not found');
|
|
return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
try {
|
|
config = Common.parseConfig(data, file);
|
|
} catch(e) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file + ' malformated');
|
|
console.error(e);
|
|
return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Alias some optional fields
|
|
*/
|
|
if (config.deploy)
|
|
deployConf = config.deploy;
|
|
if (config.static)
|
|
staticConf = config.static;
|
|
if (config.apps)
|
|
appConf = config.apps;
|
|
else if (config.pm2)
|
|
appConf = config.pm2;
|
|
else
|
|
appConf = config;
|
|
if (!Array.isArray(appConf))
|
|
appConf = [appConf];
|
|
|
|
if ((appConf = Common.verifyConfs(appConf)) instanceof Error)
|
|
return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT);
|
|
|
|
process.env.PM2_JSON_PROCESSING = true;
|
|
|
|
// Get App list
|
|
var apps_name = [];
|
|
var proc_list = {};
|
|
|
|
// Add statics to apps
|
|
staticConf.forEach(function(serve) {
|
|
appConf.push({
|
|
name: serve.name ? serve.name : `static-page-server-${serve.port}`,
|
|
script: path.resolve(__dirname, 'API', 'Serve.js'),
|
|
env: {
|
|
PM2_SERVE_PORT: serve.port,
|
|
PM2_SERVE_HOST: serve.host,
|
|
PM2_SERVE_PATH: serve.path,
|
|
PM2_SERVE_SPA: serve.spa,
|
|
PM2_SERVE_DIRECTORY: serve.directory,
|
|
PM2_SERVE_BASIC_AUTH: serve.basic_auth !== undefined,
|
|
PM2_SERVE_BASIC_AUTH_USERNAME: serve.basic_auth ? serve.basic_auth.username : null,
|
|
PM2_SERVE_BASIC_AUTH_PASSWORD: serve.basic_auth ? serve.basic_auth.password : null,
|
|
PM2_SERVE_MONITOR: serve.monitor
|
|
}
|
|
});
|
|
});
|
|
|
|
// Here we pick only the field we want from the CLI when starting a JSON
|
|
appConf.forEach(function(app) {
|
|
if (!app.env) { app.env = {}; }
|
|
app.env.io = app.io;
|
|
// --only <app>
|
|
if (opts.only) {
|
|
var apps = opts.only.split(/,| /)
|
|
if (apps.indexOf(app.name) == -1)
|
|
return false
|
|
}
|
|
// Namespace
|
|
if (!app.namespace) {
|
|
if (opts.namespace)
|
|
app.namespace = opts.namespace;
|
|
else
|
|
app.namespace = 'default';
|
|
}
|
|
// --watch
|
|
if (!app.watch && opts.watch && opts.watch === true)
|
|
app.watch = true;
|
|
// --ignore-watch
|
|
if (!app.ignore_watch && opts.ignore_watch)
|
|
app.ignore_watch = opts.ignore_watch;
|
|
if (opts.install_url)
|
|
app.install_url = opts.install_url;
|
|
// --instances <nb>
|
|
if (opts.instances && typeof(opts.instances) === 'number')
|
|
app.instances = opts.instances;
|
|
// --uid <user>
|
|
if (opts.uid)
|
|
app.uid = opts.uid;
|
|
// --gid <user>
|
|
if (opts.gid)
|
|
app.gid = opts.gid;
|
|
// Specific
|
|
if (app.append_env_to_name && opts.env)
|
|
app.name += ('-' + opts.env);
|
|
if (opts.name_prefix && app.name.indexOf(opts.name_prefix) == -1)
|
|
app.name = `${opts.name_prefix}:${app.name}`
|
|
|
|
app.username = Common.getCurrentUsername();
|
|
apps_name.push(app.name);
|
|
});
|
|
|
|
that.Client.executeRemote('getMonitorData', {}, function(err, raw_proc_list) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
/**
|
|
* Uniquify in memory process list
|
|
*/
|
|
raw_proc_list.forEach(function(proc) {
|
|
proc_list[proc.name] = proc;
|
|
});
|
|
|
|
/**
|
|
* Auto detect application already started
|
|
* and act on them depending on action
|
|
*/
|
|
eachLimit(Object.keys(proc_list), conf.CONCURRENT_ACTIONS, function(proc_name, next) {
|
|
// Skip app name (--only option)
|
|
if (apps_name.indexOf(proc_name) == -1)
|
|
return next();
|
|
|
|
if (!(action == 'reloadProcessId' ||
|
|
action == 'softReloadProcessId' ||
|
|
action == 'restartProcessId'))
|
|
throw new Error('Wrong action called');
|
|
|
|
var apps = appConf.filter(function(app) {
|
|
return app.name == proc_name;
|
|
});
|
|
|
|
var envs = apps.map(function(app){
|
|
// Binds env_diff to env and returns it.
|
|
return Common.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 Object.assign(e1, e2);
|
|
});
|
|
|
|
// When we are processing JSON, allow to keep the new env by default
|
|
env.updateEnv = true;
|
|
|
|
// Pass `env` option
|
|
that._operate(action, proc_name, env, function(err, ret) {
|
|
if (err) Common.printError(err);
|
|
|
|
// For return
|
|
apps_info = apps_info.concat(ret);
|
|
|
|
that.Client.notifyGod(action, proc_name);
|
|
// And Remove from array to spy
|
|
apps_name.splice(apps_name.indexOf(proc_name), 1);
|
|
return next();
|
|
});
|
|
|
|
}, function(err) {
|
|
if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
if (apps_name.length > 0 && action != 'start')
|
|
Common.printOut(conf.PREFIX_MSG_WARNING + 'Applications %s not running, starting...', apps_name.join(', '));
|
|
// Start missing apps
|
|
return startApps(apps_name, function(err, apps) {
|
|
apps_info = apps_info.concat(apps);
|
|
return cb ? cb(err, apps_info) : that.speedList(err ? 1 : 0);
|
|
});
|
|
});
|
|
return false;
|
|
});
|
|
|
|
function startApps(app_name_to_start, cb) {
|
|
var apps_to_start = [];
|
|
var apps_started = [];
|
|
var apps_errored = [];
|
|
|
|
appConf.forEach(function(app, i) {
|
|
if (app_name_to_start.indexOf(app.name) != -1) {
|
|
apps_to_start.push(appConf[i]);
|
|
}
|
|
});
|
|
|
|
eachLimit(apps_to_start, conf.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;
|
|
|
|
var resolved_paths = null;
|
|
|
|
// hardcode script name to use `serve` feature inside a process file
|
|
if (app.script === 'serve') {
|
|
app.script = path.resolve(__dirname, 'API', 'Serve.js')
|
|
}
|
|
|
|
try {
|
|
resolved_paths = Common.resolveAppAttributes({
|
|
cwd : that.cwd,
|
|
pm2_home : that.pm2_home
|
|
}, app);
|
|
} catch (e) {
|
|
apps_errored.push(e)
|
|
Common.err(`Error: ${e.message}`)
|
|
return next();
|
|
}
|
|
|
|
if (!resolved_paths.env) resolved_paths.env = {};
|
|
|
|
// Set PM2 HOME in case of child process using PM2 API
|
|
resolved_paths.env['PM2_HOME'] = that.pm2_home;
|
|
|
|
var additional_env = Modularizer.getAdditionalConf(resolved_paths.name);
|
|
Object.assign(resolved_paths.env, additional_env);
|
|
|
|
resolved_paths.env = Common.mergeEnvironmentVariables(resolved_paths, opts.env, deployConf);
|
|
|
|
delete resolved_paths.env.current_conf;
|
|
|
|
// Is KM linked?
|
|
resolved_paths.km_link = that.gl_is_km_linked;
|
|
|
|
if (resolved_paths.wait_ready) {
|
|
Common.warn(`App ${resolved_paths.name} has option 'wait_ready' set, waiting for app to be ready...`)
|
|
}
|
|
that.Client.executeRemote('prepare', resolved_paths, function(err, data) {
|
|
if (err) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'Process failed to launch %s', err.message ? err.message : err);
|
|
return next();
|
|
}
|
|
if (data.length === 0) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'Process config loading failed', data);
|
|
return next();
|
|
}
|
|
|
|
Common.printOut(conf.PREFIX_MSG + 'App [%s] launched (%d instances)', data[0].pm2_env.name, data.length);
|
|
apps_started = apps_started.concat(data);
|
|
next();
|
|
});
|
|
|
|
}, function(err) {
|
|
var final_error = err || apps_errored.length > 0 ? apps_errored : null
|
|
return cb ? cb(final_error, apps_started) : that.speedList();
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply a RPC method on the json file
|
|
* @private
|
|
* @method actionFromJson
|
|
* @param {string} action RPC Method
|
|
* @param {object} options
|
|
* @param {string|object} file file
|
|
* @param {string} jsonVia action type (=only 'pipe' ?)
|
|
* @param {Function}
|
|
*/
|
|
actionFromJson (action, file, opts, jsonVia, cb) {
|
|
var appConf = {};
|
|
var ret_processes = [];
|
|
var that = this;
|
|
|
|
//accept programmatic calls
|
|
if (typeof file == 'object') {
|
|
cb = typeof jsonVia == 'function' ? jsonVia : cb;
|
|
appConf = file;
|
|
}
|
|
else if (jsonVia == 'file') {
|
|
var data = null;
|
|
|
|
try {
|
|
data = fs.readFileSync(file);
|
|
} catch(e) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file +' not found');
|
|
return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
try {
|
|
appConf = Common.parseConfig(data, file);
|
|
} catch(e) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file + ' malformated');
|
|
console.error(e);
|
|
return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
} else if (jsonVia == 'pipe') {
|
|
appConf = Common.parseConfig(file, 'pipe');
|
|
} else {
|
|
Common.printError('Bad call to actionFromJson, jsonVia should be one of file, pipe');
|
|
return that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
// Backward compatibility
|
|
if (appConf.apps)
|
|
appConf = appConf.apps;
|
|
|
|
if (!Array.isArray(appConf))
|
|
appConf = [appConf];
|
|
|
|
if ((appConf = Common.verifyConfs(appConf)) instanceof Error)
|
|
return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT);
|
|
|
|
eachLimit(appConf, conf.CONCURRENT_ACTIONS, function(proc, next1) {
|
|
var name = '';
|
|
var new_env;
|
|
|
|
if (!proc.name)
|
|
name = path.basename(proc.script);
|
|
else
|
|
name = proc.name;
|
|
|
|
if (opts.only && opts.only != name)
|
|
return process.nextTick(next1);
|
|
|
|
if (opts && opts.env)
|
|
new_env = Common.mergeEnvironmentVariables(proc, opts.env);
|
|
else
|
|
new_env = Common.mergeEnvironmentVariables(proc);
|
|
|
|
that.Client.getProcessIdByName(name, function(err, ids) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return next1();
|
|
}
|
|
if (!ids) return next1();
|
|
|
|
eachLimit(ids, conf.CONCURRENT_ACTIONS, function(id, next2) {
|
|
var opts = {};
|
|
|
|
//stopProcessId could accept options to?
|
|
if (action == 'restartProcessId') {
|
|
opts = {id : id, env : new_env};
|
|
} else {
|
|
opts = id;
|
|
}
|
|
|
|
that.Client.executeRemote(action, opts, function(err, res) {
|
|
ret_processes.push(res);
|
|
if (err) {
|
|
Common.printError(err);
|
|
return next2();
|
|
}
|
|
|
|
if (action == 'restartProcessId') {
|
|
that.Client.notifyGod('restart', id);
|
|
} else if (action == 'deleteProcessId') {
|
|
that.Client.notifyGod('delete', id);
|
|
} else if (action == 'stopProcessId') {
|
|
that.Client.notifyGod('stop', id);
|
|
}
|
|
|
|
Common.printOut(conf.PREFIX_MSG + '[%s](%d) \u2713', name, id);
|
|
return next2();
|
|
});
|
|
}, function(err) {
|
|
return next1(null, ret_processes);
|
|
});
|
|
});
|
|
}, function(err) {
|
|
if (cb) return cb(null, ret_processes);
|
|
else return that.speedList();
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Main function to operate with PM2 daemon
|
|
*
|
|
* @param {String} action_name Name of action (restartProcessId, deleteProcessId, stopProcessId)
|
|
* @param {String} process_name can be 'all', a id integer or process name
|
|
* @param {Object} envs object with CLI options / environment
|
|
*/
|
|
_operate (action_name, process_name, envs, cb) {
|
|
var that = this;
|
|
var update_env = false;
|
|
var ret = [];
|
|
|
|
// Make sure all options exist
|
|
if (!envs)
|
|
envs = {};
|
|
|
|
if (typeof(envs) == 'function'){
|
|
cb = envs;
|
|
envs = {};
|
|
}
|
|
|
|
// Set via env.update (JSON processing)
|
|
if (envs.updateEnv === true)
|
|
update_env = true;
|
|
|
|
var concurrent_actions = envs.parallel || conf.CONCURRENT_ACTIONS;
|
|
|
|
if (!process.env.PM2_JSON_PROCESSING || envs.commands) {
|
|
envs = that._handleAttributeUpdate(envs);
|
|
}
|
|
|
|
/**
|
|
* Set current updated configuration if not passed
|
|
*/
|
|
if (!envs.current_conf) {
|
|
var _conf = fclone(envs);
|
|
envs = {
|
|
current_conf : _conf
|
|
}
|
|
|
|
// Is KM linked?
|
|
envs.current_conf.km_link = that.gl_is_km_linked;
|
|
}
|
|
|
|
/**
|
|
* Operate action on specific process id
|
|
*/
|
|
function processIds(ids, cb) {
|
|
Common.printOut(conf.PREFIX_MSG + 'Applying action %s on app [%s](ids: %s)', action_name, process_name, ids);
|
|
|
|
if (ids.length <= 2)
|
|
concurrent_actions = 1;
|
|
|
|
if (action_name == 'deleteProcessId')
|
|
concurrent_actions = 10;
|
|
|
|
eachLimit(ids, concurrent_actions, function(id, next) {
|
|
var opts;
|
|
|
|
// These functions need extra param to be passed
|
|
if (action_name == 'restartProcessId' ||
|
|
action_name == 'reloadProcessId' ||
|
|
action_name == 'softReloadProcessId') {
|
|
var new_env = {};
|
|
|
|
if (update_env === true) {
|
|
if (conf.PM2_PROGRAMMATIC == true)
|
|
new_env = Common.safeExtend({}, process.env);
|
|
else
|
|
new_env = Object.assign({}, process.env);
|
|
|
|
Object.keys(envs).forEach(function(k) {
|
|
new_env[k] = envs[k];
|
|
});
|
|
}
|
|
else {
|
|
new_env = envs;
|
|
}
|
|
|
|
opts = {
|
|
id : id,
|
|
env : new_env
|
|
};
|
|
}
|
|
else {
|
|
opts = id;
|
|
}
|
|
|
|
that.Client.executeRemote(action_name, opts, function(err, res) {
|
|
if (err) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'Process %s not found', id);
|
|
return next(`Process ${id} not found`);
|
|
}
|
|
|
|
if (action_name == 'restartProcessId') {
|
|
that.Client.notifyGod('restart', id);
|
|
} else if (action_name == 'deleteProcessId') {
|
|
that.Client.notifyGod('delete', id);
|
|
} else if (action_name == 'stopProcessId') {
|
|
that.Client.notifyGod('stop', id);
|
|
} else if (action_name == 'reloadProcessId') {
|
|
that.Client.notifyGod('reload', id);
|
|
} else if (action_name == 'softReloadProcessId') {
|
|
that.Client.notifyGod('graceful reload', id);
|
|
}
|
|
|
|
if (!Array.isArray(res))
|
|
res = [res];
|
|
|
|
// Filter return
|
|
res.forEach(function(proc) {
|
|
Common.printOut(conf.PREFIX_MSG + '[%s](%d) \u2713', proc.pm2_env ? proc.pm2_env.name : process_name, id);
|
|
|
|
if (action_name == 'stopProcessId' && proc.pm2_env && proc.pm2_env.cron_restart) {
|
|
Common.warn(`App ${chalk.bold(proc.pm2_env.name)} stopped but CRON RESTART is still UP ${proc.pm2_env.cron_restart}`)
|
|
}
|
|
|
|
if (!proc.pm2_env) return false;
|
|
|
|
ret.push({
|
|
name : proc.pm2_env.name,
|
|
namespace: proc.pm2_env.namespace,
|
|
pm_id : proc.pm2_env.pm_id,
|
|
status : proc.pm2_env.status,
|
|
restart_time : proc.pm2_env.restart_time,
|
|
pm2_env : {
|
|
name : proc.pm2_env.name,
|
|
namespace: proc.pm2_env.namespace,
|
|
pm_id : proc.pm2_env.pm_id,
|
|
status : proc.pm2_env.status,
|
|
restart_time : proc.pm2_env.restart_time,
|
|
env : proc.pm2_env.env
|
|
}
|
|
});
|
|
});
|
|
|
|
return next();
|
|
});
|
|
}, function(err) {
|
|
if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
return cb ? cb(null, ret) : that.speedList();
|
|
});
|
|
}
|
|
|
|
if (process_name == 'all') {
|
|
// When using shortcuts like 'all', do not delete modules
|
|
var fn
|
|
|
|
if (process.env.PM2_STATUS == 'stopping')
|
|
that.Client.getAllProcessId(function(err, ids) {
|
|
reoperate(err, ids)
|
|
});
|
|
else
|
|
that.Client.getAllProcessIdWithoutModules(function(err, ids) {
|
|
reoperate(err, ids)
|
|
});
|
|
|
|
function reoperate(err, ids) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
if (!ids || ids.length === 0) {
|
|
Common.printError(conf.PREFIX_MSG_WARNING + 'No process found');
|
|
return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
return processIds(ids, cb);
|
|
}
|
|
}
|
|
// operate using regex
|
|
else if (isNaN(process_name) && process_name[0] === '/' && process_name[process_name.length - 1] === '/') {
|
|
var regex = new RegExp(process_name.replace(/\//g, ''));
|
|
|
|
that.Client.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError('Error retrieving process list: ' + err);
|
|
return cb(err);
|
|
}
|
|
var found_proc = [];
|
|
list.forEach(function(proc) {
|
|
if (regex.test(proc.pm2_env.name)) {
|
|
found_proc.push(proc.pm_id);
|
|
}
|
|
});
|
|
|
|
if (found_proc.length === 0) {
|
|
Common.printError(conf.PREFIX_MSG_WARNING + 'No process found');
|
|
return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
return processIds(found_proc, 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;
|
|
|
|
that.Client.getProcessIdByName(process_name, allow_module_restart, function (err, ids) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
if (ids && ids.length > 0) {
|
|
/**
|
|
* Determine if the process to restart is a module
|
|
* if yes load configuration variables and merge with the current environment
|
|
*/
|
|
var additional_env = Modularizer.getAdditionalConf(process_name);
|
|
Object.assign(envs, additional_env);
|
|
return processIds(ids, cb);
|
|
}
|
|
|
|
that.Client.getProcessIdsByNamespace(process_name, allow_module_restart, function (err, ns_process_ids) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
if (!ns_process_ids || ns_process_ids.length === 0) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'Process or Namespace %s not found', process_name);
|
|
return cb ? cb(new Error('process or namespace not found')) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
/**
|
|
* Determine if the process to restart is a module
|
|
* if yes load configuration variables and merge with the current environment
|
|
*/
|
|
var ns_additional_env = Modularizer.getAdditionalConf(process_name);
|
|
Object.assign(envs, ns_additional_env);
|
|
return processIds(ns_process_ids, cb);
|
|
});
|
|
});
|
|
} else {
|
|
if (that.pm2_configuration.docker == "true" ||
|
|
that.pm2_configuration.docker == true) {
|
|
// Docker/Systemd process interaction detection
|
|
that.Client.executeRemote('getMonitorData', {}, (err, proc_list) => {
|
|
var higher_id = 0
|
|
proc_list.forEach(p => { p.pm_id > higher_id ? higher_id = p.pm_id : null })
|
|
|
|
// Is Docker/Systemd
|
|
if (process_name > higher_id)
|
|
return DockerMgmt.processCommand(that, higher_id, process_name, action_name, (err) => {
|
|
if (err) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + (err.message ? err.message : err));
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
return cb ? cb(null, ret) : that.speedList();
|
|
})
|
|
|
|
// Check if application name as number is an app name
|
|
that.Client.getProcessIdByName(process_name, function(err, ids) {
|
|
if (ids.length > 0)
|
|
return processIds(ids, cb);
|
|
|
|
// Check if application name as number is an namespace
|
|
that.Client.getProcessIdsByNamespace(process_name, function(err, ns_process_ids) {
|
|
if (ns_process_ids.length > 0)
|
|
return processIds(ns_process_ids, cb);
|
|
// Else operate on pm id
|
|
return processIds([process_name], cb);
|
|
});
|
|
});
|
|
})
|
|
}
|
|
else {
|
|
// Check if application name as number is an app name
|
|
that.Client.getProcessIdByName(process_name, function(err, ids) {
|
|
if (ids.length > 0)
|
|
return processIds(ids, cb);
|
|
|
|
// Check if application name as number is an namespace
|
|
that.Client.getProcessIdsByNamespace(process_name, function(err, ns_process_ids) {
|
|
if (ns_process_ids.length > 0)
|
|
return processIds(ns_process_ids, cb);
|
|
// Else operate on pm id
|
|
return processIds([process_name], cb);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts CamelCase Commander.js arguments
|
|
* to Underscore
|
|
* (nodeArgs -> node_args)
|
|
*/
|
|
_handleAttributeUpdate (opts) {
|
|
var conf = Config.filterOptions(opts);
|
|
var that = this;
|
|
|
|
if (typeof(conf.name) != 'string')
|
|
delete conf.name;
|
|
|
|
var argsIndex = 0;
|
|
if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0) {
|
|
conf.args = opts.rawArgs.slice(argsIndex + 1);
|
|
}
|
|
|
|
var appConf = Common.verifyConfs(conf)[0];
|
|
|
|
if (appConf instanceof Error) {
|
|
Common.printError('Error while transforming CamelCase args to underscore');
|
|
return appConf;
|
|
}
|
|
|
|
if (argsIndex == -1)
|
|
delete appConf.args;
|
|
if (appConf.name == 'undefined')
|
|
delete appConf.name;
|
|
|
|
delete appConf.exec_mode;
|
|
|
|
if (util.isArray(appConf.watch) && appConf.watch.length === 0) {
|
|
if (!~opts.rawArgs.indexOf('--watch'))
|
|
delete appConf.watch
|
|
}
|
|
|
|
// Options set via environment variables
|
|
if (process.env.PM2_DEEP_MONITORING)
|
|
appConf.deep_monitoring = true;
|
|
|
|
// Force deletion of defaults values set by commander
|
|
// to avoid overriding specified configuration by user
|
|
if (appConf.treekill === true)
|
|
delete appConf.treekill;
|
|
if (appConf.pmx === true)
|
|
delete appConf.pmx;
|
|
if (appConf.vizion === true)
|
|
delete appConf.vizion;
|
|
if (appConf.automation === true)
|
|
delete appConf.automation;
|
|
if (appConf.autorestart === true)
|
|
delete appConf.autorestart;
|
|
|
|
return appConf;
|
|
}
|
|
|
|
getProcessIdByName (name, cb) {
|
|
var that = this;
|
|
|
|
this.Client.getProcessIdByName(name, function(err, id) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
console.log(id);
|
|
return cb ? cb(null, id) : that.exitCli(conf.SUCCESS_EXIT);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Description
|
|
* @method jlist
|
|
* @param {} debug
|
|
* @return
|
|
*/
|
|
jlist (debug) {
|
|
var that = this;
|
|
|
|
that.Client.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
if (debug) {
|
|
process.stdout.write(util.inspect(list, false, null, false));
|
|
}
|
|
else {
|
|
process.stdout.write(JSON.stringify(list));
|
|
}
|
|
|
|
that.exitCli(conf.SUCCESS_EXIT);
|
|
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Display system information
|
|
* @method slist
|
|
* @return
|
|
*/
|
|
slist (tree) {
|
|
this.Client.executeRemote('getSystemData', {}, (err, sys_infos) => {
|
|
if (err) {
|
|
Common.err(err)
|
|
return this.exitCli(conf.ERROR_EXIT)
|
|
}
|
|
|
|
if (tree === true) {
|
|
var treeify = require('./tools/treeify.js')
|
|
console.log(treeify.asTree(sys_infos, true))
|
|
}
|
|
else
|
|
process.stdout.write(util.inspect(sys_infos, false, null, false))
|
|
this.exitCli(conf.SUCCESS_EXIT)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Description
|
|
* @method speedList
|
|
* @return
|
|
*/
|
|
speedList (code, apps_acted) {
|
|
var that = this;
|
|
var systemdata = null
|
|
var acted = []
|
|
|
|
if ((code != 0 && code != null)) {
|
|
return that.exitCli(code ? code : conf.SUCCESS_EXIT);
|
|
}
|
|
|
|
if (apps_acted && apps_acted.length > 0) {
|
|
apps_acted.forEach(proc => {
|
|
acted.push(proc.pm2_env ? proc.pm2_env.pm_id : proc.pm_id)
|
|
})
|
|
}
|
|
|
|
// Do nothing if PM2 called programmatically and not called from CLI (also in exitCli)
|
|
if ((conf.PM2_PROGRAMMATIC && process.env.PM2_USAGE != 'CLI'))
|
|
return false;
|
|
|
|
return that.Client.executeRemote('getMonitorData', {}, (err, proc_list) => {
|
|
doList(err, proc_list)
|
|
})
|
|
|
|
function doList(err, list) {
|
|
if (err) {
|
|
if (that.gl_retry == 0) {
|
|
that.gl_retry += 1;
|
|
return setTimeout(that.speedList.bind(that), 1400);
|
|
}
|
|
console.error('Error retrieving process list: %s.\nA process seems to be on infinite loop, retry in 5 seconds',err);
|
|
return that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
if (process.stdout.isTTY === false) {
|
|
UX.list_min(list);
|
|
}
|
|
else if (commander.miniList && !commander.silent)
|
|
UX.list_min(list);
|
|
else if (!commander.silent) {
|
|
if (that.gl_interact_infos) {
|
|
var dashboard_url = `https://app.pm2.io/#/r/${that.gl_interact_infos.public_key}`
|
|
|
|
if (that.gl_interact_infos.info_node != 'https://root.keymetrics.io') {
|
|
dashboard_url = `${that.gl_interact_infos.info_node}/#/r/${that.gl_interact_infos.public_key}`
|
|
}
|
|
|
|
Common.printOut('%s PM2+ activated | Instance Name: %s | Dash: %s',
|
|
chalk.green.bold('⇆'),
|
|
chalk.bold(that.gl_interact_infos.machine_name),
|
|
chalk.bold(dashboard_url))
|
|
}
|
|
UX.list(list, commander);
|
|
//Common.printOut(chalk.white.italic(' Use `pm2 show <id|name>` to get more details about an app'));
|
|
}
|
|
|
|
if (that.Client.daemon_mode == false) {
|
|
Common.printOut('[--no-daemon] Continue to stream logs');
|
|
Common.printOut('[--no-daemon] Exit on target PM2 exit pid=' + fs.readFileSync(conf.PM2_PID_FILE_PATH).toString());
|
|
global._auto_exit = true;
|
|
return that.streamLogs('all', 0, false, 'HH:mm:ss', false);
|
|
}
|
|
// if (process.stdout.isTTY) if looking for start logs
|
|
else if (!process.env.TRAVIS && process.env.NODE_ENV != 'test' && acted.length > 0 && (commander.attach === true)) {
|
|
Common.info(`Log streaming apps id: ${chalk.cyan(acted.join(' '))}, exit with Ctrl-C or will exit in 10secs`)
|
|
|
|
// setTimeout(() => {
|
|
// Common.info(`Log streaming exited automatically, run 'pm2 logs' to continue watching logs`)
|
|
// return that.exitCli(code ? code : conf.SUCCESS_EXIT);
|
|
// }, 10000)
|
|
|
|
return acted.forEach((proc_name) => {
|
|
that.streamLogs(proc_name, 0, false, null, false);
|
|
})
|
|
}
|
|
else {
|
|
return that.exitCli(code ? code : conf.SUCCESS_EXIT);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scale up/down a process
|
|
* @method scale
|
|
*/
|
|
scale (app_name, number, cb) {
|
|
var that = this;
|
|
|
|
function addProcs(proc, value, cb) {
|
|
(function ex(proc, number) {
|
|
if (number-- === 0) return cb();
|
|
Common.printOut(conf.PREFIX_MSG + 'Scaling up application');
|
|
that.Client.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();
|
|
that._operate('deleteProcessId', procs[i++].pm2_env.pm_id, ex.bind(this, procs, number));
|
|
})(procs, number);
|
|
}
|
|
|
|
function end() {
|
|
return cb ? cb(null, {success:true}) : that.speedList();
|
|
}
|
|
|
|
this.Client.getProcessByName(app_name, function(err, procs) {
|
|
if (err) {
|
|
Common.printError(err);
|
|
return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
if (!procs || procs.length === 0) {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'Application %s not found', app_name);
|
|
return cb ? cb(new Error('App not found')) : that.exitCli(conf.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 {
|
|
Common.printError(conf.PREFIX_MSG_ERR + 'Nothing to do');
|
|
return cb ? cb(new Error('Same process number')) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Description
|
|
* @method describeProcess
|
|
* @param {} pm2_id
|
|
* @return
|
|
*/
|
|
describe (pm2_id, cb) {
|
|
var that = this;
|
|
|
|
var found_proc = [];
|
|
|
|
that.Client.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError('Error retrieving process list: ' + err);
|
|
that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
list.forEach(function(proc) {
|
|
if ((!isNaN(pm2_id) && proc.pm_id == pm2_id) ||
|
|
(typeof(pm2_id) === 'string' && proc.name == pm2_id)) {
|
|
found_proc.push(proc);
|
|
}
|
|
});
|
|
|
|
if (found_proc.length === 0) {
|
|
Common.printError(conf.PREFIX_MSG_WARNING + '%s doesn\'t exist', pm2_id);
|
|
return cb ? cb(null, []) : that.exitCli(conf.ERROR_EXIT);
|
|
}
|
|
|
|
if (!cb) {
|
|
found_proc.forEach(function(proc) {
|
|
UX.describe(proc);
|
|
});
|
|
}
|
|
|
|
return cb ? cb(null, found_proc) : that.exitCli(conf.SUCCESS_EXIT);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* API method to perform a deep update of PM2
|
|
* @method deepUpdate
|
|
*/
|
|
deepUpdate (cb) {
|
|
var that = this;
|
|
|
|
Common.printOut(conf.PREFIX_MSG + 'Updating PM2...');
|
|
|
|
var child = sexec("npm i -g pm2@latest; pm2 update");
|
|
|
|
child.stdout.on('end', function() {
|
|
Common.printOut(conf.PREFIX_MSG + 'PM2 successfully updated');
|
|
cb ? cb(null, {success:true}) : that.exitCli(conf.SUCCESS_EXIT);
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
//////////////////////////
|
|
// Load all API methods //
|
|
//////////////////////////
|
|
|
|
require('./API/Extra.js')(API);
|
|
require('./API/Deploy.js')(API);
|
|
require('./API/Modules/index.js')(API);
|
|
|
|
require('./API/pm2-plus/link.js')(API);
|
|
require('./API/pm2-plus/process-selector.js')(API);
|
|
require('./API/pm2-plus/helpers.js')(API);
|
|
|
|
require('./API/Configuration.js')(API);
|
|
require('./API/Version.js')(API);
|
|
require('./API/Startup.js')(API);
|
|
require('./API/LogManagement.js')(API);
|
|
require('./API/Containerizer.js')(API);
|
|
|
|
|
|
module.exports = API;
|