UNPKG

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