/** * 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'); 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(chalk.grey(' sudo su -c "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(chalk.grey(' sudo su -c "env PATH=$PATH:' + path.dirname(process.execPath) + ' pm2 startup ' + platform + ' -u ' + stdout.trim() + ' --hp ' + process.env.HOME + '"')); return cb(new Error('You have to run this with elevated rights')); }); } /** * 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 (!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 = '/etc/init.d/pm2-init.sh'; var templateName = cst.UBUNTU_STARTUP_SCRIPT; var templatePath; var template; var cmd; var cmdAsUser; /** * 1# Determine startup script destination */ if (platform == 'redhat') { platform = 'centos'; } if (platform == 'systemd') { destination = '/etc/systemd/system/pm2.service'; } if (platform == 'darwin') { destination = path.join(process.env.HOME, 'Library/LaunchAgents/io.keymetrics.PM2.plist'); if (!fs.existsSync(path.dirname(destination))) { fs.mkdirSync(path.dirname(destination)); } } if (platform == 'freebsd') { destination = '/etc/rc.d/pm2'; } /** * 2# Resolve template file path */ if (!!~['freebsd', 'systemd', 'centos', 'amazon', 'gentoo', 'darwin'].indexOf(platform)) templateName = cst[platform.toUpperCase() + '_STARTUP_SCRIPT']; templatePath = path.join(cst.TEMPLATE_FOLDER, templateName); /** * 3# Read startup template file */ debug('Getting startup template script %s', templatePath); template = fs.readFileSync(templatePath, {encoding: 'utf8'}); var user = opts.user || 'root'; /** * 4# Replace template variable value */ template = template.replace(/%PM2_PATH%/g, process.mainModule.filename) .replace(/%NODE_PATH%/g, platform != 'darwin' ? path.dirname(process.execPath) : process.env.PATH) .replace(/%USER%/g, user); // Set the right user home path if (opts.hp) template = template.replace(/%HOME_PATH%/g, path.resolve(opts.hp, '.pm2')); else template = template.replace(/%HOME_PATH%/g, cst.PM2_ROOT_PATH); /** * 5# Writing generated startup script into the right folder */ Common.printOut(cst.PREFIX_MSG + 'Writing startup script 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); } /** * 6# Making startup script executable */ Common.printOut(cst.PREFIX_MSG + 'Making script booting at startup...'); switch (platform) { case 'systemd': cmdAsUser = [ 'pm2 dump', //We need an empty dump so that the first resurrect works correctly 'pm2 kill', ].join(' && '); cmd = [ 'systemctl daemon-reload', 'systemctl enable pm2', 'systemctl start pm2' ].join(' && '); break; case 'centos': case 'amazon': cmd = 'chmod +x ' + destination + '; chkconfig --add ' + path.basename(destination); fs.closeSync(fs.openSync('/var/lock/subsys/pm2-init.sh', 'w')); Common.printOut(cst.PREFIX_MSG + '/var/lock/subsys/pm2-init.sh lockfile has been added'); break; case 'gentoo': cmd = 'chmod +x ' + destination + '; rc-update add ' + path.basename(destination) + ' default'; break; case 'freebsd': cmd = 'chmod +x ' + destination; break; default : cmd = 'chmod +x ' + destination + ' && update-rc.d ' + path.basename(destination) + ' defaults'; break; } if (platform == 'systemd') { cmd = 'su ' + user + ' -c "' + cmdAsUser + '" && su root -c "' + cmd + '"'; } else if (platform == 'freebsd') { cmd = 'su root -c "' + cmd + '"'; } else if (platform != 'darwin') { cmd = 'su -c "' + cmd + '"'; } else { cmd = 'pm2 dump'; } Common.printOut(cst.PREFIX_MSG + '-' + platform + '- Using the command:\n %s', chalk.grey(cmd)); exec(cmd, function(err, stdo, stde) { if (err) { Common.printError(err); Common.printError('----- Are you sure you use the right platform command line option ? centos / redhat, amazon, ubuntu, gentoo, systemd or darwin?'); return cb(Common.retErr(err)); } Common.printOut(stde.toString().replace(/[\r\n]$/, '')); Common.printOut(stdo.toString().replace(/[\r\n]$/, '')); Common.printOut(cst.PREFIX_MSG + 'Done.'); console.log(''); Common.printOut(cst.PREFIX_MSG + 'Now you can type' ); console.log(chalk.bold.blue('$ pm2 save')); Common.printOut(cst.PREFIX_MSG + 'To save the current process list at reboot or via pm2 update'); return cb(null, { destination : destination, templatePath : templatePath }); }); }; /** * 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); } 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); } var processes = Common.parseConfig(apps, 'none'); async.eachLimit(processes, cst.CONCURRENT_ACTIONS, function(app, 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(); }); }; }