Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50654 views
1
/*
2
* forever.js: Top level include for the forever module
3
*
4
* (C) 2010 Nodejitsu Inc.
5
* MIT LICENCE
6
*
7
*/
8
9
var fs = require('fs'),
10
path = require('path'),
11
events = require('events'),
12
exec = require('child_process').exec,
13
spawn = require('child_process').spawn,
14
cliff = require('cliff'),
15
nconf = require('nconf'),
16
nssocket = require('nssocket'),
17
timespan = require('timespan'),
18
utile = require('utile'),
19
winston = require('winston'),
20
mkdirp = utile.mkdirp,
21
async = utile.async;
22
23
var forever = exports;
24
25
//
26
// Setup `forever.log` to be a custom `winston` logger.
27
//
28
forever.log = new (winston.Logger)({
29
transports: [
30
new (winston.transports.Console)()
31
]
32
});
33
34
forever.log.cli();
35
36
//
37
// ### Export Components / Settings
38
// Export `version` and important Prototypes from `lib/forever/*`
39
//
40
forever.initialized = false;
41
forever.kill = require('forever-monitor').kill;
42
forever.checkProcess = require('forever-monitor').checkProcess;
43
forever.root = path.join(process.env.HOME || '/root', '.forever');
44
forever.config = new nconf.File({ file: path.join(forever.root, 'config.json') });
45
forever.Forever = forever.Monitor = require('forever-monitor').Monitor;
46
forever.Worker = require('./forever/worker').Worker;
47
forever.cli = require('./forever/cli');
48
49
//
50
// Expose version through `pkginfo`
51
//
52
require('pkginfo')(module, 'version');
53
54
//
55
// Expose the global forever service
56
//
57
forever.__defineGetter__('service', function () {
58
return require('./forever/service');
59
});
60
61
//
62
// ### function getSockets (sockPath, callback)
63
// #### @sockPath {string} Path in which to look for UNIX domain sockets
64
// #### @callback {function} Continuation to pass control to when complete
65
// Attempts to read the files from `sockPath` if the directory does not exist,
66
// then it is created using `mkdirp`.
67
//
68
function getSockets(sockPath, callback) {
69
var sockets;
70
71
try {
72
sockets = fs.readdirSync(sockPath);
73
}
74
catch (ex) {
75
if (ex.code !== 'ENOENT') {
76
return callback(ex);
77
}
78
79
return mkdirp(sockPath, '0755', function (err) {
80
return err ? callback(err) : callback(null, []);
81
});
82
}
83
84
callback(null, sockets);
85
}
86
87
//
88
// ### function getAllProcess (callback)
89
// #### @callback {function} Continuation to respond to when complete.
90
// Returns all data for processes managed by forever.
91
//
92
function getAllProcesses(callback) {
93
var sockPath = forever.config.get('sockPath');
94
95
function getProcess(name, next) {
96
var fullPath = path.join(sockPath, name),
97
socket = new nssocket.NsSocket();
98
99
socket.connect(fullPath, function (err) {
100
if (err) {
101
next(err);
102
}
103
104
socket.dataOnce(['data'], function (data) {
105
data.socket = fullPath;
106
next(null, data);
107
socket.end();
108
});
109
110
socket.send(['data']);
111
});
112
113
socket.on('error', function (err) {
114
if (err.code === 'ECONNREFUSED') {
115
fs.unlink(fullPath, function () {
116
next();
117
});
118
}
119
else {
120
next();
121
}
122
});
123
}
124
125
getSockets(sockPath, function (err, sockets) {
126
if (err || (sockets && sockets.length === 0)) {
127
return callback(err);
128
}
129
130
async.map(sockets, getProcess, function (err, processes) {
131
callback(processes.filter(Boolean));
132
});
133
});
134
}
135
136
//
137
// ### function getAllPids ()
138
// Returns the set of all pids managed by forever.
139
// e.x. [{ pid: 12345, foreverPid: 12346 }, ...]
140
//
141
function getAllPids(processes) {
142
return !processes ? null : processes.map(function (proc) {
143
return {
144
pid: proc.pid,
145
foreverPid: proc.foreverPid
146
};
147
});
148
}
149
150
function stopOrRestart(action, event, format, target) {
151
var emitter = new events.EventEmitter(),
152
results = [],
153
pids;
154
155
function sendAction(proc, next) {
156
var socket = new nssocket.NsSocket();
157
158
socket.connect(proc.socket, function (err) {
159
if (err) {
160
next(err);
161
}
162
163
socket.dataOnce([action, 'ok'], function (data) {
164
next();
165
socket.end();
166
});
167
168
socket.send([action]);
169
});
170
171
socket.on('error', function (err) {
172
next(err);
173
});
174
}
175
176
getAllProcesses(function (processes) {
177
var procs = processes;
178
179
if (target !== undefined && target !== null) {
180
procs = forever.findByIndex(target, processes)
181
|| forever.findByScript(target, processes)
182
|| forever.findByUid(target, processes);
183
}
184
185
if (procs && procs.length > 0) {
186
async.map(procs, sendAction, function (err, results) {
187
if (err) {
188
emitter.emit('error', err);
189
}
190
191
emitter.emit(event, forever.format(format, procs));
192
});
193
}
194
else {
195
process.nextTick(function () {
196
emitter.emit('error', new Error('Cannot find forever process: ' + target));
197
});
198
}
199
});
200
201
return emitter;
202
}
203
204
//
205
// ### function load (options, [callback])
206
// #### @options {Object} Options to load into the forever module
207
// Initializes configuration for forever module
208
//
209
forever.load = function (options) {
210
//
211
// Setup the incoming options with default options.
212
//
213
options = options || {};
214
options.loglength = options.loglength || 100;
215
options.root = options.root || forever.root;
216
options.pidPath = options.pidPath || path.join(options.root, 'pids');
217
options.sockPath = options.sockPath || path.join(options.root, 'sock');
218
219
//
220
// If forever is initalized and the config directories are identical
221
// simply return without creating directories
222
//
223
if (forever.initialized && forever.config.get('root') === options.root &&
224
forever.config.get('pidPath') === options.pidPath) {
225
return;
226
}
227
228
forever.config = new nconf.File({ file: path.join(options.root, 'config.json') });
229
230
//
231
// Try to load the forever `config.json` from
232
// the specified location.
233
//
234
try {
235
forever.config.loadSync();
236
}
237
catch (ex) { }
238
239
//
240
// Setup the columns for `forever list`.
241
//
242
options.columns = options.columns || forever.config.get('columns');
243
if (!options.columns) {
244
options.columns = [
245
'uid', 'command', 'script', 'forever', 'pid', 'logfile', 'uptime'
246
];
247
}
248
249
forever.config.set('root', options.root);
250
forever.config.set('pidPath', options.pidPath);
251
forever.config.set('sockPath', options.sockPath);
252
forever.config.set('loglength', options.loglength);
253
forever.config.set('columns', options.columns);
254
255
//
256
// Attempt to see if `forever` has been configured to
257
// run in debug mode.
258
//
259
options.debug = options.debug || forever.config.get('debug') || false;
260
261
if (options.debug) {
262
//
263
// If we have been indicated to debug this forever process
264
// then setup `forever._debug` to be an instance of `winston.Logger`.
265
//
266
forever._debug();
267
}
268
269
//
270
// Syncronously create the `root` directory
271
// and the `pid` directory for forever. Although there is
272
// an additional overhead here of the sync action. It simplifies
273
// the setup of forever dramatically.
274
//
275
function tryCreate(dir) {
276
try {
277
fs.mkdirSync(dir, '0755');
278
}
279
catch (ex) { }
280
}
281
282
tryCreate(forever.config.get('root'));
283
tryCreate(forever.config.get('pidPath'));
284
tryCreate(forever.config.get('sockPath'));
285
286
//
287
// Attempt to save the new `config.json` for forever
288
//
289
try {
290
forever.config.saveSync();
291
}
292
catch (ex) { }
293
294
forever.initialized = true;
295
};
296
297
//
298
// ### @private function _debug ()
299
// Sets up debugging for this forever process
300
//
301
forever._debug = function () {
302
var debug = forever.config.get('debug');
303
304
if (!debug) {
305
forever.config.set('debug', true);
306
forever.log.add(winston.transports.File, {
307
level: 'silly',
308
filename: path.join(forever.config.get('root'), 'forever.debug.log')
309
});
310
}
311
};
312
313
//
314
// Ensure forever will always be loaded the first time it is required.
315
//
316
forever.load();
317
318
//
319
// ### function stat (logFile, script, callback)
320
// #### @logFile {string} Path to the log file for this script
321
// #### @logAppend {boolean} Optional. True Prevent failure if the log file exists.
322
// #### @script {string} Path to the target script.
323
// #### @callback {function} Continuation to pass control back to
324
// Ensures that the logFile doesn't exist and that
325
// the target script does exist before executing callback.
326
//
327
forever.stat = function (logFile, script, callback) {
328
var logAppend;
329
330
if (arguments.length === 4) {
331
logAppend = callback;
332
callback = arguments[3];
333
}
334
335
fs.stat(script, function (err, stats) {
336
if (err) {
337
return callback(new Error('script ' + script + ' does not exist.'));
338
}
339
340
return logAppend ? callback(null) : fs.stat(logFile, function (err, stats) {
341
return !err
342
? callback(new Error('log file ' + logFile + ' exists. Use the -a or --append option to append log.'))
343
: callback(null);
344
});
345
});
346
};
347
348
//
349
// ### function start (script, options)
350
// #### @script {string} Location of the script to run.
351
// #### @options {Object} Configuration for forever instance.
352
// Starts a script with forever
353
//
354
forever.start = function (script, options) {
355
if (!options.uid) {
356
options.uid = options.uid || utile.randomString(4).replace(/^\-/, '_');
357
}
358
359
if (!options.logFile) {
360
options.logFile = forever.logFilePath(options.uid + '.log');
361
}
362
363
//
364
// Create the monitor, log events, and start.
365
//
366
var monitor = new forever.Monitor(script, options);
367
forever.logEvents(monitor);
368
return monitor.start();
369
};
370
371
//
372
// ### function startDaemon (script, options)
373
// #### @script {string} Location of the script to run.
374
// #### @options {Object} Configuration for forever instance.
375
// Starts a script with forever as a daemon
376
//
377
forever.startDaemon = function (script, options) {
378
options = options || {};
379
options.uid = options.uid || utile.randomString(4).replace(/^\-/, '_');
380
options.logFile = forever.logFilePath(options.logFile || options.uid + '.log');
381
options.pidFile = forever.pidFilePath(options.pidFile || options.uid + '.pid');
382
383
var monitor, outFD, errFD, workerPath;
384
385
//
386
// This log file is forever's log file - the user's outFile and errFile
387
// options are not taken into account here. This will be an aggregate of all
388
// the app's output, as well as messages from the monitor process, where
389
// applicable.
390
//
391
outFD = fs.openSync(options.logFile, 'a');
392
errFD = fs.openSync(options.logFile, 'a');
393
monitorPath = path.resolve(__dirname, '..', 'bin', 'monitor');
394
395
monitor = spawn(process.execPath, [monitorPath, script], {
396
stdio: ['ipc', outFD, errFD],
397
detached: true
398
});
399
400
monitor.on('exit', function (code) {
401
console.error('Monitor died unexpectedly with exit code %d', code);
402
});
403
404
monitor.send(JSON.stringify(options));
405
monitor.unref();
406
};
407
408
//
409
// ### function startServer ()
410
// #### @arguments {forever.Monitor...} A list of forever.Monitor instances
411
// Starts the `forever` HTTP server for communication with the forever CLI.
412
// **NOTE:** This will change your `process.title`.
413
//
414
forever.startServer = function () {
415
var args = Array.prototype.slice.call(arguments),
416
monitors = [],
417
callback;
418
419
args.forEach(function (a) {
420
if (Array.isArray(a)) {
421
monitors = monitors.concat(a.filter(function (m) {
422
return m instanceof forever.Monitor;
423
}));
424
}
425
else if (a instanceof forever.Monitor) {
426
monitors.push(a);
427
}
428
else if (typeof a === 'function') {
429
callback = a;
430
}
431
});
432
433
async.map(monitors, function (monitor, next) {
434
var worker = new forever.Worker({
435
monitor: monitor,
436
sockPath: forever.config.get('sockPath'),
437
exitOnStop: true
438
});
439
440
worker.start(function (err) {
441
return err ? next(err) : next(null, worker);
442
});
443
}, callback || function () {});
444
};
445
446
447
//
448
// ### function stop (target, [format])
449
// #### @target {string} Index or script name to stop
450
// #### @format {boolean} Indicated if we should CLI format the returned output.
451
// Stops the process(es) with the specified index or script name
452
// in the list of all processes
453
//
454
forever.stop = function (target, format) {
455
return stopOrRestart('stop', 'stop', format, target);
456
};
457
458
//
459
// ### function restart (target, format)
460
// #### @target {string} Index or script name to restart
461
// #### @format {boolean} Indicated if we should CLI format the returned output.
462
// Restarts the process(es) with the specified index or script name
463
// in the list of all processes
464
//
465
forever.restart = function (target, format) {
466
return stopOrRestart('restart', 'restart', format, target);
467
};
468
469
//
470
// ### function restartAll (format)
471
// #### @format {boolean} Value indicating if we should format output
472
// Restarts all processes managed by forever.
473
//
474
forever.restartAll = function (format) {
475
return stopOrRestart('restart', 'restartAll', format);
476
};
477
478
//
479
// ### function stopAll (format)
480
// #### @format {boolean} Value indicating if we should format output
481
// Stops all processes managed by forever.
482
//
483
forever.stopAll = function (format) {
484
return stopOrRestart('stop', 'stopAll', format);
485
};
486
487
//
488
// ### function list (format, procs, callback)
489
// #### @format {boolean} If set, will return a formatted string of data
490
// #### @callback {function} Continuation to respond to when complete.
491
// Returns the list of all process data managed by forever.
492
//
493
forever.list = function (format, callback) {
494
getAllProcesses(function (processes) {
495
callback(null, forever.format(format, processes));
496
});
497
};
498
499
//
500
// ### function tail (target, length, callback)
501
// #### @target {string} Target script to list logs for
502
// #### @length {number} **Optional** Length of the logs to tail.
503
// #### @callback {function} Continuation to respond to when complete.
504
// Responds with the latest `length` logs for the specified `target` process
505
// managed by forever. If no `length` is supplied then `forever.config.get('loglength`)`
506
// is used.
507
//
508
forever.tail = function (target, length, callback) {
509
if (!callback && typeof length === 'function') {
510
callback = length;
511
length = 0;
512
}
513
514
length = length || forever.config.get('loglength');
515
if (!length) {
516
return callback(new Error('Cannot tail logs without a specified length'));
517
}
518
519
function tailProcess(proc, next) {
520
exec('tail -n ' + [length, proc.logFile].join(' '), function (err, stdout) {
521
if (err) {
522
return next(err);
523
}
524
525
proc.logs = stdout.split('\n');
526
proc.logs.pop();
527
528
return err ? next(err) : next(null, proc);
529
});
530
}
531
532
getAllProcesses(function (processes) {
533
if (!processes) {
534
return callback(new Error('Cannot find forever process: ' + target));
535
}
536
537
var procs = forever.findByIndex(target, processes)
538
|| forever.findByScript(target, processes);
539
540
async.mapSeries(procs, tailProcess, function (err, procs) {
541
return err
542
? callback(err)
543
: callback(null, procs);
544
});
545
});
546
};
547
548
//
549
// ### function findByIndex (index, processes)
550
// #### @index {string} Index of the process to find.
551
// #### @processes {Array} Set of processes to find in.
552
// Finds the process with the specified index.
553
//
554
forever.findByIndex = function (index, processes) {
555
var proc = processes && processes[parseInt(index, 10)];
556
return proc ? [proc] : null;
557
};
558
559
//
560
// ### function findByScript (script, processes)
561
// #### @script {string} The name of the script to find.
562
// #### @processes {Array} Set of processes to find in.
563
// Finds the process with the specified script name.
564
//
565
forever.findByScript = function (script, processes) {
566
if (!processes) return null;
567
568
var procs = processes.filter(function (p) {
569
return p.file === script;
570
});
571
572
if (procs.length === 0) procs = null;
573
574
return procs;
575
};
576
577
//
578
// ### function findByUid (uid, processes)
579
// #### @uid {string} The uid of the process to find.
580
// #### @processes {Array} Set of processes to find in.
581
// Finds the process with the specified uid.
582
//
583
forever.findByUid = function (script, processes) {
584
return !processes
585
? null
586
: processes.filter(function (p) {
587
return p.uid === script;
588
});
589
};
590
591
//
592
// ### function format (format, procs)
593
// #### @format {Boolean} Value indicating if processes should be formatted
594
// #### @procs {Array} Processes to format
595
// Returns a formatted version of the `procs` supplied based on the column
596
// configuration in `forever.config`.
597
//
598
forever.format = function (format, procs) {
599
if (!procs || procs.length === 0) {
600
return null;
601
}
602
603
var index = 0,
604
columns = forever.config.get('columns'),
605
rows = [[' '].concat(columns)],
606
formatted;
607
608
function mapColumns(prefix, mapFn) {
609
return [prefix].concat(columns.map(mapFn));
610
}
611
612
if (format) {
613
//
614
// Iterate over the procs to see which has the
615
// longest options string
616
//
617
procs.forEach(function (proc) {
618
rows.push(mapColumns('[' + index + ']', function (column) {
619
return forever.columns[column]
620
? forever.columns[column].get(proc)
621
: 'MISSING';
622
}));
623
624
index++;
625
});
626
627
formatted = cliff.stringifyRows(rows, mapColumns('white', function (column) {
628
return forever.columns[column]
629
? forever.columns[column].color
630
: 'white';
631
}));
632
}
633
634
return format ? formatted : procs;
635
};
636
637
//
638
// ### function cleanUp ()
639
// Utility function for removing excess pid and
640
// config, and log files used by forever.
641
//
642
forever.cleanUp = function (cleanLogs, allowManager) {
643
var emitter = new events.EventEmitter(),
644
pidPath = forever.config.get('pidPath');
645
646
getAllProcesses(function (processes) {
647
if (cleanLogs) {
648
forever.cleanLogsSync(processes);
649
}
650
651
function unlinkProcess(proc, done) {
652
fs.unlink(path.join(pidPath, proc.uid + '.pid'), function () {
653
//
654
// Ignore errors (in case the file doesnt exist).
655
//
656
657
if (cleanLogs && proc.logFile) {
658
//
659
// If we are cleaning logs then do so if the process
660
// has a logfile.
661
//
662
return fs.unlink(proc.logFile, function () {
663
done();
664
});
665
}
666
667
done();
668
});
669
}
670
671
function cleanProcess(proc, done) {
672
if (proc.child && proc.manager) {
673
return done();
674
}
675
else if (!proc.child && !proc.manager
676
|| (!proc.child && proc.manager && allowManager)
677
|| proc.dead) {
678
return unlinkProcess(proc, done);
679
}
680
681
//
682
// If we have a manager but no child, wait a moment
683
// in-case the child is currently restarting, but **only**
684
// if we have not already waited for this process
685
//
686
if (!proc.waited) {
687
proc.waited = true;
688
return setTimeout(function () {
689
checkProcess(proc, done);
690
}, 500);
691
}
692
693
done();
694
}
695
696
function checkProcess(proc, next) {
697
proc.child = forever.checkProcess(proc.pid);
698
proc.manager = forever.checkProcess(proc.foreverPid);
699
cleanProcess(proc, next);
700
}
701
702
if (processes && processes.length > 0) {
703
(function cleanBatch(batch) {
704
async.forEach(batch, checkProcess, function () {
705
return processes.length > 0
706
? cleanBatch(processes.splice(0, 10))
707
: emitter.emit('cleanUp');
708
});
709
})(processes.splice(0, 10));
710
}
711
else {
712
process.nextTick(function () {
713
emitter.emit('cleanUp');
714
});
715
}
716
});
717
718
return emitter;
719
};
720
721
//
722
// ### function cleanLogsSync (processes)
723
// #### @processes {Array} The set of all forever processes
724
// Removes all log files from the root forever directory
725
// that do not belong to current running forever processes.
726
//
727
forever.cleanLogsSync = function (processes) {
728
var root = forever.config.get('root'),
729
files = fs.readdirSync(root),
730
running,
731
runningLogs;
732
733
running = processes && processes.filter(function (p) {
734
return p && p.logFile;
735
});
736
737
runningLogs = running && running.map(function (p) {
738
return p.logFile.split('/').pop();
739
});
740
741
files.forEach(function (file) {
742
if (/\.log$/.test(file) && (!runningLogs || runningLogs.indexOf(file) === -1)) {
743
fs.unlinkSync(path.join(root, file));
744
}
745
});
746
};
747
748
//
749
// ### function logFilePath (logFile)
750
// #### @logFile {string} Log file path
751
// Determines the full logfile path name
752
//
753
forever.logFilePath = function (logFile, uid) {
754
return logFile && logFile[0] === '/'
755
? logFile
756
: path.join(forever.config.get('root'), logFile || (uid || 'forever') + '.log');
757
};
758
759
//
760
// ### function pidFilePath (pidFile)
761
// #### @logFile {string} Pid file path
762
// Determines the full pid file path name
763
//
764
forever.pidFilePath = function (pidFile) {
765
return pidFile && pidFile[0] === '/'
766
? pidFile
767
: path.join(forever.config.get('pidPath'), pidFile);
768
};
769
770
//
771
// ### @function logEvents (monitor)
772
// #### @monitor {forever.Monitor} Monitor to log events for
773
// Logs important restart and error events to `console.error`
774
//
775
forever.logEvents = function (monitor) {
776
monitor.on('watch:error', function (info) {
777
console.error(info.message);
778
console.error(info.error);
779
});
780
781
monitor.on('watch:restart', function (info) {
782
console.error('restaring script because ' + info.file + ' changed')
783
});
784
785
monitor.on('restart', function () {
786
console.error('Forever restarting script for ' + monitor.times + ' time')
787
});
788
789
monitor.on('exit:code', function (code) {
790
console.error('Forever detected script exited with code: ' + code);
791
});
792
};
793
794
//
795
// ### @columns {Object}
796
// Property descriptors for accessing forever column information
797
// through `forever list` and `forever.list()`
798
//
799
forever.columns = {
800
uid: {
801
color: 'white',
802
get: function (proc) {
803
return proc.uid;
804
}
805
},
806
command: {
807
color: 'grey',
808
get: function (proc) {
809
return (proc.command || 'node').grey;
810
}
811
},
812
script: {
813
color: 'grey',
814
get: function (proc) {
815
return [proc.file].concat(proc.options).join(' ').grey;
816
}
817
},
818
forever: {
819
color: 'white',
820
get: function (proc) {
821
return proc.foreverPid;
822
}
823
},
824
pid: {
825
color: 'white',
826
get: function (proc) {
827
return proc.pid;
828
}
829
},
830
logfile: {
831
color: 'magenta',
832
get: function (proc) {
833
return proc.logFile ? proc.logFile.magenta : '';
834
}
835
},
836
uptime: {
837
color: 'yellow',
838
get: function (proc) {
839
return timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow;
840
}
841
}
842
};
843
844