UNPKG

25.9 kBJavaScriptView Raw
1/**
2 * Copyright 2013-2021 the PM2 project authors. All rights reserved.
3 * Use of this source code is governed by a license that
4 * can be found in the LICENSE file.
5 */
6
7/**
8 * Common Utilities ONLY USED IN ->CLI<-
9 */
10
11var fs = require('fs');
12var path = require('path');
13var os = require('os');
14var util = require('util');
15var chalk = require('chalk');
16var fclone = require('fclone');
17var semver = require('semver');
18var dayjs = require('dayjs');
19var execSync = require('child_process').execSync;
20var isBinary = require('./tools/isbinaryfile.js');
21var cst = require('../constants.js');
22var extItps = require('./API/interpreter.json');
23var Config = require('./tools/Config');
24var pkg = require('../package.json');
25var which = require('./tools/which.js');
26var Common = module.exports;
27
28function homedir() {
29 var env = process.env;
30 var home = env.HOME;
31 var user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME;
32
33 if (process.platform === 'win32') {
34 return env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null;
35 }
36
37 if (process.platform === 'darwin') {
38 return home || (user ? '/Users/' + user : null);
39 }
40
41 if (process.platform === 'linux') {
42 return home || (process.getuid() === 0 ? '/root' : (user ? '/home/' + user : null));
43 }
44
45 return home || null;
46}
47
48function resolveHome(filepath) {
49 if (filepath[0] === '~') {
50 return path.join(homedir(), filepath.slice(1));
51 }
52 return filepath;
53}
54
55Common.determineSilentCLI = function() {
56 // pm2 should ignore -s --silent -v if they are after '--'
57 var variadicArgsDashesPos = process.argv.indexOf('--');
58 var s1opt = process.argv.indexOf('--silent')
59 var s2opt = process.argv.indexOf('-s')
60
61 if (process.env.PM2_SILENT || (variadicArgsDashesPos > -1 &&
62 (s1opt != -1 && s1opt < variadicArgsDashesPos) &&
63 (s2opt != -1 != s2opt < variadicArgsDashesPos)) ||
64 (variadicArgsDashesPos == -1 && (s1opt > -1 || s2opt > -1))) {
65 for (var key in console){
66 var code = key.charCodeAt(0);
67 if (code >= 97 && code <= 122){
68 console[key] = function(){};
69 }
70 }
71 process.env.PM2_DISCRETE_MODE = true;
72 }
73}
74
75Common.printVersion = function() {
76 var variadicArgsDashesPos = process.argv.indexOf('--');
77
78 if (process.argv.indexOf('-v') > -1 && process.argv.indexOf('-v') < variadicArgsDashesPos) {
79 console.log(pkg.version);
80 process.exit(0);
81 }
82}
83
84Common.lockReload = function() {
85 try {
86 var t1 = fs.readFileSync(cst.PM2_RELOAD_LOCKFILE).toString();
87
88 // Check if content and if time < 30 return locked
89 // Else if content detected (lock file staled), allow and rewritte
90 if (t1 && t1 != '') {
91 var diff = dayjs().diff(parseInt(t1));
92 if (diff < cst.RELOAD_LOCK_TIMEOUT)
93 return diff;
94 }
95 } catch(e) {}
96
97 try {
98 // Write latest timestamp
99 fs.writeFileSync(cst.PM2_RELOAD_LOCKFILE, dayjs().valueOf().toString());
100 return 0;
101 } catch(e) {
102 console.error(e.message || e);
103 }
104};
105
106Common.unlockReload = function() {
107 try {
108 fs.writeFileSync(cst.PM2_RELOAD_LOCKFILE, '');
109 } catch(e) {
110 console.error(e.message || e);
111 }
112};
113
114/**
115 * Resolve app paths and replace missing values with defaults.
116 * @method prepareAppConf
117 * @param app {Object}
118 * @param {} cwd
119 * @param {} outputter
120 * @return app
121 */
122Common.prepareAppConf = function(opts, app) {
123 /**
124 * Minimum validation
125 */
126 if (!app.script)
127 return new Error('No script path - aborting');
128
129 var cwd = null;
130
131 if (app.cwd) {
132 cwd = path.resolve(app.cwd);
133 process.env.PWD = app.cwd;
134 }
135
136 if (!app.node_args) {
137 app.node_args = [];
138 }
139
140 if (app.port && app.env) {
141 app.env.PORT = app.port;
142 }
143
144 // CWD option resolving
145 cwd && (cwd[0] != '/') && (cwd = path.resolve(process.cwd(), cwd));
146 cwd = cwd || opts.cwd;
147
148 // Full path script resolution
149 app.pm_exec_path = path.resolve(cwd, app.script);
150
151 // If script does not exist after resolution
152 if (!fs.existsSync(app.pm_exec_path)) {
153 var ckd;
154 // Try resolve command available in $PATH
155 if ((ckd = which(app.script))) {
156 if (typeof(ckd) !== 'string')
157 ckd = ckd.toString();
158 app.pm_exec_path = ckd;
159 }
160 else
161 // Throw critical error
162 return new Error(`Script not found: ${app.pm_exec_path}`);
163 }
164
165 /**
166 * Auto detect .map file and enable source map support automatically
167 */
168 if (app.disable_source_map_support != true) {
169 try {
170 fs.accessSync(app.pm_exec_path + '.map', fs.R_OK);
171 app.source_map_support = true;
172 } catch(e) {}
173 delete app.disable_source_map_support;
174 }
175
176 delete app.script;
177
178 // Set current env by first adding the process environment and then extending/replacing it
179 // with env specified on command-line or JSON file.
180
181 var env = {};
182
183 /**
184 * Do not copy internal pm2 environment variables if acting on process
185 * is made from a programmatic script started by PM2 or if a pm_id is present in env
186 */
187 if (cst.PM2_PROGRAMMATIC || process.env.pm_id)
188 Common.safeExtend(env, process.env);
189 else
190 env = process.env;
191
192 function filterEnv (envObj) {
193 if (app.filter_env == true)
194 return {}
195
196 if (typeof app.filter_env === 'string') {
197 delete envObj[app.filter_env]
198 return envObj
199 }
200
201 var new_env = {};
202 var allowedKeys = app.filter_env.reduce((acc, current) =>
203 acc.filter( item => !item.includes(current)), Object.keys(envObj))
204 allowedKeys.forEach( key => new_env[key] = envObj[key]);
205 return new_env
206 }
207
208 app.env = [
209 {}, (app.filter_env && app.filter_env.length > 0) ? filterEnv(process.env) : env, app.env || {}
210 ].reduce(function(e1, e2){
211 return util._extend(e1, e2);
212 });
213
214 app.pm_cwd = cwd;
215 // Interpreter
216 try {
217 Common.sink.resolveInterpreter(app);
218 } catch(e) {
219 return e
220 }
221
222 // Exec mode and cluster stuff
223 Common.sink.determineExecMode(app);
224
225 /**
226 * Scary
227 */
228 var formated_app_name = app.name.replace(/[^a-zA-Z0-9\\.\\-]/g, '-');
229
230 ['log', 'out', 'error', 'pid'].forEach(function(f){
231 var af = app[f + '_file'], ps, ext = (f == 'pid' ? 'pid':'log'), isStd = !~['log', 'pid'].indexOf(f);
232 if (af) af = resolveHome(af);
233
234 if ((f == 'log' && typeof af == 'boolean' && af) || (f != 'log' && !af)) {
235 ps = [cst['DEFAULT_' + ext.toUpperCase() + '_PATH'], formated_app_name + (isStd ? '-' + f : '') + '.' + ext];
236 } else if ((f != 'log' || (f == 'log' && af)) && af !== 'NULL' && af !== '/dev/null') {
237 ps = [cwd, af];
238
239 var dir = path.dirname(path.resolve(cwd, af));
240 if (!fs.existsSync(dir)) {
241 Common.printError(cst.PREFIX_MSG_WARNING + 'Folder does not exist: ' + dir);
242 Common.printOut(cst.PREFIX_MSG + 'Creating folder: ' + dir);
243 try {
244 require('mkdirp').sync(dir);
245 } catch (err) {
246 Common.printError(cst.PREFIX_MSG_ERR + 'Could not create folder: ' + path.dirname(af));
247 throw new Error('Could not create folder');
248 }
249 }
250
251 }
252 // PM2 paths
253 if (af !== 'NULL' && af !== '/dev/null') {
254 ps && (app['pm_' + (isStd ? f.substr(0, 3) + '_' : '') + ext + '_path'] = path.resolve.apply(null, ps));
255 } else if (path.sep === '\\') {
256 app['pm_' + (isStd ? f.substr(0, 3) + '_' : '') + ext + '_path'] = '\\\\.\\NUL';
257 } else {
258 app['pm_' + (isStd ? f.substr(0, 3) + '_' : '') + ext + '_path'] = '/dev/null';
259 }
260 delete app[f + '_file'];
261 });
262
263 return app;
264};
265
266/**
267 * Check if filename is a configuration file
268 * @param {string} filename
269 * @return {mixed} null if not conf file, json or yaml if conf
270 */
271Common.isConfigFile = function (filename) {
272 if (typeof (filename) !== 'string')
273 return null;
274 if (filename.indexOf('.json') !== -1)
275 return 'json';
276 if (filename.indexOf('.yml') > -1 || filename.indexOf('.yaml') > -1)
277 return 'yaml';
278 if (filename.indexOf('.config.js') !== -1)
279 return 'js';
280 if (filename.indexOf('.config.cjs') !== -1)
281 return 'js';
282 if (filename.indexOf('.config.mjs') !== -1)
283 return 'mjs';
284 return null;
285};
286
287/**
288 * Parses a config file like ecosystem.config.js. Supported formats: JS, JSON, JSON5, YAML.
289 * @param {string} confString contents of the config file
290 * @param {string} filename path to the config file
291 * @return {Object} config object
292 */
293Common.parseConfig = function(confObj, filename) {
294 var yamljs = require('yamljs');
295 var vm = require('vm');
296
297 if (!filename ||
298 filename == 'pipe' ||
299 filename == 'none' ||
300 filename.indexOf('.json') > -1) {
301 var code = '(' + confObj + ')';
302 var sandbox = {};
303
304 return vm.runInThisContext(code, sandbox, {
305 filename: path.resolve(filename),
306 displayErrors: false,
307 timeout: 1000
308 });
309 }
310 else if (filename.indexOf('.yml') > -1 ||
311 filename.indexOf('.yaml') > -1) {
312 return yamljs.parse(confObj.toString());
313 }
314 else if (filename.indexOf('.config.js') > -1 || filename.indexOf('.config.cjs') > -1 || filename.indexOf('.config.mjs') > -1) {
315 var confPath = require.resolve(path.resolve(filename));
316 delete require.cache[confPath];
317 return require(confPath);
318 }
319};
320
321Common.retErr = function(e) {
322 if (!e)
323 return new Error('Unidentified error');
324 if (e instanceof Error)
325 return e;
326 return new Error(e);
327}
328
329Common.sink = {};
330
331Common.sink.determineCron = function(app) {
332 var cronJob = require('cron').CronJob;
333
334 if (app.cron_restart) {
335 try {
336 Common.printOut(cst.PREFIX_MSG + 'cron restart at ' + app.cron_restart);
337 new cronJob(app.cron_restart, function() {
338 Common.printOut(cst.PREFIX_MSG + 'cron pattern for auto restart detected and valid');
339 });
340 } catch(ex) {
341 return new Error(`Cron pattern error: ${ex.message}`);
342 }
343 }
344};
345
346/**
347 * Handle alias (fork <=> fork_mode, cluster <=> cluster_mode)
348 */
349Common.sink.determineExecMode = function(app) {
350 if (app.exec_mode)
351 app.exec_mode = app.exec_mode.replace(/^(fork|cluster)$/, '$1_mode');
352
353 /**
354 * Here we put the default exec mode
355 */
356 if (!app.exec_mode &&
357 (app.instances >= 1 || app.instances === 0 || app.instances === -1) &&
358 app.exec_interpreter.indexOf('node') > -1) {
359 app.exec_mode = 'cluster_mode';
360 } else if (!app.exec_mode) {
361 app.exec_mode = 'fork_mode';
362 }
363 if (typeof app.instances == 'undefined')
364 app.instances = 1;
365};
366
367var resolveNodeInterpreter = function(app) {
368 if (app.exec_mode && app.exec_mode.indexOf('cluster') > -1) {
369 Common.printError(cst.PREFIX_MSG_WARNING + chalk.bold.yellow('Choosing the Node.js version in cluster mode is not supported'));
370 return false;
371 }
372
373 var nvm_path = cst.IS_WINDOWS ? process.env.NVM_HOME : process.env.NVM_DIR;
374 if (!nvm_path) {
375 Common.printError(cst.PREFIX_MSG_ERR + chalk.red('NVM is not available in PATH'));
376 Common.printError(cst.PREFIX_MSG_ERR + chalk.red('Fallback to node in PATH'));
377 var msg = cst.IS_WINDOWS
378 ? 'https://github.com/coreybutler/nvm-windows/releases/'
379 : '$ curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash';
380 Common.printOut(cst.PREFIX_MSG_ERR + chalk.bold('Install NVM:\n' + msg));
381 }
382 else {
383 var node_version = app.exec_interpreter.split('@')[1];
384 var path_to_node = cst.IS_WINDOWS
385 ? '/v' + node_version + '/node.exe'
386 : semver.satisfies(node_version, '>= 0.12.0')
387 ? '/versions/node/v' + node_version + '/bin/node'
388 : '/v' + node_version + '/bin/node';
389 var nvm_node_path = path.join(nvm_path, path_to_node);
390 try {
391 fs.accessSync(nvm_node_path);
392 } catch(e) {
393 Common.printOut(cst.PREFIX_MSG + 'Installing Node v%s', node_version);
394 var nvm_bin = path.join(nvm_path, 'nvm.' + (cst.IS_WINDOWS ? 'exe' : 'sh'));
395 var nvm_cmd = cst.IS_WINDOWS
396 ? nvm_bin + ' install ' + node_version
397 : '. ' + nvm_bin + ' ; nvm install ' + node_version;
398
399 Common.printOut(cst.PREFIX_MSG + 'Executing: %s', nvm_cmd);
400
401 execSync(nvm_cmd, {
402 cwd: path.resolve(process.cwd()),
403 env: process.env,
404 maxBuffer: 20 * 1024 * 1024
405 });
406
407 // in order to support both arch, nvm for Windows renames 'node.exe' to:
408 // 'node32.exe' for x32 arch
409 // 'node64.exe' for x64 arch
410 if (cst.IS_WINDOWS)
411 nvm_node_path = nvm_node_path.replace(/node/, 'node' + process.arch.slice(1))
412 }
413
414 Common.printOut(cst.PREFIX_MSG + chalk.green.bold('Setting Node to v%s (path=%s)'),
415 node_version,
416 nvm_node_path);
417
418 app.exec_interpreter = nvm_node_path;
419 }
420};
421
422/**
423 * Resolve interpreter
424 */
425Common.sink.resolveInterpreter = function(app) {
426 var noInterpreter = !app.exec_interpreter;
427 var extName = path.extname(app.pm_exec_path);
428 var betterInterpreter = extItps[extName];
429
430 // No interpreter defined and correspondance in schema hashmap
431 if (noInterpreter && betterInterpreter) {
432 app.exec_interpreter = betterInterpreter;
433 }
434 // Else if no Interpreter detect if process is binary
435 else if (noInterpreter)
436 app.exec_interpreter = isBinary(app.pm_exec_path) ? 'none' : 'node';
437 else if (app.exec_interpreter.indexOf('node@') > -1)
438 resolveNodeInterpreter(app);
439
440 if (app.exec_interpreter.indexOf('python') > -1)
441 app.env.PYTHONUNBUFFERED = '1'
442
443 /**
444 * Specific installed JS transpilers
445 */
446 if (app.exec_interpreter == 'ts-node') {
447 app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/ts-node');
448 }
449
450 if (app.exec_interpreter == 'lsc') {
451 app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/lsc');
452 }
453
454 if (app.exec_interpreter == 'coffee') {
455 app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/coffee');
456 }
457
458 if (app.exec_interpreter != 'none' && which(app.exec_interpreter) == null) {
459 // If node is not present
460 if (app.exec_interpreter == 'node') {
461 Common.warn(`Using builtin node.js version on version ${process.version}`)
462 app.exec_interpreter = cst.BUILTIN_NODE_PATH
463 }
464 else
465 throw new Error(`Interpreter ${app.exec_interpreter} is NOT AVAILABLE in PATH. (type 'which ${app.exec_interpreter}' to double check.)`)
466 }
467
468 return app;
469};
470
471Common.deepCopy = Common.serialize = Common.clone = function(obj) {
472 if (obj === null || obj === undefined) return {};
473 return fclone(obj);
474};
475
476Common.errMod = function(msg) {
477 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
478 if (msg instanceof Error)
479 return console.error(msg.message);
480 return console.error(`${cst.PREFIX_MSG_MOD_ERR}${msg}`);
481}
482
483Common.err = function(msg) {
484 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
485 if (msg instanceof Error)
486 return console.error(`${cst.PREFIX_MSG_ERR}${msg.message}`);
487 return console.error(`${cst.PREFIX_MSG_ERR}${msg}`);
488}
489
490Common.printError = function(msg) {
491 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
492 if (msg instanceof Error)
493 return console.error(msg.message);
494 return console.error.apply(console, arguments);
495};
496
497Common.log = function(msg) {
498 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
499 return console.log(`${cst.PREFIX_MSG}${msg}`);
500}
501
502Common.info = function(msg) {
503 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
504 return console.log(`${cst.PREFIX_MSG_INFO}${msg}`);
505}
506
507Common.warn = function(msg) {
508 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
509 return console.log(`${cst.PREFIX_MSG_WARNING}${msg}`);
510}
511
512Common.logMod = function(msg) {
513 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
514 return console.log(`${cst.PREFIX_MSG_MOD}${msg}`);
515}
516
517Common.printOut = function() {
518 if (process.env.PM2_SILENT === 'true' || process.env.PM2_PROGRAMMATIC === 'true') return false;
519 return console.log.apply(console, arguments);
520};
521
522
523/**
524 * Raw extend
525 */
526Common.extend = function(destination, source) {
527 if (typeof destination !== 'object') {
528 destination = {};
529 }
530 if (!source || typeof source !== 'object') {
531 return destination;
532 }
533
534 Object.keys(source).forEach(function(new_key) {
535 if (source[new_key] != '[object Object]')
536 destination[new_key] = source[new_key];
537 });
538
539 return destination;
540};
541
542/**
543 * This is useful when starting script programmatically
544 */
545Common.safeExtend = function(origin, add){
546 if (!add || typeof add != 'object') return origin;
547
548 //Ignore PM2's set environment variables from the nested env
549 var keysToIgnore = ['name', 'exec_mode', 'env', 'args', 'pm_cwd', 'exec_interpreter', 'pm_exec_path', 'node_args', 'pm_out_log_path', 'pm_err_log_path', 'pm_pid_path', 'pm_id', 'status', 'pm_uptime', 'created_at', 'windowsHide', 'username', 'merge_logs', 'kill_retry_time', 'prev_restart_delay', 'instance_var', 'unstable_restarts', 'restart_time', 'axm_actions', 'pmx_module', 'command', 'watch', 'filter_env', 'versioning', 'vizion_runing', 'MODULE_DEBUG', 'pmx', 'axm_options', 'created_at', 'watch', 'vizion', 'axm_dynamic', 'axm_monitor', 'instances', 'automation', 'autorestart', 'unstable_restart', 'treekill', 'exit_code', 'vizion'];
550
551 var keys = Object.keys(add);
552 var i = keys.length;
553 while (i--) {
554 //Only copy stuff into the env that we don't have already.
555 if(keysToIgnore.indexOf(keys[i]) == -1 && add[keys[i]] != '[object Object]')
556 origin[keys[i]] = add[keys[i]];
557 }
558 return origin;
559};
560
561
562/**
563 * Extend the app.env object of with the properties taken from the
564 * app.env_[envName] and deploy configuration.
565 * Also update current json attributes
566 *
567 * Used only for Configuration file processing
568 *
569 * @param {Object} app The app object.
570 * @param {string} envName The given environment name.
571 * @param {Object} deployConf Deployment configuration object (from JSON file or whatever).
572 * @returns {Object} The app.env variables object.
573 */
574Common.mergeEnvironmentVariables = function(app_env, env_name, deploy_conf) {
575 var app = fclone(app_env);
576
577 var new_conf = {
578 env : {}
579 }
580
581 // Stringify possible object
582 for (var key in app.env) {
583 if (typeof app.env[key] == 'object') {
584 app.env[key] = JSON.stringify(app.env[key]);
585 }
586 }
587
588 /**
589 * Extra configuration update
590 */
591 util._extend(new_conf, app)
592
593 if (env_name) {
594 // First merge variables from deploy.production.env object as least priority.
595 if (deploy_conf && deploy_conf[env_name] && deploy_conf[env_name]['env']) {
596 util._extend(new_conf.env, deploy_conf[env_name]['env']);
597 }
598
599 util._extend(new_conf.env, app.env);
600
601 // Then, last and highest priority, merge the app.env_production object.
602 if ('env_' + env_name in app) {
603 util._extend(new_conf.env, app['env_' + env_name]);
604 }
605 else {
606 Common.printOut(cst.PREFIX_MSG_WARNING + chalk.bold('Environment [%s] is not defined in process file'), env_name);
607 }
608 }
609
610 delete new_conf.exec_mode
611
612 var res = {
613 current_conf: {}
614 }
615
616 util._extend(res, new_conf.env)
617 util._extend(res.current_conf, new_conf)
618
619 // #2541 force resolution of node interpreter
620 if (app.exec_interpreter &&
621 app.exec_interpreter.indexOf('@') > -1) {
622 resolveNodeInterpreter(app);
623 res.current_conf.exec_interpreter = app.exec_interpreter
624 }
625
626 return res
627}
628
629/**
630 * This function will resolve paths, option and environment
631 * CALLED before 'prepare' God call (=> PROCESS INITIALIZATION)
632 * @method resolveAppAttributes
633 * @param {Object} opts
634 * @param {Object} opts.cwd
635 * @param {Object} opts.pm2_home
636 * @param {Object} appConf application configuration
637 * @return app
638 */
639Common.resolveAppAttributes = function(opts, conf) {
640 var conf_copy = fclone(conf);
641
642 var app = Common.prepareAppConf(opts, conf_copy);
643 if (app instanceof Error) {
644 throw new Error(app.message);
645 }
646 return app;
647}
648
649/**
650 * Verify configurations
651 * Called on EVERY Operation (start/restart/reload/stop...)
652 * @param {Array} appConfs
653 * @returns {Array}
654 */
655Common.verifyConfs = function(appConfs) {
656 if (!appConfs || appConfs.length == 0) {
657 return [];
658 }
659
660 // Make sure it is an Array.
661 appConfs = [].concat(appConfs);
662
663 var verifiedConf = [];
664
665 for (var i = 0; i < appConfs.length; i++) {
666 var app = appConfs[i];
667
668 if (app.exec_mode)
669 app.exec_mode = app.exec_mode.replace(/^(fork|cluster)$/, '$1_mode');
670
671 // JSON conf: alias cmd to script
672 if (app.cmd && !app.script) {
673 app.script = app.cmd
674 delete app.cmd
675 }
676 // JSON conf: alias command to script
677 if (app.command && !app.script) {
678 app.script = app.command
679 delete app.command
680 }
681
682 if (!app.env) {
683 app.env = {}
684 }
685
686 // Render an app name if not existing.
687 Common.renderApplicationName(app);
688
689 if (app.execute_command == true) {
690 app.exec_mode = 'fork'
691 delete app.execute_command
692 }
693
694 app.username = Common.getCurrentUsername();
695
696 /**
697 * If command is like pm2 start "python xx.py --ok"
698 * Then automatically start the script with bash -c and set a name eq to command
699 */
700 if (app.script && app.script.indexOf(' ') > -1 && cst.IS_WINDOWS === false) {
701 var _script = app.script;
702
703 if (which('bash')) {
704 app.script = 'bash';
705 app.args = ['-c', _script];
706 if (!app.name) {
707 app.name = _script
708 }
709 }
710 else if (which('sh')) {
711 app.script = 'sh';
712 app.args = ['-c', _script];
713 if (!app.name) {
714 app.name = _script
715 }
716 }
717 else {
718 warn('bash or sh not available in $PATH, keeping script as is')
719 }
720 }
721
722 /**
723 * Add log_date_format by default
724 */
725 if (app.time) {
726 app.log_date_format = 'YYYY-MM-DDTHH:mm:ss'
727 }
728
729 /**
730 * Checks + Resolve UID/GID
731 * comes from pm2 --uid <> --gid <> or --user
732 */
733 if (app.uid || app.gid || app.user) {
734 // 1/ Check if windows
735 if (cst.IS_WINDOWS === true) {
736 Common.printError(cst.PREFIX_MSG_ERR + '--uid and --git does not works on windows');
737 return new Error('--uid and --git does not works on windows');
738 }
739
740 // 2/ Verify that user is root (todo: verify if other has right)
741 if (process.env.NODE_ENV != 'test' && process.getuid && process.getuid() !== 0) {
742 Common.printError(cst.PREFIX_MSG_ERR + 'To use --uid and --gid please run pm2 as root');
743 return new Error('To use UID and GID please run PM2 as root');
744 }
745
746 // 3/ Resolve user info via /etc/password
747 var passwd = require('./tools/passwd.js')
748 var users
749 try {
750 users = passwd.getUsers()
751 } catch(e) {
752 Common.printError(e);
753 return new Error(e);
754 }
755
756 var user_info = users[app.uid || app.user]
757 if (!user_info) {
758 Common.printError(`${cst.PREFIX_MSG_ERR} User ${app.uid || app.user} cannot be found`);
759 return new Error(`${cst.PREFIX_MSG_ERR} User ${app.uid || app.user} cannot be found`);
760 }
761
762 app.env.HOME = user_info.homedir
763 app.uid = parseInt(user_info.userId)
764
765 // 4/ Resolve group id if gid is specified
766 if (app.gid) {
767 var groups
768 try {
769 groups = passwd.getGroups()
770 } catch(e) {
771 Common.printError(e);
772 return new Error(e);
773 }
774 var group_info = groups[app.gid]
775 if (!group_info) {
776 Common.printError(`${cst.PREFIX_MSG_ERR} Group ${app.gid} cannot be found`);
777 return new Error(`${cst.PREFIX_MSG_ERR} Group ${app.gid} cannot be found`);
778 }
779 app.gid = parseInt(group_info.id)
780 } else {
781 app.gid = parseInt(user_info.groupId)
782 }
783 }
784
785 /**
786 * Specific options of PM2.io
787 */
788 if (process.env.PM2_DEEP_MONITORING) {
789 app.deep_monitoring = true;
790 }
791
792 if (app.automation == false) {
793 app.pmx = false;
794 }
795
796 if (app.disable_trace) {
797 app.trace = false
798 delete app.disable_trace;
799 }
800
801 /**
802 * Instances params
803 */
804 if (app.instances == 'max') {
805 app.instances = 0;
806 }
807
808 if (typeof(app.instances) === 'string') {
809 app.instances = parseInt(app.instances) || 0;
810 }
811
812 if (app.exec_mode != 'cluster_mode' &&
813 !app.instances &&
814 typeof(app.merge_logs) == 'undefined') {
815 app.merge_logs = true;
816 }
817
818 var ret;
819
820 if (app.cron_restart) {
821 if ((ret = Common.sink.determineCron(app)) instanceof Error)
822 return ret;
823 }
824
825 /**
826 * Now validation configuration
827 */
828 var ret = Config.validateJSON(app);
829 if (ret.errors && ret.errors.length > 0){
830 ret.errors.forEach(function(err) { warn(err) });
831 return new Error(ret.errors);
832 }
833
834 verifiedConf.push(ret.config);
835 }
836
837 return verifiedConf;
838}
839
840/**
841 * Get current username
842 * Called on EVERY starting app
843 *
844 * @returns {String}
845 */
846Common.getCurrentUsername = function(){
847 var current_user = '';
848
849 if (os.userInfo) {
850 try {
851 current_user = os.userInfo().username;
852 } catch (err) {
853 // For the case of unhandled error for uv_os_get_passwd
854 // https://github.com/Unitech/pm2/issues/3184
855 }
856 }
857
858 if(current_user === '') {
859 current_user = process.env.USER || process.env.LNAME || process.env.USERNAME || process.env.SUDO_USER || process.env.C9_USER || process.env.LOGNAME;
860 }
861
862 return current_user;
863}
864
865/**
866 * Render an app name if not existing.
867 * @param {Object} conf
868 */
869Common.renderApplicationName = function(conf){
870 if (!conf.name && conf.script){
871 conf.name = conf.script !== undefined ? path.basename(conf.script) : 'undefined';
872 var lastDot = conf.name.lastIndexOf('.');
873 if (lastDot > 0){
874 conf.name = conf.name.slice(0, lastDot);
875 }
876 }
877}
878
879/**
880 * Show warnings
881 * @param {String} warning
882 */
883function warn(warning){
884 Common.printOut(cst.PREFIX_MSG_WARNING + warning);
885}