pm2/lib/API/Startup.js
2017-07-03 00:31:26 +02:00

451 lines
14 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.
*/
var debug = require('debug')('pm2:cli:startup');
var chalk = require('chalk');
var path = require('path');
var fs = require('fs');
var async = require('async');
var exec = require('child_process').exec;
var Common = require('../Common.js');
var cst = require('../../constants.js');
var spawn = require('child_process').spawn;
var shelljs = require('shelljs');
module.exports = function(CLI) {
/**
* If command is launched without root right
* Display helper
*/
function isNotRoot(platform, opts, cb) {
if (opts.user) {
Common.printOut(cst.PREFIX_MSG + 'You have to run this command as root. Execute the following command:');
console.log('sudo env PATH=$PATH:' + path.dirname(process.execPath) + ' pm2 ' + opts.args[1].name() + ' ' + platform + ' -u ' + opts.user + ' --hp ' + process.env.HOME);
return cb(new Error('You have to run this with elevated rights'));
}
return exec('whoami', function(err, stdout, stderr) {
Common.printOut(cst.PREFIX_MSG + 'You have to run this command as root. Execute the following command:');
console.log('sudo env PATH=$PATH:' + path.dirname(process.execPath) + ' ' + require.main.filename + ' ' + opts.args[1].name() + ' ' + platform + ' -u ' + stdout.trim() + ' --hp ' + process.env.HOME);
return cb(new Error('You have to run this with elevated rights'));
});
}
/**
* Detect running init system
*/
function detectInitSystem() {
var hash_map = {
'systemctl' : 'systemd',
'update-rc.d': 'upstart',
'chkconfig' : 'systemv',
'rc-update' : 'openrc',
'launchctl' : 'launchd'
};
var init_systems = Object.keys(hash_map);
for (var i = 0; i < init_systems.length; i++) {
if (shelljs.which(init_systems[i]) != null) {
break;
}
}
if (i >= init_systems.length) {
Common.printError(cst.PREFIX_MSG_ERR + 'Init system not found');
return null;
}
Common.printOut(cst.PREFIX_MSG + 'Init System found: ' + chalk.bold(hash_map[init_systems[i]]));
return hash_map[init_systems[i]];
}
CLI.prototype.uninstallStartup = function(platform, opts, cb) {
var commands;
var that = this;
var actual_platform = detectInitSystem();
var user = opts.user || process.env.USER;
var service_name = 'pm2-' + user;
var openrc_service_name = 'pm2';
var launchd_service_name = 'pm2.' + user;
if (!platform)
platform = actual_platform;
else if (actual_platform && actual_platform !== platform) {
Common.printOut('-----------------------------------------------------------')
Common.printOut(' PM2 detected ' + actual_platform + ' but you precised ' + platform)
Common.printOut(' Please verify that your choice is indeed your init system')
Common.printOut(' If you arent sure, just run : pm2 startup')
Common.printOut('-----------------------------------------------------------')
}
if (platform === null)
throw new Error('Init system not found')
if (!cb) {
cb = function(err, data) {
if (err)
return that.exitCli(cst.ERROR_EXIT);
return that.exitCli(cst.SUCCESS_EXIT);
}
}
if (process.getuid() != 0) {
return isNotRoot(platform, opts, cb);
}
if (fs.existsSync('/etc/init.d/pm2-init.sh')) {
platform = 'oldsystem';
}
switch(platform) {
case 'systemd':
commands = [
'systemctl stop ' + service_name,
'systemctl disable ' + service_name,
'rm /etc/systemd/system/' + service_name + '.service'
];
break;
case 'systemv':
commands = [
'chkconfig ' + service_name + ' off',
'rm /etc/init.d/' + service_name
];
break;
case 'oldsystem':
Common.printOut(cst.PREFIX_MSG + 'Disabling and deleting old startup system');
commands = [
'update-rc.d pm2-init.sh disable',
'update-rc.d -f pm2-init.sh remove',
'rm /etc/init.d/pm2-init.sh'
];
break;
case 'openrc':
service_name = openrc_service_name;
commands = [
'/etc/init.d/' + service_name + ' stop',
'rc-update delete ' + service_name + ' default',
'rm /etc/init.d/' + service_name
];
break;
case 'upstart':
commands = [
'update-rc.d ' + service_name + ' disable',
'update-rc.d -f ' + service_name + ' remove',
'rm /etc/init.d/' + service_name
];
break;
case 'launchd':
var destination = path.join(process.env.HOME, 'Library/LaunchAgents/' + launchd_service_name + '.plist');
commands = [
'launchctl remove com.' + launchd_service_name,
'rm ' + destination
];
};
shelljs.exec(commands.join('&& '), function(code, stdout, stderr) {
Common.printOut(stdout);
Common.printOut(stderr);
if (code == 0) {
Common.printOut(cst.PREFIX_MSG + chalk.bold('Init file disabled.'));
} else {
Common.printOut(cst.ERROR_MSG + chalk.bold('Return code : ' + code));
}
cb(null, {
commands : commands,
platform : platform
});
});
};
/**
* Startup script generation
* @method startup
* @param {string} platform type (centos|redhat|amazon|gentoo|systemd)
*/
CLI.prototype.startup = function(platform, opts, cb) {
var that = this;
var actual_platform = detectInitSystem();
var user = (opts.user || process.env.USER);
var service_name = 'pm2-' + user;
var openrc_service_name = 'pm2';
var launchd_service_name = 'pm2.' + user;
if (!platform)
platform = actual_platform;
else if (actual_platform && actual_platform !== platform) {
Common.printOut('-----------------------------------------------------------')
Common.printOut(' PM2 detected ' + actual_platform + ' but you precised ' + platform)
Common.printOut(' Please verify that your choice is indeed your init system')
Common.printOut(' If you arent sure, just run : pm2 startup')
Common.printOut('-----------------------------------------------------------')
}
if (platform == null)
throw new Error('Init system not found');
if (!cb) {
cb = function(err, data) {
if (err)
return that.exitCli(cst.ERROR_EXIT);
return that.exitCli(cst.SUCCESS_EXIT);
}
}
if (process.getuid() != 0) {
return isNotRoot(platform, opts, cb);
}
var destination;
var commands;
var template;
function getTemplate(type) {
return fs.readFileSync(path.join(__dirname, '..', 'templates/init-scripts', type + '.tpl'), {encoding: 'utf8'});
}
switch(platform) {
case 'ubuntu':
case 'centos':
case 'arch':
case 'oracle':
case 'systemd':
template = getTemplate('systemd');
destination = '/etc/systemd/system/' + service_name + '.service';
commands = [
'chmod +x ' + destination,
'systemctl enable ' + service_name,
'systemctl start ' + service_name,
'systemctl daemon-reload',
'systemctl status ' + service_name
];
break;
case 'ubuntu14':
case 'ubuntu12':
case 'upstart':
template = getTemplate('upstart');
destination = '/etc/init.d/' + service_name;
commands = [
'chmod +x ' + destination,
'mkdir -p /var/lock/subsys',
'touch /var/lock/subsys/' + service_name,
'update-rc.d ' + service_name + ' defaults'
];
break;
case 'systemv':
case 'amazon':
case 'centos6':
template = getTemplate('upstart');
destination = '/etc/init.d/' + service_name;
commands = [
'chmod +x ' + destination,
'mkdir -p /var/lock/subsys',
'touch /var/lock/subsys/' + service_name,
'chkconfig --add ' + service_name,
'chkconfig ' + service_name + ' on',
'initctl list'
];
break;
case 'macos':
case 'darwin':
case 'launchd':
template = getTemplate('launchd');
destination = path.join(process.env.HOME, 'Library/LaunchAgents/' + launchd_service_name + '.plist');
commands = [
'launchctl load -w ' + destination
]
break;
case 'freebsd':
case 'rcd':
template = getTemplate('rcd');
destination = '/etc/rc.d/' + service_name;
commands = [
'chmod +x ' + destination,
'echo "pm2_enable=YES" >> /etc/rc.conf'
];
break;
case 'openrc':
template = getTemplate('openrc');
service_name = openrc_service_name;
destination = '/etc/init.d/' + service_name;
commands = [
'chmod +x ' + destination,
'rc-update add ' + service_name + ' default'
];
break;
default:
throw new Error('Unknown platform / init system name');
}
/**
* 4# Replace template variable value
*/
template = template.replace(/%PM2_PATH%/g, process.mainModule.filename)
.replace(/%NODE_PATH%/g, path.dirname(process.execPath))
.replace(/%USER%/g, user)
.replace(/%HOME_PATH%/g, opts.hp ? path.resolve(opts.hp, '.pm2') : cst.PM2_ROOT_PATH);
Common.printOut(chalk.bold('Platform'), platform);
Common.printOut(chalk.bold('Template'));
Common.printOut(template);
Common.printOut(chalk.bold('Target path'));
Common.printOut(destination);
Common.printOut(chalk.bold('Command list'));
Common.printOut(commands);
Common.printOut(cst.PREFIX_MSG + 'Writing init configuration in ' + destination);
try {
fs.writeFileSync(destination, template);
} catch (e) {
console.error(cst.PREFIX_MSG_ERR + 'Failure when trying to write startup script');
console.error(e.message || e);
return cb(e);
}
Common.printOut(cst.PREFIX_MSG + 'Making script booting at startup...');
async.forEachLimit(commands, 1, function(command, next) {
Common.printOut(chalk.bold('>>> Executing %s'), command);
shelljs.exec(command, function(code, stdout, stderr) {
if (code === 0) {
Common.printOut(chalk.bold('[DONE] '));
return next();
} else {
Common.printOut(chalk.red('[ERROR] Exit code : ' + code))
return next(new Error(command + ' failed, see error above.'));
}
})
}, function(err) {
if (err) {
console.error(cst.PREFIX_MSG_ERR + (err.message || err));
return cb(err);
}
Common.printOut(chalk.bold.blue('+---------------------------------------+'));
Common.printOut(chalk.bold.blue((cst.PREFIX_MSG + 'Freeze a process list on reboot via:' )));
Common.printOut(chalk.bold('$ pm2 save'));
Common.printOut('');
Common.printOut(chalk.bold.blue(cst.PREFIX_MSG + 'Remove init script via:'));
Common.printOut(chalk.bold('$ pm2 unstartup ' + platform));
return cb(null, {
destination : destination,
template : template
});
});
};
/**
* Dump current processes managed by pm2 into DUMP_FILE_PATH file
* @method dump
* @param {} cb
* @return
*/
CLI.prototype.dump = function(cb) {
var env_arr = [];
var that = this;
Common.printOut(cst.PREFIX_MSG + 'Saving current process list...');
that.Client.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
Common.printError('Error retrieving process list: ' + err);
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
}
/**
* Description
* @method fin
* @param {} err
* @return
*/
function fin(err) {
try {
fs.writeFileSync(cst.DUMP_FILE_PATH, JSON.stringify(env_arr, '', 2));
} catch (e) {
console.error(e.stack || e);
Common.printOut(cst.PREFIX_MSG_ERR + 'Failed to save dump file in %s', cst.DUMP_FILE_PATH);
return that.exitCli(cst.ERROR_EXIT);
}
if (cb) return cb(null, {success:true});
Common.printOut(cst.PREFIX_MSG + 'Successfully saved in %s', cst.DUMP_FILE_PATH);
return that.exitCli(cst.SUCCESS_EXIT);
}
(function ex(apps) {
if (!apps[0]) return fin(null);
delete apps[0].pm2_env.instances;
delete apps[0].pm2_env.pm_id;
if (!apps[0].pm2_env.pmx_module)
env_arr.push(apps[0].pm2_env);
apps.shift();
return ex(apps);
})(list);
});
};
/**
* Resurrect processes
* @method resurrect
* @param {} cb
* @return
*/
CLI.prototype.resurrect = function(cb) {
var apps = {};
var that = this;
Common.printOut(cst.PREFIX_MSG + 'Restoring processes located in %s', cst.DUMP_FILE_PATH);
try {
apps = fs.readFileSync(cst.DUMP_FILE_PATH);
} catch(e) {
Common.printError(cst.PREFIX_MSG_ERR + 'No processes saved; DUMP file doesn\'t exist');
// if (cb) return cb(Common.retErr(e));
// else return that.exitCli(cst.ERROR_EXIT);
return that.speedList();
}
var processes = Common.parseConfig(apps, 'none');
that.Client.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
Common.printError(err);
return that.exitCli(1);
}
var current = [];
var target = [];
list.forEach(function(app) {
if (!current[app.name])
current[app.name] = 0;
current[app.name]++;
});
processes.forEach(function(app) {
if (!target[app.name])
target[app.name] = 0;
target[app.name]++;
});
var tostart = Object.keys(target).filter(function(i) {
return Object.keys(current).indexOf(i) < 0;
})
async.eachLimit(processes, cst.CONCURRENT_ACTIONS, function(app, next) {
if (tostart.indexOf(app.name) == -1)
return next();
that.Client.executeRemote('prepare', app, function(err, dt) {
if (err)
Common.printError(err);
else
Common.printOut(cst.PREFIX_MSG + 'Process %s restored', app.pm_exec_path);
next();
});
}, function(err) {
return cb ? cb(null, apps) : that.speedList();
});
});
};
}