UNPKG

26.3 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
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 Object.assign(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 * Definition of known config file extensions with their type
268 */
269Common.knonwConfigFileExtensions = {
270 '.json': 'json',
271 '.yml': 'yaml',
272 '.yaml': 'yaml',
273 '.config.js': 'js',
274 '.config.cjs': 'js',
275 '.config.mjs': 'mjs'
276}
277
278/**
279 * Check if filename is a configuration file
280 * @param {string} filename
281 * @return {mixed} null if not conf file, json or yaml if conf
282 */
283Common.isConfigFile = function (filename) {
284 if (typeof (filename) !== 'string')
285 return null;
286
287 for (let extension in Common.knonwConfigFileExtensions) {
288 if (filename.indexOf(extension) !== -1) {
289 return Common.knonwConfigFileExtensions[extension];
290 }
291 }
292
293 return null;
294};
295
296Common.getConfigFileCandidates = function (name) {
297 return Object.keys(Common.knonwConfigFileExtensions).map((extension) => name + extension);
298}
299
300/**
301 * Parses a config file like ecosystem.config.js. Supported formats: JS, JSON, JSON5, YAML.
302 * @param {string} confString contents of the config file
303 * @param {string} filename path to the config file
304 * @return {Object} config object
305 */
306Common.parseConfig = function(confObj, filename) {
307 var yamljs = require('yamljs');
308 var vm = require('vm');
309
310 var isConfigFile = Common.isConfigFile(filename);
311
312 if (!filename ||
313 filename == 'pipe' ||
314 filename == 'none' ||
315 isConfigFile == 'json') {
316 var code = '(' + confObj + ')';
317 var sandbox = {};
318
319 return vm.runInThisContext(code, sandbox, {
320 filename: path.resolve(filename),
321 displayErrors: false,
322 timeout: 1000
323 });
324 }
325 else if (isConfigFile == 'yaml') {
326 return yamljs.parse(confObj.toString());
327 }
328 else if (isConfigFile == 'js' || isConfigFile == 'mjs') {
329 var confPath = require.resolve(path.resolve(filename));
330 delete require.cache[confPath];
331 return require(confPath);
332 }
333};
334
335Common.retErr = function(e) {
336 if (!e)
337 return new Error('Unidentified error');
338 if (e instanceof Error)
339 return e;
340 return new Error(e);
341}
342
343Common.sink = {};
344
345Common.sink.determineCron = function(app) {
346 if (app.cron_restart == 0 || app.cron_restart == '0') {
347 Common.printOut(cst.PREFIX_MSG + 'disabling cron restart');
348 return
349 }
350
351 if (app.cron_restart) {
352 const Croner = require('croner');
353
354 try {
355 Common.printOut(cst.PREFIX_MSG + 'cron restart at ' + app.cron_restart);
356 Croner(app.cron_restart);
357 } catch(ex) {
358 return new Error(`Cron pattern error: ${ex.message}`);
359 }
360 }
361};
362
363/**
364 * Handle alias (fork <=> fork_mode, cluster <=> cluster_mode)
365 */
366Common.sink.determineExecMode = function(app) {
367 if (app.exec_mode)
368 app.exec_mode = app.exec_mode.replace(/^(fork|cluster)$/, '$1_mode');
369
370 /**
371 * Here we put the default exec mode
372 */
373 if (!app.exec_mode &&
374 (app.instances >= 1 || app.instances === 0 || app.instances === -1) &&
375 app.exec_interpreter.indexOf('node') > -1) {
376 app.exec_mode = 'cluster_mode';
377 } else if (!app.exec_mode) {
378 app.exec_mode = 'fork_mode';
379 }
380 if (typeof app.instances == 'undefined')
381 app.instances = 1;
382};
383
384var resolveNodeInterpreter = function(app) {
385 if (app.exec_mode && app.exec_mode.indexOf('cluster') > -1) {
386 Common.printError(cst.PREFIX_MSG_WARNING + chalk.bold.yellow('Choosing the Node.js version in cluster mode is not supported'));
387 return false;
388 }
389
390 var nvm_path = cst.IS_WINDOWS ? process.env.NVM_HOME : process.env.NVM_DIR;
391 if (!nvm_path) {
392 Common.printError(cst.PREFIX_MSG_ERR + chalk.red('NVM is not available in PATH'));
393 Common.printError(cst.PREFIX_MSG_ERR + chalk.red('Fallback to node in PATH'));
394 var msg = cst.IS_WINDOWS
395 ? 'https://github.com/coreybutler/nvm-windows/releases/'
396 : '$ curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash';
397 Common.printOut(cst.PREFIX_MSG_ERR + chalk.bold('Install NVM:\n' + msg));
398 }
399 else {
400 var node_version = app.exec_interpreter.split('@')[1];
401 var path_to_node = cst.IS_WINDOWS
402 ? '/v' + node_version + '/node.exe'
403 : semver.satisfies(node_version, '>= 0.12.0')
404 ? '/versions/node/v' + node_version + '/bin/node'
405 : '/v' + node_version + '/bin/node';
406 var nvm_node_path = path.join(nvm_path, path_to_node);
407 try {
408 fs.accessSync(nvm_node_path);
409 } catch(e) {
410 Common.printOut(cst.PREFIX_MSG + 'Installing Node v%s', node_version);
411 var nvm_bin = path.join(nvm_path, 'nvm.' + (cst.IS_WINDOWS ? 'exe' : 'sh'));
412 var nvm_cmd = cst.IS_WINDOWS
413 ? nvm_bin + ' install ' + node_version
414 : '. ' + nvm_bin + ' ; nvm install ' + node_version;
415
416 Common.printOut(cst.PREFIX_MSG + 'Executing: %s', nvm_cmd);
417
418 execSync(nvm_cmd, {
419 cwd: path.resolve(process.cwd()),
420 env: process.env,
421 maxBuffer: 20 * 1024 * 1024
422 });
423
424 // in order to support both arch, nvm for Windows renames 'node.exe' to:
425 // 'node32.exe' for x32 arch
426 // 'node64.exe' for x64 arch
427 if (cst.IS_WINDOWS)
428 nvm_node_path = nvm_node_path.replace(/node/, 'node' + process.arch.slice(1))
429 }
430
431 Common.printOut(cst.PREFIX_MSG + chalk.green.bold('Setting Node to v%s (path=%s)'),
432 node_version,
433 nvm_node_path);
434
435 app.exec_interpreter = nvm_node_path;
436 }
437};
438
439/**
440 * Resolve interpreter
441 */
442Common.sink.resolveInterpreter = function(app) {
443 var noInterpreter = !app.exec_interpreter;
444 var extName = path.extname(app.pm_exec_path);
445 var betterInterpreter = extItps[extName];
446
447 // No interpreter defined and correspondance in schema hashmap
448 if (noInterpreter && betterInterpreter) {
449 app.exec_interpreter = betterInterpreter;
450
451 if (betterInterpreter == "python") {
452 if (which('python') == null) {
453 if (which('python3') == null)
454 Common.printError(cst.PREFIX_MSG_WARNING + chalk.bold.yellow('python and python3 binaries not available in PATH'));
455 else
456 app.exec_interpreter = 'python3';
457 }
458 }
459 }
460 // Else if no Interpreter detect if process is binary
461 else if (noInterpreter)
462 app.exec_interpreter = isBinary(app.pm_exec_path) ? 'none' : 'node';
463 else if (app.exec_interpreter.indexOf('node@') > -1)
464 resolveNodeInterpreter(app);
465
466 if (app.exec_interpreter.indexOf('python') > -1)
467 app.env.PYTHONUNBUFFERED = '1'
468
469 if (app.exec_interpreter == 'lsc') {
470 app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/lsc');
471 }
472
473 if (app.exec_interpreter == 'coffee') {
474 app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/coffee');
475 }
476
477 if (app.exec_interpreter != 'none' && which(app.exec_interpreter) == null) {
478 // If node is not present
479 if (app.exec_interpreter == 'node') {
480 Common.warn(`Using builtin node.js version on version ${process.version}`)
481 app.exec_interpreter = cst.BUILTIN_NODE_PATH
482 }
483 else
484 throw new Error(`Interpreter ${app.exec_interpreter} is NOT AVAILABLE in PATH. (type 'which ${app.exec_interpreter}' to double check.)`)
485 }
486
487 return app;
488};
489
490Common.deepCopy = Common.serialize = Common.clone = function(obj) {
491 if (obj === null || obj === undefined) return {};
492 return fclone(obj);
493};
494
495Common.errMod = function(msg) {
496 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
497 if (msg instanceof Error)
498 return console.error(msg.message);
499 return console.error(`${cst.PREFIX_MSG_MOD_ERR}${msg}`);
500}
501
502Common.err = function(msg) {
503 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
504 if (msg instanceof Error)
505 return console.error(`${cst.PREFIX_MSG_ERR}${msg.message}`);
506 return console.error(`${cst.PREFIX_MSG_ERR}${msg}`);
507}
508
509Common.printError = function(msg) {
510 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
511 if (msg instanceof Error)
512 return console.error(msg.message);
513 return console.error.apply(console, arguments);
514};
515
516Common.log = function(msg) {
517 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
518 return console.log(`${cst.PREFIX_MSG}${msg}`);
519}
520
521Common.info = function(msg) {
522 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
523 return console.log(`${cst.PREFIX_MSG_INFO}${msg}`);
524}
525
526Common.warn = function(msg) {
527 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
528 return console.log(`${cst.PREFIX_MSG_WARNING}${msg}`);
529}
530
531Common.logMod = function(msg) {
532 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
533 return console.log(`${cst.PREFIX_MSG_MOD}${msg}`);
534}
535
536Common.printOut = function() {
537 if (process.env.PM2_SILENT === 'true' || process.env.PM2_PROGRAMMATIC === 'true') return false;
538 return console.log.apply(console, arguments);
539};
540
541
542/**
543 * Raw extend
544 */
545Common.extend = function(destination, source) {
546 if (typeof destination !== 'object') {
547 destination = {};
548 }
549 if (!source || typeof source !== 'object') {
550 return destination;
551 }
552
553 Object.keys(source).forEach(function(new_key) {
554 if (source[new_key] != '[object Object]')
555 destination[new_key] = source[new_key];
556 });
557
558 return destination;
559};
560
561/**
562 * This is useful when starting script programmatically
563 */
564Common.safeExtend = function(origin, add){
565 if (!add || typeof add != 'object') return origin;
566
567 //Ignore PM2's set environment variables from the nested env
568 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', 'stop_exit_codes', 'unstable_restart', 'treekill', 'exit_code', 'vizion'];
569
570 var keys = Object.keys(add);
571 var i = keys.length;
572 while (i--) {
573 //Only copy stuff into the env that we don't have already.
574 if(keysToIgnore.indexOf(keys[i]) == -1 && add[keys[i]] != '[object Object]')
575 origin[keys[i]] = add[keys[i]];
576 }
577 return origin;
578};
579
580
581/**
582 * Extend the app.env object of with the properties taken from the
583 * app.env_[envName] and deploy configuration.
584 * Also update current json attributes
585 *
586 * Used only for Configuration file processing
587 *
588 * @param {Object} app The app object.
589 * @param {string} envName The given environment name.
590 * @param {Object} deployConf Deployment configuration object (from JSON file or whatever).
591 * @returns {Object} The app.env variables object.
592 */
593Common.mergeEnvironmentVariables = function(app_env, env_name, deploy_conf) {
594 var app = fclone(app_env);
595
596 var new_conf = {
597 env : {}
598 }
599
600 // Stringify possible object
601 for (var key in app.env) {
602 if (typeof app.env[key] == 'object') {
603 app.env[key] = JSON.stringify(app.env[key]);
604 }
605 }
606
607 /**
608 * Extra configuration update
609 */
610 Object.assign(new_conf, app);
611
612 if (env_name) {
613 // First merge variables from deploy.production.env object as least priority.
614 if (deploy_conf && deploy_conf[env_name] && deploy_conf[env_name]['env']) {
615 Object.assign(new_conf.env, deploy_conf[env_name]['env']);
616 }
617
618 Object.assign(new_conf.env, app.env);
619
620 // Then, last and highest priority, merge the app.env_production object.
621 if ('env_' + env_name in app) {
622 Object.assign(new_conf.env, app['env_' + env_name]);
623 }
624 else {
625 Common.printOut(cst.PREFIX_MSG_WARNING + chalk.bold('Environment [%s] is not defined in process file'), env_name);
626 }
627 }
628
629 delete new_conf.exec_mode
630
631 var res = {
632 current_conf: {}
633 }
634
635 Object.assign(res, new_conf.env);
636 Object.assign(res.current_conf, new_conf);
637
638 // #2541 force resolution of node interpreter
639 if (app.exec_interpreter &&
640 app.exec_interpreter.indexOf('@') > -1) {
641 resolveNodeInterpreter(app);
642 res.current_conf.exec_interpreter = app.exec_interpreter
643 }
644
645 return res
646}
647
648/**
649 * This function will resolve paths, option and environment
650 * CALLED before 'prepare' God call (=> PROCESS INITIALIZATION)
651 * @method resolveAppAttributes
652 * @param {Object} opts
653 * @param {Object} opts.cwd
654 * @param {Object} opts.pm2_home
655 * @param {Object} appConf application configuration
656 * @return app
657 */
658Common.resolveAppAttributes = function(opts, conf) {
659 var conf_copy = fclone(conf);
660
661 var app = Common.prepareAppConf(opts, conf_copy);
662 if (app instanceof Error) {
663 throw new Error(app.message);
664 }
665 return app;
666}
667
668/**
669 * Verify configurations
670 * Called on EVERY Operation (start/restart/reload/stop...)
671 * @param {Array} appConfs
672 * @returns {Array}
673 */
674Common.verifyConfs = function(appConfs) {
675 if (!appConfs || appConfs.length == 0) {
676 return [];
677 }
678
679 // Make sure it is an Array.
680 appConfs = [].concat(appConfs);
681
682 var verifiedConf = [];
683
684 for (var i = 0; i < appConfs.length; i++) {
685 var app = appConfs[i];
686
687 if (app.exec_mode)
688 app.exec_mode = app.exec_mode.replace(/^(fork|cluster)$/, '$1_mode');
689
690 // JSON conf: alias cmd to script
691 if (app.cmd && !app.script) {
692 app.script = app.cmd
693 delete app.cmd
694 }
695 // JSON conf: alias command to script
696 if (app.command && !app.script) {
697 app.script = app.command
698 delete app.command
699 }
700
701 if (!app.env) {
702 app.env = {}
703 }
704
705 // Render an app name if not existing.
706 Common.renderApplicationName(app);
707
708 if (app.execute_command == true) {
709 app.exec_mode = 'fork'
710 delete app.execute_command
711 }
712
713 app.username = Common.getCurrentUsername();
714
715 /**
716 * If command is like pm2 start "python xx.py --ok"
717 * Then automatically start the script with bash -c and set a name eq to command
718 */
719 if (app.script && app.script.indexOf(' ') > -1 && cst.IS_WINDOWS === false) {
720 var _script = app.script;
721
722 if (which('bash')) {
723 app.script = 'bash';
724 app.args = ['-c', _script];
725 if (!app.name) {
726 app.name = _script
727 }
728 }
729 else if (which('sh')) {
730 app.script = 'sh';
731 app.args = ['-c', _script];
732 if (!app.name) {
733 app.name = _script
734 }
735 }
736 else {
737 warn('bash or sh not available in $PATH, keeping script as is')
738 }
739 }
740
741 /**
742 * Add log_date_format by default
743 */
744 if (app.time || process.env.ASZ_MODE) {
745 app.log_date_format = 'YYYY-MM-DDTHH:mm:ss'
746 }
747
748 /**
749 * Checks + Resolve UID/GID
750 * comes from pm2 --uid <> --gid <> or --user
751 */
752 if (app.uid || app.gid || app.user) {
753 // 1/ Check if windows
754 if (cst.IS_WINDOWS === true) {
755 Common.printError(cst.PREFIX_MSG_ERR + '--uid and --git does not works on windows');
756 return new Error('--uid and --git does not works on windows');
757 }
758
759 // 2/ Verify that user is root (todo: verify if other has right)
760 if (process.env.NODE_ENV != 'test' && process.getuid && process.getuid() !== 0) {
761 Common.printError(cst.PREFIX_MSG_ERR + 'To use --uid and --gid please run pm2 as root');
762 return new Error('To use UID and GID please run PM2 as root');
763 }
764
765 // 3/ Resolve user info via /etc/password
766 var passwd = require('./tools/passwd.js')
767 var users
768 try {
769 users = passwd.getUsers()
770 } catch(e) {
771 Common.printError(e);
772 return new Error(e);
773 }
774
775 var user_info = users[app.uid || app.user]
776 if (!user_info) {
777 Common.printError(`${cst.PREFIX_MSG_ERR} User ${app.uid || app.user} cannot be found`);
778 return new Error(`${cst.PREFIX_MSG_ERR} User ${app.uid || app.user} cannot be found`);
779 }
780
781 app.env.HOME = user_info.homedir
782 app.uid = parseInt(user_info.userId)
783
784 // 4/ Resolve group id if gid is specified
785 if (app.gid) {
786 var groups
787 try {
788 groups = passwd.getGroups()
789 } catch(e) {
790 Common.printError(e);
791 return new Error(e);
792 }
793 var group_info = groups[app.gid]
794 if (!group_info) {
795 Common.printError(`${cst.PREFIX_MSG_ERR} Group ${app.gid} cannot be found`);
796 return new Error(`${cst.PREFIX_MSG_ERR} Group ${app.gid} cannot be found`);
797 }
798 app.gid = parseInt(group_info.id)
799 } else {
800 app.gid = parseInt(user_info.groupId)
801 }
802 }
803
804 /**
805 * Specific options of PM2.io
806 */
807 if (process.env.PM2_DEEP_MONITORING) {
808 app.deep_monitoring = true;
809 }
810
811 if (app.automation == false) {
812 app.pmx = false;
813 }
814
815 if (app.disable_trace) {
816 app.trace = false
817 delete app.disable_trace;
818 }
819
820 /**
821 * Instances params
822 */
823 if (app.instances == 'max') {
824 app.instances = 0;
825 }
826
827 if (typeof(app.instances) === 'string') {
828 app.instances = parseInt(app.instances) || 0;
829 }
830
831 if (app.exec_mode != 'cluster_mode' &&
832 !app.instances &&
833 typeof(app.merge_logs) == 'undefined') {
834 app.merge_logs = true;
835 }
836
837 var ret;
838
839 if (app.cron_restart) {
840 if ((ret = Common.sink.determineCron(app)) instanceof Error)
841 return ret;
842 }
843
844 /**
845 * Now validation configuration
846 */
847 var ret = Config.validateJSON(app);
848 if (ret.errors && ret.errors.length > 0){
849 ret.errors.forEach(function(err) { warn(err) });
850 return new Error(ret.errors);
851 }
852
853 verifiedConf.push(ret.config);
854 }
855
856 return verifiedConf;
857}
858
859/**
860 * Get current username
861 * Called on EVERY starting app
862 *
863 * @returns {String}
864 */
865Common.getCurrentUsername = function(){
866 var current_user = '';
867
868 if (os.userInfo) {
869 try {
870 current_user = os.userInfo().username;
871 } catch (err) {
872 // For the case of unhandled error for uv_os_get_passwd
873 // https://github.com/Unitech/pm2/issues/3184
874 }
875 }
876
877 if(current_user === '') {
878 current_user = process.env.USER || process.env.LNAME || process.env.USERNAME || process.env.SUDO_USER || process.env.C9_USER || process.env.LOGNAME;
879 }
880
881 return current_user;
882}
883
884/**
885 * Render an app name if not existing.
886 * @param {Object} conf
887 */
888Common.renderApplicationName = function(conf){
889 if (!conf.name && conf.script){
890 conf.name = conf.script !== undefined ? path.basename(conf.script) : 'undefined';
891 var lastDot = conf.name.lastIndexOf('.');
892 if (lastDot > 0){
893 conf.name = conf.name.slice(0, lastDot);
894 }
895 }
896}
897
898/**
899 * Show warnings
900 * @param {String} warning
901 */
902function warn(warning){
903 Common.printOut(cst.PREFIX_MSG_WARNING + warning);
904}