UNPKG

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