mirror of
https://github.com/Unitech/pm2.git
synced 2025-12-08 20:35:53 +00:00
346 lines
9.3 KiB
JavaScript
346 lines
9.3 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')('interface:driver');
|
|
var Url = require('url');
|
|
var Cipher = require('../Cipher.js');
|
|
var PushInteractor = require('../PushInteractor');
|
|
var Conf = require('../../Configuration.js');
|
|
var Password = require('../Password.js');
|
|
|
|
/**
|
|
* Allowed remote PM2 methods
|
|
* with options
|
|
* - password_required : force to pass a password in parameter
|
|
* - password_optional : if a password is set, force it
|
|
* - lock : enable the locking system (block parallel commands)
|
|
*/
|
|
var PM2_REMOTE_METHOD_ALLOWED = {
|
|
'restart' : {},
|
|
'reload' : {},
|
|
'gracefulReload' : {},
|
|
'reset' : {},
|
|
'scale' : {},
|
|
|
|
'install' : { password_required : true },
|
|
'uninstall' : { password_required : true },
|
|
'stop' : { password_required : true },
|
|
'delete' : { password_required : true },
|
|
'set' : {},
|
|
'multiset' : {},
|
|
'deepUpdate' : { password_required : true },
|
|
|
|
'pullAndRestart' : { password_optional : true },
|
|
'forward' : { password_optional : true },
|
|
'backward' : { password_optional : true },
|
|
|
|
'startLogging' : {},
|
|
'stopLogging' : {},
|
|
|
|
'resetTransactionCache': {},
|
|
'resetFileCache': {},
|
|
|
|
// This is just for testing purproses
|
|
'ping' : { password_required : true }
|
|
};
|
|
|
|
var Pm2Actions = module.exports = {
|
|
/**
|
|
* Methods to trigger PM2 actions from remote
|
|
*/
|
|
pm2Actions : function() {
|
|
var self = this;
|
|
|
|
function executionBox(msg, cb) {
|
|
/**
|
|
* Exemple
|
|
* msg = {
|
|
* method_name : 'restart',
|
|
* parameters : {}
|
|
* }
|
|
*/
|
|
console.log('PM2 action from remote triggered "pm2 %s %j"',
|
|
msg.method_name,
|
|
msg.parameters);
|
|
|
|
var method_name = JSON.parse(JSON.stringify(msg.method_name));
|
|
|
|
var parameters = '';
|
|
|
|
try {
|
|
parameters = JSON.parse(JSON.stringify(msg.parameters));
|
|
}
|
|
catch(e) {
|
|
console.error(e.stack || e);
|
|
parameters = msg.parameters;
|
|
}
|
|
|
|
if (!method_name) {
|
|
console.error('no method name');
|
|
return cb(new Error('no method name defined'));
|
|
}
|
|
|
|
if (!PM2_REMOTE_METHOD_ALLOWED[method_name]) {
|
|
console.error('method %s not allowed', method_name);
|
|
return cb(new Error('method ' + method_name + ' not allowed'));
|
|
}
|
|
|
|
if (method_name === 'startLogging') {
|
|
global._logs = true;
|
|
// Stop streaming logs automatically after timeout
|
|
setTimeout(function() {
|
|
global._logs = false;
|
|
}, 120000);
|
|
return cb(null, 'Log streaming enabled');
|
|
} else if (method_name === 'stopLogging') {
|
|
global._logs = false;
|
|
return cb(null, 'Log streaming disabled');
|
|
} else if (method_name === 'resetTransactionCache') {
|
|
PushInteractor.aggregator.clearData();
|
|
return cb(null, 'Transaction cache has beem reset');
|
|
} else if (method_name === 'resetFileCache') {
|
|
PushInteractor.cache.reset();
|
|
return cb(null, 'File cache has beem reset');
|
|
}
|
|
|
|
self.pm2_instance.remote(method_name, parameters, cb);
|
|
return false;
|
|
}
|
|
|
|
function sendBackResult(data) {
|
|
self.socket.send('trigger:pm2:result', data);
|
|
};
|
|
|
|
this.socket.data('trigger:pm2:action', function(raw_msg) {
|
|
var d = require('domain').create();
|
|
|
|
var msg = {};
|
|
|
|
/**
|
|
* Uncipher Data
|
|
*/
|
|
if (process.env.NODE_ENV &&
|
|
(process.env.NODE_ENV == 'test' ||
|
|
process.env.NODE_ENV == 'local_test'))
|
|
msg = raw_msg;
|
|
else
|
|
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
|
|
|
|
d.on('error', function(e) {
|
|
console.error('Error caught in domain');
|
|
console.error(e.stack || e);
|
|
|
|
/**
|
|
* Send error back to
|
|
*/
|
|
sendBackResult({
|
|
ret : {
|
|
err : e,
|
|
data : null
|
|
},
|
|
meta : {
|
|
method_name : msg.method_name,
|
|
app_name : msg.parameters.name,
|
|
machine_name : self.conf.MACHINE_NAME,
|
|
public_key : self.conf.PUBLIC_KEY
|
|
}
|
|
});
|
|
});
|
|
|
|
d.run(function() {
|
|
if (!msg)
|
|
throw new Error('Wrong SECRET KEY to uncipher package');
|
|
|
|
/**
|
|
* Execute command
|
|
*/
|
|
executionBox(msg, function(err, data) {
|
|
if (err) console.error(err.stack || JSON.stringify(err));
|
|
|
|
/**
|
|
* Send back the result
|
|
*/
|
|
sendBackResult({
|
|
ret : {
|
|
err : err,
|
|
data : data || null
|
|
},
|
|
meta : {
|
|
method_name : msg.method_name,
|
|
app_name : msg.parameters.name,
|
|
machine_name : self.conf.MACHINE_NAME,
|
|
public_key : self.conf.PUBLIC_KEY
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
});
|
|
},
|
|
|
|
/****************************************************
|
|
*
|
|
*
|
|
* Scoped PM2 Actions with streaming and multi args
|
|
*
|
|
*
|
|
****************************************************/
|
|
pm2ScopedActions : function() {
|
|
var self = this;
|
|
|
|
this.socket.data('trigger:pm2:scoped:action', function(raw_msg) {
|
|
var msg = {};
|
|
|
|
if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' ||
|
|
process.env.NODE_ENV == 'local_test'))
|
|
msg = raw_msg;
|
|
else {
|
|
/**
|
|
* Uncipher Data
|
|
*/
|
|
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
|
|
}
|
|
|
|
if (!msg.uuid ||
|
|
!msg.action_name) {
|
|
console.error('PM2 Scoped: Parameter missing!');
|
|
return sendEvent('pm2:scoped:error', {
|
|
at : Date.now(),
|
|
out : 'Parameter missing',
|
|
msg : msg.uuid || null
|
|
});
|
|
}
|
|
|
|
sendEvent('pm2:scoped:stream', {
|
|
at : Date.now(),
|
|
out : 'Action ' + msg.action_name + ' received',
|
|
uuid : msg.uuid
|
|
});
|
|
|
|
executionBox(msg, function(err, data) {
|
|
if (err) {
|
|
console.error(err.stack || err);
|
|
return sendEvent('pm2:scoped:error', {
|
|
at : Date.now(),
|
|
out : err.stack || err,
|
|
uuid : msg.uuid
|
|
});
|
|
}
|
|
return sendEvent('pm2:scoped:end', {
|
|
at : Date.now(),
|
|
out : data,
|
|
uuid : msg.uuid
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Compact event in Push Interactor *pipe*
|
|
*/
|
|
function sendEvent(event, data) {
|
|
var packet = {
|
|
at : Date.now(),
|
|
data : {
|
|
data : data.out,
|
|
uuid : data.uuid
|
|
}
|
|
};
|
|
|
|
if (!PushInteractor._packet[event])
|
|
PushInteractor._packet[event] = [];
|
|
|
|
PushInteractor._packet[event].push(packet);
|
|
|
|
if (process.env.NODE_ENV == 'local_test')
|
|
process.send({event : event, data : data});
|
|
};
|
|
|
|
/**
|
|
* Processing
|
|
*/
|
|
function executionBox(msg, cb) {
|
|
var action_name = msg.action_name;
|
|
var opts = msg.options;
|
|
|
|
if (!PM2_REMOTE_METHOD_ALLOWED[action_name]) {
|
|
console.error('method %s not allowed', action_name);
|
|
return cb(new Error('method ' + action_name + ' not allowed'));
|
|
}
|
|
|
|
var action_conf = PM2_REMOTE_METHOD_ALLOWED[action_name];
|
|
|
|
/**
|
|
* Password checking
|
|
*/
|
|
if (action_conf.password_required === true) {
|
|
if (!msg.password) {
|
|
console.error('Missing password in query');
|
|
return cb('Missing password in query');
|
|
}
|
|
|
|
var passwd = Conf.getSync('pm2:passwd');
|
|
|
|
if (passwd === null) {
|
|
console.error('Password at PM2 level is missing');
|
|
return cb('Password at PM2 level is missing please set password via pm2 set pm2:passwd <password>');
|
|
}
|
|
|
|
if (Password.verify(msg.password, passwd) != true) {
|
|
console.error('Password does not match');
|
|
return cb('Password does not match');
|
|
}
|
|
}
|
|
|
|
if (action_conf.lock === false)
|
|
opts.lock = false;
|
|
|
|
/**
|
|
* Fork the remote action in another process
|
|
* so we can catch the stdout/stderr and emit it
|
|
*/
|
|
var fork = require('child_process').fork;
|
|
|
|
process.env.fork_params = JSON.stringify({ action : action_name, opts : opts});
|
|
|
|
console.log('Executing: pm2 %s %s', action_name, opts.args ? opts.args.join(' ') : '');
|
|
|
|
var app = fork(__dirname + '/ScopedExecution.js', [], {
|
|
silent : true
|
|
});
|
|
|
|
app.stdout.on('data', function(dt) {
|
|
console.log(dt.toString());
|
|
sendEvent('pm2:scoped:stream', {
|
|
at : Date.now(),
|
|
out : dt.toString(),
|
|
uuid : msg.uuid
|
|
});
|
|
});
|
|
|
|
app.once('error', function(dt) {
|
|
console.error('Error got?', dt);
|
|
sendEvent('pm2:scoped:error', {
|
|
at : Date.now(),
|
|
out : 'Shit happening ' + JSON.stringify(dt),
|
|
msg : msg.uuid
|
|
});
|
|
});
|
|
|
|
app.on('message', function(dt) {
|
|
var ret = JSON.parse(dt);
|
|
if (ret.isFinished != true) return false;
|
|
|
|
console.log('Action %s finished (err= %s)',
|
|
action_name, ret.err);
|
|
return cb(ret.err, ret.dt);
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|
|
};
|