UNPKG

22.3 kBJavaScriptView Raw
1
2/***************************
3 *
4 * Extra methods
5 *
6 **************************/
7
8var cst = require('../../constants.js');
9var Common = require('../Common.js');
10var UX = require('./UX');
11var chalk = require('chalk');
12var path = require('path');
13var fs = require('fs');
14var fmt = require('../tools/fmt.js');
15var dayjs = require('dayjs');
16var pkg = require('../../package.json');
17const copyDirSync = require('../tools/copydirSync.js')
18
19module.exports = function(CLI) {
20 /**
21 * Get version of the daemonized PM2
22 * @method getVersion
23 * @callback cb
24 */
25 CLI.prototype.getVersion = function(cb) {
26 var that = this;
27
28 that.Client.executeRemote('getVersion', {}, function(err) {
29 return cb ? cb.apply(null, arguments) : that.exitCli(cst.SUCCESS_EXIT);
30 });
31 };
32
33 /**
34 * Install pm2-sysmonit
35 */
36 CLI.prototype.launchSysMonitoring = function(cb) {
37 if ((this.pm2_configuration && this.pm2_configuration.sysmonit != 'true') ||
38 process.env.TRAVIS ||
39 global.it === 'function' ||
40 cst.IS_WINDOWS === true)
41 return cb ? cb(null) : null
42
43 var filepath
44
45 try {
46 filepath = path.dirname(require.resolve('pm2-sysmonit'))
47 } catch(e) {
48 return cb ? cb(null) : null
49 }
50
51 this.start({
52 script: filepath
53 }, {
54 started_as_module : true
55 }, (err, res) => {
56 if (err) {
57 Common.printError(cst.PREFIX_MSG_ERR + 'Error while trying to serve : ' + err.message || err);
58 return cb ? cb(err) : this.speedList(cst.ERROR_EXIT);
59 }
60 return cb ? cb(null) : this.speedList();
61 });
62 };
63
64 /**
65 * Show application environment
66 * @method env
67 * @callback cb
68 */
69 CLI.prototype.env = function(app_id, cb) {
70 var procs = []
71 var printed = 0
72
73 this.Client.executeRemote('getMonitorData', {}, (err, list) => {
74 list.forEach(l => {
75 if (app_id == l.pm_id) {
76 printed++
77 var env = Common.safeExtend({}, l.pm2_env)
78 Object.keys(env).forEach(key => {
79 console.log(`${key}: ${chalk.green(env[key])}`)
80 })
81 }
82 })
83
84 if (printed == 0) {
85 Common.err(`Modules with id ${app_id} not found`)
86 return cb ? cb.apply(null, arguments) : this.exitCli(cst.ERROR_EXIT);
87 }
88 return cb ? cb.apply(null, arguments) : this.exitCli(cst.SUCCESS_EXIT);
89 })
90 };
91
92 /**
93 * Get version of the daemonized PM2
94 * @method getVersion
95 * @callback cb
96 */
97 CLI.prototype.report = function() {
98 var that = this;
99
100 var Log = require('./Log');
101
102 that.Client.executeRemote('getReport', {}, function(err, report) {
103
104 console.log()
105 console.log()
106 console.log()
107 console.log('```')
108 fmt.title('PM2 report')
109 fmt.field('Date', new Date());
110 fmt.sep();
111
112 if (report && !err) {
113 fmt.title(chalk.bold.blue('Daemon'));
114 fmt.field('pm2d version', report.pm2_version);
115 fmt.field('node version', report.node_version);
116 fmt.field('node path', report.node_path);
117 fmt.field('argv', report.argv);
118 fmt.field('argv0', report.argv0);
119 fmt.field('user', report.user);
120 fmt.field('uid', report.uid);
121 fmt.field('gid', report.gid);
122 fmt.field('uptime', dayjs(new Date()).diff(report.started_at, 'minute') + 'min');
123 }
124
125 fmt.sep();
126 fmt.title(chalk.bold.blue('CLI'));
127 fmt.field('local pm2', pkg.version);
128 fmt.field('node version', process.versions.node);
129 fmt.field('node path', process.env['_'] || 'not found');
130 fmt.field('argv', process.argv);
131 fmt.field('argv0', process.argv0);
132 fmt.field('user', process.env.USER || process.env.LNAME || process.env.USERNAME);
133 if (cst.IS_WINDOWS === false && process.geteuid)
134 fmt.field('uid', process.geteuid());
135 if (cst.IS_WINDOWS === false && process.getegid)
136 fmt.field('gid', process.getegid());
137
138 var os = require('os');
139
140 fmt.sep();
141 fmt.title(chalk.bold.blue('System info'));
142 fmt.field('arch', os.arch());
143 fmt.field('platform', os.platform());
144 fmt.field('type', os.type());
145 fmt.field('cpus', os.cpus()[0].model);
146 fmt.field('cpus nb', Object.keys(os.cpus()).length);
147 fmt.field('freemem', os.freemem());
148 fmt.field('totalmem', os.totalmem());
149 fmt.field('home', os.homedir());
150
151 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
152
153 fmt.sep();
154 fmt.title(chalk.bold.blue('PM2 list'));
155 UX.list(list, that.gl_interact_infos);
156
157 fmt.sep();
158 fmt.title(chalk.bold.blue('Daemon logs'));
159 Log.tail([{
160 path : cst.PM2_LOG_FILE_PATH,
161 app_name : 'PM2',
162 type : 'PM2'
163 }], 20, false, function() {
164 console.log('```')
165 console.log()
166 console.log()
167
168 console.log(chalk.bold.green('Please copy/paste the above report in your issue on https://github.com/Unitech/pm2/issues'));
169
170 console.log()
171 console.log()
172 that.exitCli(cst.SUCCESS_EXIT);
173 });
174 });
175 });
176 };
177
178 CLI.prototype.getPID = function(app_name, cb) {
179 var that = this;
180
181 if (typeof(app_name) === 'function') {
182 cb = app_name;
183 app_name = null;
184 }
185
186 this.Client.executeRemote('getMonitorData', {}, function(err, list) {
187 if (err) {
188 Common.printError(cst.PREFIX_MSG_ERR + err);
189 return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
190 }
191
192 var pids = [];
193
194 list.forEach(function(app) {
195 if (!app_name || app_name == app.name)
196 pids.push(app.pid);
197 })
198
199 if (!cb) {
200 Common.printOut(pids.join("\n"))
201 return that.exitCli(cst.SUCCESS_EXIT);
202 }
203 return cb(null, pids);
204 })
205 }
206
207 /**
208 * Create PM2 memory snapshot
209 * @method getVersion
210 * @callback cb
211 */
212 CLI.prototype.profile = function(type, time, cb) {
213 var that = this;
214 var dayjs = require('dayjs');
215 var cmd
216
217 if (type == 'cpu') {
218 cmd = {
219 ext: '.cpuprofile',
220 action: 'profileCPU'
221 }
222 }
223 if (type == 'mem') {
224 cmd = {
225 ext: '.heapprofile',
226 action: 'profileMEM'
227 }
228 }
229
230 var file = path.join(process.cwd(), dayjs().format('dd-HH:mm:ss') + cmd.ext);
231 time = time || 10000
232
233 console.log(`Starting ${cmd.action} profiling for ${time}ms...`)
234 that.Client.executeRemote(cmd.action, {
235 pwd : file,
236 timeout: time
237 }, function(err) {
238 if (err) {
239 console.error(err);
240 return that.exitCli(1);
241 }
242 console.log(`Profile done in ${file}`)
243 return cb ? cb.apply(null, arguments) : that.exitCli(cst.SUCCESS_EXIT);
244 });
245 };
246
247
248 function basicMDHighlight(lines) {
249 console.log('\n\n+-------------------------------------+')
250 console.log(chalk.bold('README.md content:'))
251 lines = lines.split('\n')
252 var isInner = false
253 lines.forEach(l => {
254 if (l.startsWith('#'))
255 console.log(chalk.bold.green(l))
256 else if (isInner || l.startsWith('```')) {
257 if (isInner && l.startsWith('```'))
258 isInner = false
259 else if (isInner == false)
260 isInner = true
261 console.log(chalk.grey(l))
262 }
263 else if (l.startsWith('`'))
264 console.log(chalk.grey(l))
265 else
266 console.log(l)
267 })
268 console.log('+-------------------------------------+')
269 }
270 /**
271 * pm2 create command
272 * create boilerplate of application for fast try
273 * @method boilerplate
274 */
275 CLI.prototype.boilerplate = function(cb) {
276 var i = 0
277 var projects = []
278 var enquirer = require('enquirer')
279 const forEach = require('async/forEach')
280
281 fs.readdir(path.join(__dirname, '../templates/sample-apps'), (err, items) => {
282 forEach(items, (app, next) => {
283 var fp = path.join(__dirname, '../templates/sample-apps', app)
284 fs.readFile(path.join(fp, 'package.json'), (err, dt) => {
285 var meta = JSON.parse(dt)
286 meta.fullpath = fp
287 meta.folder_name = app
288 projects.push(meta)
289 next()
290 })
291 }, () => {
292 const prompt = new enquirer.Select({
293 name: 'boilerplate',
294 message: 'Select a boilerplate',
295 choices: projects.map((p, i) => {
296 return {
297 message: `${chalk.bold.blue(p.name)} ${p.description}`,
298 value: `${i}`
299 }
300 })
301 });
302
303 prompt.run()
304 .then(answer => {
305 var p = projects[parseInt(answer)]
306 basicMDHighlight(fs.readFileSync(path.join(p.fullpath, 'README.md')).toString())
307 console.log(chalk.bold(`>> Project copied inside folder ./${p.folder_name}/\n`))
308 copyDirSync(p.fullpath, path.join(process.cwd(), p.folder_name));
309 this.start(path.join(p.fullpath, 'ecosystem.config.js'), {
310 cwd: p.fullpath
311 }, () => {
312 return cb ? cb.apply(null, arguments) : this.speedList(cst.SUCCESS_EXIT);
313 })
314 })
315 .catch(e => {
316 return cb ? cb.apply(null, arguments) : this.speedList(cst.SUCCESS_EXIT);
317 });
318
319 })
320 })
321 }
322
323 /**
324 * Description
325 * @method sendLineToStdin
326 */
327 CLI.prototype.sendLineToStdin = function(pm_id, line, separator, cb) {
328 var that = this;
329
330 if (!cb && typeof(separator) == 'function') {
331 cb = separator;
332 separator = null;
333 }
334
335 var packet = {
336 pm_id : pm_id,
337 line : line + (separator || '\n')
338 };
339
340 that.Client.executeRemote('sendLineToStdin', packet, function(err, res) {
341 if (err) {
342 Common.printError(cst.PREFIX_MSG_ERR + err);
343 return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
344 }
345 return cb ? cb(null, res) : that.speedList();
346 });
347 };
348
349 /**
350 * Description
351 * @method attachToProcess
352 */
353 CLI.prototype.attach = function(pm_id, separator, cb) {
354 var that = this;
355 var readline = require('readline');
356
357 if (isNaN(pm_id)) {
358 Common.printError('pm_id must be a process number (not a process name)');
359 return cb ? cb(Common.retErr('pm_id must be number')) : that.exitCli(cst.ERROR_EXIT);
360 }
361
362 if (typeof(separator) == 'function') {
363 cb = separator;
364 separator = null;
365 }
366
367 var rl = readline.createInterface({
368 input: process.stdin,
369 output: process.stdout
370 });
371
372 rl.on('close', function() {
373 return cb ? cb() : that.exitCli(cst.SUCCESS_EXIT);
374 });
375
376 that.Client.launchBus(function(err, bus, socket) {
377 if (err) {
378 Common.printError(err);
379 return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
380 }
381
382 bus.on('log:*', function(type, packet) {
383 if (packet.process.pm_id !== parseInt(pm_id))
384 return;
385 process.stdout.write(packet.data);
386 });
387 });
388
389 rl.on('line', function(line) {
390 that.sendLineToStdin(pm_id, line, separator, function() {});
391 });
392 };
393
394 /**
395 * Description
396 * @method sendDataToProcessId
397 */
398 CLI.prototype.sendDataToProcessId = function(proc_id, packet, cb) {
399 var that = this;
400
401 if (typeof proc_id === 'object' && typeof packet === 'function') {
402 // the proc_id is packet.
403 cb = packet;
404 packet = proc_id;
405 } else {
406 packet.id = proc_id;
407 }
408
409 that.Client.executeRemote('sendDataToProcessId', packet, function(err, res) {
410 if (err) {
411 Common.printError(err);
412 return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
413 }
414 Common.printOut('successfully sent data to process');
415 return cb ? cb(null, res) : that.speedList();
416 });
417 };
418
419 /**
420 * Used for custom actions, allows to trigger function inside an app
421 * To expose a function you need to use keymetrics/pmx
422 *
423 * @method msgProcess
424 * @param {Object} opts
425 * @param {String} id process id
426 * @param {String} action_name function name to trigger
427 * @param {Object} [opts.opts] object passed as first arg of the function
428 * @param {String} [uuid] optional unique identifier when logs are emitted
429 *
430 */
431 CLI.prototype.msgProcess = function(opts, cb) {
432 var that = this;
433
434 that.Client.executeRemote('msgProcess', opts, cb);
435 };
436
437 /**
438 * Trigger a PMX custom action in target application
439 * Custom actions allows to interact with an application
440 *
441 * @method trigger
442 * @param {String|Number} pm_id process id or application name
443 * @param {String} action_name name of the custom action to trigger
444 * @param {Mixed} params parameter to pass to target action
445 * @param {Function} cb callback
446 */
447 CLI.prototype.trigger = function(pm_id, action_name, params, cb) {
448 if (typeof(params) === 'function') {
449 cb = params;
450 params = null;
451 }
452 var cmd = {
453 msg : action_name
454 };
455 var counter = 0;
456 var process_wait_count = 0;
457 var that = this;
458 var results = [];
459
460 if (params)
461 cmd.opts = params;
462 if (isNaN(pm_id))
463 cmd.name = pm_id;
464 else
465 cmd.id = pm_id;
466
467 this.launchBus(function(err, bus) {
468 bus.on('axm:reply', function(ret) {
469 if (ret.process.name == pm_id || ret.process.pm_id == pm_id || ret.process.namespace == pm_id || pm_id == 'all') {
470 results.push(ret);
471 Common.printOut('[%s:%s:%s]=%j', ret.process.name, ret.process.pm_id, ret.process.namespace, ret.data.return);
472 if (++counter == process_wait_count)
473 return cb ? cb(null, results) : that.exitCli(cst.SUCCESS_EXIT);
474 }
475 });
476
477 that.msgProcess(cmd, function(err, data) {
478 if (err) {
479 Common.printError(err);
480 return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
481 }
482
483 if (data.process_count == 0) {
484 Common.printError('Not any process has received a command (offline or unexistent)');
485 return cb ? cb(Common.retErr('Unknown process')) : that.exitCli(cst.ERROR_EXIT);
486 }
487
488 process_wait_count = data.process_count;
489 Common.printOut(chalk.bold('%s processes have received command %s'),
490 data.process_count, action_name);
491 });
492 });
493 };
494
495 /**
496 * Description
497 * @method sendSignalToProcessName
498 * @param {} signal
499 * @param {} process_name
500 * @return
501 */
502 CLI.prototype.sendSignalToProcessName = function(signal, process_name, cb) {
503 var that = this;
504
505 that.Client.executeRemote('sendSignalToProcessName', {
506 signal : signal,
507 process_name : process_name
508 }, function(err, list) {
509 if (err) {
510 Common.printError(err);
511 return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
512 }
513 Common.printOut('successfully sent signal %s to process name %s', signal, process_name);
514 return cb ? cb(null, list) : that.speedList();
515 });
516 };
517
518 /**
519 * Description
520 * @method sendSignalToProcessId
521 * @param {} signal
522 * @param {} process_id
523 * @return
524 */
525 CLI.prototype.sendSignalToProcessId = function(signal, process_id, cb) {
526 var that = this;
527
528 that.Client.executeRemote('sendSignalToProcessId', {
529 signal : signal,
530 process_id : process_id
531 }, function(err, list) {
532 if (err) {
533 Common.printError(err);
534 return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
535 }
536 Common.printOut('successfully sent signal %s to process id %s', signal, process_id);
537 return cb ? cb(null, list) : that.speedList();
538 });
539 };
540
541 /**
542 * API method to launch a process that will serve directory over http
543 */
544 CLI.prototype.autoinstall = function (cb) {
545 var filepath = path.resolve(path.dirname(module.filename), '../Sysinfo/ServiceDetection/ServiceDetection.js');
546
547 this.start(filepath, (err, res) => {
548 if (err) {
549 Common.printError(cst.PREFIX_MSG_ERR + 'Error while trying to serve : ' + err.message || err);
550 return cb ? cb(err) : this.speedList(cst.ERROR_EXIT);
551 }
552 return cb ? cb(null) : this.speedList();
553 });
554 }
555
556 /**
557 * API method to launch a process that will serve directory over http
558 *
559 * @param {Object} opts options
560 * @param {String} opts.path path to be served
561 * @param {Number} opts.port port on which http will bind
562 * @param {Boolean} opts.spa single page app served
563 * @param {String} opts.basicAuthUsername basic auth username
564 * @param {String} opts.basicAuthPassword basic auth password
565 * @param {Object} commander commander object
566 * @param {Function} cb optional callback
567 */
568 CLI.prototype.serve = function (target_path, port, opts, commander, cb) {
569 var that = this;
570 var servePort = process.env.PM2_SERVE_PORT || port || 8080;
571 var servePath = path.resolve(process.env.PM2_SERVE_PATH || target_path || '.');
572
573 var filepath = path.resolve(path.dirname(module.filename), './Serve.js');
574
575 if (typeof commander.name === 'string')
576 opts.name = commander.name
577 else
578 opts.name = 'static-page-server-' + servePort
579 if (!opts.env)
580 opts.env = {};
581 opts.env.PM2_SERVE_PORT = servePort;
582 opts.env.PM2_SERVE_PATH = servePath;
583 opts.env.PM2_SERVE_SPA = opts.spa;
584 if (opts.basicAuthUsername && opts.basicAuthPassword) {
585 opts.env.PM2_SERVE_BASIC_AUTH = 'true';
586 opts.env.PM2_SERVE_BASIC_AUTH_USERNAME = opts.basicAuthUsername;
587 opts.env.PM2_SERVE_BASIC_AUTH_PASSWORD = opts.basicAuthPassword;
588 }
589 if (opts.monitor) {
590 opts.env.PM2_SERVE_MONITOR = opts.monitor
591 }
592 opts.cwd = servePath;
593
594 this.start(filepath, opts, function (err, res) {
595 if (err) {
596 Common.printError(cst.PREFIX_MSG_ERR + 'Error while trying to serve : ' + err.message || err);
597 return cb ? cb(err) : that.speedList(cst.ERROR_EXIT);
598 }
599 Common.printOut(cst.PREFIX_MSG + 'Serving ' + servePath + ' on port ' + servePort);
600 return cb ? cb(null, res) : that.speedList();
601 });
602 }
603
604 /**
605 * Ping daemon - if PM2 daemon not launched, it will launch it
606 * @method ping
607 */
608 CLI.prototype.ping = function(cb) {
609 var that = this;
610
611 that.Client.executeRemote('ping', {}, function(err, res) {
612 if (err) {
613 Common.printError(err);
614 return cb ? cb(new Error(err)) : that.exitCli(cst.ERROR_EXIT);
615 }
616 Common.printOut(res);
617 return cb ? cb(null, res) : that.exitCli(cst.SUCCESS_EXIT);
618 });
619 };
620
621
622 /**
623 * Execute remote command
624 */
625 CLI.prototype.remote = function(command, opts, cb) {
626 var that = this;
627
628 that[command](opts.name, function(err_cmd, ret) {
629 if (err_cmd)
630 console.error(err_cmd);
631 console.log('Command %s finished', command);
632 return cb(err_cmd, ret);
633 });
634 };
635
636 /**
637 * This remote method allows to pass multiple arguments
638 * to PM2
639 * It is used for the new scoped PM2 action system
640 */
641 CLI.prototype.remoteV2 = function(command, opts, cb) {
642 var that = this;
643
644 if (that[command].length == 1)
645 return that[command](cb);
646
647 opts.args.push(cb);
648 return that[command].apply(this, opts.args);
649 };
650
651
652 /**
653 * Description
654 * @method generateSample
655 * @param {} name
656 * @return
657 */
658 CLI.prototype.generateSample = function(mode) {
659 var that = this;
660 var templatePath;
661
662 if (mode == 'simple')
663 templatePath = path.join(cst.TEMPLATE_FOLDER, cst.APP_CONF_TPL_SIMPLE);
664 else
665 templatePath = path.join(cst.TEMPLATE_FOLDER, cst.APP_CONF_TPL);
666
667 var sample = fs.readFileSync(templatePath);
668 var dt = sample.toString();
669 var f_name = 'ecosystem.config.js';
670 var pwd = process.env.PWD || process.cwd();
671
672 try {
673 fs.writeFileSync(path.join(pwd, f_name), dt);
674 } catch (e) {
675 console.error(e.stack || e);
676 return that.exitCli(cst.ERROR_EXIT);
677 }
678 Common.printOut('File %s generated', path.join(pwd, f_name));
679 that.exitCli(cst.SUCCESS_EXIT);
680 };
681
682 /**
683 * Description
684 * @method dashboard
685 * @return
686 */
687 CLI.prototype.dashboard = function(cb) {
688 var that = this;
689
690 var Dashboard = require('./Dashboard');
691
692 if (cb)
693 return cb(new Error('Dashboard cant be called programmatically'));
694
695 Dashboard.init();
696
697 this.Client.launchBus(function (err, bus) {
698 if (err) {
699 console.error('Error launchBus: ' + err);
700 that.exitCli(cst.ERROR_EXIT);
701 }
702 bus.on('log:*', function(type, data) {
703 Dashboard.log(type, data)
704 })
705 });
706
707 process.on('SIGINT', function() {
708 this.Client.disconnectBus(function() {
709 process.exit(cst.SUCCESS_EXIT);
710 });
711 });
712
713 function refreshDashboard() {
714 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
715 if (err) {
716 console.error('Error retrieving process list: ' + err);
717 that.exitCli(cst.ERROR_EXIT);
718 }
719
720 Dashboard.refresh(list);
721
722 setTimeout(function() {
723 refreshDashboard();
724 }, 800);
725 });
726 }
727
728 refreshDashboard();
729 };
730
731 CLI.prototype.monit = function(cb) {
732 var that = this;
733
734 var Monit = require('./Monit.js');
735
736 if (cb) return cb(new Error('Monit cant be called programmatically'));
737
738 Monit.init();
739
740 function launchMonitor() {
741 that.Client.executeRemote('getMonitorData', {}, function(err, list) {
742 if (err) {
743 console.error('Error retrieving process list: ' + err);
744 that.exitCli(conf.ERROR_EXIT);
745 }
746
747 Monit.refresh(list);
748
749 setTimeout(function() {
750 launchMonitor();
751 }, 400);
752 });
753 }
754
755 launchMonitor();
756 };
757
758 CLI.prototype.inspect = function(app_name, cb) {
759 const that = this;
760 this.trigger(app_name, 'internal:inspect', function (err, res) {
761
762 if(res && res[0]) {
763 if (res[0].data.return === '') {
764 Common.printOut(`Inspect disabled on ${app_name}`);
765 } else {
766 Common.printOut(`Inspect enabled on ${app_name} => go to chrome : chrome://inspect !!!`);
767 }
768 } else {
769 Common.printOut(`Unable to activate inspect mode on ${app_name} !!!`);
770 }
771
772 that.exitCli(cst.SUCCESS_EXIT);
773 });
774 };
775};