UNPKG

60.3 kBJavaScriptView Raw
1/**
2 * Copyright 2013-2021 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
18var DockerMgmt = require('./API/ExtraMgmt/Docker.js')
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/UX');
27var pkg = require('../package.json');
28var hf = require('./API/Modules/flagExt.js');
29var Configuration = require('./Configuration.js');
30const semver = require('semver')
31const sexec = require('./tools/sexec.js')
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.pm2_configuration = 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 that.launchSysMonitoring(() => {})
185 // If new pm2 instance has been popped
186 // Lauch all modules
187 that.launchAll(that, function(err_mod) {
188 return cb(err, meta);
189 });
190 });
191 }
192
193 /**
194 * Usefull when custom PM2 created with independent flag set to true
195 * This will cleanup the newly created instance
196 * by removing folder, killing PM2 and so on
197 *
198 * @param {Function} cb callback once cleanup is successfull
199 */
200 destroy (cb) {
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 sexec(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', (err, procs) => {
335 return cb ? cb(err, procs) : this.speedList()
336 })
337 }
338 else {
339 that._startScript(cmd, opts, (err, procs) => {
340 return cb ? cb(err, procs) : this.speedList(0)
341 })
342 }
343 }
344
345 /**
346 * Reset process counters
347 *
348 * @method resetMetaProcess
349 */
350 reset (process_name, cb) {
351 var that = this;
352
353 function processIds(ids, cb) {
354 eachLimit(ids, conf.CONCURRENT_ACTIONS, function(id, next) {
355 that.Client.executeRemote('resetMetaProcessId', id, function(err, res) {
356 if (err) console.error(err);
357 Common.printOut(conf.PREFIX_MSG + 'Resetting meta for process id %d', id);
358 return next();
359 });
360 }, function(err) {
361 if (err) return cb(Common.retErr(err));
362 return cb ? cb(null, {success:true}) : that.speedList();
363 });
364 }
365
366 if (process_name == 'all') {
367 that.Client.getAllProcessId(function(err, ids) {
368 if (err) {
369 Common.printError(err);
370 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
371 }
372 return processIds(ids, cb);
373 });
374 }
375 else if (isNaN(process_name)) {
376 that.Client.getProcessIdByName(process_name, function(err, ids) {
377 if (err) {
378 Common.printError(err);
379 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
380 }
381 if (ids.length === 0) {
382 Common.printError('Unknown process name');
383 return cb ? cb(new Error('Unknown process name')) : that.exitCli(conf.ERROR_EXIT);
384 }
385 return processIds(ids, cb);
386 });
387 } else {
388 processIds([process_name], cb);
389 }
390 }
391
392 /**
393 * Update daemonized PM2 Daemon
394 *
395 * @param {Function} cb callback when pm2 has been upgraded
396 */
397 update (cb) {
398 var that = this;
399
400 Common.printOut('Be sure to have the latest version by doing `npm install pm2@latest -g` before doing this procedure.');
401
402 // Dump PM2 processes
403 that.Client.executeRemote('notifyKillPM2', {}, function() {});
404
405 that.getVersion(function(err, new_version) {
406 // If not linked to PM2 plus, and update PM2 to latest, display motd.update
407 if (!that.gl_is_km_linked && !err && (pkg.version != new_version)) {
408 var dt = fs.readFileSync(path.join(__dirname, that._conf.PM2_UPDATE));
409 console.log(dt.toString());
410 }
411
412 that.dump(function(err) {
413 that.killDaemon(function() {
414 that.Client.launchDaemon({interactor:false}, function(err, child) {
415 that.Client.launchRPC(function() {
416 that.resurrect(function() {
417 Common.printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated'));
418 that.launchSysMonitoring(() => {})
419 that.launchAll(that, function() {
420 KMDaemon.launchAndInteract(that._conf, {
421 pm2_version: pkg.version
422 }, function(err, data, interactor_proc) {
423 })
424 setTimeout(() => {
425 return cb ? cb(null, {success:true}) : that.speedList();
426 }, 250)
427 });
428 });
429 });
430 });
431 });
432 });
433 });
434
435 return false;
436 }
437
438 /**
439 * Reload an application
440 *
441 * @param {String} process_name Application Name or All
442 * @param {Object} opts Options
443 * @param {Function} cb Callback
444 */
445 reload (process_name, opts, cb) {
446 var that = this;
447
448 if (typeof(opts) == "function") {
449 cb = opts;
450 opts = {};
451 }
452
453 var delay = Common.lockReload();
454 if (delay > 0 && opts.force != true) {
455 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');
456 return cb ? cb(new Error('Reload in progress')) : that.exitCli(conf.ERROR_EXIT);
457 }
458
459 if (Common.isConfigFile(process_name))
460 that._startJson(process_name, opts, 'reloadProcessId', function(err, apps) {
461 Common.unlockReload();
462 if (err)
463 return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
464 return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT);
465 });
466 else {
467 if (opts && opts.env) {
468 var err = 'Using --env [env] without passing the ecosystem.config.js does not work'
469 Common.err(err);
470 Common.unlockReload();
471 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
472 }
473
474 if (opts && !opts.updateEnv)
475 Common.printOut(IMMUTABLE_MSG);
476
477 that._operate('reloadProcessId', process_name, opts, function(err, apps) {
478 Common.unlockReload();
479
480 if (err)
481 return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
482 return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT);
483 });
484 }
485 }
486
487 /**
488 * Restart process
489 *
490 * @param {String} cmd Application Name / Process id / JSON application file / 'all'
491 * @param {Object} opts Extra options to be updated
492 * @param {Function} cb Callback
493 */
494 restart (cmd, opts, cb) {
495 if (typeof(opts) == "function") {
496 cb = opts;
497 opts = {};
498 }
499 var that = this;
500
501 if (typeof(cmd) === 'number')
502 cmd = cmd.toString();
503
504 if (cmd == "-") {
505 // Restart from PIPED JSON
506 process.stdin.resume();
507 process.stdin.setEncoding('utf8');
508 process.stdin.on('data', function (param) {
509 process.stdin.pause();
510 that.actionFromJson('restartProcessId', param, opts, 'pipe', cb);
511 });
512 }
513 else if (Common.isConfigFile(cmd) || typeof(cmd) === 'object')
514 that._startJson(cmd, opts, 'restartProcessId', cb);
515 else {
516 if (opts && opts.env) {
517 var err = 'Using --env [env] without passing the ecosystem.config.js does not work'
518 Common.err(err);
519 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
520 }
521 if (opts && !opts.updateEnv)
522 Common.printOut(IMMUTABLE_MSG);
523 that._operate('restartProcessId', cmd, opts, cb);
524 }
525 }
526
527 /**
528 * Delete process
529 *
530 * @param {String} process_name Application Name / Process id / Application file / 'all'
531 * @param {Function} cb Callback
532 */
533 delete (process_name, jsonVia, cb) {
534 var that = this;
535
536 if (typeof(jsonVia) === "function") {
537 cb = jsonVia;
538 jsonVia = null;
539 }
540
541 if (typeof(process_name) === "number") {
542 process_name = process_name.toString();
543 }
544
545 if (jsonVia == 'pipe')
546 return that.actionFromJson('deleteProcessId', process_name, commander, 'pipe', (err, procs) => {
547 return cb ? cb(err, procs) : this.speedList()
548 });
549 if (Common.isConfigFile(process_name))
550 return that.actionFromJson('deleteProcessId', process_name, commander, 'file', (err, procs) => {
551 return cb ? cb(err, procs) : this.speedList()
552 });
553 else {
554 that._operate('deleteProcessId', process_name, (err, procs) => {
555 return cb ? cb(err, procs) : this.speedList()
556 });
557 }
558 }
559
560 /**
561 * Stop process
562 *
563 * @param {String} process_name Application Name / Process id / Application file / 'all'
564 * @param {Function} cb Callback
565 */
566 stop (process_name, cb) {
567 var that = this;
568
569 if (typeof(process_name) === 'number')
570 process_name = process_name.toString();
571
572 if (process_name == "-") {
573 process.stdin.resume();
574 process.stdin.setEncoding('utf8');
575 process.stdin.on('data', function (param) {
576 process.stdin.pause();
577 that.actionFromJson('stopProcessId', param, commander, 'pipe', (err, procs) => {
578 return cb ? cb(err, procs) : this.speedList()
579 })
580 });
581 }
582 else if (Common.isConfigFile(process_name))
583 that.actionFromJson('stopProcessId', process_name, commander, 'file', (err, procs) => {
584 return cb ? cb(err, procs) : this.speedList()
585 });
586 else
587 that._operate('stopProcessId', process_name, (err, procs) => {
588 return cb ? cb(err, procs) : this.speedList()
589 });
590 }
591
592 /**
593 * Get list of all processes managed
594 *
595 * @param {Function} cb Callback
596 */
597 list (opts, cb) {
598 var that = this;
599
600 if (typeof(opts) == 'function') {
601 cb = opts;
602 opts = null;
603 }
604
605 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
606 if (err) {
607 Common.printError(err);
608 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
609 }
610
611 if (opts && opts.rawArgs && opts.rawArgs.indexOf('--watch') > -1) {
612 var dayjs = require('dayjs');
613 function show() {
614 process.stdout.write('\x1b[2J');
615 process.stdout.write('\x1b[0f');
616 console.log('Last refresh: ', dayjs().format());
617 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
618 UX.list(list, null);
619 });
620 }
621
622 show();
623 setInterval(show, 900);
624 return false;
625 }
626
627 return cb ? cb(null, list) : that.speedList(null);
628 });
629 }
630
631 /**
632 * Kill Daemon
633 *
634 * @param {Function} cb Callback
635 */
636 killDaemon (cb) {
637 process.env.PM2_STATUS = 'stopping'
638
639 var that = this;
640
641 that.Client.executeRemote('notifyKillPM2', {}, function() {});
642
643 that._operate('deleteProcessId', 'all', function(err, list) {
644 Common.printOut(conf.PREFIX_MSG + '[v] All Applications Stopped');
645 process.env.PM2_SILENT = 'false';
646
647 that.killAgent(function(err, data) {
648 if (!err) {
649 Common.printOut(conf.PREFIX_MSG + '[v] Agent Stopped');
650 }
651
652 that.Client.killDaemon(function(err, res) {
653 if (err) Common.printError(err);
654 Common.printOut(conf.PREFIX_MSG + '[v] PM2 Daemon Stopped');
655 return cb ? cb(err, res) : that.exitCli(conf.SUCCESS_EXIT);
656 });
657
658 });
659 })
660 }
661
662 kill (cb) {
663 this.killDaemon(cb);
664 }
665
666 /////////////////////
667 // Private methods //
668 /////////////////////
669
670 /**
671 * Method to START / RESTART a script
672 *
673 * @private
674 * @param {string} script script name (will be resolved according to location)
675 */
676 _startScript (script, opts, cb) {
677 if (typeof opts == "function") {
678 cb = opts;
679 opts = {};
680 }
681 var that = this;
682
683 /**
684 * Commander.js tricks
685 */
686 var app_conf = Config.filterOptions(opts);
687 var appConf = {};
688
689 if (typeof app_conf.name == 'function')
690 delete app_conf.name;
691
692 delete app_conf.args;
693
694 // Retrieve arguments via -- <args>
695 var argsIndex;
696
697 if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0)
698 app_conf.args = opts.rawArgs.slice(argsIndex + 1);
699 else if (opts.scriptArgs)
700 app_conf.args = opts.scriptArgs;
701
702 app_conf.script = script;
703 if(!app_conf.namespace)
704 app_conf.namespace = 'default';
705
706 if ((appConf = Common.verifyConfs(app_conf)) instanceof Error) {
707 Common.err(appConf)
708 return cb ? cb(Common.retErr(appConf)) : that.exitCli(conf.ERROR_EXIT);
709 }
710
711 app_conf = appConf[0];
712
713 if (opts.watchDelay) {
714 if (typeof opts.watchDelay === "string" && opts.watchDelay.indexOf("ms") !== -1)
715 app_conf.watch_delay = parseInt(opts.watchDelay);
716 else {
717 app_conf.watch_delay = parseFloat(opts.watchDelay) * 1000;
718 }
719 }
720
721 var mas = [];
722 if(typeof opts.ext != 'undefined')
723 hf.make_available_extension(opts, mas); // for -e flag
724 mas.length > 0 ? app_conf.ignore_watch = mas : 0;
725
726 /**
727 * If -w option, write configuration to configuration.json file
728 */
729 if (app_conf.write) {
730 var dst_path = path.join(process.env.PWD || process.cwd(), app_conf.name + '-pm2.json');
731 Common.printOut(conf.PREFIX_MSG + 'Writing configuration to', chalk.blue(dst_path));
732 // pretty JSON
733 try {
734 fs.writeFileSync(dst_path, JSON.stringify(app_conf, null, 2));
735 } catch (e) {
736 console.error(e.stack || e);
737 }
738 }
739
740 series([
741 restartExistingProcessName,
742 restartExistingNameSpace,
743 restartExistingProcessId,
744 restartExistingProcessPathOrStartNew
745 ], function(err, data) {
746 if (err instanceof Error)
747 return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT);
748
749 var ret = {};
750
751 data.forEach(function(_dt) {
752 if (_dt !== undefined)
753 ret = _dt;
754 });
755
756 return cb ? cb(null, ret) : that.speedList();
757 });
758
759 /**
760 * If start <app_name> start/restart application
761 */
762 function restartExistingProcessName(cb) {
763 if (!isNaN(script) ||
764 (typeof script === 'string' && script.indexOf('/') != -1) ||
765 (typeof script === 'string' && path.extname(script) !== ''))
766 return cb(null);
767
768 that.Client.getProcessIdByName(script, function(err, ids) {
769 if (err && cb) return cb(err);
770 if (ids.length > 0) {
771 that._operate('restartProcessId', script, opts, function(err, list) {
772 if (err) return cb(err);
773 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
774 return cb(true, list);
775 });
776 }
777 else return cb(null);
778 });
779 }
780
781 /**
782 * If start <namespace> start/restart namespace
783 */
784 function restartExistingNameSpace(cb) {
785 if (!isNaN(script) ||
786 (typeof script === 'string' && script.indexOf('/') != -1) ||
787 (typeof script === 'string' && path.extname(script) !== ''))
788 return cb(null);
789
790 if (script !== 'all') {
791 that.Client.getProcessIdsByNamespace(script, function (err, ids) {
792 if (err && cb) return cb(err);
793 if (ids.length > 0) {
794 that._operate('restartProcessId', script, opts, function (err, list) {
795 if (err) return cb(err);
796 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
797 return cb(true, list);
798 });
799 }
800 else return cb(null);
801 });
802 }
803 else {
804 that._operate('restartProcessId', 'all', function(err, list) {
805 if (err) return cb(err);
806 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
807 return cb(true, list);
808 });
809 }
810 }
811
812 function restartExistingProcessId(cb) {
813 if (isNaN(script)) return cb(null);
814
815 that._operate('restartProcessId', script, opts, function(err, list) {
816 if (err) return cb(err);
817 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
818 return cb(true, list);
819 });
820 }
821
822 /**
823 * Restart a process with the same full path
824 * Or start it
825 */
826 function restartExistingProcessPathOrStartNew(cb) {
827 that.Client.executeRemote('getMonitorData', {}, function(err, procs) {
828 if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT);
829
830 var full_path = path.resolve(that.cwd, script);
831 var managed_script = null;
832
833 procs.forEach(function(proc) {
834 if (proc.pm2_env.pm_exec_path == full_path &&
835 proc.pm2_env.name == app_conf.name)
836 managed_script = proc;
837 });
838
839 if (managed_script &&
840 (managed_script.pm2_env.status == conf.STOPPED_STATUS ||
841 managed_script.pm2_env.status == conf.STOPPING_STATUS ||
842 managed_script.pm2_env.status == conf.ERRORED_STATUS)) {
843 // Restart process if stopped
844 var app_name = managed_script.pm2_env.name;
845
846 that._operate('restartProcessId', app_name, opts, function(err, list) {
847 if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT);
848 Common.printOut(conf.PREFIX_MSG + 'Process successfully started');
849 return cb(true, list);
850 });
851 return false;
852 }
853 else if (managed_script && !opts.force) {
854 Common.err('Script already launched, add -f option to force re-execution');
855 return cb(new Error('Script already launched'));
856 }
857
858 var resolved_paths = null;
859
860 try {
861 resolved_paths = Common.resolveAppAttributes({
862 cwd : that.cwd,
863 pm2_home : that.pm2_home
864 }, app_conf);
865 } catch(e) {
866 Common.err(e.message);
867 return cb(Common.retErr(e));
868 }
869
870 Common.printOut(conf.PREFIX_MSG + 'Starting %s in %s (%d instance' + (resolved_paths.instances > 1 ? 's' : '') + ')',
871 resolved_paths.pm_exec_path, resolved_paths.exec_mode, resolved_paths.instances);
872
873 if (!resolved_paths.env) resolved_paths.env = {};
874
875 // Set PM2 HOME in case of child process using PM2 API
876 resolved_paths.env['PM2_HOME'] = that.pm2_home;
877
878 var additional_env = Modularizer.getAdditionalConf(resolved_paths.name);
879 util._extend(resolved_paths.env, additional_env);
880
881 // Is KM linked?
882 resolved_paths.km_link = that.gl_is_km_linked;
883
884 that.Client.executeRemote('prepare', resolved_paths, function(err, data) {
885 if (err) {
886 Common.printError(conf.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err);
887 return cb(Common.retErr(err));
888 }
889
890 Common.printOut(conf.PREFIX_MSG + 'Done.');
891 return cb(true, data);
892 });
893 return false;
894 });
895 }
896 }
897
898 /**
899 * Method to start/restart/reload processes from a JSON file
900 * It will start app not started
901 * Can receive only option to skip applications
902 *
903 * @private
904 */
905 _startJson (file, opts, action, pipe, cb) {
906 var config = {};
907 var appConf = {};
908 var staticConf = [];
909 var deployConf = {};
910 var apps_info = [];
911 var that = this;
912
913 /**
914 * Get File configuration
915 */
916 if (typeof(cb) === 'undefined' && typeof(pipe) === 'function') {
917 cb = pipe;
918 }
919 if (typeof(file) === 'object') {
920 config = file;
921 } else if (pipe === 'pipe') {
922 config = Common.parseConfig(file, 'pipe');
923 } else {
924 var data = null;
925
926 var isAbsolute = path.isAbsolute(file)
927 var file_path = isAbsolute ? file : path.join(that.cwd, file);
928
929 debug('Resolved filepath %s', file_path);
930
931 try {
932 data = fs.readFileSync(file_path);
933 } catch(e) {
934 Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file +' not found');
935 return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
936 }
937
938 try {
939 config = Common.parseConfig(data, file);
940 } catch(e) {
941 Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file + ' malformated');
942 console.error(e);
943 return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
944 }
945 }
946
947 /**
948 * Alias some optional fields
949 */
950 if (config.deploy)
951 deployConf = config.deploy;
952 if (config.static)
953 staticConf = config.static;
954 if (config.apps)
955 appConf = config.apps;
956 else if (config.pm2)
957 appConf = config.pm2;
958 else
959 appConf = config;
960 if (!Array.isArray(appConf))
961 appConf = [appConf];
962
963 if ((appConf = Common.verifyConfs(appConf)) instanceof Error)
964 return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT);
965
966 process.env.PM2_JSON_PROCESSING = true;
967
968 // Get App list
969 var apps_name = [];
970 var proc_list = {};
971
972 // Add statics to apps
973 staticConf.forEach(function(serve) {
974 appConf.push({
975 name: serve.name ? serve.name : `static-page-server-${serve.port}`,
976 script: path.resolve(__dirname, 'API', 'Serve.js'),
977 env: {
978 PM2_SERVE_PORT: serve.port,
979 PM2_SERVE_HOST: serve.host,
980 PM2_SERVE_PATH: serve.path,
981 PM2_SERVE_SPA: serve.spa,
982 PM2_SERVE_DIRECTORY: serve.directory,
983 PM2_SERVE_BASIC_AUTH: serve.basic_auth !== undefined,
984 PM2_SERVE_BASIC_AUTH_USERNAME: serve.basic_auth ? serve.basic_auth.username : null,
985 PM2_SERVE_BASIC_AUTH_PASSWORD: serve.basic_auth ? serve.basic_auth.password : null,
986 PM2_SERVE_MONITOR: serve.monitor
987 }
988 });
989 });
990
991 // Here we pick only the field we want from the CLI when starting a JSON
992 appConf.forEach(function(app) {
993 if (!app.env) { app.env = {}; }
994 app.env.io = app.io;
995 // --only <app>
996 if (opts.only) {
997 var apps = opts.only.split(/,| /)
998 if (apps.indexOf(app.name) == -1)
999 return false
1000 }
1001 // Namespace
1002 if (!app.namespace) {
1003 if (opts.namespace)
1004 app.namespace = opts.namespace;
1005 else
1006 app.namespace = 'default';
1007 }
1008 // --watch
1009 if (!app.watch && opts.watch && opts.watch === true)
1010 app.watch = true;
1011 // --ignore-watch
1012 if (!app.ignore_watch && opts.ignore_watch)
1013 app.ignore_watch = opts.ignore_watch;
1014 if (opts.install_url)
1015 app.install_url = opts.install_url;
1016 // --instances <nb>
1017 if (opts.instances && typeof(opts.instances) === 'number')
1018 app.instances = opts.instances;
1019 // --uid <user>
1020 if (opts.uid)
1021 app.uid = opts.uid;
1022 // --gid <user>
1023 if (opts.gid)
1024 app.gid = opts.gid;
1025 // Specific
1026 if (app.append_env_to_name && opts.env)
1027 app.name += ('-' + opts.env);
1028 if (opts.name_prefix && app.name.indexOf(opts.name_prefix) == -1)
1029 app.name = `${opts.name_prefix}:${app.name}`
1030
1031 app.username = Common.getCurrentUsername();
1032 apps_name.push(app.name);
1033 });
1034
1035 that.Client.executeRemote('getMonitorData', {}, function(err, raw_proc_list) {
1036 if (err) {
1037 Common.printError(err);
1038 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1039 }
1040
1041 /**
1042 * Uniquify in memory process list
1043 */
1044 raw_proc_list.forEach(function(proc) {
1045 proc_list[proc.name] = proc;
1046 });
1047
1048 /**
1049 * Auto detect application already started
1050 * and act on them depending on action
1051 */
1052 eachLimit(Object.keys(proc_list), conf.CONCURRENT_ACTIONS, function(proc_name, next) {
1053 // Skip app name (--only option)
1054 if (apps_name.indexOf(proc_name) == -1)
1055 return next();
1056
1057 if (!(action == 'reloadProcessId' ||
1058 action == 'softReloadProcessId' ||
1059 action == 'restartProcessId'))
1060 throw new Error('Wrong action called');
1061
1062 var apps = appConf.filter(function(app) {
1063 return app.name == proc_name;
1064 });
1065
1066 var envs = apps.map(function(app){
1067 // Binds env_diff to env and returns it.
1068 return Common.mergeEnvironmentVariables(app, opts.env, deployConf);
1069 });
1070
1071 // Assigns own enumerable properties of all
1072 // Notice: if people use the same name in different apps,
1073 // duplicated envs will be overrode by the last one
1074 var env = envs.reduce(function(e1, e2){
1075 return util._extend(e1, e2);
1076 });
1077
1078 // When we are processing JSON, allow to keep the new env by default
1079 env.updateEnv = true;
1080
1081 // Pass `env` option
1082 that._operate(action, proc_name, env, function(err, ret) {
1083 if (err) Common.printError(err);
1084
1085 // For return
1086 apps_info = apps_info.concat(ret);
1087
1088 that.Client.notifyGod(action, proc_name);
1089 // And Remove from array to spy
1090 apps_name.splice(apps_name.indexOf(proc_name), 1);
1091 return next();
1092 });
1093
1094 }, function(err) {
1095 if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1096 if (apps_name.length > 0 && action != 'start')
1097 Common.printOut(conf.PREFIX_MSG_WARNING + 'Applications %s not running, starting...', apps_name.join(', '));
1098 // Start missing apps
1099 return startApps(apps_name, function(err, apps) {
1100 apps_info = apps_info.concat(apps);
1101 return cb ? cb(err, apps_info) : that.speedList(err ? 1 : 0);
1102 });
1103 });
1104 return false;
1105 });
1106
1107 function startApps(app_name_to_start, cb) {
1108 var apps_to_start = [];
1109 var apps_started = [];
1110 var apps_errored = [];
1111
1112 appConf.forEach(function(app, i) {
1113 if (app_name_to_start.indexOf(app.name) != -1) {
1114 apps_to_start.push(appConf[i]);
1115 }
1116 });
1117
1118 eachLimit(apps_to_start, conf.CONCURRENT_ACTIONS, function(app, next) {
1119 if (opts.cwd)
1120 app.cwd = opts.cwd;
1121 if (opts.force_name)
1122 app.name = opts.force_name;
1123 if (opts.started_as_module)
1124 app.pmx_module = true;
1125
1126 var resolved_paths = null;
1127
1128 // hardcode script name to use `serve` feature inside a process file
1129 if (app.script === 'serve') {
1130 app.script = path.resolve(__dirname, 'API', 'Serve.js')
1131 }
1132
1133 try {
1134 resolved_paths = Common.resolveAppAttributes({
1135 cwd : that.cwd,
1136 pm2_home : that.pm2_home
1137 }, app);
1138 } catch (e) {
1139 apps_errored.push(e)
1140 Common.err(`Error: ${e.message}`)
1141 return next();
1142 }
1143
1144 if (!resolved_paths.env) resolved_paths.env = {};
1145
1146 // Set PM2 HOME in case of child process using PM2 API
1147 resolved_paths.env['PM2_HOME'] = that.pm2_home;
1148
1149 var additional_env = Modularizer.getAdditionalConf(resolved_paths.name);
1150 util._extend(resolved_paths.env, additional_env);
1151
1152 resolved_paths.env = Common.mergeEnvironmentVariables(resolved_paths, opts.env, deployConf);
1153
1154 delete resolved_paths.env.current_conf;
1155
1156 // Is KM linked?
1157 resolved_paths.km_link = that.gl_is_km_linked;
1158
1159 if (resolved_paths.wait_ready) {
1160 Common.warn(`App ${resolved_paths.name} has option 'wait_ready' set, waiting for app to be ready...`)
1161 }
1162 that.Client.executeRemote('prepare', resolved_paths, function(err, data) {
1163 if (err) {
1164 Common.printError(conf.PREFIX_MSG_ERR + 'Process failed to launch %s', err.message ? err.message : err);
1165 return next();
1166 }
1167 if (data.length === 0) {
1168 Common.printError(conf.PREFIX_MSG_ERR + 'Process config loading failed', data);
1169 return next();
1170 }
1171
1172 Common.printOut(conf.PREFIX_MSG + 'App [%s] launched (%d instances)', data[0].pm2_env.name, data.length);
1173 apps_started = apps_started.concat(data);
1174 next();
1175 });
1176
1177 }, function(err) {
1178 var final_error = err || apps_errored.length > 0 ? apps_errored : null
1179 return cb ? cb(final_error, apps_started) : that.speedList();
1180 });
1181 return false;
1182 }
1183 }
1184
1185 /**
1186 * Apply a RPC method on the json file
1187 * @private
1188 * @method actionFromJson
1189 * @param {string} action RPC Method
1190 * @param {object} options
1191 * @param {string|object} file file
1192 * @param {string} jsonVia action type (=only 'pipe' ?)
1193 * @param {Function}
1194 */
1195 actionFromJson (action, file, opts, jsonVia, cb) {
1196 var appConf = {};
1197 var ret_processes = [];
1198 var that = this;
1199
1200 //accept programmatic calls
1201 if (typeof file == 'object') {
1202 cb = typeof jsonVia == 'function' ? jsonVia : cb;
1203 appConf = file;
1204 }
1205 else if (jsonVia == 'file') {
1206 var data = null;
1207
1208 try {
1209 data = fs.readFileSync(file);
1210 } catch(e) {
1211 Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file +' not found');
1212 return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
1213 }
1214
1215 try {
1216 appConf = Common.parseConfig(data, file);
1217 } catch(e) {
1218 Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file + ' malformated');
1219 console.error(e);
1220 return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT);
1221 }
1222 } else if (jsonVia == 'pipe') {
1223 appConf = Common.parseConfig(file, 'pipe');
1224 } else {
1225 Common.printError('Bad call to actionFromJson, jsonVia should be one of file, pipe');
1226 return that.exitCli(conf.ERROR_EXIT);
1227 }
1228
1229 // Backward compatibility
1230 if (appConf.apps)
1231 appConf = appConf.apps;
1232
1233 if (!Array.isArray(appConf))
1234 appConf = [appConf];
1235
1236 if ((appConf = Common.verifyConfs(appConf)) instanceof Error)
1237 return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT);
1238
1239 eachLimit(appConf, conf.CONCURRENT_ACTIONS, function(proc, next1) {
1240 var name = '';
1241 var new_env;
1242
1243 if (!proc.name)
1244 name = path.basename(proc.script);
1245 else
1246 name = proc.name;
1247
1248 if (opts.only && opts.only != name)
1249 return process.nextTick(next1);
1250
1251 if (opts && opts.env)
1252 new_env = Common.mergeEnvironmentVariables(proc, opts.env);
1253 else
1254 new_env = Common.mergeEnvironmentVariables(proc);
1255
1256 that.Client.getProcessIdByName(name, function(err, ids) {
1257 if (err) {
1258 Common.printError(err);
1259 return next1();
1260 }
1261 if (!ids) return next1();
1262
1263 eachLimit(ids, conf.CONCURRENT_ACTIONS, function(id, next2) {
1264 var opts = {};
1265
1266 //stopProcessId could accept options to?
1267 if (action == 'restartProcessId') {
1268 opts = {id : id, env : new_env};
1269 } else {
1270 opts = id;
1271 }
1272
1273 that.Client.executeRemote(action, opts, function(err, res) {
1274 ret_processes.push(res);
1275 if (err) {
1276 Common.printError(err);
1277 return next2();
1278 }
1279
1280 if (action == 'restartProcessId') {
1281 that.Client.notifyGod('restart', id);
1282 } else if (action == 'deleteProcessId') {
1283 that.Client.notifyGod('delete', id);
1284 } else if (action == 'stopProcessId') {
1285 that.Client.notifyGod('stop', id);
1286 }
1287
1288 Common.printOut(conf.PREFIX_MSG + '[%s](%d) \u2713', name, id);
1289 return next2();
1290 });
1291 }, function(err) {
1292 return next1(null, ret_processes);
1293 });
1294 });
1295 }, function(err) {
1296 if (cb) return cb(null, ret_processes);
1297 else return that.speedList();
1298 });
1299 }
1300
1301
1302 /**
1303 * Main function to operate with PM2 daemon
1304 *
1305 * @param {String} action_name Name of action (restartProcessId, deleteProcessId, stopProcessId)
1306 * @param {String} process_name can be 'all', a id integer or process name
1307 * @param {Object} envs object with CLI options / environment
1308 */
1309 _operate (action_name, process_name, envs, cb) {
1310 var that = this;
1311 var update_env = false;
1312 var ret = [];
1313
1314 // Make sure all options exist
1315 if (!envs)
1316 envs = {};
1317
1318 if (typeof(envs) == 'function'){
1319 cb = envs;
1320 envs = {};
1321 }
1322
1323 // Set via env.update (JSON processing)
1324 if (envs.updateEnv === true)
1325 update_env = true;
1326
1327 var concurrent_actions = envs.parallel || conf.CONCURRENT_ACTIONS;
1328
1329 if (!process.env.PM2_JSON_PROCESSING || envs.commands) {
1330 envs = that._handleAttributeUpdate(envs);
1331 }
1332
1333 /**
1334 * Set current updated configuration if not passed
1335 */
1336 if (!envs.current_conf) {
1337 var _conf = fclone(envs);
1338 envs = {
1339 current_conf : _conf
1340 }
1341
1342 // Is KM linked?
1343 envs.current_conf.km_link = that.gl_is_km_linked;
1344 }
1345
1346 /**
1347 * Operate action on specific process id
1348 */
1349 function processIds(ids, cb) {
1350 Common.printOut(conf.PREFIX_MSG + 'Applying action %s on app [%s](ids: %s)', action_name, process_name, ids);
1351
1352 if (ids.length <= 2)
1353 concurrent_actions = 1;
1354
1355 if (action_name == 'deleteProcessId')
1356 concurrent_actions = 10;
1357
1358 eachLimit(ids, concurrent_actions, function(id, next) {
1359 var opts;
1360
1361 // These functions need extra param to be passed
1362 if (action_name == 'restartProcessId' ||
1363 action_name == 'reloadProcessId' ||
1364 action_name == 'softReloadProcessId') {
1365 var new_env = {};
1366
1367 if (update_env === true) {
1368 if (conf.PM2_PROGRAMMATIC == true)
1369 new_env = Common.safeExtend({}, process.env);
1370 else
1371 new_env = util._extend({}, process.env);
1372
1373 Object.keys(envs).forEach(function(k) {
1374 new_env[k] = envs[k];
1375 });
1376 }
1377 else {
1378 new_env = envs;
1379 }
1380
1381 opts = {
1382 id : id,
1383 env : new_env
1384 };
1385 }
1386 else {
1387 opts = id;
1388 }
1389
1390 that.Client.executeRemote(action_name, opts, function(err, res) {
1391 if (err) {
1392 Common.printError(conf.PREFIX_MSG_ERR + 'Process %s not found', id);
1393 return next(`Process ${id} not found`);
1394 }
1395
1396 if (action_name == 'restartProcessId') {
1397 that.Client.notifyGod('restart', id);
1398 } else if (action_name == 'deleteProcessId') {
1399 that.Client.notifyGod('delete', id);
1400 } else if (action_name == 'stopProcessId') {
1401 that.Client.notifyGod('stop', id);
1402 } else if (action_name == 'reloadProcessId') {
1403 that.Client.notifyGod('reload', id);
1404 } else if (action_name == 'softReloadProcessId') {
1405 that.Client.notifyGod('graceful reload', id);
1406 }
1407
1408 if (!Array.isArray(res))
1409 res = [res];
1410
1411 // Filter return
1412 res.forEach(function(proc) {
1413 Common.printOut(conf.PREFIX_MSG + '[%s](%d) \u2713', proc.pm2_env ? proc.pm2_env.name : process_name, id);
1414
1415 if (action_name == 'stopProcessId' && proc.pm2_env && proc.pm2_env.cron_restart) {
1416 Common.warn(`App ${chalk.bold(proc.pm2_env.name)} stopped but CRON RESTART is still UP ${proc.pm2_env.cron_restart}`)
1417 }
1418
1419 if (!proc.pm2_env) return false;
1420
1421 ret.push({
1422 name : proc.pm2_env.name,
1423 namespace: proc.pm2_env.namespace,
1424 pm_id : proc.pm2_env.pm_id,
1425 status : proc.pm2_env.status,
1426 restart_time : proc.pm2_env.restart_time,
1427 pm2_env : {
1428 name : proc.pm2_env.name,
1429 namespace: proc.pm2_env.namespace,
1430 pm_id : proc.pm2_env.pm_id,
1431 status : proc.pm2_env.status,
1432 restart_time : proc.pm2_env.restart_time,
1433 env : proc.pm2_env.env
1434 }
1435 });
1436 });
1437
1438 return next();
1439 });
1440 }, function(err) {
1441 if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1442 return cb ? cb(null, ret) : that.speedList();
1443 });
1444 }
1445
1446 if (process_name == 'all') {
1447 // When using shortcuts like 'all', do not delete modules
1448 var fn
1449
1450 if (process.env.PM2_STATUS == 'stopping')
1451 that.Client.getAllProcessId(function(err, ids) {
1452 reoperate(err, ids)
1453 });
1454 else
1455 that.Client.getAllProcessIdWithoutModules(function(err, ids) {
1456 reoperate(err, ids)
1457 });
1458
1459 function reoperate(err, ids) {
1460 if (err) {
1461 Common.printError(err);
1462 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1463 }
1464 if (!ids || ids.length === 0) {
1465 Common.printError(conf.PREFIX_MSG_WARNING + 'No process found');
1466 return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT);
1467 }
1468 return processIds(ids, cb);
1469 }
1470 }
1471 // operate using regex
1472 else if (isNaN(process_name) && process_name[0] === '/' && process_name[process_name.length - 1] === '/') {
1473 var regex = new RegExp(process_name.replace(/\//g, ''));
1474
1475 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
1476 if (err) {
1477 Common.printError('Error retrieving process list: ' + err);
1478 return cb(err);
1479 }
1480 var found_proc = [];
1481 list.forEach(function(proc) {
1482 if (regex.test(proc.pm2_env.name)) {
1483 found_proc.push(proc.pm_id);
1484 }
1485 });
1486
1487 if (found_proc.length === 0) {
1488 Common.printError(conf.PREFIX_MSG_WARNING + 'No process found');
1489 return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT);
1490 }
1491
1492 return processIds(found_proc, cb);
1493 });
1494 }
1495 else if (isNaN(process_name)) {
1496 /**
1497 * We can not stop or delete a module but we can restart it
1498 * to refresh configuration variable
1499 */
1500 var allow_module_restart = action_name == 'restartProcessId' ? true : false;
1501
1502 that.Client.getProcessIdByName(process_name, allow_module_restart, function (err, ids) {
1503 if (err) {
1504 Common.printError(err);
1505 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1506 }
1507 if (ids && ids.length > 0) {
1508 /**
1509 * Determine if the process to restart is a module
1510 * if yes load configuration variables and merge with the current environment
1511 */
1512 var additional_env = Modularizer.getAdditionalConf(process_name);
1513 util._extend(envs, additional_env);
1514 return processIds(ids, cb);
1515 }
1516
1517 that.Client.getProcessIdsByNamespace(process_name, allow_module_restart, function (err, ns_process_ids) {
1518 if (err) {
1519 Common.printError(err);
1520 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1521 }
1522 if (!ns_process_ids || ns_process_ids.length === 0) {
1523 Common.printError(conf.PREFIX_MSG_ERR + 'Process or Namespace %s not found', process_name);
1524 return cb ? cb(new Error('process or namespace not found')) : that.exitCli(conf.ERROR_EXIT);
1525 }
1526
1527 /**
1528 * Determine if the process to restart is a module
1529 * if yes load configuration variables and merge with the current environment
1530 */
1531 var ns_additional_env = Modularizer.getAdditionalConf(process_name);
1532 util._extend(envs, ns_additional_env);
1533 return processIds(ns_process_ids, cb);
1534 });
1535 });
1536 } else {
1537 if (that.pm2_configuration.docker == "true" ||
1538 that.pm2_configuration.docker == true) {
1539 // Docker/Systemd process interaction detection
1540 that.Client.executeRemote('getMonitorData', {}, (err, proc_list) => {
1541 var higher_id = 0
1542 proc_list.forEach(p => { p.pm_id > higher_id ? higher_id = p.pm_id : null })
1543
1544 // Is Docker/Systemd
1545 if (process_name > higher_id)
1546 return DockerMgmt.processCommand(that, higher_id, process_name, action_name, (err) => {
1547 if (err) {
1548 Common.printError(conf.PREFIX_MSG_ERR + (err.message ? err.message : err));
1549 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1550 }
1551
1552 return cb ? cb(null, ret) : that.speedList();
1553 })
1554
1555 // Check if application name as number is an app name
1556 that.Client.getProcessIdByName(process_name, function(err, ids) {
1557 if (ids.length > 0)
1558 return processIds(ids, cb);
1559
1560 // Check if application name as number is an namespace
1561 that.Client.getProcessIdsByNamespace(process_name, function(err, ns_process_ids) {
1562 if (ns_process_ids.length > 0)
1563 return processIds(ns_process_ids, cb);
1564 // Else operate on pm id
1565 return processIds([process_name], cb);
1566 });
1567 });
1568 })
1569 }
1570 else {
1571 // Check if application name as number is an app name
1572 that.Client.getProcessIdByName(process_name, function(err, ids) {
1573 if (ids.length > 0)
1574 return processIds(ids, cb);
1575
1576 // Check if application name as number is an namespace
1577 that.Client.getProcessIdsByNamespace(process_name, function(err, ns_process_ids) {
1578 if (ns_process_ids.length > 0)
1579 return processIds(ns_process_ids, cb);
1580 // Else operate on pm id
1581 return processIds([process_name], cb);
1582 });
1583 });
1584 }
1585 }
1586 }
1587
1588 /**
1589 * Converts CamelCase Commander.js arguments
1590 * to Underscore
1591 * (nodeArgs -> node_args)
1592 */
1593 _handleAttributeUpdate (opts) {
1594 var conf = Config.filterOptions(opts);
1595 var that = this;
1596
1597 if (typeof(conf.name) != 'string')
1598 delete conf.name;
1599
1600 var argsIndex = 0;
1601 if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0) {
1602 conf.args = opts.rawArgs.slice(argsIndex + 1);
1603 }
1604
1605 var appConf = Common.verifyConfs(conf)[0];
1606
1607 if (appConf instanceof Error) {
1608 Common.printError('Error while transforming CamelCase args to underscore');
1609 return appConf;
1610 }
1611
1612 if (argsIndex == -1)
1613 delete appConf.args;
1614 if (appConf.name == 'undefined')
1615 delete appConf.name;
1616
1617 delete appConf.exec_mode;
1618
1619 if (util.isArray(appConf.watch) && appConf.watch.length === 0) {
1620 if (!~opts.rawArgs.indexOf('--watch'))
1621 delete appConf.watch
1622 }
1623
1624 // Options set via environment variables
1625 if (process.env.PM2_DEEP_MONITORING)
1626 appConf.deep_monitoring = true;
1627
1628 // Force deletion of defaults values set by commander
1629 // to avoid overriding specified configuration by user
1630 if (appConf.treekill === true)
1631 delete appConf.treekill;
1632 if (appConf.pmx === true)
1633 delete appConf.pmx;
1634 if (appConf.vizion === true)
1635 delete appConf.vizion;
1636 if (appConf.automation === true)
1637 delete appConf.automation;
1638 if (appConf.autorestart === true)
1639 delete appConf.autorestart;
1640
1641 return appConf;
1642 }
1643
1644 getProcessIdByName (name, cb) {
1645 var that = this;
1646
1647 this.Client.getProcessIdByName(name, function(err, id) {
1648 if (err) {
1649 Common.printError(err);
1650 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1651 }
1652 console.log(id);
1653 return cb ? cb(null, id) : that.exitCli(conf.SUCCESS_EXIT);
1654 });
1655 }
1656
1657 /**
1658 * Description
1659 * @method jlist
1660 * @param {} debug
1661 * @return
1662 */
1663 jlist (debug) {
1664 var that = this;
1665
1666 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
1667 if (err) {
1668 Common.printError(err);
1669 return that.exitCli(conf.ERROR_EXIT);
1670 }
1671
1672 if (debug) {
1673 process.stdout.write(util.inspect(list, false, null, false));
1674 }
1675 else {
1676 process.stdout.write(JSON.stringify(list));
1677 }
1678
1679 that.exitCli(conf.SUCCESS_EXIT);
1680
1681 });
1682 }
1683
1684 /**
1685 * Display system information
1686 * @method slist
1687 * @return
1688 */
1689 slist (tree) {
1690 this.Client.executeRemote('getSystemData', {}, (err, sys_infos) => {
1691 if (err) {
1692 Common.err(err)
1693 return this.exitCli(conf.ERROR_EXIT)
1694 }
1695
1696 if (tree === true) {
1697 var treeify = require('./tools/treeify.js')
1698 console.log(treeify.asTree(sys_infos, true))
1699 }
1700 else
1701 process.stdout.write(util.inspect(sys_infos, false, null, false))
1702 this.exitCli(conf.SUCCESS_EXIT)
1703 })
1704 }
1705
1706 /**
1707 * Description
1708 * @method speedList
1709 * @return
1710 */
1711 speedList (code, apps_acted) {
1712 var that = this;
1713 var systemdata = null
1714 var acted = []
1715
1716 if ((code != 0 && code != null)) {
1717 return that.exitCli(code ? code : conf.SUCCESS_EXIT);
1718 }
1719
1720 if (apps_acted && apps_acted.length > 0) {
1721 apps_acted.forEach(proc => {
1722 acted.push(proc.pm2_env ? proc.pm2_env.pm_id : proc.pm_id)
1723 })
1724 }
1725
1726 // Do nothing if PM2 called programmatically and not called from CLI (also in exitCli)
1727 if ((conf.PM2_PROGRAMMATIC && process.env.PM2_USAGE != 'CLI'))
1728 return false;
1729
1730 return that.Client.executeRemote('getMonitorData', {}, (err, proc_list) => {
1731 doList(err, proc_list)
1732 })
1733
1734 function doList(err, list) {
1735 if (err) {
1736 if (that.gl_retry == 0) {
1737 that.gl_retry += 1;
1738 return setTimeout(that.speedList.bind(that), 1400);
1739 }
1740 console.error('Error retrieving process list: %s.\nA process seems to be on infinite loop, retry in 5 seconds',err);
1741 return that.exitCli(conf.ERROR_EXIT);
1742 }
1743 if (process.stdout.isTTY === false) {
1744 UX.list_min(list);
1745 }
1746 else if (commander.miniList && !commander.silent)
1747 UX.list_min(list);
1748 else if (!commander.silent) {
1749 if (that.gl_interact_infos) {
1750 var dashboard_url = `https://app.pm2.io/#/r/${that.gl_interact_infos.public_key}`
1751
1752 if (that.gl_interact_infos.info_node != 'https://root.keymetrics.io') {
1753 dashboard_url = `${that.gl_interact_infos.info_node}/#/r/${that.gl_interact_infos.public_key}`
1754 }
1755
1756 Common.printOut('%s PM2+ activated | Instance Name: %s | Dash: %s',
1757 chalk.green.bold('⇆'),
1758 chalk.bold(that.gl_interact_infos.machine_name),
1759 chalk.bold(dashboard_url))
1760 }
1761 UX.list(list, commander);
1762 //Common.printOut(chalk.white.italic(' Use `pm2 show <id|name>` to get more details about an app'));
1763 }
1764
1765 if (that.Client.daemon_mode == false) {
1766 Common.printOut('[--no-daemon] Continue to stream logs');
1767 Common.printOut('[--no-daemon] Exit on target PM2 exit pid=' + fs.readFileSync(conf.PM2_PID_FILE_PATH).toString());
1768 global._auto_exit = true;
1769 return that.streamLogs('all', 0, false, 'HH:mm:ss', false);
1770 }
1771 // if (process.stdout.isTTY) if looking for start logs
1772 else if (!process.env.TRAVIS && process.env.NODE_ENV != 'test' && acted.length > 0 && (commander.attach === true)) {
1773 Common.info(`Log streaming apps id: ${chalk.cyan(acted.join(' '))}, exit with Ctrl-C or will exit in 10secs`)
1774
1775 // setTimeout(() => {
1776 // Common.info(`Log streaming exited automatically, run 'pm2 logs' to continue watching logs`)
1777 // return that.exitCli(code ? code : conf.SUCCESS_EXIT);
1778 // }, 10000)
1779
1780 return acted.forEach((proc_name) => {
1781 that.streamLogs(proc_name, 0, false, null, false);
1782 })
1783 }
1784 else {
1785 return that.exitCli(code ? code : conf.SUCCESS_EXIT);
1786 }
1787 }
1788 }
1789
1790 /**
1791 * Scale up/down a process
1792 * @method scale
1793 */
1794 scale (app_name, number, cb) {
1795 var that = this;
1796
1797 function addProcs(proc, value, cb) {
1798 (function ex(proc, number) {
1799 if (number-- === 0) return cb();
1800 Common.printOut(conf.PREFIX_MSG + 'Scaling up application');
1801 that.Client.executeRemote('duplicateProcessId', proc.pm2_env.pm_id, ex.bind(this, proc, number));
1802 })(proc, number);
1803 }
1804
1805 function rmProcs(procs, value, cb) {
1806 var i = 0;
1807
1808 (function ex(procs, number) {
1809 if (number++ === 0) return cb();
1810 that._operate('deleteProcessId', procs[i++].pm2_env.pm_id, ex.bind(this, procs, number));
1811 })(procs, number);
1812 }
1813
1814 function end() {
1815 return cb ? cb(null, {success:true}) : that.speedList();
1816 }
1817
1818 this.Client.getProcessByName(app_name, function(err, procs) {
1819 if (err) {
1820 Common.printError(err);
1821 return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT);
1822 }
1823
1824 if (!procs || procs.length === 0) {
1825 Common.printError(conf.PREFIX_MSG_ERR + 'Application %s not found', app_name);
1826 return cb ? cb(new Error('App not found')) : that.exitCli(conf.ERROR_EXIT);
1827 }
1828
1829 var proc_number = procs.length;
1830
1831 if (typeof(number) === 'string' && number.indexOf('+') >= 0) {
1832 number = parseInt(number, 10);
1833 return addProcs(procs[0], number, end);
1834 }
1835 else if (typeof(number) === 'string' && number.indexOf('-') >= 0) {
1836 number = parseInt(number, 10);
1837 return rmProcs(procs[0], number, end);
1838 }
1839 else {
1840 number = parseInt(number, 10);
1841 number = number - proc_number;
1842
1843 if (number < 0)
1844 return rmProcs(procs, number, end);
1845 else if (number > 0)
1846 return addProcs(procs[0], number, end);
1847 else {
1848 Common.printError(conf.PREFIX_MSG_ERR + 'Nothing to do');
1849 return cb ? cb(new Error('Same process number')) : that.exitCli(conf.ERROR_EXIT);
1850 }
1851 }
1852 });
1853 }
1854
1855 /**
1856 * Description
1857 * @method describeProcess
1858 * @param {} pm2_id
1859 * @return
1860 */
1861 describe (pm2_id, cb) {
1862 var that = this;
1863
1864 var found_proc = [];
1865
1866 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
1867 if (err) {
1868 Common.printError('Error retrieving process list: ' + err);
1869 that.exitCli(conf.ERROR_EXIT);
1870 }
1871
1872 list.forEach(function(proc) {
1873 if ((!isNaN(pm2_id) && proc.pm_id == pm2_id) ||
1874 (typeof(pm2_id) === 'string' && proc.name == pm2_id)) {
1875 found_proc.push(proc);
1876 }
1877 });
1878
1879 if (found_proc.length === 0) {
1880 Common.printError(conf.PREFIX_MSG_WARNING + '%s doesn\'t exist', pm2_id);
1881 return cb ? cb(null, []) : that.exitCli(conf.ERROR_EXIT);
1882 }
1883
1884 if (!cb) {
1885 found_proc.forEach(function(proc) {
1886 UX.describe(proc);
1887 });
1888 }
1889
1890 return cb ? cb(null, found_proc) : that.exitCli(conf.SUCCESS_EXIT);
1891 });
1892 }
1893
1894 /**
1895 * API method to perform a deep update of PM2
1896 * @method deepUpdate
1897 */
1898 deepUpdate (cb) {
1899 var that = this;
1900
1901 Common.printOut(conf.PREFIX_MSG + 'Updating PM2...');
1902
1903 var child = sexec("npm i -g pm2@latest; pm2 update");
1904
1905 child.stdout.on('end', function() {
1906 Common.printOut(conf.PREFIX_MSG + 'PM2 successfully updated');
1907 cb ? cb(null, {success:true}) : that.exitCli(conf.SUCCESS_EXIT);
1908 });
1909 }
1910};
1911
1912
1913//////////////////////////
1914// Load all API methods //
1915//////////////////////////
1916
1917require('./API/Extra.js')(API);
1918require('./API/Deploy.js')(API);
1919require('./API/Modules/index.js')(API);
1920
1921require('./API/pm2-plus/link.js')(API);
1922require('./API/pm2-plus/process-selector.js')(API);
1923require('./API/pm2-plus/helpers.js')(API);
1924
1925require('./API/Configuration.js')(API);
1926require('./API/Version.js')(API);
1927require('./API/Startup.js')(API);
1928require('./API/LogManagement.js')(API);
1929require('./API/Containerizer.js')(API);
1930
1931
1932module.exports = API;