mirror of
https://github.com/Unitech/pm2.git
synced 2025-12-08 20:35:53 +00:00
434 lines
11 KiB
JavaScript
434 lines
11 KiB
JavaScript
/**
|
|
* Copyright 2013 the PM2 project authors. All rights reserved.
|
|
* Use of this source code is governed by a license that
|
|
* can be found in the LICENSE file.
|
|
*/
|
|
/**
|
|
* @file Common utilities
|
|
* @project PM2
|
|
*/
|
|
|
|
/**
|
|
* Module dependencies
|
|
*/
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var util = require('util');
|
|
var mkdirp = require('mkdirp');
|
|
var cronJob = require('cron').CronJob;
|
|
var isBinary = require('./tools/isbinaryfile.js');
|
|
var Utility = require('./Utility.js');
|
|
var async = require('async');
|
|
var cst = require('../constants.js');
|
|
var extItps = require('./CLI/interpreter.json');
|
|
var shelljs = require('shelljs');
|
|
var p = path;
|
|
var Satan = require('./Satan.js');
|
|
|
|
var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js');
|
|
|
|
/**
|
|
* Common methods (used by CLI and God)
|
|
*/
|
|
|
|
var Common = module.exports;
|
|
|
|
/**
|
|
* Resolve app paths and replace missing values with defaults.
|
|
* @method prepareAppConf
|
|
* @param app {Object}
|
|
* @param {} cwd
|
|
* @param {} outputter
|
|
* @return app
|
|
*/
|
|
Common.prepareAppConf = function(app, outputter) {
|
|
/**
|
|
* Minimum validation
|
|
*/
|
|
if (!app.script)
|
|
return new Error('No script path - aborting');
|
|
|
|
// Forbidden application name
|
|
if (app.name == 'push')
|
|
return new Error('Push application name is not allowed');
|
|
|
|
if (app.automation == false)
|
|
app.pmx = false;
|
|
|
|
if (!app.node_args)
|
|
app.node_args = [];
|
|
|
|
if (app.port && app.env)
|
|
app.env.PORT = app.port;
|
|
|
|
// CRON
|
|
var ret;
|
|
if ((ret = Common.sink.determineCron(app)) instanceof Error)
|
|
return ret;
|
|
|
|
var cwd = null;
|
|
|
|
if (app.cwd) {
|
|
cwd = p.resolve(app.cwd);
|
|
process.env.PWD = app.cwd;
|
|
}
|
|
|
|
// CWD option resolving
|
|
cwd && (cwd[0] != '/') && (cwd = p.resolve(process.cwd(), cwd));
|
|
cwd = cwd || process.cwd();
|
|
|
|
// Full path script resolution
|
|
app.pm_exec_path = p.resolve(cwd, app.script);
|
|
|
|
// If script does not exists after resolution
|
|
if (!fs.existsSync(app.pm_exec_path)) {
|
|
var ckd;
|
|
// Try resolve command available in $PATH
|
|
if ((ckd = shelljs.which(app.script)))
|
|
app.pm_exec_path = ckd;
|
|
else
|
|
// Throw critical error
|
|
return new Error('script not found : ' + app.pm_exec_path);
|
|
}
|
|
|
|
/**
|
|
* Auto detect .map file and enable source map support automatically
|
|
*/
|
|
if (app.disable_source_map_support != true) {
|
|
try {
|
|
if (fs.accessSync) {
|
|
fs.accessSync(app.pm_exec_path + '.map', fs.R_OK);
|
|
app.source_map_support = true;
|
|
}
|
|
else {
|
|
// Support for Node 0.10.x
|
|
if (fs.existsSync(app.pm_exec_path + '.map')) {
|
|
app.source_map_support = true;
|
|
}
|
|
}
|
|
} catch(e) {}
|
|
delete app.disable_source_map_support;
|
|
}
|
|
|
|
delete app.script;
|
|
|
|
// Set current env by first adding the process environment and then extending/replacing it
|
|
// with env specified on command-line or JSON file.
|
|
|
|
var env = {};
|
|
|
|
/**
|
|
* Do not copy internal pm2 environment variables if acting on process
|
|
* is made from a programmatic script started by PM2
|
|
*/
|
|
if (process.env.PM2_PROGRAMMATIC)
|
|
Common.safeExtend(env, process.env);
|
|
else
|
|
env = process.env;
|
|
|
|
app.env = [{}, env, app.env || {}, {pm_cwd: cwd}].reduce(function(e1, e2){
|
|
return util._extend(e1, e2);
|
|
});
|
|
|
|
|
|
app.pm_cwd = cwd;
|
|
|
|
// Interpreter
|
|
Common.sink.resolveInterpreter(app);
|
|
|
|
// Exec mode and cluster stuff
|
|
Common.sink.determineExecMode(app);
|
|
|
|
/**
|
|
* Scary
|
|
*/
|
|
var formated_app_name = app.name.replace(/[^a-zA-Z0-9\\.\\-]/g, '-');
|
|
|
|
['log', 'out', 'error', 'pid'].forEach(function(f){
|
|
|
|
var af = app[f + '_file'], ps, ext = (f == 'pid' ? 'pid':'log'), isStd = !~['log', 'pid'].indexOf(f);
|
|
if ((f == 'log' && typeof af == 'boolean' && af) || (f != 'log' && !af)) {
|
|
ps = [cst['DEFAULT_' + ext.toUpperCase() + '_PATH'], formated_app_name + (isStd ? '-' + f : '') + '.' + ext];
|
|
} else if (f != 'log' || (f == 'log' && af)) {
|
|
ps = [cwd, af];
|
|
|
|
if (!fs.existsSync(path.dirname(af))) {
|
|
Common.printError(cst.PREFIX_MSG_ERR + 'Folder does not exists: ' + path.dirname(af));
|
|
Common.printOut(cst.PREFIX_MSG + 'Creating folder: ' + path.dirname(af));
|
|
mkdirp(path.dirname(af), function(err) {
|
|
if (!err) return;
|
|
Common.printError(cst.PREFIX_MSG_ERR + 'Could not create folder: ' + path.dirname(af));
|
|
throw new Error('Could not create folder');
|
|
});
|
|
}
|
|
|
|
}
|
|
// PM2 paths
|
|
ps && (app['pm_' + (isStd ? f.substr(0, 3) + '_' : '') + ext + '_path'] = p.resolve.apply(null, ps));
|
|
delete app[f + '_file'];
|
|
});
|
|
|
|
return app;
|
|
};
|
|
|
|
Common.sink = {};
|
|
|
|
Common.sink.determineCron = function(app) {
|
|
if (app.cron_restart) {
|
|
try {
|
|
Common.printOut(cst.PREFIX_MSG + 'cron restart at ' + app.cron_restart);
|
|
new cronJob(app.cron_restart, function() {
|
|
Common.printOut(cst.PREFIX_MSG + 'cron pattern for auto restart detected and valid');
|
|
});
|
|
} catch(ex) {
|
|
return new Error('Cron pattern is not valid, trace: ' + ex.stack);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle alias (fork <=> fork_mode, cluster <=> cluster_mode)
|
|
*/
|
|
Common.sink.determineExecMode = function(app) {
|
|
if (typeof app.instances == 'undefined')
|
|
app.instances = 1;
|
|
if (app.exec_mode)
|
|
app.exec_mode = app.exec_mode.replace(/^(fork|cluster)$/, '$1_mode');
|
|
|
|
/**
|
|
* Here we put the default exec mode
|
|
*/
|
|
if (!app.exec_mode && app.instances > 1) {
|
|
app.exec_mode = 'cluster_mode';
|
|
} else if (!app.exec_mode) {
|
|
app.exec_mode = 'fork_mode';
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Resolve interpreter
|
|
*/
|
|
Common.sink.resolveInterpreter = function(app) {
|
|
var noInterpreter = (!app.exec_interpreter || 'none' == app.exec_interpreter),
|
|
extName = p.extname(app.pm_exec_path),
|
|
betterInterpreter = extItps[extName];
|
|
|
|
var thereIsNVMInstalled = false;
|
|
|
|
// No interpreter defined and correspondance in schema hashmap
|
|
if (noInterpreter && betterInterpreter)
|
|
app.exec_interpreter = betterInterpreter;
|
|
// Else if no Interpreter detect if process is binary
|
|
else if (noInterpreter)
|
|
app.exec_interpreter = isBinary(app.pm_exec_path) ? 'none' : 'node';
|
|
else if (app.exec_interpreter.indexOf('node@') > -1 ||
|
|
app.node_version && thereIsNVMInstalled)
|
|
console.log('Special interpreter defined');
|
|
return app;
|
|
};
|
|
|
|
Common.deepCopy = Common.serialize = Common.clone = function(obj) {
|
|
if (obj === null || obj === undefined) return {};
|
|
|
|
return Utility.clone(obj);
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method exitCli
|
|
* @param {} code
|
|
* @return CallExpression
|
|
*/
|
|
Common.exitCli = function(code) {
|
|
InteractorDaemonizer.disconnectRPC(function() {
|
|
Satan.disconnectRPC(function() {
|
|
code = code || 0;
|
|
// Safe exits process after all streams are drained.
|
|
// file descriptor flag.
|
|
var fds = 0;
|
|
// exits process when stdout (1) and sdterr(2) are both drained.
|
|
function tryToExit() {
|
|
if ((fds & 1) && (fds & 2)) {
|
|
process.exit(code);
|
|
}
|
|
}
|
|
|
|
[process.stdout, process.stderr].forEach(function(std) {
|
|
var fd = std.fd;
|
|
if (!std.bufferSize) {
|
|
// bufferSize equals 0 means current stream is drained.
|
|
fds = fds | fd;
|
|
} else {
|
|
// Appends nothing to the std queue, but will trigger `tryToExit` event on `drain`.
|
|
std.write && std.write('', function() {
|
|
fds = fds | fd;
|
|
tryToExit();
|
|
});
|
|
}
|
|
// Does not write anything more.
|
|
delete std.write;
|
|
});
|
|
tryToExit();
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method printError
|
|
* @param {} msg
|
|
* @return CallExpression
|
|
*/
|
|
Common.printError = function(msg) {
|
|
if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
|
|
if (msg instanceof Error)
|
|
return console.error(msg.message);
|
|
return console.error.apply(console, arguments);
|
|
};
|
|
|
|
/**
|
|
* Description
|
|
* @method printOut
|
|
* @return
|
|
*/
|
|
Common.printOut = function() {
|
|
if (process.env.PM2_SILENT === 'true' || process.env.PM2_PROGRAMMATIC === 'true') return false;
|
|
return console.log.apply(console, arguments);
|
|
};
|
|
|
|
Common.getAllModulesId = function(cb) {
|
|
var found_proc = [];
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError('Error retrieving process list: ' + err);
|
|
return cb(err);
|
|
}
|
|
|
|
list.forEach(function(proc) {
|
|
if (proc.pm2_env.pmx_module)
|
|
found_proc.push(proc.pm_id);
|
|
});
|
|
|
|
return cb(null, found_proc);
|
|
});
|
|
};
|
|
|
|
Common.getAllProcess = function(cb) {
|
|
var found_proc = [];
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError('Error retrieving process list: ' + err);
|
|
return cb(err);
|
|
}
|
|
|
|
list.forEach(function(proc) {
|
|
found_proc.push(proc);
|
|
});
|
|
|
|
return cb(null, found_proc);
|
|
});
|
|
};
|
|
|
|
Common.getAllProcessId = function(cb) {
|
|
var found_proc = [];
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError('Error retrieving process list: ' + err);
|
|
return cb(err);
|
|
}
|
|
|
|
list.forEach(function(proc) {
|
|
if (!proc.pm2_env.pmx_module)
|
|
found_proc.push(proc.pm_id);
|
|
});
|
|
|
|
return cb(null, found_proc);
|
|
});
|
|
};
|
|
|
|
Common.getProcessIdByName = function(name, force_all, cb) {
|
|
var found_proc = [];
|
|
var full_details = {};
|
|
|
|
if (typeof(cb) === 'undefined') {
|
|
cb = force_all;
|
|
force_all = false;
|
|
}
|
|
|
|
if (typeof(name) == 'number')
|
|
name = name.toString();
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError('Error retrieving process list: ' + err);
|
|
return cb(err);
|
|
}
|
|
|
|
list.forEach(function(proc) {
|
|
if ((proc.pm2_env.name == name || proc.pm2_env.pm_exec_path == p.resolve(name)) &&
|
|
!(proc.pm2_env.pmx_module && !force_all)) {
|
|
found_proc.push(proc.pm_id);
|
|
full_details[proc.pm_id] = proc;
|
|
}
|
|
});
|
|
|
|
return cb(null, found_proc, full_details);
|
|
});
|
|
};
|
|
|
|
Common.getProcessByName = function(name, cb) {
|
|
var found_proc = [];
|
|
|
|
Satan.executeRemote('getMonitorData', {}, function(err, list) {
|
|
if (err) {
|
|
Common.printError('Error retrieving process list: ' + err);
|
|
return cb(err);
|
|
}
|
|
|
|
list.forEach(function(proc) {
|
|
if (proc.pm2_env.name == name ||
|
|
proc.pm2_env.pm_exec_path == p.resolve(name)) {
|
|
found_proc.push(proc);
|
|
}
|
|
});
|
|
|
|
return cb(null, found_proc);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Raw extend
|
|
*/
|
|
Common.extend = function(destination, source){
|
|
if (!source || typeof source != 'object') return destination;
|
|
|
|
Object.keys(source).forEach(function(new_key) {
|
|
if (source[new_key] != '[object Object]')
|
|
destination[new_key] = source[new_key];
|
|
});
|
|
|
|
return destination;
|
|
};
|
|
|
|
/**
|
|
* This is useful when starting script programmatically
|
|
*/
|
|
Common.safeExtend = function(origin, add){
|
|
if (!add || typeof add != 'object') return origin;
|
|
|
|
//Ignore PM2's set environment variables from the nested env
|
|
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', 'unstable_restarts', 'restart_time', 'pm_id', 'axm_actions', 'pmx_module', 'command', 'watch', 'versioning', 'vizion_runing', 'MODULE_DEBUG'];
|
|
|
|
var keys = Object.keys(add);
|
|
var i = keys.length;
|
|
while (i--) {
|
|
//Only copy stuff into the env that we don't have already.
|
|
if(keysToIgnore.indexOf(keys[i]) == -1 && add[keys[i]] != '[object Object]')
|
|
origin[keys[i]] = add[keys[i]];
|
|
}
|
|
return origin;
|
|
};
|