feat: 초기 프로젝트 설정 및 룰.md 파일 추가

This commit is contained in:
2025-07-28 09:53:31 +09:00
commit 09a4d38512
8165 changed files with 1021855 additions and 0 deletions

View File

@@ -0,0 +1,372 @@
'use strict'
var cst = require('../../../constants.js');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs');
const Table = require('cli-tableau');
const pkg = require('../../../package.json')
const IOAPI = require('@pm2/js-api')
const promptly = require('promptly')
var CLIStrategy = require('./auth-strategies/CliAuth')
var WebStrategy = require('./auth-strategies/WebAuth')
const exec = require('child_process').exec
const OAUTH_CLIENT_ID_WEB = '138558311'
const OAUTH_CLIENT_ID_CLI = '0943857435'
module.exports = class PM2ioHandler {
static usePM2Client (instance) {
this.pm2 = instance
}
static strategy () {
switch (process.platform) {
case 'darwin': {
return new WebStrategy({
client_id: OAUTH_CLIENT_ID_WEB
})
}
case 'win32': {
return new WebStrategy({
client_id: OAUTH_CLIENT_ID_WEB
})
}
case 'linux': {
const isDesktop = process.env.XDG_CURRENT_DESKTOP || process.env.XDG_SESSION_DESKTOP || process.env.DISPLAY
const isSSH = process.env.SSH_TTY || process.env.SSH_CONNECTION
if (isDesktop && !isSSH) {
return new WebStrategy({
client_id: OAUTH_CLIENT_ID_WEB
})
} else {
return new CLIStrategy({
client_id: OAUTH_CLIENT_ID_CLI
})
}
}
default: {
return new CLIStrategy({
client_id: OAUTH_CLIENT_ID_CLI
})
}
}
}
static init () {
this._strategy = this.strategy()
/**
* If you are using a local backend you should give those options :
* {
* services: {
* API: 'http://localhost:3000',
* OAUTH: 'http://localhost:3100'
* }
* }
*/
this.io = new IOAPI().use(this._strategy)
}
static launch (command, opts) {
// first init the strategy and the io client
this.init()
switch (command) {
case 'connect' :
case 'login' :
case 'register' :
case undefined :
case 'authenticate' : {
this.authenticate()
break
}
case 'validate' : {
this.validateAccount(opts)
break
}
case 'help' :
case 'welcome': {
var dt = fs.readFileSync(path.join(__dirname, './pres/welcome'));
console.log(dt.toString());
return process.exit(0)
}
case 'logout': {
this._strategy.isAuthenticated().then(isConnected => {
// try to kill the agent anyway
this.pm2.killAgent(err => {})
if (isConnected === false) {
console.log(`${cst.PM2_IO_MSG} Already disconnected`)
return process.exit(0)
}
this._strategy._retrieveTokens((err, tokens) => {
if (err) {
console.log(`${cst.PM2_IO_MSG} Successfully disconnected`)
return process.exit(0)
}
this._strategy.deleteTokens(this.io).then(_ => {
console.log(`${cst.PM2_IO_MSG} Successfully disconnected`)
return process.exit(0)
}).catch(err => {
console.log(`${cst.PM2_IO_MSG_ERR} Unexpected error: ${err.message}`)
return process.exit(1)
})
})
}).catch(err => {
console.error(`${cst.PM2_IO_MSG_ERR} Failed to logout: ${err.message}`)
console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
})
break
}
case 'create': {
this._strategy.isAuthenticated().then(res => {
// if the user isn't authenticated, we make them do the whole flow
if (res !== true) {
this.authenticate()
} else {
this.createBucket(this.createBucketHandler.bind(this))
}
}).catch(err => {
console.error(`${cst.PM2_IO_MSG_ERR} Failed to create to the bucket: ${err.message}`)
console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
})
break
}
case 'web': {
this._strategy.isAuthenticated().then(res => {
// if the user isn't authenticated, we make them do the whole flow
if (res === false) {
console.error(`${cst.PM2_IO_MSG_ERR} You need to be authenticated to do that, please use: pm2 plus login`)
return process.exit(1)
}
this._strategy._retrieveTokens(() => {
return this.openUI()
})
}).catch(err => {
console.error(`${cst.PM2_IO_MSG_ERR} Failed to open the UI: ${err.message}`)
console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
})
break
}
default : {
console.log(`${cst.PM2_IO_MSG_ERR} Invalid command ${command}, available : login,register,validate,connect or web`)
process.exit(1)
}
}
}
static openUI () {
this.io.bucket.retrieveAll().then(res => {
const buckets = res.data
if (buckets.length === 0) {
return this.createBucket((err, bucket) => {
if (err) {
console.error(`${cst.PM2_IO_MSG_ERR} Failed to connect to the bucket: ${err.message}`)
if (bucket) {
console.error(`${cst.PM2_IO_MSG_ERR} You can retry using: pm2 plus link ${bucket.secret_id} ${bucket.public_id}`)
}
console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
return process.exit(0)
}
const targetURL = `https://app.pm2.io/#/bucket/${bucket._id}`
console.log(`${cst.PM2_IO_MSG} Please follow the popup or go to this URL :`, '\n', ' ', targetURL)
this.open(targetURL)
return process.exit(0)
})
}
var table = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true},
head : ['Bucket name', 'Plan type']
})
buckets.forEach(function(bucket) {
table.push([bucket.name, bucket.credits.offer_type])
})
console.log(table.toString())
console.log(`${cst.PM2_IO_MSG} If you don't want to open the UI to a bucket, type 'none'`)
const choices = buckets.map(bucket => bucket.name)
choices.push('none')
promptly.choose(`${cst.PM2_IO_MSG} Type the name of the bucket you want to connect to :`, choices, (err, value) => {
if (value === 'none') process.exit(0)
const bucket = buckets.find(bucket => bucket.name === value)
if (bucket === undefined) return process.exit(0)
const targetURL = `https://app.pm2.io/#/bucket/${bucket._id}`
console.log(`${cst.PM2_IO_MSG} Please follow the popup or go to this URL :`, '\n', ' ', targetURL)
this.open(targetURL)
return process.exit(0)
})
})
}
static validateAccount (token) {
this.io.auth.validEmail(token)
.then(res => {
console.log(`${cst.PM2_IO_MSG} Email succesfully validated.`)
console.log(`${cst.PM2_IO_MSG} You can now proceed and use: pm2 plus connect`)
return process.exit(0)
}).catch(err => {
if (err.status === 401) {
console.error(`${cst.PM2_IO_MSG_ERR} Invalid token`)
return process.exit(1)
} else if (err.status === 301) {
console.log(`${cst.PM2_IO_MSG} Email succesfully validated.`)
console.log(`${cst.PM2_IO_MSG} You can now proceed and use: pm2 plus connect`)
return process.exit(0)
}
const msg = err.data ? err.data.error_description || err.data.msg : err.message
console.error(`${cst.PM2_IO_MSG_ERR} Failed to validate your email: ${msg}`)
console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
return process.exit(1)
})
}
static createBucketHandler (err, bucket) {
if (err) {
console.trace(`${cst.PM2_IO_MSG_ERR} Failed to connect to the bucket: ${err.message}`)
if (bucket) {
console.error(`${cst.PM2_IO_MSG_ERR} You can retry using: pm2 plus link ${bucket.secret_id} ${bucket.public_id}`)
}
console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
return process.exit(0)
}
if (bucket === undefined) {
return process.exit(0)
}
console.log(`${cst.PM2_IO_MSG} Successfully connected to bucket ${bucket.name}`)
var targetURL = `https://app.pm2.io/#/bucket/${bucket._id}`
console.log(`${cst.PM2_IO_MSG} You can use the web interface over there: ${targetURL}`)
this.open(targetURL)
return process.exit(0)
}
static createBucket (cb) {
console.log(`${cst.PM2_IO_MSG} By default we allow you to trial PM2 Plus for 14 days without any credit card.`)
this.io.bucket.create({
name: 'PM2 Plus Monitoring'
}).then(res => {
const bucket = res.data.bucket
console.log(`${cst.PM2_IO_MSG} Successfully created the bucket`)
this.pm2.link({
public_key: bucket.public_id,
secret_key: bucket.secret_id,
pm2_version: pkg.version
}, (err) => {
if (err) {
return cb(new Error('Failed to connect your local PM2 to your bucket'), bucket)
} else {
return cb(null, bucket)
}
})
}).catch(err => {
return cb(new Error(`Failed to create a bucket: ${err.message}`))
})
}
/**
* Connect the local agent to a specific bucket
* @param {Function} cb
*/
static connectToBucket (cb) {
this.io.bucket.retrieveAll().then(res => {
const buckets = res.data
if (buckets.length === 0) {
return this.createBucket(cb)
}
var table = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true},
head : ['Bucket name', 'Plan type']
})
buckets.forEach(function(bucket) {
table.push([bucket.name, bucket.payment.offer_type])
})
console.log(table.toString())
console.log(`${cst.PM2_IO_MSG} If you don't want to connect to a bucket, type 'none'`)
const choices = buckets.map(bucket => bucket.name)
choices.push('none')
promptly.choose(`${cst.PM2_IO_MSG} Type the name of the bucket you want to connect to :`, choices, (err, value) => {
if (value === 'none') return cb()
const bucket = buckets.find(bucket => bucket.name === value)
if (bucket === undefined) return cb()
this.pm2.link({
public_key: bucket.public_id,
secret_key: bucket.secret_id,
pm2_version: pkg.version
}, (err) => {
return err ? cb(err) : cb(null, bucket)
})
})
})
}
/**
* Authenticate the user with either of the strategy
* @param {Function} cb
*/
static authenticate () {
this._strategy._retrieveTokens((err, tokens) => {
if (err) {
const msg = err.data ? err.data.error_description || err.data.msg : err.message
console.log(`${cst.PM2_IO_MSG_ERR} Unexpected error : ${msg}`)
return process.exit(1)
}
console.log(`${cst.PM2_IO_MSG} Successfully authenticated`)
this.io.user.retrieve().then(res => {
const user = res.data
this.io.user.retrieve().then(res => {
const tmpUser = res.data
console.log(`${cst.PM2_IO_MSG} Successfully validated`)
this.connectToBucket(this.createBucketHandler.bind(this))
})
})
})
}
static open (target, appName, callback) {
let opener
const escape = function (s) {
return s.replace(/"/g, '\\"')
}
if (typeof (appName) === 'function') {
callback = appName
appName = null
}
switch (process.platform) {
case 'darwin': {
opener = appName ? `open -a "${escape(appName)}"` : `open`
break
}
case 'win32': {
opener = appName ? `start "" ${escape(appName)}"` : `start ""`
break
}
default: {
opener = appName ? escape(appName) : `xdg-open`
break
}
}
if (process.env.SUDO_USER) {
opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener
}
return exec(`${opener} "${escape(target)}"`, callback)
}
}

View File

@@ -0,0 +1,288 @@
'use strict'
const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy')
const querystring = require('querystring');
const http = require('http')
const fs = require('fs')
const url = require('url')
const exec = require('child_process').exec
const tryEach = require('async/tryEach')
const path = require('path')
const os = require('os')
const needle = require('needle')
const chalk = require('chalk')
const cst = require('../../../../constants.js')
const promptly = require('promptly')
module.exports = class CliStrategy extends AuthStrategy {
// the client will try to call this but we handle this part ourselves
retrieveTokens (km, cb) {
this.authenticated = false
this.callback = cb
this.km = km
this.BASE_URI = 'https://id.keymetrics.io';
}
// so the cli know if we need to tell user to login/register
isAuthenticated () {
return new Promise((resolve, reject) => {
if (this.authenticated) return resolve(true)
let tokensPath = cst.PM2_IO_ACCESS_TOKEN
fs.readFile(tokensPath, (err, tokens) => {
if (err && err.code === 'ENOENT') return resolve(false)
if (err) return reject(err)
// verify that the token is valid
try {
tokens = JSON.parse(tokens || '{}')
} catch (err) {
fs.unlinkSync(tokensPath)
return resolve(false)
}
// if the refresh tokens is here, the user could be automatically authenticated
return resolve(typeof tokens.refresh_token === 'string')
})
})
}
verifyToken (refresh) {
return this.km.auth.retrieveToken({
client_id: this.client_id,
refresh_token: refresh
})
}
// called when we are sure the user asked to be logged in
_retrieveTokens (optionalCallback) {
const km = this.km
const cb = this.callback
tryEach([
// try to find the token via the environment
(next) => {
if (!process.env.PM2_IO_TOKEN) {
return next(new Error('No token in env'))
}
this.verifyToken(process.env.PM2_IO_TOKEN)
.then((res) => {
return next(null, res.data)
}).catch(next)
},
// try to find it in the file system
(next) => {
fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => {
if (err) return next(err)
// verify that the token is valid
tokens = JSON.parse(tokens || '{}')
if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) {
return next(null, tokens)
}
this.verifyToken(tokens.refresh_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
},
// otherwise make the whole flow
(next) => {
return this.authenticate((err, data) => {
if (err instanceof Error) return next(err)
// verify that the token is valid
this.verifyToken(data.refresh_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
}
], (err, result) => {
// if present run the optional callback
if (typeof optionalCallback === 'function') {
optionalCallback(err, result)
}
if (result.refresh_token) {
this.authenticated = true
let file = cst.PM2_IO_ACCESS_TOKEN
fs.writeFile(file, JSON.stringify(result), () => {
return cb(err, result)
})
} else {
return cb(err, result)
}
})
}
authenticate (cb) {
console.log(`${cst.PM2_IO_MSG} Using non-browser authentication.`)
promptly.confirm(`${cst.PM2_IO_MSG} Do you have a pm2.io account? (y/n)`, (err, answer) => {
// Either login or register
return answer === true ? this.login(cb) : this.register(cb)
})
}
login (cb) {
let retry = () => {
promptly.prompt(`${cst.PM2_IO_MSG} Your username or email: `, (err, username) => {
if (err) return retry();
promptly.password(`${cst.PM2_IO_MSG} Your password: `, { replace : '*' }, (err, password) => {
if (err) return retry();
console.log(`${cst.PM2_IO_MSG} Authenticating ...`)
this._loginUser({
username: username,
password: password
}, (err, data) => {
if (err) {
console.error(`${cst.PM2_IO_MSG_ERR} Failed to authenticate: ${err.message}`)
return retry()
}
return cb(null, data)
})
})
})
}
retry()
}
register (cb) {
console.log(`${cst.PM2_IO_MSG} No problem ! We just need few informations to create your account`)
var retry = () => {
promptly.prompt(`${cst.PM2_IO_MSG} Please choose an username :`, {
validator : this._validateUsername,
retry : true
}, (err, username) => {
promptly.prompt(`${cst.PM2_IO_MSG} Please choose an email :`, {
validator : this._validateEmail,
retry : true
},(err, email) => {
promptly.password(`${cst.PM2_IO_MSG} Please choose a password :`, { replace : '*' }, (err, password) => {
promptly.confirm(`${cst.PM2_IO_MSG} Do you accept the terms and privacy policy (https://pm2.io/legals/terms_conditions.pdf) ? (y/n)`, (err, answer) => {
if (err) {
console.error(chalk.bold.red(err));
return retry()
} else if (answer === false) {
console.error(`${cst.PM2_IO_MSG_ERR} You must accept the terms and privacy policy to contiue.`)
return retry()
}
this._registerUser({
email : email,
password : password,
username : username
}, (err, data) => {
console.log('\n')
if (err) {
console.error(`${cst.PM2_IO_MSG_ERR} Unexpect error: ${err.message}`)
console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`)
return process.exit(1)
}
return cb(undefined, data)
})
})
})
})
})
}
retry()
}
/**
* Register function
* @param opts.username
* @param opts.password
* @param opts.email
*/
_registerUser (opts, cb) {
const data = Object.assign(opts, {
password_confirmation: opts.password,
accept_terms: true
})
needle.post(this.BASE_URI + '/api/oauth/register', data, {
json: true,
headers: {
'X-Register-Provider': 'pm2-register',
'x-client-id': this.client_id
}
}, function (err, res, body) {
if (err) return cb(err)
if (body.email && body.email.message) return cb(new Error(body.email.message))
if (body.username && body.username.message) return cb(new Error(body.username.message))
if (!body.access_token) return cb(new Error(body.msg))
return cb(null, {
refresh_token : body.refresh_token.token,
access_token : body.access_token.token
})
});
}
_loginUser (user_info, cb) {
const URL_AUTH = '/api/oauth/authorize?response_type=token&scope=all&client_id=' +
this.client_id + '&redirect_uri=http://localhost:43532';
needle.get(this.BASE_URI + URL_AUTH, (err, res) => {
if (err) return cb(err);
var cookie = res.cookies;
needle.post(this.BASE_URI + '/api/oauth/login', user_info, {
cookies : cookie
}, (err, resp, body) => {
if (err) return cb(err)
if (resp.statusCode != 200) return cb('Wrong credentials')
var location = resp.headers['x-redirect']
needle.get(this.BASE_URI + location, {
cookies : cookie
}, (err, res) => {
if (err) return cb(err);
var refresh_token = querystring.parse(url.parse(res.headers.location).query).access_token;
needle.post(this.BASE_URI + '/api/oauth/token', {
client_id : this.client_id,
grant_type : 'refresh_token',
refresh_token : refresh_token,
scope : 'all'
}, (err, res, body) => {
if (err) return cb(err)
return cb(null, body)
})
})
})
})
}
_validateEmail (email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (re.test(email) == false)
throw new Error('Not an email');
return email;
}
_validateUsername (value) {
if (value.length < 6) {
throw new Error('Min length of 6');
}
return value;
};
deleteTokens (km) {
return new Promise((resolve, reject) => {
// revoke the refreshToken
km.auth.revoke()
.then(res => {
// remove the token from the filesystem
let file = cst.PM2_IO_ACCESS_TOKEN
fs.unlinkSync(file)
return resolve(res)
}).catch(reject)
})
}
}

View File

@@ -0,0 +1,187 @@
'use strict'
const cst = require('../../../../constants.js');
const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy')
const http = require('http')
const fs = require('fs')
const url = require('url')
const exec = require('child_process').exec
const tryEach = require('async/tryEach');
module.exports = class WebStrategy extends AuthStrategy {
// the client will try to call this but we handle this part ourselves
retrieveTokens (km, cb) {
this.authenticated = false
this.callback = cb
this.km = km
}
// so the cli know if we need to tell user to login/register
isAuthenticated () {
return new Promise((resolve, reject) => {
if (this.authenticated) return resolve(true)
let tokensPath = cst.PM2_IO_ACCESS_TOKEN
fs.readFile(tokensPath, (err, tokens) => {
if (err && err.code === 'ENOENT') return resolve(false)
if (err) return reject(err)
// verify that the token is valid
try {
tokens = JSON.parse(tokens || '{}')
} catch (err) {
fs.unlinkSync(tokensPath)
return resolve(false)
}
// if the refresh tokens is here, the user could be automatically authenticated
return resolve(typeof tokens.refresh_token === 'string')
})
})
}
// called when we are sure the user asked to be logged in
_retrieveTokens (optionalCallback) {
const km = this.km
const cb = this.callback
let verifyToken = (refresh) => {
return km.auth.retrieveToken({
client_id: this.client_id,
refresh_token: refresh
})
}
tryEach([
// try to find the token via the environment
(next) => {
if (!process.env.PM2_IO_TOKEN) {
return next(new Error('No token in env'))
}
verifyToken(process.env.PM2_IO_TOKEN)
.then((res) => {
return next(null, res.data)
}).catch(next)
},
// try to find it in the file system
(next) => {
fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => {
if (err) return next(err)
// verify that the token is valid
tokens = JSON.parse(tokens || '{}')
if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) {
return next(null, tokens)
}
verifyToken(tokens.refresh_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
},
// otherwise make the whole flow
(next) => {
return this.loginViaWeb((data) => {
// verify that the token is valid
verifyToken(data.access_token)
.then((res) => {
return next(null, res.data)
}).catch(err => next(err))
})
}
], (err, result) => {
// if present run the optional callback
if (typeof optionalCallback === 'function') {
optionalCallback(err, result)
}
if (result.refresh_token) {
this.authenticated = true
let file = cst.PM2_IO_ACCESS_TOKEN
fs.writeFile(file, JSON.stringify(result), () => {
return cb(err, result)
})
} else {
return cb(err, result)
}
})
}
loginViaWeb (cb) {
const redirectURL = `${this.oauth_endpoint}${this.oauth_query}`
console.log(`${cst.PM2_IO_MSG} Please follow the popup or go to this URL :`, '\n', ' ', redirectURL)
let shutdown = false
let server = http.createServer((req, res) => {
// only handle one request
if (shutdown === true) return res.end()
shutdown = true
let query = url.parse(req.url, true).query
res.write(`
<head>
<script>
</script>
</head>
<body>
<h2 style="text-align: center">
You can go back to your terminal now :)
</h2>
</body>`)
res.end()
server.close()
return cb(query)
})
server.listen(43532, () => {
this.open(redirectURL)
})
}
deleteTokens (km) {
return new Promise((resolve, reject) => {
// revoke the refreshToken
km.auth.revoke()
.then(res => {
// remove the token from the filesystem
let file = cst.PM2_IO_ACCESS_TOKEN
fs.unlinkSync(file)
return resolve(res)
}).catch(reject)
})
}
open (target, appName, callback) {
let opener
const escape = function (s) {
return s.replace(/"/g, '\\"')
}
if (typeof (appName) === 'function') {
callback = appName
appName = null
}
switch (process.platform) {
case 'darwin': {
opener = appName ? `open -a "${escape(appName)}"` : `open`
break
}
case 'win32': {
opener = appName ? `start "" ${escape(appName)}"` : `start ""`
break
}
default: {
opener = appName ? escape(appName) : `xdg-open`
break
}
}
if (process.env.SUDO_USER) {
opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener
}
return exec(`${opener} "${escape(target)}"`, callback)
}
}

View File

@@ -0,0 +1,97 @@
var cst = require('../../../constants.js');
var Common = require('../../Common.js');
const chalk = require('chalk');
const forEach = require('async/forEach');
const open = require('../../tools/open.js');
const Modules = require('../Modules');
function processesAreAlreadyMonitored(CLI, cb) {
CLI.Client.executeRemote('getMonitorData', {}, function(err, list) {
if (err) return cb(false);
var l = list.filter(l => l.pm2_env.km_link == true)
var l2 = list.filter(l => l.name == 'pm2-server-monit')
return cb(l.length > 0 && l2.length > 0 ? true : false)
})
}
module.exports = function(CLI) {
CLI.prototype.openDashboard = function() {
if (!this.gl_interact_infos) {
Common.printError(chalk.bold.white('Agent if offline, type `$ pm2 plus` to log in'));
return this.exitCli(cst.ERROR_EXIT);
}
var uri = `https://app.pm2.io/#/r/${this.gl_interact_infos.public_key}`
console.log(cst.PM2_IO_MSG + ` Opening ${uri}`)
open(uri);
setTimeout(_ => {
this.exitCli();
}, 200);
};
CLI.prototype.clearSetup = function (opts, cb) {
const modules = ['event-loop-inspector']
this.gl_is_km_linked = false
forEach(modules, (_module, next) => {
Modules.uninstall(this, _module, () => {
next()
});
}, (err) => {
this.reload('all', () => {
return cb()
})
})
}
/**
* Install required package and enable flags for current running processes
*/
CLI.prototype.minimumSetup = function (opts, cb) {
var self = this;
this.gl_is_km_linked = true
function install(cb) {
var modules = []
if (opts.type === 'enterprise' || opts.type === 'plus') {
modules = ['pm2-logrotate', 'pm2-server-monit']
if (opts.type === 'enterprise') {
modules.push('deep-metrics')
}
}
forEach(modules, (_module, next) => {
Modules.install(self, _module, {}, () => {
next()
});
}, (err) => {
self.reload('all', () => {
return cb()
})
})
}
processesAreAlreadyMonitored(self, (already_monitored) => {
if (already_monitored) {
console.log(cst.PM2_IO_MSG + ` PM2 ${opts.type || ''} bundle already installed`);
return cb()
}
if (opts.installAll)
return install(cb)
// promptly.confirm(chalk.bold('Install all pm2 plus dependencies ? (y/n)'), (err, answer) => {
// if (!err && answer === true)
return install(cb)
// self.reload('all', () => {
// return cb()
// })
// });
})
}
}

View File

@@ -0,0 +1,126 @@
var cst = require('../../../constants.js');
var Common = require('../../Common.js');
var chalk = require('chalk');
var fs = require('fs');
var KMDaemon = require('@pm2/agent/src/InteractorClient');
var pkg = require('../../../package.json')
module.exports = function(CLI) {
CLI.prototype.linkManagement = function(cmd, public_key, machine, opts, cb) {
var that = this;
// pm2 link stop || kill
if (cmd == 'stop' || cmd == 'kill') {
that.gl_is_km_linked = false
console.log(cst.PM2_IO_MSG + ' Stopping agent...');
return that.killAgent(function(err) {
if (err) {
Common.printError(err);
return process.exit(cst.ERROR_EXIT);
}
console.log(cst.PM2_IO_MSG + ' Stopped');
that.reload('all', () => {
return process.exit(cst.SUCCESS_EXIT);
})
});
}
// pm2 link info
if (cmd == 'info') {
console.log(cst.PM2_IO_MSG + ' Getting agent information...');
that.agentInfos(function(err, infos) {
if (err) {
console.error(cst.PM2_IO_MSG_ERR + ' ' + err.message);
return that.exitCli(cst.ERROR_EXIT);
}
console.log(infos);
return that.exitCli(cst.SUCCESS_EXIT);
});
return false;
}
// pm2 link delete
if (cmd == 'delete') {
that.gl_is_km_linked = false
console.log(cst.PM2_IO_MSG + ' Permanently disable agent...');
that.killAgent(function(err) {
try {
fs.unlinkSync(cst.INTERACTION_CONF);
} catch(e) {
console.log(cst.PM2_IO_MSG + ' No interaction config file found');
return process.exit(cst.SUCCESS_EXIT);
}
console.log(cst.PM2_IO_MSG + ' Agent interaction ended');
if (!cb)
return process.exit(cst.SUCCESS_EXIT);
return cb()
});
return false;
}
if (cmd && !public_key) {
console.error(cst.PM2_IO_MSG + ' Command [%s] unknown or missing public key', cmd);
return process.exit(cst.ERROR_EXIT);
}
// pm2 link xxx yyy
var infos;
if (!cmd) {
infos = null;
}
else
infos = {
public_key : public_key,
secret_key : cmd,
machine_name : machine,
info_node : opts.infoNode || null,
pm2_version: pkg.version
}
that.link(infos, cb)
};
CLI.prototype.link = function(infos, cb) {
var that = this;
process.env.WS_JSON_PATCH = true
KMDaemon.launchAndInteract(cst, infos, function(err, dt) {
if (err) {
Common.printError(cst.PM2_IO_MSG + ' Run `$ pm2 plus` to connect')
return that.exitCli(cst.ERROR_EXIT);
}
console.log(chalk.bold.green('[+] PM2+ activated!'))
if (!cb) {
return that.exitCli(cst.SUCCESS_EXIT);
}
return cb(null, dt)
});
};
CLI.prototype.agentInfos = function(cb) {
KMDaemon.getInteractInfo(this._conf, function(err, data) {
if (err)
return cb(Common.retErr(err));
return cb(null, data);
});
};
CLI.prototype.killAgent = function(cb) {
var that = this;
KMDaemon.killInteractorDaemon(that._conf, function(err) {
if (err)
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.SUCCESS_EXIT);
return cb ? cb(null) : that.exitCli(cst.SUCCESS_EXIT);
});
};
CLI.prototype.unlink = function(cb) {
this.linkManagement('delete', cb);
};
};

View File

@@ -0,0 +1,16 @@
██████╗ ███╗ ███╗██████╗ ██╗ ██╗ ██████╗
██╔══██╗████╗ ████║╚════██╗ ██║ ██╔╝██╔═══██╗
██████╔╝██╔████╔██║ █████╔╝ ██║ ██╔╝ ██║ ██║
██╔═══╝ ██║╚██╔╝██║██╔═══╝ ██║ ██╔╝ ██║ ██║
██║ ██║ ╚═╝ ██║███████╗ ██║██╔╝ ╚██████╔╝
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝╚═╝ ╚═════╝
https://pm2.io/
Harden your Node.js Production Environment
- Real-time Monitoring Web Interface
- Pro Active Alerting System
- Production Profiling for Memory and CPU
- PM2 Runtime High Availability Fallback

View File

@@ -0,0 +1,26 @@
-------------
██████╗ ███╗ ███╗██████╗ ██╗ ██╗ ██████╗
██╔══██╗████╗ ████║╚════██╗ ██║ ██╔╝██╔═══██╗
██████╔╝██╔████╔██║ █████╔╝ ██║ ██╔╝ ██║ ██║
██╔═══╝ ██║╚██╔╝██║██╔═══╝ ██║ ██╔╝ ██║ ██║
██║ ██║ ╚═╝ ██║███████╗ ██║██╔╝ ╚██████╔╝
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝╚═╝ ╚═════╝
https://pm2.io/
Harden your Node.js Production Environment
- Real-time Monitoring Web Interface
- Pro Active Alerting System
- Production Profiling for Memory and CPU
- PM2 Runtime High Availability Fallback
Start using it by typing:
$ pm2 plus
-------------

View File

@@ -0,0 +1,28 @@
-------------
PM2 Plus Edition
PM2 Plus is a monitoring dashboard
specialized for Node.js Apps.
Create an account:
$ pm2 plus register
Connect your local PM2 to the PM2 Plus servers:
$ pm2 plus connect
See our UI with your own realtime dashboard:
$ pm2 plus web
More details available there:
http://pm2.io/plus
-------------
Having complex or specific needs?
You can also checkout our enterprise offer at:
http://pm2.io/enterprise
-------------

View File

@@ -0,0 +1,52 @@
const fs = require('fs');
const forEachLimit = require('async/forEachLimit');
var cst = require('../../../constants.js');
var Common = require('../../Common.js');
module.exports = function(CLI) {
/**
* Monitor Selectively Processes (auto filter in interaction)
* @param String state 'monitor' or 'unmonitor'
* @param String target <pm_id|name|all>
* @param Function cb callback
*/
CLI.prototype.monitorState = function(state, target, cb) {
var that = this;
if (!target) {
Common.printError(cst.PREFIX_MSG_ERR + 'Please specify an <app_name|pm_id>');
return cb ? cb(new Error('argument missing')) : that.exitCli(cst.ERROR_EXIT);
}
function monitor (pm_id, cb) {
// State can be monitor or unmonitor
that.Client.executeRemote(state, pm_id, cb);
}
if (target === 'all') {
that.Client.getAllProcessId(function (err, procs) {
if (err) {
Common.printError(err);
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
}
forEachLimit(procs, 1, monitor, function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
});
} else if (!Number.isInteger(parseInt(target))) {
this.Client.getProcessIdByName(target, true, function (err, procs) {
if (err) {
Common.printError(err);
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
}
forEachLimit(procs, 1, monitor, function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
});
} else {
monitor(parseInt(target), function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
}
};
}