1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | var cst = require('../constants.js');
|
18 | var rpc = require('pm2-axon-rpc');
|
19 | var axon = require('pm2-axon');
|
20 | var debug = require('debug')('pm2:satan');
|
21 | var util = require('util');
|
22 | var fs = require('fs');
|
23 | var p = require('path');
|
24 | var Utility = require('./Utility.js');
|
25 | var domain = require('domain');
|
26 | var eachLimit = require('async/eachLimit');
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | var Satan = module.exports = {};
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | Satan.start = function(noDaemonMode, cb) {
|
42 | if (typeof(noDaemonMode) == "function") {
|
43 | cb = noDaemonMode;
|
44 | noDaemonMode = false;
|
45 | }
|
46 |
|
47 | Satan._noDaemonMode = noDaemonMode;
|
48 |
|
49 | Satan.pingDaemon(function(ab) {
|
50 |
|
51 | if (ab == false) {
|
52 | if (noDaemonMode) {
|
53 | debug('Launching in no daemon mode');
|
54 | Satan.remoteWrapper();
|
55 | return Satan.launchRPC(function() {
|
56 | require('./Modularizer.js').launchAll(cb);
|
57 | });
|
58 | }
|
59 |
|
60 | Satan.printOut(cst.PREFIX_MSG + 'Spawning PM2 daemon');
|
61 |
|
62 |
|
63 | return Satan.launchDaemon(function(err, child) {
|
64 | if (err) {
|
65 | console.error(err);
|
66 | return cb ? cb(err) : process.exit(cst.ERROR_EXIT);
|
67 | }
|
68 | Satan.printOut(cst.PREFIX_MSG + 'PM2 Successfully daemonized');
|
69 |
|
70 | return Satan.launchRPC(function() {
|
71 | require('./Modularizer.js').launchAll(cb);
|
72 | });
|
73 | });
|
74 | }
|
75 |
|
76 | return Satan.launchRPC(cb);
|
77 | });
|
78 | };
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | Satan.processStateHandler = function(God) {
|
87 | |
88 |
|
89 |
|
90 |
|
91 |
|
92 | function gracefullExit() {
|
93 | Satan.printOut('pm2 has been killed by signal, dumping process list before exit...');
|
94 |
|
95 | God.dumpProcessList(function() {
|
96 |
|
97 | var processes = God.getFormatedProcesses();
|
98 |
|
99 | eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) {
|
100 | console.log('Deleting process %s', proc.pm2_env.pm_id);
|
101 | God.deleteProcessId(proc.pm2_env.pm_id, function() {
|
102 | return next();
|
103 | });
|
104 | return false;
|
105 | }, function(err) {
|
106 | try {
|
107 | fs.unlinkSync(cst.PM2_PID_FILE_PATH);
|
108 | } catch(e) {}
|
109 | Satan.printOut('[PM2] Exited peacefully');
|
110 | process.exit(cst.SUCCESS_EXIT);
|
111 | });
|
112 | });
|
113 | }
|
114 |
|
115 | try {
|
116 | fs.writeFileSync(cst.PM2_PID_FILE_PATH, process.pid);
|
117 | } catch (e) {
|
118 | console.error(e.stack || e);
|
119 | }
|
120 |
|
121 | process.on('SIGILL', function() {
|
122 | global.gc();
|
123 | Satan.printOut('Running garbage collector');
|
124 | });
|
125 |
|
126 | process.on('SIGTERM', gracefullExit);
|
127 | process.on('SIGINT', gracefullExit);
|
128 | process.on('SIGQUIT', gracefullExit);
|
129 | process.on('SIGUSR2', function() {
|
130 | God.reloadLogs({}, function() {});
|
131 | });
|
132 | };
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | Satan.remoteWrapper = function() {
|
140 |
|
141 | var God = require('./God');
|
142 | var self = this;
|
143 |
|
144 | var pkg = require('../package.json');
|
145 | var rpc_socket_ready = false;
|
146 | var pub_socket_ready = false;
|
147 |
|
148 | Satan.processStateHandler(God);
|
149 |
|
150 | function sendReady() {
|
151 |
|
152 | if (rpc_socket_ready == true && pub_socket_ready == true) {
|
153 | if (typeof(process.send) === 'function') {
|
154 | process.send({
|
155 | online : true,
|
156 | success : true,
|
157 | pid : process.pid,
|
158 | pm2_version : pkg.version
|
159 | });
|
160 | }
|
161 | };
|
162 | }
|
163 |
|
164 | |
165 |
|
166 |
|
167 |
|
168 | |
169 |
|
170 |
|
171 | var pub = axon.socket('pub-emitter');
|
172 |
|
173 | this.pub_socket = pub.bind(cst.DAEMON_PUB_PORT);
|
174 |
|
175 | this.pub_socket.once('bind', function() {
|
176 | Satan.printOut('BUS system [READY] on port %s', cst.DAEMON_PUB_PORT);
|
177 | pub_socket_ready = true;
|
178 | sendReady();
|
179 | });
|
180 |
|
181 | |
182 |
|
183 |
|
184 | var rep = axon.socket('rep');
|
185 |
|
186 | var server = new rpc.Server(rep);
|
187 |
|
188 | Satan.printOut('[[[[ PM2/God daemon launched ]]]]');
|
189 |
|
190 | this.rpc_socket = rep.bind(cst.DAEMON_RPC_PORT);
|
191 |
|
192 | this.rpc_socket.once('bind', function() {
|
193 | Satan.printOut('RPC interface [READY] on port %s', cst.DAEMON_RPC_PORT);
|
194 | rpc_socket_ready = true;
|
195 | sendReady();
|
196 | });
|
197 |
|
198 | server.expose({
|
199 | prepare : God.prepare,
|
200 | getMonitorData : God.getMonitorData,
|
201 | getSystemData : God.getSystemData,
|
202 |
|
203 | startProcessId : God.startProcessId,
|
204 | stopProcessId : God.stopProcessId,
|
205 | restartProcessId : God.restartProcessId,
|
206 | deleteProcessId : God.deleteProcessId,
|
207 |
|
208 | softReloadProcessId : God.softReloadProcessId,
|
209 | reloadProcessId : God.reloadProcessId,
|
210 | duplicateProcessId : God.duplicateProcessId,
|
211 | resetMetaProcessId : God.resetMetaProcessId,
|
212 | stopWatch : God.stopWatch,
|
213 | restartWatch : God.restartWatch,
|
214 | notifyByProcessId : God.notifyByProcessId,
|
215 |
|
216 | killMe : God.killMe,
|
217 | notifyKillPM2 : God.notifyKillPM2,
|
218 |
|
219 | findByFullPath : God.findByFullPath,
|
220 |
|
221 | msgProcess : God.msgProcess,
|
222 | sendDataToProcessId : God.sendDataToProcessId,
|
223 | sendSignalToProcessId : God.sendSignalToProcessId,
|
224 | sendSignalToProcessName : God.sendSignalToProcessName,
|
225 |
|
226 | ping : God.ping,
|
227 | getVersion : God.getVersion,
|
228 | reloadLogs : God.reloadLogs
|
229 | });
|
230 |
|
231 | |
232 |
|
233 |
|
234 |
|
235 | God.bus.on('axm:action', function axmActions(msg) {
|
236 | var pm2_env = msg.process;
|
237 | var exists = false;
|
238 | var axm_action = msg.data;
|
239 |
|
240 | if (!pm2_env || !God.clusters_db[pm2_env.pm_id])
|
241 | return console.error('Unknown id %s', pm2_env.pm_id);
|
242 |
|
243 | if (!God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions)
|
244 | God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions = [];
|
245 |
|
246 | God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.forEach(function(actions) {
|
247 | if (actions.action_name == axm_action.action_name)
|
248 | exists = true;
|
249 | });
|
250 |
|
251 | if (exists === false) {
|
252 | debug('Adding action', axm_action);
|
253 | God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.push(axm_action);
|
254 | }
|
255 |
|
256 | return God;
|
257 | });
|
258 |
|
259 | |
260 |
|
261 |
|
262 | God.bus.on('axm:option:configuration', function axmMonitor(msg) {
|
263 | if (!msg.process)
|
264 | return console.error('[axm:option:configuration] no process defined');
|
265 |
|
266 | if (!God.clusters_db[msg.process.pm_id])
|
267 | return console.error('[axm:option:configuration] Unknown id %s', msg.process.pm_id);
|
268 |
|
269 | try {
|
270 |
|
271 | if (msg.data.name)
|
272 | God.clusters_db[msg.process.pm_id].pm2_env.name = msg.data.name;
|
273 |
|
274 | Object.keys(msg.data).forEach(function(conf_key) {
|
275 | God.clusters_db[msg.process.pm_id].pm2_env.axm_options[conf_key] = Utility.clone(msg.data[conf_key]);
|
276 | });
|
277 | } catch(e) {
|
278 | console.error(e.stack || e);
|
279 | }
|
280 | msg = null;
|
281 | });
|
282 |
|
283 | |
284 |
|
285 |
|
286 | God.bus.on('axm:monitor', function axmMonitor(msg) {
|
287 | if (!msg.process)
|
288 | return console.error('[axm:monitor] no process defined');
|
289 |
|
290 | if (!msg.process || !God.clusters_db[msg.process.pm_id])
|
291 | return console.error('Unknown id %s', msg.process.pm_id);
|
292 |
|
293 | util._extend(God.clusters_db[msg.process.pm_id].pm2_env.axm_monitor, Utility.clone(msg.data));
|
294 | msg = null;
|
295 | });
|
296 |
|
297 | |
298 |
|
299 |
|
300 | God.bus.onAny(function(data_v) {
|
301 | if (['axm:action',
|
302 | 'axm:monitor',
|
303 | 'axm:option:setPID',
|
304 | 'axm:option:configuration'].indexOf(this.event) > -1) {
|
305 | data_v = null;
|
306 | return false;
|
307 | }
|
308 | pub.emit(this.event, Utility.clone(data_v));
|
309 | data_v = null;
|
310 | });
|
311 | };
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 | Satan.launchDaemon = function launchDaemon(cb) {
|
328 | debug('Launching daemon');
|
329 |
|
330 | var SatanJS = p.resolve(p.dirname(module.filename), 'Satan.js');
|
331 | var InteractorDaemonizer = require('@pm2/agent/src/InteractorClient');
|
332 |
|
333 | var node_args = [];
|
334 |
|
335 | var out, err;
|
336 |
|
337 | if (process.env.TRAVIS) {
|
338 |
|
339 | out = 1;
|
340 | err = 2;
|
341 | }
|
342 | else {
|
343 | out = fs.openSync(cst.PM2_LOG_FILE_PATH, 'a'),
|
344 | err = fs.openSync(cst.PM2_LOG_FILE_PATH, 'a');
|
345 | }
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | |
352 |
|
353 |
|
354 | if (process.env.PM2_NODE_OPTIONS)
|
355 | node_args = node_args.concat(process.env.PM2_NODE_OPTIONS.split(' '));
|
356 | node_args.push(SatanJS);
|
357 |
|
358 | var resolved_home = process.env.PM2_HOME || process.env.HOME || process.env.HOMEPATH;
|
359 |
|
360 | debug("PM2 home path: %s", resolved_home);
|
361 | debug("Node.js engine full path: %s", process.execPath);
|
362 | debug("Node.js with V8 arguments: %s", node_args);
|
363 |
|
364 | var child = require('child_process').spawn(process.execPath || 'node', node_args, {
|
365 | detached : true,
|
366 | cwd : process.cwd(),
|
367 | env : util._extend({
|
368 | 'SILENT' : cst.DEBUG ? !cst.DEBUG : true,
|
369 | 'HOME' : resolved_home
|
370 | }, process.env),
|
371 | stdio : ['ipc', out, err]
|
372 | });
|
373 |
|
374 | function onError(e) {
|
375 | console.error(e.stack || e);
|
376 | return cb ? cb(e.stack || e) : false;
|
377 | }
|
378 |
|
379 | child.once('error', onError);
|
380 |
|
381 | child.unref();
|
382 |
|
383 | child.once('message', function(msg) {
|
384 | debug('PM2 daemon launched with return message: ', msg);
|
385 | child.removeListener('error', onError);
|
386 | child.disconnect();
|
387 | InteractorDaemonizer.launchAndInteract({}, {}, function(err, data) {
|
388 | if (data)
|
389 | debug('Interactor launched');
|
390 | return cb ? cb(null, child) : false;
|
391 | });
|
392 | });
|
393 | };
|
394 |
|
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 |
|
401 |
|
402 | Satan.pingDaemon = function pingDaemon(cb) {
|
403 | var req = axon.socket('req');
|
404 | var client = new rpc.Client(req);
|
405 |
|
406 | debug('[PING PM2] Trying to connect to server');
|
407 |
|
408 | client.sock.once('reconnect attempt', function() {
|
409 | client.sock.close();
|
410 | debug('Daemon not launched');
|
411 | process.nextTick(function() {
|
412 | return cb(false);
|
413 | });
|
414 | });
|
415 |
|
416 | client.sock.once('connect', function() {
|
417 | client.sock.once('close', function() {
|
418 | return cb(true);
|
419 | });
|
420 | client.sock.close();
|
421 | debug('Daemon alive');
|
422 | });
|
423 |
|
424 | req.connect(cst.DAEMON_RPC_PORT);
|
425 | };
|
426 |
|
427 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 | Satan.launchRPC = function launchRPC(cb) {
|
435 | debug('Launching RPC client on socket file %s', cst.DAEMON_RPC_PORT);
|
436 | var req = axon.socket('req');
|
437 | Satan.client = new rpc.Client(req);
|
438 |
|
439 | Satan.client.sock.once('connect', function() {
|
440 | debug('Connected to Daemon');
|
441 | process.emit('satan:client:ready');
|
442 | setTimeout(function() {
|
443 | return cb ? cb(null) : false;
|
444 | }, 4);
|
445 | });
|
446 |
|
447 | this.client_sock = req.connect(cst.DAEMON_RPC_PORT);
|
448 | };
|
449 |
|
450 | Satan.launchBus = function launchEventSystem(cb) {
|
451 | var self = this;
|
452 | this.sub = axon.socket('sub-emitter');
|
453 | this.sub_sock = this.sub.connect(cst.DAEMON_PUB_PORT);
|
454 |
|
455 | this.sub_sock.once('connect', function() {
|
456 | return cb(null, self.sub);
|
457 | });
|
458 | };
|
459 |
|
460 | Satan.disconnectBus = function disconnectBus(cb) {
|
461 | this.sub_sock.once('close', function() {
|
462 | return cb ? cb() : false;
|
463 | });
|
464 | this.sub_sock.close();
|
465 | };
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 | Satan.disconnectRPC = function disconnectRPC(cb) {
|
472 | debug('Disconnecting PM2 RPC');
|
473 |
|
474 | if (!Satan.client_sock || !Satan.client_sock.close) {
|
475 | return cb({
|
476 | success : false,
|
477 | msg : 'RPC connection to PM2 is not launched'
|
478 | });
|
479 | }
|
480 |
|
481 | if (Satan.client_sock.connected == false ||
|
482 | Satan.client_sock.closing == true) {
|
483 | return cb({
|
484 | success : false,
|
485 | msg : 'RPC closed'
|
486 | });
|
487 | }
|
488 |
|
489 | try {
|
490 | var timer;
|
491 |
|
492 | Satan.client_sock.once('close', function() {
|
493 | clearTimeout(timer);
|
494 | debug('PM2 RPC cleanly closed');
|
495 | return cb ? cb(null, {success:true}) : false;
|
496 | });
|
497 |
|
498 | timer = setTimeout(function() {
|
499 | if (Satan.client_sock.destroy)
|
500 | Satan.client_sock.destroy();
|
501 | return cb ? cb(null, {success:true}) : false;
|
502 | }, 200);
|
503 |
|
504 | Satan.client_sock.close();
|
505 | } catch(e) {
|
506 | debug('Error while disconnecting RPC PM2', e.stack || e);
|
507 | return cb ? cb(e.stack || e) : false;
|
508 | };
|
509 | return false;
|
510 | };
|
511 |
|
512 |
|
513 |
|
514 |
|
515 |
|
516 |
|
517 |
|
518 | Satan.getExposedMethods = function getExposedMethods(cb) {
|
519 | Satan.client.methods(cb);
|
520 | };
|
521 |
|
522 | Satan.printOut = function() {
|
523 | if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
|
524 | return console.log.apply(console, arguments);
|
525 | };
|
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 |
|
532 |
|
533 |
|
534 |
|
535 | Satan.executeRemote = function executeRemote(method, env, fn) {
|
536 | var env_watch = false;
|
537 |
|
538 | if (env.env && env.env.watch)
|
539 | env_watch = env.env.watch;
|
540 |
|
541 | env_watch = util.isArray(env_watch) && env_watch.length === 0 ? !!~process.argv.indexOf('--watch') : env_watch;
|
542 |
|
543 |
|
544 | if (method.indexOf('delete') !== -1) {
|
545 | Satan.stopWatch(method, env);
|
546 |
|
547 | } else if(method.indexOf('kill') !== -1) {
|
548 | Satan.stopWatch('deleteAll', env);
|
549 |
|
550 | } else if (~process.argv.indexOf('--watch') && method.indexOf('stop') !== -1) {
|
551 | Satan.stopWatch(method, env);
|
552 |
|
553 | } else if (env_watch && method.indexOf('restart') !== -1) {
|
554 | Satan.restartWatch(method, env);
|
555 | }
|
556 |
|
557 | if (!Satan.client || !Satan.client.call) {
|
558 | if (fn) return fn(new Error('Could not connect to local pm2, have you called pm2.connect(function()})'));
|
559 | console.error('Did you forgot to call pm2.connect(function() { }) before interacting with PM2 ?');
|
560 | return process.exit(0);
|
561 | }
|
562 |
|
563 | debug('Calling daemon method pm2:%s', method);
|
564 | return Satan.client.call(method, env, fn);
|
565 | };
|
566 |
|
567 | Satan.notifyGod = function(action_name, id, cb) {
|
568 | Satan.executeRemote('notifyByProcessId', {
|
569 | id : id,
|
570 | action_name : action_name,
|
571 | manually : true
|
572 | }, function() {
|
573 | debug('God notified');
|
574 | return cb ? cb() : false;
|
575 | });
|
576 | };
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 |
|
583 | Satan.killDaemon = function killDaemon(fn) {
|
584 | var timeout;
|
585 |
|
586 | function quit() {
|
587 | Satan.disconnectRPC(function() {
|
588 | debug('RPC disconnected');
|
589 | return fn ? fn(null, {success:true}) : false;
|
590 | });
|
591 | }
|
592 |
|
593 | process.once('SIGQUIT', function() {
|
594 | debug('Received SIGQUIT');
|
595 | clearTimeout(timeout);
|
596 | quit();
|
597 | });
|
598 |
|
599 | timeout = setTimeout(function() {
|
600 | quit();
|
601 | }, 3000);
|
602 |
|
603 |
|
604 | Satan.executeRemote('killMe', {pid : process.pid}, function() {});
|
605 | };
|
606 |
|
607 |
|
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 |
|
615 | Satan.restartWatch = function restartWatch(method, env, fn) {
|
616 | debug('Calling restartWatch');
|
617 | Satan.client.call('restartWatch', method, env, function() {
|
618 | debug('Restart watching');
|
619 | return fn ? fn() : false;
|
620 | });
|
621 | };
|
622 |
|
623 |
|
624 |
|
625 |
|
626 |
|
627 |
|
628 |
|
629 |
|
630 |
|
631 | Satan.stopWatch = function stopWatch(method, env, fn) {
|
632 | debug('Calling stopWatch');
|
633 | Satan.client.call('stopWatch', method, env, function() {
|
634 | debug('Stop watching');
|
635 | return fn ? fn() : false;
|
636 | });
|
637 | };
|
638 |
|
639 |
|
640 |
|
641 |
|
642 |
|
643 | if (require.main === module) {
|
644 |
|
645 | var pkg = require('../package.json');
|
646 |
|
647 | process.title = 'PM2 v' + pkg.version + ': God Daemon';
|
648 |
|
649 | if (process.env.NODE_ENV == 'test') {
|
650 | Satan.remoteWrapper();
|
651 | }
|
652 | else {
|
653 | var d = domain.create();
|
654 |
|
655 | d.once('error', function(err) {
|
656 | console.error('[PM2] Error caught by domain:\n' + (err.stack || err));
|
657 | console.error('[PM2] Trying to update PM2...');
|
658 |
|
659 | require('child_process').spawn('node', [process.env['_'], 'update'], {
|
660 | detached: true,
|
661 | stdio: 'inherit'
|
662 | });
|
663 |
|
664 | });
|
665 |
|
666 | d.run(function() {
|
667 | Satan.remoteWrapper();
|
668 | });
|
669 | }
|
670 | }
|