/** * 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) { console.log(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 startup ' + 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) { console.log(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 + ' startup ' + 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', 'launchctl' : 'launchd', 'chkconfig' : 'rcd' }; 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, cb) { var commands; var that = this; if (!platform) platform = detectInitSystem(); if (!cb) { cb = function(err, data) { if (err) return that.exitCli(cst.ERROR_EXIT); return that.exitCli(cst.SUCCESS_EXIT); } } if (process.getuid() != 0) { console.log('sudo env PATH=$PATH:' + path.dirname(process.execPath) + ' ' + require.main.filename + ' unstartup ' + platform); return cb(); } if (fs.existsSync('/etc/init.d/pm2-init.sh')) { platform = 'oldsystem'; } switch(platform) { case 'systemd': commands = [ 'systemctl stop pm2', 'systemctl disable pm2', 'systemctl status pm2', 'rm /etc/systemd/system/pm2.service' ]; 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 'upstart': commands = [ 'update-rc.d pm2 disable', 'update-rc.d -f pm2 remove', 'rm /etc/init.d/pm2' ]; break; case 'launchd': var destination = path.join(process.env.HOME, 'Library/LaunchAgents/PM2.plist'); commands = [ 'launchctl remove com.PM2', 'rm ' + destination ]; }; shelljs.exec(commands.join('; '), function(code, stdout, stderr) { console.log(stdout); console.log(stderr); if (code == 0) { Common.printOut(cst.PREFIX_MSG + chalk.bold('Init file disabled.')); } 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; if (!platform) if ((platform = detectInitSystem()) == 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/pm2.service'; commands = [ 'chmod +x ' + destination, 'systemctl enable pm2', 'systemctl start pm2', 'systemctl daemon-reload', 'systemctl status pm2' ]; break; case 'ubuntu14': case 'ubuntu12': case 'upstart': template = getTemplate('upstart'); destination = '/etc/init.d/pm2'; commands = [ 'chmod +x ' + destination, 'touch /var/lock/subsys/pm2', 'update-rc.d pm2 defaults', 'service pm2 start', 'initctl list' ]; break; case 'macos': case 'darwin': case 'launchd': // https://nathangrigg.com/2012/07/schedule-jobs-using-launchd#launchctl template = getTemplate('launchd'); destination = path.join(process.env.HOME, 'Library/LaunchAgents/PM2.plist'); commands = [ 'chmod +x ' + destination, 'launchctl load ' + destination, 'launchctl start ' + destination, 'launchctl list' ] break; case 'freebsd': case 'rcd': template = getTemplate('rcd'); destination = '/etc/rc.d/pm2'; commands = [ 'chmod +x ' + destination, 'echo "pm2_enable=YES" > /etc/rc.conf' ]; 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, opts.user || process.env.USER) .replace(/%HOME_PATH%/g, opts.hp ? path.resolve(opts.hp, '.pm2') : cst.PM2_ROOT_PATH); console.log(chalk.bold('Platform'), platform); console.log(chalk.bold('Template')); console.log(template); console.log(chalk.bold('Target path')); console.log(destination); console.log(chalk.bold('Command list')); console.log(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) { console.log(chalk.bold('>>> Executing %s'), command); shelljs.exec(command, function(err, stdout) { console.log(chalk.bold('[DONE] ')); next(); }) }, function() { console.log(chalk.bold.blue('+---------------------------------------+')); Common.printOut(chalk.bold.blue((cst.PREFIX_MSG + 'Freeze a process list on reboot via:' ))); Common.printOut(chalk.bold('$ pm2 save')); console.log(''); 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(); }); }); }; }