diff --git a/app.js b/app.js index cf28e756f2..8ff5647023 100644 --- a/app.js +++ b/app.js @@ -133,89 +133,79 @@ function start() { winston.verbose('* using themes stored in: %s', nconf.get('themes_path')); } + process.on('SIGTERM', shutdown); + process.on('SIGINT', shutdown); + process.on('SIGHUP', restart); + process.on('message', function(message) { + if (typeof message !== 'object') { + return; + } + var meta = require('./src/meta'); + var emitter = require('./src/emitter'); + switch (message.action) { + case 'reload': + meta.reload(); + break; + case 'js-propagate': + meta.js.cache = message.cache; + meta.js.map = message.map; + meta.js.hash = message.hash; + emitter.emit('meta:js.compiled'); + winston.verbose('[cluster] Client-side javascript and mapping propagated to worker %s', process.pid); + break; + case 'css-propagate': + meta.css.cache = message.cache; + meta.css.acpCache = message.acpCache; + meta.css.hash = message.hash; + emitter.emit('meta:css.compiled'); + winston.verbose('[cluster] Stylesheets propagated to worker %s', process.pid); + break; + case 'templates:compiled': + emitter.emit('templates:compiled'); + break; + } + }); - var webserver = require('./src/webserver'); + process.on('uncaughtException', function(err) { + winston.error(err.stack); + console.log(err.stack); - require('./src/database').init(function(err) { + require('./src/meta').js.killMinifier(); + shutdown(1); + }); + + async.waterfall([ + function(next) { + require('./src/database').init(next); + }, + function(next) { + require('./src/meta').configs.init(next); + }, + function(next) { + require('./src/upgrade').check(next); + }, + function(schema_ok, next) { + if (!schema_ok && nconf.get('check-schema') !== false) { + winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:'); + winston.warn(' ./nodebb upgrade'); + process.exit(); + return; + } + var webserver = require('./src/webserver'); + require('./src/socket.io').init(webserver.server); + + if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) { + require('./src/notifications').init(); + require('./src/user').startJobs(); + } + + webserver.listen(); + } + ], function(err) { if (err) { winston.error(err.stack); process.exit(); } - var meta = require('./src/meta'); - meta.configs.init(function () { - var templates = require('templates.js'), - sockets = require('./src/socket.io'), - plugins = require('./src/plugins'), - upgrade = require('./src/upgrade'); - - templates.setGlobal('relative_path', nconf.get('relative_path')); - - upgrade.check(function(schema_ok) { - if (schema_ok || nconf.get('check-schema') === false) { - webserver.init(); - sockets.init(webserver.server); - - if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) { - require('./src/notifications').init(); - require('./src/user').startJobs(); - } - - webserver.listen(); - - async.waterfall([ - async.apply(meta.themes.setupPaths), - async.apply(plugins.ready), - async.apply(meta.templates.compile) - ], function(err) { - if (err) { - winston.error(err.stack); - process.exit(); - } - - if (process.send) { - process.send({ - action: 'ready' - }); - } - }); - - process.on('SIGTERM', shutdown); - process.on('SIGINT', shutdown); - process.on('SIGHUP', restart); - process.on('message', function(message) { - switch(message.action) { - case 'reload': - meta.reload(); - break; - case 'js-propagate': - meta.js.cache = message.cache; - meta.js.map = message.map; - meta.js.hash = message.hash; - winston.verbose('[cluster] Client-side javascript and mapping propagated to worker %s', process.pid); - break; - case 'css-propagate': - meta.css.cache = message.cache; - meta.css.acpCache = message.acpCache; - meta.css.hash = message.hash; - winston.verbose('[cluster] Stylesheets propagated to worker %s', process.pid); - break; - } - }); - - process.on('uncaughtException', function(err) { - winston.error(err.stack); - console.log(err.stack); - - meta.js.killMinifier(); - shutdown(1); - }); - } else { - winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:'); - winston.warn(' ./nodebb upgrade'); - process.exit(); - } - }); - }); }); } @@ -243,7 +233,7 @@ function setup() { winston.error('There was a problem completing NodeBB setup: ', err.message); } else { if (data.hasOwnProperty('password')) { - process.stdout.write('An administrative user was automatically created for you:\n') + process.stdout.write('An administrative user was automatically created for you:\n'); process.stdout.write(' Username: ' + data.username + '\n'); process.stdout.write(' Password: ' + data.password + '\n'); process.stdout.write('\n'); diff --git a/loader.js b/loader.js index d94afb669c..ec549b31c9 100644 --- a/loader.js +++ b/loader.js @@ -82,7 +82,7 @@ Loader.addWorkerEvents = function(worker) { if (message && typeof message === 'object' && message.action) { switch (message.action) { case 'ready': - if (Loader.js.cache) { + if (Loader.js.cache && !worker.isPrimary) { worker.send({ action: 'js-propagate', cache: Loader.js.cache, @@ -91,7 +91,7 @@ Loader.addWorkerEvents = function(worker) { }); } - if (Loader.css.cache) { + if (Loader.css.cache && !worker.isPrimary) { worker.send({ action: 'css-propagate', cache: Loader.css.cache, @@ -99,6 +99,8 @@ Loader.addWorkerEvents = function(worker) { hash: Loader.css.hash }); } + + break; case 'restart': console.log('[cluster] Restarting...'); @@ -132,6 +134,11 @@ Loader.addWorkerEvents = function(worker) { hash: message.hash }, worker.pid); break; + case 'templates:compiled': + Loader.notifyWorkers({ + action: 'templates:compiled', + }, worker.pid); + break; } } }); diff --git a/src/emitter.js b/src/emitter.js index 4d9b0f0164..468f604aaf 100644 --- a/src/emitter.js +++ b/src/emitter.js @@ -6,39 +6,30 @@ var eventEmitter = new (require('events')).EventEmitter(); eventEmitter.all = function(events, callback) { var eventList = events.slice(0); - function onEvent(event) { - eventEmitter.on(events[event], function() { - eventList.splice(eventList.indexOf(events[event]), 1); - + events.forEach(function onEvent(event) { + eventEmitter.on(event, function() { + var index = eventList.indexOf(event); + if (index === -1) { + return; + } + eventList.splice(index, 1); if (eventList.length === 0) { callback(); } }); - } - - for (var ev in events) { - if (events.hasOwnProperty(ev)) { - onEvent(ev); - } - } + }); }; eventEmitter.any = function(events, callback) { - function onEvent(event) { - eventEmitter.on(events[event], function() { + events.forEach(function onEvent(event) { + eventEmitter.on(event, function() { if (events !== null) { callback(); } events = null; }); - } - - for (var ev in events) { - if (events.hasOwnProperty(ev)) { - onEvent(ev); - } - } + }); }; module.exports = eventEmitter; \ No newline at end of file diff --git a/src/install.js b/src/install.js index e7bb669402..8b7c97ffc5 100644 --- a/src/install.js +++ b/src/install.js @@ -499,7 +499,10 @@ install.setup = function (callback) { setCopyrightWidget, function (next) { var upgrade = require('./upgrade'); - upgrade.check(function(uptodate) { + upgrade.check(function(err, uptodate) { + if (err) { + return next(err); + } if (!uptodate) { upgrade.upgrade(next); } else { next(); } }); diff --git a/src/meta/templates.js b/src/meta/templates.js index 78596d59a4..35bb80f06e 100644 --- a/src/meta/templates.js +++ b/src/meta/templates.js @@ -15,6 +15,7 @@ var mkdirp = require('mkdirp'), Templates = {}; Templates.compile = function(callback) { + callback = callback || function() {}; var fromFile = nconf.get('from-file') || ''; if (nconf.get('isPrimary') === 'false' || fromFile.match('tpl')) { @@ -22,11 +23,7 @@ Templates.compile = function(callback) { winston.info('[minifier] Compiling templates skipped'); } - emitter.emit('templates:compiled'); - if (callback) { - callback(); - } - return; + return callback(); } var coreTemplatesPath = nconf.get('core_templates_path'), @@ -119,15 +116,20 @@ Templates.compile = function(callback) { }, function(err) { if (err) { winston.error('[meta/templates] ' + err.stack); - } else { - compileIndex(viewsPath, function() { - winston.verbose('[meta/templates] Successfully compiled templates.'); - emitter.emit('templates:compiled'); - if (callback) { - callback(); - } - }); + return callback(err); } + + compileIndex(viewsPath, function() { + winston.verbose('[meta/templates] Successfully compiled templates.'); + + emitter.emit('templates:compiled'); + if (process.send) { + process.send({ + action: 'templates:compiled' + }); + } + callback(); + }); }); }); }); diff --git a/src/plugins.js b/src/plugins.js index a32403e2ab..156ba49a3b 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -39,9 +39,10 @@ var fs = require('fs'), Plugins.libraryPaths.push(libraryPath); }; - Plugins.init = function(nbbApp, nbbMiddleware) { + Plugins.init = function(nbbApp, nbbMiddleware, callback) { + callback = callback || function() {}; if (Plugins.initialized) { - return; + return callback(); } app = nbbApp; @@ -55,7 +56,7 @@ var fs = require('fs'), Plugins.reload(function(err) { if (err) { winston.error('[plugins] NodeBB encountered a problem while loading plugins', err.message); - return; + return callback(err); } if (global.env === 'development') { @@ -64,6 +65,7 @@ var fs = require('fs'), Plugins.initialized = true; emitter.emit('plugins:loaded'); + callback(); }); Plugins.registerHook('core', { @@ -72,14 +74,6 @@ var fs = require('fs'), }); }; - Plugins.ready = function(callback) { - if (!Plugins.initialized) { - emitter.once('plugins:loaded', callback); - } else { - callback(); - } - }; - Plugins.reload = function(callback) { // Resetting all local plugin data Plugins.libraries = {}; diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 4f9611e56a..a5f5c2493d 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -40,44 +40,42 @@ var router = express.Router(); router.hotswapId = 'auth'; - plugins.ready(function() { - loginStrategies.length = 0; + loginStrategies.length = 0; - if (plugins.hasListeners('action:auth.overrideLogin')) { - winston.warn('[authentication] Login override detected, skipping local login strategy.'); - plugins.fireHook('action:auth.overrideLogin'); - } else { - passport.use(new passportLocal({passReqToCallback: true}, Auth.login)); + if (plugins.hasListeners('action:auth.overrideLogin')) { + winston.warn('[authentication] Login override detected, skipping local login strategy.'); + plugins.fireHook('action:auth.overrideLogin'); + } else { + passport.use(new passportLocal({passReqToCallback: true}, Auth.login)); + } + + plugins.fireHook('filter:auth.init', loginStrategies, function(err) { + if (err) { + winston.error('filter:auth.init - plugin failure'); + return callback(err); } - plugins.fireHook('filter:auth.init', loginStrategies, function(err) { - if (err) { - winston.error('filter:auth.init - plugin failure'); - return callback(err); + loginStrategies.forEach(function(strategy) { + if (strategy.url) { + router.get(strategy.url, passport.authenticate(strategy.name, { + scope: strategy.scope + })); } - loginStrategies.forEach(function(strategy) { - if (strategy.url) { - router.get(strategy.url, passport.authenticate(strategy.name, { - scope: strategy.scope - })); - } - - router.get(strategy.callbackURL, passport.authenticate(strategy.name, { - successReturnToOrRedirect: nconf.get('relative_path') + '/', - failureRedirect: nconf.get('relative_path') + '/login' - })); - }); + router.get(strategy.callbackURL, passport.authenticate(strategy.name, { + successReturnToOrRedirect: nconf.get('relative_path') + '/', + failureRedirect: nconf.get('relative_path') + '/login' + })); + }); - router.post('/logout', Auth.middleware.applyCSRF, logout); - router.post('/register', Auth.middleware.applyCSRF, register); - router.post('/login', Auth.middleware.applyCSRF, login); + router.post('/logout', Auth.middleware.applyCSRF, logout); + router.post('/register', Auth.middleware.applyCSRF, register); + router.post('/login', Auth.middleware.applyCSRF, login); - hotswap.replace('auth', router); - if (typeof callback === 'function') { - callback(); - } - }); + hotswap.replace('auth', router); + if (typeof callback === 'function') { + callback(); + } }); }; diff --git a/src/upgrade.js b/src/upgrade.js index a11e890a52..5b7c8f65ed 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -25,18 +25,21 @@ var db = require('./database'), Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { - if(!value) { + if (err) { + return callback(err); + } + + if (!value) { db.set('schemaDate', latestSchema, function(err) { - callback(true); + if (err) { + return callback(err); + } + callback(null, true); }); return; } - if (parseInt(value, 10) >= latestSchema) { - callback(true); - } else { - callback(false); - } + callback(null, parseInt(value, 10) >= latestSchema); }); }; diff --git a/src/webserver.js b/src/webserver.js index c5f47aafcb..e538c00381 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -5,7 +5,7 @@ var path = require('path'), fs = require('fs'), nconf = require('nconf'), express = require('express'), - WebServer = express(), + app = express(), server, winston = require('winston'), async = require('async'), @@ -18,195 +18,216 @@ var path = require('path'), routes = require('./routes'), emitter = require('./emitter'), - helpers = require('./../public/src/modules/helpers'), - net; + helpers = require('../public/src/modules/helpers'); -if(nconf.get('ssl')) { +if (nconf.get('ssl')) { server = require('https').createServer({ key: fs.readFileSync(nconf.get('ssl').key), cert: fs.readFileSync(nconf.get('ssl').cert) - }, WebServer); + }, app); } else { - server = require('http').createServer(WebServer); + server = require('http').createServer(app); } -(function (app) { - var port = nconf.get('port'); +module.exports.server = server; - if (Array.isArray(port)) { - if (!port.length) { - winston.error('[startup] empty ports array in config.json'); - process.exit(); - } - - winston.warn('[startup] If you want to start nodebb on multiple ports please use loader.js'); - winston.warn('[startup] Defaulting to first port in array, ' + port[0]); - port = port[0]; - if (!port) { - winston.error('[startup] Invalid port, exiting'); - process.exit(); - } +server.on('error', function(err) { + winston.error(err); + if (err.code === 'EADDRINUSE') { + winston.error('NodeBB address in use, exiting...'); + process.exit(0); + } else { + throw err; } +}); - module.exports.init = function() { - var skipJS, skipLess, fromFile = nconf.get('from-file') || ''; +if (server.setTimeout) { + server.setTimeout(10000); +} - emailer.registerApp(app); +module.exports.listen = function() { + emailer.registerApp(app); - if (fromFile.match('js')) { - winston.info('[minifier] Minifying client-side JS skipped'); - skipJS = true; - } + middleware = middleware(app); + + helpers.register(); + + logger.init(app); + + emitter.all(['templates:compiled', 'meta:js.compiled', 'meta:css.compiled'], function() { + winston.info('NodeBB Ready'); + emitter.emit('nodebb:ready'); + listen(); + }); - if (fromFile.match('less')) { - winston.info('[minifier] Compiling LESS files skipped'); - skipLess = true; + initializeNodeBB(function(err) { + if (err) { + winston.error(err); + process.exit(); } + if (process.send) { + process.send({ + action: 'ready' + }); + } + }); +}; + +function initializeNodeBB(callback) { + var skipJS, skipLess, fromFile = nconf.get('from-file') || ''; + + if (fromFile.match('js')) { + winston.info('[minifier] Minifying client-side JS skipped'); + skipJS = true; + } + + if (fromFile.match('less')) { + winston.info('[minifier] Compiling LESS files skipped'); + skipLess = true; + } - // Preparation dependent on plugins - plugins.ready(function() { + async.waterfall([ + async.apply(cacheStaticFiles), + async.apply(meta.themes.setupPaths), + function(next) { + plugins.init(app, middleware, next); + }, + function(next) { async.parallel([ + async.apply(meta.templates.compile), async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, app.enabled('minification')), async.apply(!skipLess ? meta.css.minify : meta.css.getFromFile), async.apply(meta.sounds.init) - ]); - + ], next); + }, + function(results, next) { plugins.fireHook('static:app.preload', { app: app, middleware: middleware - }, function(err) { - if (err) { - return winston.error('[plugins] Encountered error while executing pre-router plugins hooks: ' + err.message); - } - - routes(app, middleware); - }); - }); - - middleware = middleware(app); - plugins.init(app, middleware); + }, next); + }, + function(results, next) { + routes(app, middleware); + next(); + } + ], callback); +} - // Load server-side template helpers - helpers.register(); +function cacheStaticFiles(callback) { + if (global.env === 'development') { + return callback(); + } - // Cache static files on production - if (global.env !== 'development') { - app.enable('cache'); - app.enable('minification'); + app.enable('cache'); + app.enable('minification'); - // Configure cache-buster timestamp - require('child_process').exec('git describe --tags', { - cwd: path.join(__dirname, '../') - }, function(err, stdOut) { - if (!err) { - meta.config['cache-buster'] = stdOut.trim(); - } else { - fs.stat(path.join(__dirname, '../package.json'), function(err, stats) { - meta.config['cache-buster'] = new Date(stats.mtime).getTime(); - }); + // Configure cache-buster timestamp + require('child_process').exec('git describe --tags', { + cwd: path.join(__dirname, '../') + }, function(err, stdOut) { + if (!err) { + meta.config['cache-buster'] = stdOut.trim(); + callback(); + } else { + fs.stat(path.join(__dirname, '../package.json'), function(err, stats) { + if (err) { + return callback(err); } + meta.config['cache-buster'] = new Date(stats.mtime).getTime(); + callback(); }); } + }); +} - if (port !== 80 && port !== 443 && nconf.get('use_port') === false) { - winston.info('Enabling \'trust proxy\''); - app.enable('trust proxy'); - } +function listen(callback) { + var port = nconf.get('port'); - if ((port === 80 || port === 443) && process.env.NODE_ENV !== 'development') { - winston.info('Using ports 80 and 443 is not recommend; use a proxy instead. See README.md'); + if (Array.isArray(port)) { + if (!port.length) { + winston.error('[startup] empty ports array in config.json'); + process.exit(); } - }; - - server.on('error', function(err) { - winston.error(err.stack); - console.log(err.stack); - if (err.code === 'EADDRINUSE') { - winston.error('NodeBB address in use, exiting...'); - process.exit(0); - } else { - throw err; + + winston.warn('[startup] If you want to start nodebb on multiple ports please use loader.js'); + winston.warn('[startup] Defaulting to first port in array, ' + port[0]); + port = port[0]; + if (!port) { + winston.error('[startup] Invalid port, exiting'); + process.exit(); } - }); + } - module.exports.server = server; + if (port !== 80 && port !== 443 && nconf.get('use_port') === false) { + winston.info('Enabling \'trust proxy\''); + app.enable('trust proxy'); + } - emitter.all(['templates:compiled', 'meta:js.compiled', 'meta:css.compiled'], function() { - winston.info('NodeBB Ready'); - emitter.emit('nodebb:ready'); - }); + if ((port === 80 || port === 443) && process.env.NODE_ENV !== 'development') { + winston.info('Using ports 80 and 443 is not recommend; use a proxy instead. See README.md'); + } - server.setTimout && server.setTimeout(10000); + var isSocket = isNaN(port), + args = isSocket ? [port] : [port, nconf.get('bind_address')], + bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port, + oldUmask; - module.exports.listen = function() { - logger.init(app); + args.push(function(err) { + if (err) { + winston.info('[startup] NodeBB was unable to listen on: ' + bind_address); + process.exit(); + } - var isSocket = isNaN(port), - args = isSocket ? [port] : [port, nconf.get('bind_address')], - bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port, - oldUmask; + winston.info('NodeBB is now listening on: ' + (isSocket ? port : bind_address)); + if (oldUmask) { + process.umask(oldUmask); + } + }); - args.push(function(err) { - if (err) { - winston.info('[startup] NodeBB was unable to listen on: ' + bind_address); + // Alter umask if necessary + if (isSocket) { + oldUmask = process.umask('0000'); + module.exports.testSocket(port, function(err) { + if (!err) { + server.listen.apply(server, args); + } else { + winston.error('[startup] NodeBB was unable to secure domain socket access (' + port + ')'); + winston.error('[startup] ' + err.message); process.exit(); } - - winston.info('NodeBB is now listening on: ' + (isSocket ? port : bind_address)); - if (oldUmask) { - process.umask(oldUmask); - } }); + } else { + server.listen.apply(server, args); + } +} - // Alter umask if necessary - if (isSocket) { - oldUmask = process.umask('0000'); - net = require('net'); - module.exports.testSocket(port, function(err) { - if (!err) { - emitter.on('nodebb:ready', function() { - server.listen.apply(server, args); - }); +module.exports.testSocket = function(socketPath, callback) { + if (typeof socketPath !== 'string') { + return callback(new Error('invalid socket path : ' + socketPath)); + } + var net = require('net'); + async.series([ + function(next) { + fs.exists(socketPath, function(exists) { + if (exists) { + next(); } else { - winston.error('[startup] NodeBB was unable to secure domain socket access (' + port + ')'); - winston.error('[startup] ' + err.message); - process.exit(); + callback(); } }); - } else { - emitter.on('nodebb:ready', function() { - server.listen.apply(server, args); + }, + function(next) { + var testSocket = new net.Socket(); + testSocket.on('error', function(err) { + next(err.code !== 'ECONNREFUSED' ? err : null); }); - } - }; + testSocket.connect({ path: socketPath }, function() { + // Something's listening here, abort + callback(new Error('port-in-use')); + }); + }, + async.apply(fs.unlink, socketPath), // The socket was stale, kick it out of the way + ], callback); +}; - module.exports.testSocket = function(socketPath, callback) { - if (typeof socketPath !== 'string') { - return callback(new Error('invalid socket path : ' + socketPath)); - } - async.series([ - function(next) { - fs.exists(socketPath, function(exists) { - if (exists) { - next(); - } else { - callback(); - } - }); - }, - function(next) { - var testSocket = new net.Socket(); - testSocket.on('error', function(err) { - next(err.code !== 'ECONNREFUSED' ? err : null); - }); - testSocket.connect({ path: socketPath }, function() { - // Something's listening here, abort - callback(new Error('port-in-use')); - }); - }, - async.apply(fs.unlink, socketPath), // The socket was stale, kick it out of the way - ], callback); - }; - -}(WebServer));