#!/usr/bin/env node 'use strict'; var semver = require('semver'); if (semver.satisfies(process.versions.node, '>= 6.0.0')) require('v8-compile-cache'); process.env.PM2_USAGE = 'CLI'; var cst = require('../constants.js'); var commander = require('commander'); var chalk = require('chalk'); var forEachLimit = require('async/forEachLimit'); var debug = require('debug')('pm2:cli'); var PM2 = require('../lib/API.js'); var pkg = require('../package.json'); var tabtab = require('../lib/completion.js'); var Common = require('../lib/Common.js'); var PM2ioHandler = require('../lib/API/pm2-plus/PM2IO') Common.determineSilentCLI(); Common.printVersion(); var pm2 = new PM2(); PM2ioHandler.usePM2Client(pm2) commander.version(pkg.version) .option('-v --version', 'print pm2 version') .option('-s --silent', 'hide all messages', false) .option('--ext ', 'watch only this file extensions') .option('-n --name ', 'set a name for the process in the process list') .option('-m --mini-list', 'display a compacted list without formatting') .option('--interpreter ', 'set a specific interpreter to use for executing app, default: node') .option('--interpreter-args ', 'set arguments to pass to the interpreter (alias of --node-args)') .option('--node-args ', 'space delimited arguments to pass to node') .option('-o --output ', 'specify log file for stdout') .option('-e --error ', 'specify log file for stderr') .option('-l --log [path]', 'specify log file which gathers both stdout and stderr') .option('--log-type ', 'specify log output style (raw by default, json optional)') .option('--log-date-format ', 'add custom prefix timestamp to logs') .option('--time', 'enable time logging') .option('--disable-logs', 'disable all logs storage') .option('--env ', 'specify which set of environment variables from ecosystem file must be injected') .option('-a --update-env', 'force an update of the environment with restart/reload (-a <=> apply)') .option('-f --force', 'force actions') .option('-i --instances ', 'launch [number] instances (for networked app)(load balanced)') .option('--parallel ', 'number of parallel actions (for restart/reload)') .option('-p --pid ', 'specify pid file') .option('-k --kill-timeout ', 'delay before sending final SIGKILL signal to process') .option('--listen-timeout ', 'listen timeout on application reload') .option('--max-memory-restart ', 'Restart the app if an amount of memory is exceeded (in bytes)') .option('--restart-delay ', 'specify a delay between restarts (in milliseconds)') .option('--exp-backoff-restart-delay ', 'specify a delay between restarts (in milliseconds)') .option('-x --execute-command', 'execute a program using fork system') .option('--max-restarts [count]', 'only restart the script COUNT times') .option('-u --user ', 'define user when generating startup script') .option('--uid ', 'run target script with rights') .option('--gid ', 'run target script with rights') .option('--cwd ', 'run target script as ') .option('--hp ', 'define home path when generating startup script') .option('--wait-ip', 'override systemd script to wait for full internet connectivity to launch pm2') .option('--service-name ', 'define service name when generating startup script') .option('-c --cron ', 'restart a running process based on a cron pattern') .option('-w --write', 'write configuration in local folder') .option('--no-daemon', 'run pm2 daemon in the foreground if it doesn\'t exist already') .option('--source-map-support', 'force source map support') .option('--only ', 'with json declaration, allow to only act on one application') .option('--disable-source-map-support', 'force source map support') .option('--wait-ready', 'ask pm2 to wait for ready event from your app') .option('--merge-logs', 'merge logs from different instances but keep error and out separated') .option('--watch [paths]', 'watch application folder for changes', function(v, m) { m.push(v); return m;}, []) .option('--ignore-watch ', 'List of paths to ignore (name or regex)') .option('--watch-delay ', 'specify a restart delay after changing files (--watch-delay 4 (in sec) or 4000ms)') .option('--no-color', 'skip colors') .option('--no-vizion', 'start an app without vizion feature (versioning control)') .option('--no-autorestart', 'start an app without automatic restart') .option('--no-treekill', 'Only kill the main process, not detached children') .option('--no-pmx', 'start an app without pmx') .option('--no-automation', 'start an app without pmx') .option('--trace', 'enable transaction tracing with km') .option('--disable-trace', 'disable transaction tracing with km') .option('--attach', 'attach logging after your start/restart/stop/reload') .option('--sort ', 'sort process according to field\'s name') .option('--v8', 'enable v8 data collecting') .option('--event-loop-inspector', 'enable event-loop-inspector dump in pmx') .option('--deep-monitoring', 'enable all monitoring tools (equivalent to --v8 --event-loop-inspector --trace)') .usage('[cmd] app'); function displayUsage() { console.log('usage: pm2 [options] ') console.log(''); console.log('pm2 -h, --help all available commands and options'); console.log('pm2 examples display pm2 usage examples'); console.log('pm2 -h help on a specific command'); console.log(''); console.log('Access pm2 files in ~/.pm2'); } function displayExamples() { console.log('- Start and add a process to the pm2 process list:') console.log(''); console.log(chalk.cyan(' $ pm2 start app.js --name app')); console.log(''); console.log('- Show the process list:'); console.log(''); console.log(chalk.cyan(' $ pm2 ls')); console.log(''); console.log('- Stop and delete a process from the pm2 process list:'); console.log(''); console.log(chalk.cyan(' $ pm2 delete app')); console.log(''); console.log('- Stop, start and restart a process from the process list:'); console.log(''); console.log(chalk.cyan(' $ pm2 stop app')); console.log(chalk.cyan(' $ pm2 start app')); console.log(chalk.cyan(' $ pm2 restart app')); console.log(''); console.log('- Clusterize an app to all CPU cores available:'); console.log(''); console.log(chalk.cyan(' $ pm2 start -i max')); console.log(''); console.log('- Update pm2 :'); console.log(''); console.log(chalk.cyan(' $ npm install pm2 -g && pm2 update')); console.log(''); console.log('- Install pm2 auto completion:') console.log(''); console.log(chalk.cyan(' $ pm2 completion install')) console.log(''); console.log('Check the full documentation on https://pm2.io/doc'); console.log(''); } function beginCommandProcessing() { pm2.getVersion(function(err, remote_version) { if (!err && (pkg.version != remote_version)) { console.log(''); console.log(chalk.red.bold('>>>> In-memory PM2 is out-of-date, do:\n>>>> $ pm2 update')); console.log('In memory PM2 version:', chalk.blue.bold(remote_version)); console.log('Local PM2 version:', chalk.blue.bold(pkg.version)); console.log(''); } }); commander.parse(process.argv); } function checkCompletion(){ return tabtab.complete('pm2', function(err, data) { if(err || !data) return; if(/^--\w?/.test(data.last)) return tabtab.log(commander.options.map(function (data) { return data.long; }), data); if(/^-\w?/.test(data.last)) return tabtab.log(commander.options.map(function (data) { return data.short; }), data); // array containing commands after which process name should be listed var cmdProcess = ['stop', 'restart', 'scale', 'reload', 'delete', 'reset', 'pull', 'forward', 'backward', 'logs', 'describe', 'desc', 'show']; if (cmdProcess.indexOf(data.prev) > -1) { pm2.list(function(err, list){ tabtab.log(list.map(function(el){ return el.name }), data); pm2.disconnect(); }); } else if (data.prev == 'pm2') { tabtab.log(commander.commands.map(function (data) { return data._name; }), data); pm2.disconnect(); } else pm2.disconnect(); }); }; var _arr = process.argv.indexOf('--') > -1 ? process.argv.slice(0, process.argv.indexOf('--')) : process.argv; if (_arr.indexOf('log') > -1) { process.argv[_arr.indexOf('log')] = 'logs'; } if (_arr.indexOf('--no-daemon') > -1) { // // Start daemon if it does not exist // // Function checks if --no-daemon option is present, // and starts daemon in the same process if it does not exist // console.log('pm2 launched in no-daemon mode (you can add DEBUG="*" env variable to get more messages)'); var pm2NoDaeamon = new PM2({ daemon_mode : false }); pm2NoDaeamon.connect(function() { pm2 = pm2NoDaeamon; beginCommandProcessing(); }); } else if (_arr.indexOf('startup') > -1 || _arr.indexOf('unstartup') > -1) { setTimeout(function() { commander.parse(process.argv); }, 100); } else { // HERE we instanciate the Client object pm2.connect(function() { debug('Now connected to daemon'); if (process.argv.slice(2)[0] === 'completion') { checkCompletion(); //Close client if completion related installation var third = process.argv.slice(3)[0]; if ( third == null || third === 'install' || third === 'uninstall') pm2.disconnect(); } else { beginCommandProcessing(); } }); } // // Helper function to fail when unknown command arguments are passed // function failOnUnknown(fn) { return function(arg) { if (arguments.length > 1) { console.log(cst.PREFIX_MSG + '\nUnknown command argument: ' + arg); commander.outputHelp(); process.exit(cst.ERROR_EXIT); } return fn.apply(this, arguments); }; } /** * @todo to remove at some point once it's fixed in official commander.js * https://github.com/tj/commander.js/issues/475 * * Patch Commander.js Variadic feature */ function patchCommanderArg(cmd) { var argsIndex; if ((argsIndex = commander.rawArgs.indexOf('--')) >= 0) { var optargs = commander.rawArgs.slice(argsIndex + 1); cmd = cmd.slice(0, cmd.indexOf(optargs[0])); } return cmd; } // // Start command // commander.command('start [name|file|ecosystem|id...]') .option('--watch', 'Watch folder for changes') .option('--fresh', 'Rebuild Dockerfile') .option('--daemon', 'Run container in Daemon mode (debug purposes)') .option('--container', 'Start application in container mode') .option('--dist', 'with --container; change local Dockerfile to containerize all files in current directory') .option('--image-name [name]', 'with --dist; set the exported image name') .option('--node-version [major]', 'with --container, set a specific major Node.js version') .option('--dockerdaemon', 'for debugging purpose') .description('start and daemonize an app') .action(function(cmd, opts) { if (opts.container == true && opts.dist == true) return pm2.dockerMode(cmd, opts, 'distribution'); else if (opts.container == true) return pm2.dockerMode(cmd, opts, 'development'); if (cmd == "-") { process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function (cmd) { process.stdin.pause(); pm2._startJson(cmd, commander, 'restartProcessId', 'pipe'); }); } else { // Commander.js patch cmd = patchCommanderArg(cmd); if (cmd.length === 0) { cmd = [cst.APP_CONF_DEFAULT_FILE]; } forEachLimit(cmd, 1, function(script, next) { pm2.start(script, commander, next); }, function(err) { pm2.speedList(err ? 1 : 0); }); } }); commander.command('trigger [params]') .description('trigger process action') .action(function(pm_id, action_name, params) { pm2.trigger(pm_id, action_name, params); }); commander.command('deploy ') .description('deploy your json') .action(function(cmd) { pm2.deploy(cmd, commander); }); commander.command('startOrRestart ') .description('start or restart JSON file') .action(function(file) { pm2._startJson(file, commander, 'restartProcessId'); }); commander.command('startOrReload ') .description('start or gracefully reload JSON file') .action(function(file) { pm2._startJson(file, commander, 'reloadProcessId'); }); commander.command('pid [app_name]') .description('return pid of [app_name] or all') .action(function(app) { pm2.getPID(app); }); commander.command('startOrGracefulReload ') .description('start or gracefully reload JSON file') .action(function(file) { pm2._startJson(file, commander, 'softReloadProcessId'); }); // // Stop specific id // commander.command('stop ') .option('--watch', 'Stop watching folder for changes') .description('stop a process') .action(function(param) { forEachLimit(param, 1, function(script, next) { pm2.stop(script, next); }, function(err) { pm2.speedList(err ? 1 : 0); }); }); // // Stop All processes // commander.command('restart ') .option('--watch', 'Toggle watching folder for changes') .description('restart a process') .action(function(param) { // Commander.js patch param = patchCommanderArg(param); forEachLimit(param, 1, function(script, next) { pm2.restart(script, commander, next); }, function(err) { pm2.speedList(err ? 1 : 0); }); }); // // Scale up/down a process in cluster mode // commander.command('scale ') .description('scale up/down a process in cluster mode depending on total_number param') .action(function(app_name, number) { pm2.scale(app_name, number); }); // // snapshot PM2 // commander.command('profile:mem [time]') .description('Sample PM2 heap memory') .action(function(time) { pm2.profile('mem', time); }); // // snapshot PM2 // commander.command('profile:cpu [time]') .description('Profile PM2 cpu') .action(function(time) { pm2.profile('cpu', time); }); // // Reload process(es) // commander.command('reload ') .description('reload processes (note that its for app using HTTP/HTTPS)') .action(function(pm2_id) { pm2.reload(pm2_id, commander); }); commander.command('id ') .description('get process id by name') .action(function(name) { pm2.getProcessIdByName(name); }); // Inspect a process commander.command('inspect ') .description('inspect a process') .action(function(cmd) { pm2.inspect(cmd, commander); }); // // Stop and delete a process by name from database // commander.command('delete ') .alias('del') .description('stop and delete a process from pm2 process list') .action(function(name) { if (name == "-") { process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function (param) { process.stdin.pause(); pm2.delete(param, 'pipe'); }); } else forEachLimit(name, 1, function(script, next) { pm2.delete(script,'', next); }, function(err) { pm2.speedList(err ? 1 : 0); }); }); // // Send system signal to process // commander.command('sendSignal ') .description('send a system signal to the target process') .action(function(signal, pm2_id) { if (isNaN(parseInt(pm2_id))) { console.log(cst.PREFIX_MSG + 'Sending signal to process name ' + pm2_id); pm2.sendSignalToProcessName(signal, pm2_id); } else { console.log(cst.PREFIX_MSG + 'Sending signal to process id ' + pm2_id); pm2.sendSignalToProcessId(signal, pm2_id); } }); // // Stop and delete a process by name from database // commander.command('ping') .description('ping pm2 daemon - if not up it will launch it') .action(function() { pm2.ping(); }); commander.command('updatePM2') .description('update in-memory PM2 with local PM2') .action(function() { pm2.update(); }); commander.command('update') .description('(alias) update in-memory PM2 with local PM2') .action(function() { pm2.update(); }); /** * Module specifics */ commander.command('install ') .alias('module:install') .option('--tarball', 'is local tarball') .option('--http', 'is remote tarball') .option('--docker', 'is docker container') .option('--v1', 'install module in v1 manner (do not use it)') .option('--safe [time]', 'keep module backup, if new module fail = restore with previous') .description('install or update a module and run it forever') .action(function(plugin_name, opts) { require('util')._extend(commander, opts) pm2.install(plugin_name, commander); }); commander.command('module:update ') .description('update a module and run it forever') .action(function(plugin_name) { pm2.install(plugin_name); }); commander.command('module:generate [app_name]') .description('Generate a sample module in current folder') .action(function(app_name) { pm2.generateModuleSample(app_name); }); commander.command('uninstall ') .alias('module:uninstall') .description('stop and uninstall a module') .action(function(plugin_name) { pm2.uninstall(plugin_name); }); commander.command('package [target]') .description('Check & Package TAR type module') .action(function(target) { pm2.package(target); }); commander.command('publish [folder]') .option('--npm', 'publish on npm') .alias('module:publish') .description('Publish the module you are currently on') .action(function(folder, opts) { pm2.publish(folder, opts); }); commander.command('set [key] [value]') .description('sets the specified config ') .action(function(key, value) { pm2.set(key, value); }); commander.command('multiset ') .description('multiset eg "key1 val1 key2 val2') .action(function(str) { pm2.multiset(str); }); commander.command('get [key]') .description('get value for ') .action(function(key) { pm2.get(key); }); commander.command('conf [key] [value]') .description('get / set module config values') .action(function(key, value) { pm2.conf(key, value); }); commander.command('config [value]') .description('get / set module config values') .action(function(key, value) { pm2.conf(key, value); }); commander.command('unset ') .description('clears the specified config ') .action(function(key) { pm2.unset(key); }); commander.command('report') .description('give a full pm2 report for https://github.com/Unitech/pm2/issues') .action(function(key) { pm2.report(); }); // // PM2 I/O // commander.command('link [secret] [public] [name]') .option('--info-node [url]', 'set url info node') .option('--ws', 'websocket mode') .option('--axon', 'axon mode') .description('link with the pm2 monitoring dashboard') .action(pm2.linkManagement.bind(pm2)); commander.command('unlink') .description('unlink with the pm2 monitoring dashboard') .action(function() { pm2.unlink(); }); commander.command('monitor [name]') .description('monitor target process') .action(function(name) { if (name === undefined) { return plusHandler() } pm2.monitorState('monitor', name); }); commander.command('unmonitor [name]') .description('unmonitor target process') .action(function(name) { pm2.monitorState('unmonitor', name); }); commander.command('open') .description('open the pm2 monitoring dashboard') .action(function(name) { pm2.openDashboard(); }); function plusHandler (command, opts) { if (opts && opts.infoNode) { process.env.KEYMETRICS_NODE = opts.infoNode } return PM2ioHandler.launch(command, opts) } commander.command('plus [command] [option]') .alias('register') .option('--info-node [url]', 'set url info node for on-premise pm2 plus') .option('-d --discrete', 'silent mode') .option('-a --install-all', 'install all modules (force yes)') .description('enable pm2 plus') .action(plusHandler); commander.command('login') .description('Login to pm2 plus') .action(function() { return plusHandler('login') }); commander.command('logout') .description('Logout from pm2 plus') .action(function() { return plusHandler('logout') }); // // Web interface // commander.command('web') .description('launch a health API on ' + cst.WEB_IPADDR + ':' + cst.WEB_PORT) .action(function() { console.log('Launching web interface on ' + cst.WEB_IPADDR + ':' + cst.WEB_PORT); pm2.web(); }); // // Save processes to file // commander.command('dump') .alias('save') .description('dump all processes for resurrecting them later') .action(failOnUnknown(function() { pm2.dump(); })); // // Delete dump file // commander.command('cleardump') .description('Create empty dump file') .action(failOnUnknown(function() { pm2.clearDump(); })); // // Save processes to file // commander.command('send ') .description('send stdin to ') .action(function(pm_id, line) { pm2.sendLineToStdin(pm_id, line); }); // // Attach to stdin/stdout // Not TTY ready // commander.command('attach [command separator]') .description('attach stdin/stdout to application identified by ') .action(function(pm_id, separator) { pm2.attach(pm_id, separator); }); // // Resurrect // commander.command('resurrect') .description('resurrect previously dumped processes') .action(failOnUnknown(function() { console.log(cst.PREFIX_MSG + 'Resurrecting'); pm2.resurrect(); })); // // Set pm2 to startup // commander.command('unstartup [platform]') .description('disable the pm2 startup hook') .action(function(platform) { pm2.uninstallStartup(platform, commander); }); // // Set pm2 to startup // commander.command('startup [platform]') .description('enable the pm2 startup hook') .action(function(platform) { pm2.startup(platform, commander); }); // // Logrotate // commander.command('logrotate') .description('copy default logrotate configuration') .action(function(cmd) { pm2.logrotate(commander); }); // // Sample generate // commander.command('ecosystem [mode]') .alias('init') .description('generate a process conf file. (mode = null or simple)') .action(function(mode) { pm2.generateSample(mode); }); commander.command('reset ') .description('reset counters for process') .action(function(proc_id) { pm2.reset(proc_id); }); commander.command('describe ') .description('describe all parameters of a process id') .action(function(proc_id) { pm2.describe(proc_id); }); commander.command('desc ') .description('(alias) describe all parameters of a process id') .action(function(proc_id) { pm2.describe(proc_id); }); commander.command('info ') .description('(alias) describe all parameters of a process id') .action(function(proc_id) { pm2.describe(proc_id); }); commander.command('env ') .description('(alias) describe all parameters of a process id') .action(function(proc_id) { pm2.env(proc_id); }); commander.command('show ') .description('(alias) describe all parameters of a process id') .action(function(proc_id) { pm2.describe(proc_id); }); // // List command // commander .command('list') .alias('ls') .description('list all processes') .action(function() { pm2.list(commander) }); commander.command('l') .description('(alias) list all processes') .action(function() { pm2.list() }); commander.command('ps') .description('(alias) list all processes') .action(function() { pm2.list() }); commander.command('status') .description('(alias) list all processes') .action(function() { pm2.list() }); // List in raw json commander.command('jlist') .description('list all processes in JSON format') .action(function() { pm2.jlist() }); // List in prettified Json commander.command('prettylist') .description('print json in a prettified JSON') .action(failOnUnknown(function() { pm2.jlist(true); })); // // Dashboard command // commander.command('monit') .description('launch termcaps monitoring') .action(function() { pm2.dashboard(); }); commander.command('imonit') .description('launch legacy termcaps monitoring') .action(function() { pm2.monit(); }); commander.command('dashboard') .alias('dash') .description('launch dashboard with monitoring and logs') .action(function() { pm2.dashboard(); }); // // Flushing command // commander.command('flush [api]') .description('flush logs') .action(function(api) { pm2.flush(api); }); /* old version commander.command('flush') .description('flush logs') .action(failOnUnknown(function() { pm2.flush(); })); */ // // Reload all logs // commander.command('reloadLogs') .description('reload all logs') .action(function() { pm2.reloadLogs(); }); // // Log streaming // commander.command('logs [id|name]') .option('--json', 'json log output') .option('--format', 'formated log output') .option('--raw', 'raw output') .option('--err', 'only shows error output') .option('--out', 'only shows standard output') .option('--lines ', 'output the last N lines, instead of the last 15 by default') .option('--timestamp [format]', 'add timestamps (default format YYYY-MM-DD-HH:mm:ss)') .option('--nostream', 'print logs without lauching the log stream') .description('stream logs file. Default stream all logs') .action(function(id, cmd) { var Logs = require('../lib/API/Log.js'); if (!id) id = 'all'; var line = 15; var raw = false; var exclusive = false; var timestamp = false; if(!isNaN(parseInt(cmd.lines))) { line = parseInt(cmd.lines); } if (cmd.parent.rawArgs.indexOf('--raw') !== -1) raw = true; if (cmd.timestamp) timestamp = typeof cmd.timestamp === 'string' ? cmd.timestamp : 'YYYY-MM-DD-HH:mm:ss'; if (cmd.out === true) exclusive = 'out'; if (cmd.err === true) exclusive = 'err'; if (cmd.nostream === true) pm2.printLogs(id, line, raw, timestamp, exclusive); else if (cmd.json === true) Logs.jsonStream(pm2.Client, id); else if (cmd.format === true) Logs.formatStream(pm2.Client, id, false, 'YYYY-MM-DD-HH:mm:ssZZ'); else pm2.streamLogs(id, line, raw, timestamp, exclusive); }); // // Kill // commander.command('kill') .description('kill daemon') .action(failOnUnknown(function(arg) { pm2.killDaemon(function() { process.exit(cst.SUCCESS_EXIT); }); })); // // Update repository for a given app // commander.command('pull [commit_id]') .description('updates repository for a given app') .action(function(pm2_name, commit_id) { if (commit_id !== undefined) { pm2._pullCommitId({ pm2_name: pm2_name, commit_id: commit_id }); } else pm2.pullAndRestart(pm2_name); }); // // Update repository to the next commit for a given app // commander.command('forward ') .description('updates repository to the next commit for a given app') .action(function(pm2_name) { pm2.forward(pm2_name); }); // // Downgrade repository to the previous commit for a given app // commander.command('backward ') .description('downgrades repository to the previous commit for a given app') .action(function(pm2_name) { pm2.backward(pm2_name); }); // // Perform a deep update of PM2 // commander.command('deepUpdate') .description('performs a deep update of PM2') .action(function() { pm2.deepUpdate(); }); // // Launch a http server that expose a given path on given port // commander.command('serve [path] [port]') .alias('expose') .option('--port [port]', 'specify port to listen to') .description('serve a directory over http via port') .action(function (path, port, cmd) { pm2.serve(path, port || cmd.port, commander); }); commander.command('examples') .description('display pm2 usage examples') .action(() => { console.log(cst.PREFIX_MSG + chalk.grey('pm2 usage examples:\n')); displayExamples(); process.exit(cst.SUCCESS_EXIT); }) // // Catch all // commander.command('*') .action(function() { console.log(cst.PREFIX_MSG + 'Command not found\n'); displayUsage(); // Check if it does not forget to close fds from RPC process.exit(cst.ERROR_EXIT); }); // // Display help if 0 arguments passed to pm2 // if (process.argv.length == 2) { commander.parse(process.argv); displayUsage(); // Check if it does not forget to close fds from RPC process.exit(cst.ERROR_EXIT); }