UNPKG

53.8 kBJavaScriptView Raw
1/**
2 * Copyright 2013 the PM2 project authors. All rights reserved.
3 * Use of this source code is governed by a license that
4 * can be found in the LICENSE file.
5 */
6'use strict';
7
8const commander = require('commander');
9const fs = require('fs');
10const path = require('path');
11const eachLimit = require('async/eachLimit');
12const series = require('async/series');
13const debug = require('debug')('pm2:cli');
14const util = require('util');
15const chalk = require('chalk');
16const fclone = require('fclone');
17
18
19var conf = require('../constants.js');
20var Client = require('./Client');
21var Common = require('./Common');
22var KMDaemon = require('@pm2/agent/src/InteractorClient');
23var Config = require('./tools/Config');
24var Modularizer = require('./API/Modules/Modularizer.js');
25var path_structure = require('../paths.js');
26var UX = require('./API/CliUx');
27var pkg = require('../package.json');
28var flagWatch = require("./API/Modules/flagWatch.js");
29var hf = require('./API/Modules/flagExt.js');
30var Configuration = require('./Configuration.js');
31const semver = require('semver')
32
33var IMMUTABLE_MSG = chalk.bold.blue('Use --update-env to update environment variables');
34
35/**
36 * Main Function to be imported
37 * can be aliased to PM2
38 *
39 * To use it when PM2 is installed as a module:
40 *
41 * var PM2 = require('pm2');
42 *
43 * var pm2 = PM2(<opts>);
44 *
45 *
46 * @param {Object} opts
47 * @param {String} [opts.cwd=<current>] override pm2 cwd for starting scripts
48 * @param {String} [opts.pm2_home=[<paths.js>]] pm2 directory for log, pids, socket files
49 * @param {Boolean} [opts.independent=false] unique PM2 instance (random pm2_home)
50 * @param {Boolean} [opts.daemon_mode=true] should be called in the same process or not
51 * @param {String} [opts.public_key=null] pm2 plus bucket public key
52 * @param {String} [opts.secret_key=null] pm2 plus bucket secret key
53 * @param {String} [opts.machine_name=null] pm2 plus instance name
54 */
55class API {
56
57 constructor (opts) {
58 if (!opts) opts = {};
59 var that = this;
60
61 this.daemon_mode = typeof(opts.daemon_mode) == 'undefined' ? true : opts.daemon_mode;
62 this.pm2_home = conf.PM2_ROOT_PATH;
63 this.public_key = conf.PUBLIC_KEY || opts.public_key || null;
64 this.secret_key = conf.SECRET_KEY || opts.secret_key || null;
65 this.machine_name = conf.MACHINE_NAME || opts.machine_name || null
66
67 /**
68 * CWD resolution
69 */
70 this.cwd = process.cwd();
71 if (opts.cwd) {
72 this.cwd = path.resolve(opts.cwd);
73 }
74
75 /**
76 * PM2 HOME resolution
77 */
78 if (opts.pm2_home && opts.independent == true)
79 throw new Error('You cannot set a pm2_home and independent instance in same time');
80
81 if (opts.pm2_home) {
82 // Override default conf file
83 this.pm2_home = opts.pm2_home;
84 conf = util._extend(conf, path_structure(this.pm2_home));
85 }
86 else if (opts.independent == true && conf.IS_WINDOWS === false) {
87 // Create an unique pm2 instance
88 const crypto = require('crypto');
89 var random_file = crypto.randomBytes(8).toString('hex');
90 this.pm2_home = path.join('/tmp', random_file);
91
92 // If we dont explicitly tell to have a daemon
93 // It will go as in proc
94 if (typeof(opts.daemon_mode) == 'undefined')
95 this.daemon_mode = false;
96 conf = util._extend(conf, path_structure(this.pm2_home));
97 }
98
99 this._conf = conf;
100
101 if (conf.IS_WINDOWS) {
102 // Weird fix, may need to be dropped
103 // @todo windows connoisseur double check
104 if (process.stdout._handle && process.stdout._handle.setBlocking)
105 process.stdout._handle.setBlocking(true);
106 }
107
108 this.Client = new Client({
109 pm2_home: that.pm2_home,
110 conf: this._conf,
111 secret_key: this.secret_key,
112 public_key: this.public_key,
113 daemon_mode: this.daemon_mode,
114 machine_name: this.machine_name
115 });
116
117 this.user_conf = Configuration.getSync('pm2')
118
119 this.gl_interact_infos = null;
120 this.gl_is_km_linked = false;
121
122 try {
123 var pid = fs.readFileSync(conf.INTERACTOR_PID_PATH);
124 pid = parseInt(pid.toString().trim());
125 process.kill(pid, 0);
126 that.gl_is_km_linked = true;
127 } catch (e) {
128 that.gl_is_km_linked = false;
129 }
130
131 // For testing purposes
132 if (this.secret_key && process.env.NODE_ENV == 'local_test')
133 that.gl_is_km_linked = true;
134
135 KMDaemon.ping(this._conf, function(err, result) {
136 if (!err && result === true) {
137 fs.readFile(conf.INTERACTION_CONF, (err, _conf) => {
138 if (!err) {
139 try {
140 that.gl_interact_infos = JSON.parse(_conf.toString())
141 } catch(e) {
142 var json5 = require('./tools/json5.js')
143 try {
144 that.gl_interact_infos = json5.parse(_conf.toString())
145 } catch(e) {
146 console.error(e)
147 that.gl_interact_infos = null
148 }
149 }
150 }
151 })
152 }
153 })
154
155 this.gl_retry = 0;
156 }
157
158 /**
159 * Connect to PM2
160 * Calling this command is now optional
161 *
162 * @param {Function} cb callback once pm2 is ready for commands
163 */
164 connect (noDaemon, cb) {
165 var that = this;
166 this.start_timer = new Date();
167
168 if (typeof(cb) == 'undefined') {
169 cb = noDaemon;
170 noDaemon = false;
171 } else if (noDaemon === true) {
172 // Backward compatibility with PM2 1.x
173 this.Client.daemon_mode = false;
174 this.daemon_mode = false;
175 }
176
177 this.Client.start(function(err, meta) {
178 if (err)
179 return cb(err);
180
181 if (meta.new_pm2_instance == false && that.daemon_mode === true)
182 return cb(err, meta);
183
184 // If new pm2 instance has been popped
185 // Lauch all modules
186 that.launchAll(that, function(err_mod) {
187 return cb(err, meta);
188 });
189 });
190 }
191
192 /**
193 * Usefull when custom PM2 created with independent flag set to true
194 * This will cleanup the newly created instance
195 * by removing folder, killing PM2 and so on
196 *
197 * @param {Function} cb callback once cleanup is successfull
198 */
199 destroy (cb) {
200 var exec = require('shelljs').exec;
201 var that = this;
202
203 debug('Killing and deleting current deamon');
204
205 this.killDaemon(function() {
206 var cmd = 'rm -rf ' + that.pm2_home;
207 var test_path = path.join(that.pm2_home, 'module_conf.json');
208 var test_path_2 = path.join(that.pm2_home, 'pm2.pid');
209
210 if (that.pm2_home.indexOf('.pm2') > -1)
211 return cb(new Error('Destroy is not a allowed method on .pm2'));
212
213 fs.access(test_path, fs.R_OK, function(err) {
214 if (err) return cb(err);
215 debug('Deleting temporary folder %s', that.pm2_home);
216 exec(cmd, cb);
217 });
218 });
219 }
220
221 /**
222 * Disconnect from PM2 instance
223 * This will allow your software to exit by itself
224 *
225 * @param {Function} [cb] optional callback once connection closed
226 */
227 disconnect (cb) {
228 var that = this;
229
230 if (!cb) cb = function() {};
231
232 this.Client.close(function(err, data) {
233 debug('The session lasted %ds', (new Date() - that.start_timer) / 1000);
234 return cb(err, data);
235 });
236 };
237
238 /**
239 * Alias on disconnect
240 * @param cb
241 */
242 close (cb) {
243 this.disconnect(cb);
244 }
245
246 /**
247 * Launch modules
248 *
249 * @param {Function} cb callback once pm2 has launched modules
250 */
251 launchModules (cb) {
252 this.launchAll(this, cb);
253 }
254
255 /**
256 * Enable bus allowing to retrieve various process event
257 * like logs, restarts, reloads
258 *
259 * @param {Function} cb callback called with 1st param err and 2nb param the bus
260 */
261 launchBus (cb) {
262 this.Client.launchBus(cb);
263 }
264
265 /**
266 * Exit methods for API
267 * @param {Integer} code exit code for terminal
268 */
269 exitCli (code) {
270 var that = this;
271
272 // Do nothing if PM2 called programmatically (also in speedlist)
273 if (conf.PM2_PROGRAMMATIC && process.env.PM2_USAGE != 'CLI') return false;
274
275 KMDaemon.disconnectRPC(function() {
276 that.Client.close(function() {
277 code = code || 0;
278 // Safe exits process after all streams are drained.
279 // file descriptor flag.
280 var fds = 0;
281 // exits process when stdout (1) and sdterr(2) are both drained.
282 function tryToExit() {
283 if ((fds & 1) && (fds & 2)) {
284 debug('This command took %ds to execute', (new Date() - that.start_timer) / 1000);
285 process.exit(code);
286 }
287 }
288
289 [process.stdout, process.stderr].forEach(function(std) {
290 var fd = std.fd;
291 if (!std.bufferSize) {
292 // bufferSize equals 0 means current stream is drained.
293 fds = fds | fd;
294 } else {
295 // Appends nothing to the std queue, but will trigger `tryToExit` event on `drain`.
296 std.write && std.write('', function() {
297 fds = fds | fd;
298 tryToExit();
299 });
300 }
301 // Does not write anything more.
302 delete std.write;
303 });
304 tryToExit();
305 });
306 });
307 }
308
309////////////////////////////
310// Application management //
311////////////////////////////
312
313 /**
314 * Start a file or json with configuration
315 * @param {Object||String} cmd script to start or json
316 * @param {Function} cb called when application has been started
317 */
318 start (cmd, opts, cb) {
319 if (typeof(opts) == "function") {
320 cb = opts;
321 opts = {};
322 }
323 if (!opts) opts = {};
324
325 if (semver.lt(process.version, '6.0.0')) {
326 Common.printOut(conf.PREFIX_MSG_WARNING + 'Node 4 is deprecated, please upgrade to use pm2 to have all features');
327 }
328
329 var that = this;
330 if (util.isArray(opts.watch) && opts.watch.length === 0)
331 opts.watch = (opts.rawArgs ? !!~opts.rawArgs.indexOf('--watch') : !!~process.argv.indexOf('--watch')) || false;
332
333 if (Common.isConfigFile(cmd) || (typeof(cmd) === 'object'))
334 that._startJson(cmd, opts, 'restartProcessId', cb);
335 else {
336 that._startScript(cmd, opts, cb);
337 }
338 }
339
340 /**
341 * Reset process counters
342 *
343 * @method resetMetaProcess
344 */
345 reset (process_name, cb) {
346 var that = this;
347
348 function processIds(ids, cb) {
349 eachLimit(ids, conf.CONCURRENT_ACTIONS, function(id, next) {
350 that.Client.executeRemote('resetMetaProcessId', id, function(err, res) {
351 if (err) console.error(err);
352 Common.printOut(conf.PREFIX_MSG + 'Resetting meta for process id %d', id);
353 return next();
354 });
355 }, function(err) {
356 if (err) return cb(Common.retErr(err));
357 return cb ? cb(null, {success:true}) : that.speedList();
358 });
359 }
360
361 if (process_name == 'all') {
362 that.Client.getAllProcessId(function(err, ids) {
363 if (err) {
364 Common.printError(err);
365 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
366 }
367 return processIds(ids, cb);
368 });
369 }
370 else if (isNaN(process_name)) {
371 that.Client.getProcessIdByName(process_name, function(err, ids) {
372 if (err) {
373 Common.printError(err);
374 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
375 }
376 if (ids.length === 0) {
377 Common.printError('Unknown process name');
378 return cb ? cb(new Error('Unknown process name')) : that.exitCli(conf.ERROR_EXIT);
379 }
380 return processIds(ids, cb);
381 });
382 } else {
383 processIds([process_name], cb);
384 }
385 }
386
387 /**
388 * Update daemonized PM2 Daemon
389 *
390 * @param {Function} cb callback when pm2 has been upgraded
391 */
392 update (cb) {
393 var that = this;
394
395 Common.printOut('Be sure to have the latest version by doing `npm install pm2@latest -g` before doing this procedure.');
396
397 // Dump PM2 processes
398 that.Client.executeRemote('notifyKillPM2', {}, function() {});
399
400 that.getVersion(function(err, new_version) {
401 // If not linked to PM2 plus, and update PM2 to latest, display motd.update
402 if (!that.gl_is_km_linked && !err && (pkg.version != new_version)) {
403 var dt = fs.readFileSync(path.join(__dirname, that._conf.PM2_UPDATE));
404 console.log(dt.toString());
405 }
406
407 that.dump(function(err) {
408 debug('Dumping successfull', err);
409 that.killDaemon(function() {
410 debug('------------------ Everything killed', arguments);
411 that.Client.launchDaemon({interactor:false}, function(err, child) {
412 that.Client.launchRPC(function() {
413 that.resurrect(function() {
414 Common.printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated'));
415 that.launchAll(that, function() {
416 KMDaemon.launchAndInteract(that._conf, null, function(err, data, interactor_proc) {
417 return cb ? cb(null, {success:true}) : that.speedList();
418 });
419 });
420 });
421 });
422 });
423 });
424 });
425 });
426
427 return false;
428 }
429
430 /**
431 * Reload an application
432 *
433 * @param {String} process_name Application Name or All
434 * @param {Object} opts Options
435 * @param {Function} cb Callback
436 */
437 reload (process_name, opts, cb) {
438 var that = this;
439
440 if (typeof(opts) == "function") {
441 cb = opts;
442 opts = {};
443 }
444
445 var delay = Common.lockReload();
446 if (delay > 0 && opts.force != true) {
447 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');
448 return cb ? cb(new Error('Reload in progress')) : that.exitCli(conf.ERROR_EXIT);
449 }
450
451 if (Common.isConfigFile(process_name))
452 that._startJson(process_name, opts, 'reloadProcessId', function(err, apps) {
453 Common.unlockReload();
454 if (err)
455 return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
456 return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT);
457 });
458 else {
459 if (opts && opts.env) {
460 var err = 'Using --env [env] without passing the ecosystem.config.js does not work'
461 Common.err(err);
462 Common.unlockReload();
463 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
464 }
465
466 if (opts && !opts.updateEnv)
467 Common.printOut(IMMUTABLE_MSG);
468
469 that._operate('reloadProcessId', process_name, opts, function(err, apps) {
470 Common.unlockReload();
471
472 if (err)
473 return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
474 return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT);
475 });
476 }
477 }
478
479 /**
480 * Restart process
481 *
482 * @param {String} cmd Application Name / Process id / JSON application file / 'all'
483 * @param {Object} opts Extra options to be updated
484 * @param {Function} cb Callback
485 */
486 restart (cmd, opts, cb) {
487 if (typeof(opts) == "function") {
488 cb = opts;
489 opts = {};
490 }
491 var that = this;
492
493 if (typeof(cmd) === 'number')
494 cmd = cmd.toString();
495
496 if (cmd == "-") {
497 // Restart from PIPED JSON
498 process.stdin.resume();
499 process.stdin.setEncoding('utf8');
500 process.stdin.on('data', function (param) {
501 process.stdin.pause();
502 that.actionFromJson('restartProcessId', param, opts, 'pipe', cb);
503 });
504 }
505 else if (Common.isConfigFile(cmd) || typeof(cmd) === 'object')
506 that._startJson(cmd, opts, 'restartProcessId', cb);
507 else {
508 if (opts && opts.env) {
509 var err = 'Using --env [env] without passing the ecosystem.config.js does not work'
510 Common.err(err);
511 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
512 }
513 if (opts && !opts.updateEnv)
514 Common.printOut(IMMUTABLE_MSG);
515 that._operate('restartProcessId', cmd, opts, cb);
516 }
517 }
518
519 /**
520 * Delete process
521 *
522 * @param {String} process_name Application Name / Process id / Application file / 'all'
523 * @param {Function} cb Callback
524 */
525 delete (process_name, jsonVia, cb) {
526 var that = this;
527
528 if (typeof(jsonVia) === "function") {
529 cb = jsonVia;
530 jsonVia = null;
531 }
532 if (typeof(process_name) === "number") {
533 process_name = process_name.toString();
534 }
535
536 if (jsonVia == 'pipe')
537 return that.actionFromJson('deleteProcessId', process_name, commander, 'pipe', cb);
538 if (Common.isConfigFile(process_name))
539 return that.actionFromJson('deleteProcessId', process_name, commander, 'file', cb);
540 else {
541 that._operate('deleteProcessId', process_name, cb);
542 }
543 }
544
545 /**
546 * Stop process
547 *
548 * @param {String} process_name Application Name / Process id / Application file / 'all'
549 * @param {Function} cb Callback
550 */
551 stop (process_name, cb) {
552 var that = this;
553
554 if (typeof(process_name) === 'number')
555 process_name = process_name.toString();
556
557 if (process_name == "-") {
558 process.stdin.resume();
559 process.stdin.setEncoding('utf8');
560 process.stdin.on('data', function (param) {
561 process.stdin.pause();
562 that.actionFromJson('stopProcessId', param, commander, 'pipe', cb);
563 });
564 }
565 else if (Common.isConfigFile(process_name))
566 that.actionFromJson('stopProcessId', process_name, commander, 'file', cb);
567 else
568 that._operate('stopProcessId', process_name, cb);
569 }
570
571 /**
572 * Get list of all processes managed
573 *
574 * @param {Function} cb Callback
575 */
576 list (opts, cb) {
577 var that = this;
578
579 if (typeof(opts) == 'function') {
580 cb = opts;
581 opts = null;
582 }
583
584 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
585 if (err) {
586 Common.printError(err);
587 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
588 }
589
590 if (opts && opts.rawArgs && opts.rawArgs.indexOf('--watch') > -1) {
591 var moment = require('moment');
592 function show() {
593 process.stdout.write('\\033[2J');
594 process.stdout.write('\\033[0f');
595 console.log('Last refresh: ', moment().format('LTS'));
596 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
597 UX.dispAsTable(list, null);
598 });
599 }
600
601 show();
602 setInterval(show, 900);
603 return false;
604 }
605
606 return cb ? cb(null, list) : that.speedList(null, list);
607 });
608 }
609
610 /**
611 * Kill Daemon
612 *
613 * @param {Function} cb Callback
614 */
615 killDaemon (cb) {
616 process.env.PM2_STATUS = 'stopping'
617
618 var that = this;
619
620 that.Client.executeRemote('notifyKillPM2', {}, function() {});
621
622 Common.printOut(conf.PREFIX_MSG + '[v] Modules Stopped');
623
624 that._operate('deleteProcessId', 'all', function(err, list) {
625 Common.printOut(conf.PREFIX_MSG + '[v] All Applications Stopped');
626 process.env.PM2_SILENT = 'false';
627
628 that.killAgent(function(err, data) {
629 if (!err) {
630 Common.printOut(conf.PREFIX_MSG + '[v] Agent Stopped');
631 }
632
633 that.Client.killDaemon(function(err, res) {
634 if (err) Common.printError(err);
635 Common.printOut(conf.PREFIX_MSG + '[v] PM2 Daemon Stopped');
636 return cb ? cb(err, res) : that.exitCli(conf.SUCCESS_EXIT);
637 });
638
639 });
640 })
641 }
642
643 kill (cb) {
644 this.killDaemon(cb);
645 }
646
647 /////////////////////
648 // Private methods //
649 /////////////////////
650
651 /**
652 * Method to START / RESTART a script
653 *
654 * @private
655 * @param {string} script script name (will be resolved according to location)
656 */
657 _startScript (script, opts, cb) {
658 if (typeof opts == "function") {
659 cb = opts;
660 opts = {};
661 }
662 var that = this;
663
664 /**
665 * Commander.js tricks
666 */
667 var app_conf = Config.filterOptions(opts);
668 var appConf = {};
669
670 var ignoreFileArray = [];
671
672 if (typeof app_conf.name == 'function')
673 delete app_conf.name;
674
675 delete app_conf.args;
676
677 // Retrieve arguments via -- <args>
678 var argsIndex;
679
680 if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0)
681 app_conf.args = opts.rawArgs.slice(argsIndex + 1);
682 else if (opts.scriptArgs)
683 app_conf.args = opts.scriptArgs;
684
685 app_conf.script = script;
686
687 if ((appConf = Common.verifyConfs(app_conf)) instanceof Error)
688 return cb ? cb(Common.retErr(appConf)) : that.exitCli(conf.ERROR_EXIT);
689
690 app_conf = appConf[0];
691
692 if (opts.ignoreWatch) {
693 flagWatch.handleFolders(opts.ignoreWatch, ignoreFileArray);
694 if (app_conf.ignore_watch) {
695 app_conf.ignore_watch = ignoreFileArray;
696 }
697 }
698
699 if (opts.watchDelay) {
700 if (typeof opts.watchDelay === "string" && opts.watchDelay.indexOf("ms") !== -1)
701 app_conf.watch_delay = parseInt(opts.watchDelay);
702 else {
703 app_conf.watch_delay = parseFloat(opts.watchDelay) * 1000;
704 }
705 }
706
707 var mas = [];
708 if(typeof opts.ext != 'undefined')
709 hf.make_available_extension(opts, mas); // for -e flag
710 mas.length > 0 ? app_conf.ignore_watch = mas : 0;
711
712 /**
713 * If -w option, write configuration to configuration.json file
714 */
715 if (app_conf.write) {
716 var dst_path = path.join(process.env.PWD || process.cwd(), app_conf.name + '-pm2.json');
717 Common.printOut(conf.PREFIX_MSG + 'Writing configuration to', chalk.blue(dst_path));
718 // pretty JSON
719 try {
720 fs.writeFileSync(dst_path, JSON.stringify(app_conf, null, 2));
721 } catch (e) {
722 console.error(e.stack || e);
723 }
724 }
725
726 series([
727 restartExistingProcessName,
728 restartExistingProcessId,
729 restartExistingProcessPathOrStartNew
730 ], function(err, data) {
731 if (err instanceof Error)
732 return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
733
734 var ret = {};
735
736 data.forEach(function(_dt) {
737 if (_dt !== undefined)
738 ret = _dt;
739 });
740
741 return cb ? cb(null, ret) : that.speedList();
742 });
743
744 /**
745 * If start <app_name> start/restart application
746 */
747 function restartExistingProcessName(cb) {
748 if (!isNaN(script) ||
749 (typeof script === 'string' && script.indexOf('/') != -1) ||
750 (typeof script === 'string' && path.extname(script) !== ''))
751 return cb(null);
752
753 if (script !== 'all') {
754 that.Client.getProcessIdByName(script, function(err, ids) {
755 if (err && cb) return cb(err);
756 if (ids.length > 0) {
757 that._operate('restartProcessId', script, opts, function(err, list) {
758 if (err) return cb(err);
759 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
760 return cb(true, list);
761 });
762 }
763 else return cb(null);
764 });
765 }
766 else {
767 that._operate('restartProcessId', 'all', function(err, list) {
768 if (err) return cb(err);
769 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
770 return cb(true, list);
771 });
772 }
773 }
774
775 function restartExistingProcessId(cb) {
776 if (isNaN(script)) return cb(null);
777
778 that._operate('restartProcessId', script, opts, function(err, list) {
779 if (err) return cb(err);
780 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
781 return cb(true, list);
782 });
783 }
784
785 /**
786 * Restart a process with the same full path
787 * Or start it
788 */
789 function restartExistingProcessPathOrStartNew(cb) {
790 that.Client.executeRemote('getMonitorData', {}, function(err, procs) {
791 if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT);
792
793 var full_path = path.resolve(that.cwd, script);
794 var managed_script = null;
795
796 procs.forEach(function(proc) {
797 if (proc.pm2_env.pm_exec_path == full_path &&
798 proc.pm2_env.name == app_conf.name)
799 managed_script = proc;
800 });
801
802 if (managed_script &&
803 (managed_script.pm2_env.status == conf.STOPPED_STATUS ||
804 managed_script.pm2_env.status == conf.STOPPING_STATUS ||
805 managed_script.pm2_env.status == conf.ERRORED_STATUS)) {
806 // Restart process if stopped
807 var app_name = managed_script.pm2_env.name;
808
809 that._operate('restartProcessId', app_name, opts, function(err, list) {
810 if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT);
811 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
812 return cb(true, list);
813 });
814 return false;
815 }
816 else if (managed_script && !opts.force) {
817 Common.printError(conf.PREFIX_MSG_ERR + 'Script already launched, add -f option to force re-execution');
818 return cb(new Error('Script already launched'));
819 }
820
821 var resolved_paths = null;
822
823 try {
824 resolved_paths = Common.resolveAppAttributes({
825 cwd : that.cwd,
826 pm2_home : that.pm2_home
827 }, app_conf);
828 } catch(e) {
829 Common.printError(e);
830 return cb(Common.retErr(e));
831 }
832
833 Common.printOut(conf.PREFIX_MSG + 'Starting %s in %s (%d instance' + (resolved_paths.instances > 1 ? 's' : '') + ')',
834 resolved_paths.pm_exec_path, resolved_paths.exec_mode, resolved_paths.instances);
835
836 if (!resolved_paths.env) resolved_paths.env = {};
837
838 // Set PM2 HOME in case of child process using PM2 API
839 resolved_paths.env['PM2_HOME'] = that.pm2_home;
840
841 var additional_env = Modularizer.getAdditionalConf(resolved_paths.name);
842 util._extend(resolved_paths.env, additional_env);
843
844 // Is KM linked?
845 resolved_paths.km_link = that.gl_is_km_linked;
846
847 that.Client.executeRemote('prepare', resolved_paths, function(err, data) {
848 if (err) {
849 Common.printError(conf.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err);
850 return cb(Common.retErr(err));
851 }
852
853 Common.printOut(conf.PREFIX_MSG + 'Done.');
854 return cb(true, data);
855 });
856 return false;
857 });
858 }
859 }
860
861 /**
862 * Method to start/restart/reload processes from a JSON file
863 * It will start app not started
864 * Can receive only option to skip applications
865 *
866 * @private
867 */
868 _startJson (file, opts, action, pipe, cb) {
869 var config = {};
870 var appConf = {};
871 var deployConf = {};
872 var apps_info = [];
873 var that = this;
874
875 /**
876 * Get File configuration
877 */
878 if (typeof(cb) === 'undefined' && typeof(pipe) === 'function') {
879 cb = pipe;
880 }
881 if (typeof(file) === 'object') {
882 config = file;
883 } else if (pipe === 'pipe') {
884 config = Common.parseConfig(file, 'pipe');
885 } else {
886 var data = null;
887
888 var isAbsolute = path.isAbsolute(file)
889 var file_path = isAbsolute ? file : path.join(that.cwd, file);
890
891 debug('Resolved filepath %s', file_path);
892
893 try {
894 data = fs.readFileSync(file_path);
895 } catch(e) {
896 Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file +' not found');
897 return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
898 }
899
900 try {
901 config = Common.parseConfig(data, file);
902 } catch(e) {
903 Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file + ' malformated');
904 console.error(e);
905 return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
906 }
907 }
908
909 /**
910 * Alias some optional fields
911 */
912 if (config.deploy)
913 deployConf = config.deploy;
914 if (config.apps)
915 appConf = config.apps;
916 else if (config.pm2)
917 appConf = config.pm2;
918 else
919 appConf = config;
920 if (!Array.isArray(appConf))
921 appConf = [appConf];
922
923 if ((appConf = Common.verifyConfs(appConf)) instanceof Error)
924 return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT);
925
926 process.env.PM2_JSON_PROCESSING = true;
927
928 // Get App list
929 var apps_name = [];
930 var proc_list = {};
931
932 // Here we pick only the field we want from the CLI when starting a JSON
933 appConf.forEach(function(app) {
934 if (!app.env) { app.env = {}; }
935 app.env.io = app.io;
936 // --only <app>
937 if (opts.only) {
938 var apps = opts.only.split(/,| /)
939 if (apps.indexOf(app.name) == -1)
940 return false
941 }
942 // --watch
943 if (!app.watch && opts.watch && opts.watch === true)
944 app.watch = true;
945 // --ignore-watch
946 if (!app.ignore_watch && opts.ignore_watch)
947 app.ignore_watch = opts.ignore_watch;
948 if (opts.install_url)
949 app.install_url = opts.install_url
950 // --instances <nb>
951 if (opts.instances && typeof(opts.instances) === 'number')
952 app.instances = opts.instances;
953 // --uid <user>
954 if (opts.uid)
955 app.uid = opts.uid;
956 // --gid <user>
957 if (opts.gid)
958 app.gid = opts.gid;
959 // Specific
960 if (app.append_env_to_name && opts.env)
961 app.name += ('-' + opts.env);
962 if (opts.name_prefix && app.name.indexOf(opts.name_prefix) == -1)
963 app.name = `${opts.name_prefix}:${app.name}`
964
965 app.username = Common.getCurrentUsername();
966 apps_name.push(app.name);
967 });
968
969 that.Client.executeRemote('getMonitorData', {}, function(err, raw_proc_list) {
970 if (err) {
971 Common.printError(err);
972 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
973 }
974
975 /**
976 * Uniquify in memory process list
977 */
978 raw_proc_list.forEach(function(proc) {
979 proc_list[proc.name] = proc;
980 });
981
982 /**
983 * Auto detect application already started
984 * and act on them depending on action
985 */
986 eachLimit(Object.keys(proc_list), conf.CONCURRENT_ACTIONS, function(proc_name, next) {
987 // Skip app name (--only option)
988 if (apps_name.indexOf(proc_name) == -1)
989 return next();
990
991 if (!(action == 'reloadProcessId' ||
992 action == 'softReloadProcessId' ||
993 action == 'restartProcessId'))
994 throw new Error('Wrong action called');
995
996 var apps = appConf.filter(function(app) {
997 return app.name == proc_name;
998 });
999
1000 var envs = apps.map(function(app){
1001 // Binds env_diff to env and returns it.
1002 return Common.mergeEnvironmentVariables(app, opts.env, deployConf);
1003 });
1004
1005 // Assigns own enumerable properties of all
1006 // Notice: if people use the same name in different apps,
1007 // duplicated envs will be overrode by the last one
1008 var env = envs.reduce(function(e1, e2){
1009 return util._extend(e1, e2);
1010 });
1011
1012 // When we are processing JSON, allow to keep the new env by default
1013 env.updateEnv = true;
1014
1015 // Pass `env` option
1016 that._operate(action, proc_name, env, function(err, ret) {
1017 if (err) Common.printError(err);
1018
1019 // For return
1020 apps_info = apps_info.concat(ret);
1021
1022 that.Client.notifyGod(action, proc_name);
1023 // And Remove from array to spy
1024 apps_name.splice(apps_name.indexOf(proc_name), 1);
1025 return next();
1026 });
1027
1028 }, function(err) {
1029 if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1030 if (apps_name.length > 0 && action != 'start')
1031 Common.printOut(conf.PREFIX_MSG_WARNING + 'Applications %s not running, starting...', apps_name.join(', '));
1032 // Start missing apps
1033 return startApps(apps_name, function(err, apps) {
1034 apps_info = apps_info.concat(apps);
1035 return cb ? cb(err, apps_info) : that.speedList(err ? 1 : 0);
1036 });
1037 });
1038 return false;
1039 });
1040
1041 function startApps(app_name_to_start, cb) {
1042 var apps_to_start = [];
1043 var apps_started = [];
1044 var apps_errored = [];
1045
1046 appConf.forEach(function(app, i) {
1047 if (app_name_to_start.indexOf(app.name) != -1) {
1048 apps_to_start.push(appConf[i]);
1049 }
1050 });
1051
1052 eachLimit(apps_to_start, conf.CONCURRENT_ACTIONS, function(app, next) {
1053 if (opts.cwd)
1054 app.cwd = opts.cwd;
1055 if (opts.force_name)
1056 app.name = opts.force_name;
1057 if (opts.started_as_module)
1058 app.pmx_module = true;
1059
1060 var resolved_paths = null;
1061
1062 // hardcode script name to use `serve` feature inside a process file
1063 if (app.script === 'serve') {
1064 app.script = path.resolve(__dirname, 'API', 'Serve.js')
1065 }
1066
1067 try {
1068 resolved_paths = Common.resolveAppAttributes({
1069 cwd : that.cwd,
1070 pm2_home : that.pm2_home
1071 }, app);
1072 } catch (e) {
1073 apps_errored.push(e)
1074 return next();
1075 }
1076
1077 if (!resolved_paths.env) resolved_paths.env = {};
1078
1079 // Set PM2 HOME in case of child process using PM2 API
1080 resolved_paths.env['PM2_HOME'] = that.pm2_home;
1081
1082 var additional_env = Modularizer.getAdditionalConf(resolved_paths.name);
1083 util._extend(resolved_paths.env, additional_env);
1084
1085 resolved_paths.env = Common.mergeEnvironmentVariables(resolved_paths, opts.env, deployConf);
1086
1087 delete resolved_paths.env.current_conf;
1088
1089 // Is KM linked?
1090 resolved_paths.km_link = that.gl_is_km_linked;
1091
1092 if (resolved_paths.wait_ready) {
1093 Common.warn(`App ${resolved_paths.name} has option 'wait_ready' set, waiting for app to be ready...`)
1094 }
1095
1096 that.Client.executeRemote('prepare', resolved_paths, function(err, data) {
1097 if (err) {
1098 Common.printError(conf.PREFIX_MSG_ERR + 'Process failed to launch %s', err.message ? err.message : err);
1099 return next();
1100 }
1101 if (data.length === 0) {
1102 Common.printError(conf.PREFIX_MSG_ERR + 'Process config loading failed', data);
1103 return next();
1104 }
1105
1106 Common.printOut(conf.PREFIX_MSG + 'App [%s] launched (%d instances)', data[0].pm2_env.name, data.length);
1107 apps_started = apps_started.concat(data);
1108 next();
1109 });
1110
1111 }, function(err) {
1112 var final_error = err || apps_errored.length > 0 ? apps_errored : null
1113 return cb ? cb(final_error, apps_started) : that.speedList();
1114 });
1115 return false;
1116 }
1117 }
1118
1119 /**
1120 * Apply a RPC method on the json file
1121 * @private
1122 * @method actionFromJson
1123 * @param {string} action RPC Method
1124 * @param {object} options
1125 * @param {string|object} file file
1126 * @param {string} jsonVia action type (=only 'pipe' ?)
1127 * @param {Function}
1128 */
1129 actionFromJson (action, file, opts, jsonVia, cb) {
1130 var appConf = {};
1131 var ret_processes = [];
1132 var that = this;
1133
1134 //accept programmatic calls
1135 if (typeof file == 'object') {
1136 cb = typeof jsonVia == 'function' ? jsonVia : cb;
1137 appConf = file;
1138 }
1139 else if (jsonVia == 'file') {
1140 var data = null;
1141
1142 try {
1143 data = fs.readFileSync(file);
1144 } catch(e) {
1145 Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file +' not found');
1146 return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
1147 }
1148
1149 try {
1150 appConf = Common.parseConfig(data, file);
1151 } catch(e) {
1152 Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file + ' malformated');
1153 console.error(e);
1154 return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
1155 }
1156 } else if (jsonVia == 'pipe') {
1157 appConf = Common.parseConfig(file, 'pipe');
1158 } else {
1159 Common.printError('Bad call to actionFromJson, jsonVia should be one of file, pipe');
1160 return that.exitCli(conf.ERROR_EXIT);
1161 }
1162
1163 // Backward compatibility
1164 if (appConf.apps)
1165 appConf = appConf.apps;
1166
1167 if (!Array.isArray(appConf))
1168 appConf = [appConf];
1169
1170 if ((appConf = Common.verifyConfs(appConf)) instanceof Error)
1171 return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT);
1172
1173 eachLimit(appConf, conf.CONCURRENT_ACTIONS, function(proc, next1) {
1174 var name = '';
1175 var new_env;
1176
1177 if (!proc.name)
1178 name = path.basename(proc.script);
1179 else
1180 name = proc.name;
1181
1182 if (opts.only && opts.only != name)
1183 return process.nextTick(next1);
1184
1185 if (opts && opts.env)
1186 new_env = Common.mergeEnvironmentVariables(proc, opts.env);
1187 else
1188 new_env = Common.mergeEnvironmentVariables(proc);
1189
1190 that.Client.getProcessIdByName(name, function(err, ids) {
1191 if (err) {
1192 Common.printError(err);
1193 return next1();
1194 }
1195 if (!ids) return next1();
1196
1197 eachLimit(ids, conf.CONCURRENT_ACTIONS, function(id, next2) {
1198 var opts = {};
1199
1200 //stopProcessId could accept options to?
1201 if (action == 'restartProcessId') {
1202 opts = {id : id, env : new_env};
1203 } else {
1204 opts = id;
1205 }
1206
1207 that.Client.executeRemote(action, opts, function(err, res) {
1208 ret_processes.push(res);
1209 if (err) {
1210 Common.printError(err);
1211 return next2();
1212 }
1213
1214 if (action == 'restartProcessId') {
1215 that.Client.notifyGod('restart', id);
1216 } else if (action == 'deleteProcessId') {
1217 that.Client.notifyGod('delete', id);
1218 } else if (action == 'stopProcessId') {
1219 that.Client.notifyGod('stop', id);
1220 }
1221
1222 Common.printOut(conf.PREFIX_MSG + '[%s](%d) \u2713', name, id);
1223 return next2();
1224 });
1225 }, function(err) {
1226 return next1(null, ret_processes);
1227 });
1228 });
1229 }, function(err) {
1230 if (cb) return cb(null, ret_processes);
1231 else return that.speedList();
1232 });
1233 }
1234
1235
1236 /**
1237 * Main function to operate with PM2 daemon
1238 *
1239 * @param {String} action_name Name of action (restartProcessId, deleteProcessId, stopProcessId)
1240 * @param {String} process_name can be 'all', a id integer or process name
1241 * @param {Object} envs object with CLI options / environment
1242 */
1243 _operate (action_name, process_name, envs, cb) {
1244 var that = this;
1245 var update_env = false;
1246 var ret = [];
1247
1248 // Make sure all options exist
1249 if (!envs)
1250 envs = {};
1251
1252 if (typeof(envs) == 'function'){
1253 cb = envs;
1254 envs = {};
1255 }
1256
1257 // Set via env.update (JSON processing)
1258 if (envs.updateEnv === true)
1259 update_env = true;
1260
1261 var concurrent_actions = envs.parallel || conf.CONCURRENT_ACTIONS;
1262
1263 if (!process.env.PM2_JSON_PROCESSING || envs.commands) {
1264 envs = that._handleAttributeUpdate(envs);
1265 }
1266
1267 /**
1268 * Set current updated configuration if not passed
1269 */
1270 if (!envs.current_conf) {
1271 var _conf = fclone(envs);
1272 envs = {
1273 current_conf : _conf
1274 }
1275
1276 // Is KM linked?
1277 envs.current_conf.km_link = that.gl_is_km_linked;
1278 }
1279
1280 /**
1281 * Operate action on specific process id
1282 */
1283 function processIds(ids, cb) {
1284 Common.printOut(conf.PREFIX_MSG + 'Applying action %s on app [%s](ids: %s)', action_name, process_name, ids);
1285
1286 if (ids.length <= 2)
1287 concurrent_actions = 1;
1288
1289 if (action_name == 'deleteProcessId')
1290 concurrent_actions = 10;
1291
1292 eachLimit(ids, concurrent_actions, function(id, next) {
1293 var opts;
1294
1295 // These functions need extra param to be passed
1296 if (action_name == 'restartProcessId' ||
1297 action_name == 'reloadProcessId' ||
1298 action_name == 'softReloadProcessId') {
1299 var new_env = {};
1300
1301 if (update_env === true) {
1302 if (conf.PM2_PROGRAMMATIC == true)
1303 new_env = Common.safeExtend({}, process.env);
1304 else
1305 new_env = util._extend({}, process.env);
1306
1307 Object.keys(envs).forEach(function(k) {
1308 new_env[k] = envs[k];
1309 });
1310 }
1311 else {
1312 new_env = envs;
1313 }
1314
1315 opts = {
1316 id : id,
1317 env : new_env
1318 };
1319 }
1320 else {
1321 opts = id;
1322 }
1323
1324 that.Client.executeRemote(action_name, opts, function(err, res) {
1325 if (err) {
1326 Common.printError(conf.PREFIX_MSG_ERR + 'Process %s not found', id);
1327 return next('Process not found');
1328 }
1329
1330 if (action_name == 'restartProcessId') {
1331 that.Client.notifyGod('restart', id);
1332 } else if (action_name == 'deleteProcessId') {
1333 that.Client.notifyGod('delete', id);
1334 } else if (action_name == 'stopProcessId') {
1335 that.Client.notifyGod('stop', id);
1336 } else if (action_name == 'reloadProcessId') {
1337 that.Client.notifyGod('reload', id);
1338 } else if (action_name == 'softReloadProcessId') {
1339 that.Client.notifyGod('graceful reload', id);
1340 }
1341
1342 if (!Array.isArray(res))
1343 res = [res];
1344
1345 // Filter return
1346 res.forEach(function(proc) {
1347 Common.printOut(conf.PREFIX_MSG + '[%s](%d) \u2713', proc.pm2_env ? proc.pm2_env.name : process_name, id);
1348
1349 if (!proc.pm2_env) return false;
1350
1351 ret.push({
1352 name : proc.pm2_env.name,
1353 pm_id : proc.pm2_env.pm_id,
1354 status : proc.pm2_env.status,
1355 restart_time : proc.pm2_env.restart_time,
1356 pm2_env : {
1357 name : proc.pm2_env.name,
1358 pm_id : proc.pm2_env.pm_id,
1359 status : proc.pm2_env.status,
1360 restart_time : proc.pm2_env.restart_time,
1361 env : proc.pm2_env.env
1362 }
1363 });
1364 });
1365
1366 return next();
1367 });
1368 }, function(err) {
1369 if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1370 return cb ? cb(null, ret) : that.speedList();
1371 });
1372 }
1373
1374 if (process_name == 'all') {
1375 // When using shortcuts like 'all', do not delete modules
1376 var fn
1377
1378 if (process.env.PM2_STATUS == 'stopping')
1379 that.Client.getAllProcessId(function(err, ids) {
1380 reoperate(err, ids)
1381 });
1382 else
1383 that.Client.getAllProcessIdWithoutModules(function(err, ids) {
1384 reoperate(err, ids)
1385 });
1386
1387 function reoperate(err, ids) {
1388 if (err) {
1389 Common.printError(err);
1390 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1391 }
1392 if (!ids || ids.length === 0) {
1393 Common.printError(conf.PREFIX_MSG_WARNING + 'No process found');
1394 return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT);
1395 }
1396 return processIds(ids, cb);
1397 }
1398 }
1399 // operate using regex
1400 else if (isNaN(process_name) && process_name[0] === '/' && process_name[process_name.length - 1] === '/') {
1401 var regex = new RegExp(process_name.replace(/\//g, ''));
1402
1403 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
1404 if (err) {
1405 Common.printError('Error retrieving process list: ' + err);
1406 return cb(err);
1407 }
1408 var found_proc = [];
1409 list.forEach(function(proc) {
1410 if (regex.test(proc.pm2_env.name)) {
1411 found_proc.push(proc.pm_id);
1412 }
1413 });
1414
1415 if (found_proc.length === 0) {
1416 Common.printError(conf.PREFIX_MSG_WARNING + 'No process found');
1417 return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT);
1418 }
1419
1420 return processIds(found_proc, cb);
1421 });
1422 }
1423 else if (isNaN(process_name)) {
1424 /**
1425 * We can not stop or delete a module but we can restart it
1426 * to refresh configuration variable
1427 */
1428 var allow_module_restart = action_name == 'restartProcessId' ? true : false;
1429
1430 that.Client.getProcessIdByName(process_name, allow_module_restart, function(err, ids) {
1431 if (err) {
1432 Common.printError(err);
1433 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1434 }
1435 if (!ids || ids.length === 0) {
1436 Common.printError(conf.PREFIX_MSG_ERR + 'Process %s not found', process_name);
1437 return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT);
1438 }
1439
1440 /**
1441 * Determine if the process to restart is a module
1442 * if yes load configuration variables and merge with the current environment
1443 */
1444 var additional_env = Modularizer.getAdditionalConf(process_name);
1445 util._extend(envs, additional_env);
1446
1447 return processIds(ids, cb);
1448 });
1449 } else {
1450 // Check if application name as number is an app name
1451 that.Client.getProcessIdByName(process_name, function(err, ids) {
1452 if (ids.length > 0)
1453 return processIds(ids, cb);
1454 // Else operate on pm id
1455 return processIds([process_name], cb);
1456 });
1457 }
1458 }
1459
1460 /**
1461 * Converts CamelCase Commander.js arguments
1462 * to Underscore
1463 * (nodeArgs -> node_args)
1464 */
1465 _handleAttributeUpdate (opts) {
1466 var conf = Config.filterOptions(opts);
1467 var that = this;
1468
1469 if (typeof(conf.name) != 'string')
1470 delete conf.name;
1471
1472 var argsIndex = 0;
1473 if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0) {
1474 conf.args = opts.rawArgs.slice(argsIndex + 1);
1475 }
1476
1477 var appConf = Common.verifyConfs(conf)[0];
1478
1479 if (appConf instanceof Error) {
1480 Common.printError('Error while transforming CamelCase args to underscore');
1481 return appConf;
1482 }
1483
1484 if (argsIndex == -1)
1485 delete appConf.args;
1486 if (appConf.name == 'undefined')
1487 delete appConf.name;
1488
1489 delete appConf.exec_mode;
1490
1491 if (util.isArray(appConf.watch) && appConf.watch.length === 0) {
1492 if (!~opts.rawArgs.indexOf('--watch'))
1493 delete appConf.watch
1494 }
1495
1496 // Options set via environment variables
1497 if (process.env.PM2_DEEP_MONITORING)
1498 appConf.deep_monitoring = true;
1499
1500 // Force deletion of defaults values set by commander
1501 // to avoid overriding specified configuration by user
1502 if (appConf.treekill === true)
1503 delete appConf.treekill;
1504 if (appConf.pmx === true)
1505 delete appConf.pmx;
1506 if (appConf.vizion === true)
1507 delete appConf.vizion;
1508 if (appConf.automation === true)
1509 delete appConf.automation;
1510 if (appConf.autorestart === true)
1511 delete appConf.autorestart;
1512
1513 return appConf;
1514 }
1515
1516 getProcessIdByName (name, cb) {
1517 var that = this;
1518
1519 this.Client.getProcessIdByName(name, function(err, id) {
1520 if (err) {
1521 Common.printError(err);
1522 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1523 }
1524 console.log(id);
1525 return cb ? cb(null, id) : that.exitCli(conf.SUCCESS_EXIT);
1526 });
1527 }
1528
1529 /**
1530 * Description
1531 * @method jlist
1532 * @param {} debug
1533 * @return
1534 */
1535 jlist (debug) {
1536 var that = this;
1537
1538 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
1539 if (err) {
1540 Common.printError(err);
1541 that.exitCli(conf.ERROR_EXIT);
1542 }
1543
1544 if (debug) {
1545 process.stdout.write(util.inspect(list, false, null, false));
1546 }
1547 else {
1548 process.stdout.write(JSON.stringify(list));
1549 }
1550
1551 that.exitCli(conf.SUCCESS_EXIT);
1552
1553 });
1554 }
1555
1556 /**
1557 * Description
1558 * @method speedList
1559 * @return
1560 */
1561 speedList (code, list) {
1562 var that = this;
1563
1564 // Do nothing if PM2 called programmatically and not called from CLI (also in exitCli)
1565 if (conf.PM2_PROGRAMMATIC && process.env.PM2_USAGE != 'CLI') return false;
1566
1567 if (list) {
1568 return doList(null, list)
1569 }
1570
1571 that.Client.executeRemote('getMonitorData', {}, doList);
1572
1573 function doList(err, list) {
1574 if (err) {
1575 if (that.gl_retry == 0) {
1576 that.gl_retry += 1;
1577 return setTimeout(that.speedList.bind(that), 1400);
1578 }
1579 console.error('Error retrieving process list: %s.\nA process seems to be on infinite loop, retry in 5 seconds',err);
1580 return that.exitCli(conf.ERROR_EXIT);
1581 }
1582 if (process.stdout.isTTY === false) {
1583 UX.miniDisplay(list);
1584 }
1585 else if (commander.miniList && !commander.silent)
1586 UX.miniDisplay(list);
1587 else if (!commander.silent) {
1588 if (that.gl_interact_infos) {
1589 Common.printOut('%s PM2+ activated | Web: %s | Server: %s | Conn: %s',
1590 chalk.green.bold('⇆'),
1591 chalk.bold('https://app.pm2.io/#/r/' + that.gl_interact_infos.public_key),
1592 chalk.bold(that.gl_interact_infos.machine_name),
1593 that.gl_interact_infos.agent_transport_websocket === 'true' ? 'Websocket' : 'Axon');
1594 if (that.gl_interact_infos.info_node != 'https://root.keymetrics.io') {
1595 Common.printOut(`PM2+ on-premise link: ${that.gl_interact_infos.info_node}`)
1596 }
1597 }
1598 UX.dispAsTable(list, commander);
1599 Common.printOut(chalk.white.italic(' Use `pm2 show <id|name>` to get more details about an app'));
1600 }
1601
1602 if (that.Client.daemon_mode == false) {
1603 Common.printOut('[--no-daemon] Continue to stream logs');
1604 Common.printOut('[--no-daemon] Exit on target PM2 exit pid=' + fs.readFileSync(conf.PM2_PID_FILE_PATH).toString());
1605 global._auto_exit = true;
1606 return that.streamLogs('all', 0, false, 'HH:mm:ss', false);
1607 }
1608 else if (commander.attach === true) {
1609 return that.streamLogs('all', 0, false, null, false);
1610 }
1611 else {
1612 return that.exitCli(code ? code : conf.SUCCESS_EXIT);
1613 }
1614 }
1615 }
1616
1617 /**
1618 * Scale up/down a process
1619 * @method scale
1620 */
1621 scale (app_name, number, cb) {
1622 var that = this;
1623
1624 function addProcs(proc, value, cb) {
1625 (function ex(proc, number) {
1626 if (number-- === 0) return cb();
1627 Common.printOut(conf.PREFIX_MSG + 'Scaling up application');
1628 that.Client.executeRemote('duplicateProcessId', proc.pm2_env.pm_id, ex.bind(this, proc, number));
1629 })(proc, number);
1630 }
1631
1632 function rmProcs(procs, value, cb) {
1633 var i = 0;
1634
1635 (function ex(procs, number) {
1636 if (number++ === 0) return cb();
1637 that._operate('deleteProcessId', procs[i++].pm2_env.pm_id, ex.bind(this, procs, number));
1638 })(procs, number);
1639 }
1640
1641 function end() {
1642 return cb ? cb(null, {success:true}) : that.speedList();
1643 }
1644
1645 this.Client.getProcessByName(app_name, function(err, procs) {
1646 if (err) {
1647 Common.printError(err);
1648 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1649 }
1650
1651 if (!procs || procs.length === 0) {
1652 Common.printError(conf.PREFIX_MSG_ERR + 'Application %s not found', app_name);
1653 return cb ? cb(new Error('App not found')) : that.exitCli(conf.ERROR_EXIT);
1654 }
1655
1656 var proc_number = procs.length;
1657
1658 if (typeof(number) === 'string' && number.indexOf('+') >= 0) {
1659 number = parseInt(number, 10);
1660 return addProcs(procs[0], number, end);
1661 }
1662 else if (typeof(number) === 'string' && number.indexOf('-') >= 0) {
1663 number = parseInt(number, 10);
1664 return rmProcs(procs[0], number, end);
1665 }
1666 else {
1667 number = parseInt(number, 10);
1668 number = number - proc_number;
1669
1670 if (number < 0)
1671 return rmProcs(procs, number, end);
1672 else if (number > 0)
1673 return addProcs(procs[0], number, end);
1674 else {
1675 Common.printError(conf.PREFIX_MSG_ERR + 'Nothing to do');
1676 return cb ? cb(new Error('Same process number')) : that.exitCli(conf.ERROR_EXIT);
1677 }
1678 }
1679 });
1680 }
1681
1682 /**
1683 * Description
1684 * @method describeProcess
1685 * @param {} pm2_id
1686 * @return
1687 */
1688 describe (pm2_id, cb) {
1689 var that = this;
1690
1691 var found_proc = [];
1692
1693 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
1694 if (err) {
1695 Common.printError('Error retrieving process list: ' + err);
1696 that.exitCli(conf.ERROR_EXIT);
1697 }
1698
1699 list.forEach(function(proc) {
1700 if ((!isNaN(pm2_id) && proc.pm_id == pm2_id) ||
1701 (typeof(pm2_id) === 'string' && proc.name == pm2_id)) {
1702 found_proc.push(proc);
1703 }
1704 });
1705
1706 if (found_proc.length === 0) {
1707 Common.printError(conf.PREFIX_MSG_WARNING + '%s doesn\'t exist', pm2_id);
1708 return cb ? cb(null, []) : that.exitCli(conf.ERROR_EXIT);
1709 }
1710
1711 if (!cb) {
1712 found_proc.forEach(function(proc) {
1713 UX.describeTable(proc);
1714 });
1715 }
1716
1717 return cb ? cb(null, found_proc) : that.exitCli(conf.SUCCESS_EXIT);
1718 });
1719 }
1720
1721 /**
1722 * API method to perform a deep update of PM2
1723 * @method deepUpdate
1724 */
1725 deepUpdate (cb) {
1726 var that = this;
1727
1728 Common.printOut(conf.PREFIX_MSG + 'Updating PM2...');
1729
1730 var exec = require('shelljs').exec;
1731 var child = exec("npm i -g pm2@latest; pm2 update", {async : true});
1732
1733 child.stdout.on('end', function() {
1734 Common.printOut(conf.PREFIX_MSG + 'PM2 successfully updated');
1735 cb ? cb(null, {success:true}) : that.exitCli(conf.SUCCESS_EXIT);
1736 });
1737 }
1738};
1739
1740
1741//////////////////////////
1742// Load all API methods //
1743//////////////////////////
1744
1745require('./API/Extra.js')(API);
1746require('./API/Deploy.js')(API);
1747require('./API/Modules/index.js')(API);
1748
1749require('./API/pm2-plus/link.js')(API);
1750require('./API/pm2-plus/process-selector.js')(API);
1751require('./API/pm2-plus/helpers.js')(API);
1752
1753require('./API/Configuration.js')(API);
1754require('./API/Version.js')(API);
1755require('./API/Startup.js')(API);
1756require('./API/LogManagement.js')(API);
1757require('./API/Containerizer.js')(API);
1758
1759
1760module.exports = API;