diff --git a/app.js b/app.js index 186e220dde..73cb0c3efa 100644 --- a/app.js +++ b/app.js @@ -50,15 +50,9 @@ winston.err = function(err) { winston.info('NodeBB v' + pkg.version + ' Copyright (C) 2013 DesignCreatePlay Inc.'); winston.info('This program comes with ABSOLUTELY NO WARRANTY.'); winston.info('This is free software, and you are welcome to redistribute it under certain conditions.'); -winston.info('==='); +winston.info(''); - - -if(nconf.get('upgrade')) { - meta.configs.init(function() { - require('./src/upgrade').upgrade(); - }); -} else if (!nconf.get('setup') && nconf.get('base_url')) { +if (!nconf.get('setup') && !nconf.get('upgrade') && nconf.get('base_url')) { nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path') + '/'); nconf.set('upload_url', nconf.get('url') + 'uploads/'); @@ -82,77 +76,39 @@ if(nconf.get('upgrade')) { 'categories': require('./src/admin/categories.js') }; - DEVELOPMENT = true; - - global.configuration = {}; global.templates = {}; + templates.init([ + 'header', 'footer', 'logout', 'outgoing', 'admin/header', 'admin/footer', 'admin/index', + 'emails/reset', 'emails/reset_plaintext', 'emails/email_confirm', 'emails/email_confirm_plaintext', + 'emails/header', 'emails/footer', - (function(config) { - config['ROOT_DIRECTORY'] = __dirname; - - templates.init([ - 'header', 'footer', 'logout', 'outgoing', 'admin/header', 'admin/footer', 'admin/index', - 'emails/reset', 'emails/reset_plaintext', 'emails/email_confirm', 'emails/email_confirm_plaintext', - 'emails/header', 'emails/footer', - - 'noscript/header', 'noscript/home', 'noscript/category', 'noscript/topic' - ]); - - templates.ready(webserver.init); - - //setup scripts to be moved outside of the app in future. - function setup_categories() { - if (process.env.NODE_ENV === 'development') winston.info('Checking categories...'); - categories.getAllCategories(function(data) { - if (data.categories.length === 0) { - winston.info('Setting up default categories...'); + 'noscript/header', 'noscript/home', 'noscript/category', 'noscript/topic' + ]); - fs.readFile(config.ROOT_DIRECTORY + '/install/data/categories.json', function(err, default_categories) { - default_categories = JSON.parse(default_categories); - - for (var category in default_categories) { - admin.categories.create(default_categories[category]); - } - }); - - - winston.info('Hardcoding uid 1 as an admin'); - var user = require('./src/user.js'); - user.makeAdministrator(1); - - - } else { - if (process.env.NODE_ENV === 'development') winston.info('Categories OK. Found ' + data.categories.length + ' categories.'); - } - }); - } - - setup_categories(); - }(global.configuration)); + templates.ready(webserver.init); }); +} else if (nconf.get('upgrade')) { + meta.configs.init(function() { + require('./src/upgrade').upgrade(); + }); } else { // New install, ask setup questions if (nconf.get('setup')) winston.info('NodeBB Setup Triggered via Command Line'); else winston.warn('Configuration not found, starting NodeBB setup'); + meta.config = {}; var install = require('./src/install'); - process.stdout.write( - "\nWelcome to NodeBB!\nThis looks like a new installation, so you'll have to answer a " + - "few questions about your environment before we can proceed.\n\n" + - "Press enter to accept the default setting (shown in brackets).\n\n\n" - ); + winston.info('Welcome to NodeBB!'); + winston.info('This looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.'); + winston.info('Press enter to accept the default setting (shown in brackets).'); install.setup(function(err) { if (err) { winston.error('There was a problem completing NodeBB setup: ', err.message); } else { - if (!nconf.get('setup')) { - process.stdout.write( - "Please start NodeBB again and register a new user. This user will automatically become an administrator.\n\n" - ); - } + winston.info('NodeBB Setup Completed.'); } process.exit(); diff --git a/package.json b/package.json index d89e08bb59..af8c787d61 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "winston": "~0.7.2", "nodebb-plugin-mentions": "~0.1.0", "nodebb-plugin-markdown": "~0.1.0", - "rss": "~0.2.0" + "rss": "~0.2.0", + "prompt": "~0.2.11" }, "bugs": { "url": "https://github.com/designcreateplay/NodeBB/issues" diff --git a/public/src/templates.js b/public/src/templates.js index be97fdb3c7..5a1bde3378 100644 --- a/public/src/templates.js +++ b/public/src/templates.js @@ -65,7 +65,7 @@ for (var t in templatesToLoad) { (function(file) { - fs.readFile(global.configuration.ROOT_DIRECTORY + '/public/templates/' + file + '.tpl', function(err, html) { + fs.readFile(__dirname + '/../templates/' + file + '.tpl', function(err, html) { var template = function() { this.toString = function() { return this.html; @@ -90,8 +90,6 @@ config = config_data[0]; available_templates = templates_data[0]; - - templates.ready(); }); } diff --git a/public/src/utils.js b/public/src/utils.js index 50323b7327..0deda43610 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -17,8 +17,8 @@ //Adapted from http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search walk: function(dir, done) { - var main_dir = global.configuration.ROOT_DIRECTORY + '/public/templates/'; - var results = []; + var results = [], + templateExtract = /\/([\w\d\-_]+)\.tpl$/; fs.readdir(dir, function(err, list) { if (err) return done(err); var pending = list.length; @@ -32,7 +32,10 @@ if (!--pending) done(null, results); }); } else { - results.push(file.replace(main_dir, '').replace('.tpl', '')); + var templateMatch = file.match(templateExtract); + + if (templateMatch) results.push(templateMatch[1]); + // results.push(file.replace(main_dir, '').replace('.tpl', '')); if (!--pending) done(null, results); } }); diff --git a/src/admin/categories.js b/src/admin/categories.js index c48fdc1a41..cb0ec7c86e 100644 --- a/src/admin/categories.js +++ b/src/admin/categories.js @@ -24,7 +24,7 @@ var RDB = require('./../redis.js'), RDB.set('categoryslug:' + slug + ':cid', cid); - if (callback) callback({'status': 1}); + if (callback) callback(null); }); }; diff --git a/src/groups.js b/src/groups.js index 144cf98f4f..c6f7098774 100644 --- a/src/groups.js +++ b/src/groups.js @@ -1,5 +1,6 @@ var async = require('async'), User = require('./user'), + RDB = RDB || require('./redis'), Groups = { list: function(options, callback) { RDB.hvals('group:gid', function(err, gids) { diff --git a/src/install.js b/src/install.js index e032dc7202..04b555f13f 100644 --- a/src/install.js +++ b/src/install.js @@ -4,95 +4,186 @@ var async = require('async'), url = require('url'), path = require('path'), meta = require('./meta'), + User = require('./user'), + Groups = require('./groups'), + Categories = require('./categories'), + prompt = require('prompt'), + admin = { + categories: require('./admin/categories') + }, + winston = require('winston'), + install = { questions: [ - 'base_url|Publically accessible URL of this installation? (http://localhost)', - 'port|Port number of your install? (4567)', - 'use_port|Will you be using a port number to access NodeBB? (y)', - 'redis:host|Host IP or address of your Redis instance? (127.0.0.1)', - 'redis:port|Host port of your Redis instance? (6379)', - 'redis:password|Password of your Redis database? (no password)', - 'secret|Your NodeBB secret? (keyboard mash for a bit here)' - ], - defaults: { - "base_url": 'http://localhost', - "port": 4567, - "use_port": true, - "redis": { - "host": '127.0.0.1', - "port": 6379, - "password": '' + { + name: 'base_url', + description: 'URL of this installation', + 'default': 'http://localhost', + pattern: /^http(?:s)?:\/\//, + message: 'Base URL must begin with \'http://\' or \'https://\'', }, - "secret": utils.generateUUID(), - "bcrypt_rounds": 12, - "upload_path": '/public/uploads' - }, - ask: function(question, callback) { - process.stdin.resume(); - process.stdout.write(question + ': '); - - process.stdin.once('data', function(data) { - callback(data.toString().trim()); - }); - }, + { + name: 'port', + description: 'Port number of your NodeBB', + 'default': 4567 + }, + { + name: 'use_port', + description: 'Use a port number to access NodeBB?', + 'default': 'y', + pattern: /y[es]*|n[o]?/, + message: 'Please enter \'yes\' or \'no\'', + }, + { + name: 'secret', + description: 'Please enter a NodeBB secret', + 'default': utils.generateUUID() + }, + { + name: 'redis:host', + description: 'Host IP or address of your Redis instance', + 'default': '127.0.0.1' + }, + { + name: 'redis:port', + description: 'Host port of your Redis instance', + 'default': 6379 + }, + { + name: 'redis:password', + description: 'Password of your Redis database' + } + ], setup: function(callback) { - var config = {}; - for(d in install.defaults) config[d] = install.defaults[d]; + async.series([ + function(next) { + // prompt prepends "prompt: " to questions, let's clear that. + prompt.start(); + prompt.message = ''; + prompt.delimiter = ''; - async.eachSeries(install.questions, function(question, next) { - var question = question.split('|'); - install.ask(question[1], function(value) { - switch(question[0]) { - case 'use_port': - value = value.toLowerCase(); - if (['y', 'yes', ''].indexOf(value) === -1) config[question[0]] = false; - break; - case 'redis:host': - config.redis = config.redis || {}; - if (value !== '') config.redis.host = value; - break; - case 'redis:port': - config.redis = config.redis || {}; - if (value !== '') config.redis.port = value; - break; - case 'redis:password': - config.redis = config.redis || {}; - if (value !== '') config.redis.password = value; - break; + prompt.get(install.questions, function(err, config) { + if (!config) { + winston.warn('NodeBB Setup Aborted.'); + process.exit(); + } - default: - if (value !== '') config[question[0]] = value; - break; - } + // Translate redis properties into redis object + config.redis = { + host: config['redis:host'], + port: config['redis:port'], + password: config['redis:password'] + }; + delete config['redis:host']; + delete config['redis:port']; + delete config['redis:password']; - next(); - }); - }, function() { - var urlObject = url.parse(config.base_url), - relative_path = (urlObject.pathname && urlObject.pathname.length > 1) ? urlObject.pathname : '', - host = urlObject.host, - protocol = urlObject.protocol, - server_conf = config, - client_conf = { - socket: { - address: protocol + '//' + host + (config.use_port ? ':' + config.port : '') - }, - api_url: protocol + '//' + host + (config.use_port ? ':' + config.port : '') + relative_path + '/api/', - relative_path: relative_path - }; + // Add hardcoded values + config['bcrypt_rounds'] = 12, + config['upload_path'] = '/public/uploads'; - server_conf.base_url = protocol + '//' + host; - server_conf.relative_path = relative_path; + var urlObject = url.parse(config.base_url), + relative_path = (urlObject.pathname && urlObject.pathname.length > 1) ? urlObject.pathname : '', + host = urlObject.host, + protocol = urlObject.protocol, + server_conf = config, + client_conf = { + socket: { + address: protocol + '//' + host + (config.use_port ? ':' + config.port : '') + }, + api_url: protocol + '//' + host + (config.use_port ? ':' + config.port : '') + relative_path + '/api/', + relative_path: relative_path + }; - meta.configs.set('postDelay', 10000); - meta.configs.set('minimumPostLength', 8); - meta.configs.set('minimumTitleLength', 3); - meta.configs.set('minimumUsernameLength', 2); - meta.configs.set('maximumUsernameLength', 16); - meta.configs.set('minimumPasswordLength', 6); - meta.configs.set('imgurClientID', ''); + server_conf.base_url = protocol + '//' + host; + server_conf.relative_path = relative_path; - install.save(server_conf, client_conf, callback); + meta.configs.set('postDelay', 10000); + meta.configs.set('minimumPostLength', 8); + meta.configs.set('minimumTitleLength', 3); + meta.configs.set('minimumUsernameLength', 2); + meta.configs.set('maximumUsernameLength', 16); + meta.configs.set('minimumPasswordLength', 6); + meta.configs.set('imgurClientID', ''); + + install.save(server_conf, client_conf, next); + }); + }, + function(next) { + // Check if an administrator needs to be created + Groups.getGidFromName('Administrators', function(err, gid) { + if (err) return next(new Error('Could not save configs')); + + if (gid) { + Groups.get(gid, {}, function(err, groupObj) { + if (groupObj.count > 0) { + winston.info('Administrator found, skipping Admin setup'); + next(); + } else install.createAdmin(next); + }); + } else install.createAdmin(next); + }); + }, + function(next) { + // Categories + categories.getAllCategories(function(data) { + if (data.categories.length === 0) { + winston.warn('No categories found, populating instance with default categories') + + fs.readFile(path.join(__dirname, '../', 'install/data/categories.json'), function(err, default_categories) { + default_categories = JSON.parse(default_categories); + + async.eachSeries(default_categories, function(category, next) { + admin.categories.create(category, next); + }, function(err) { + if (!err) next(); + else winston.error('Could not set up categories'); + }); + }); + } else { + winston.info('Categories OK. Found ' + data.categories.length + ' categories.'); + next(); + } + }); + } + ], callback); + }, + createAdmin: function(callback) { + winston.warn('No administrators have been detected, running initial user setup'); + var questions = [ + { + name: 'username', + description: 'Administrator username', + required: true, + type: 'string' + }, + { + name: 'email', + description: 'Administrator email address', + pattern: /.+@.+/, + required: true + }, + { + name: 'password', + description: 'Password', + required: true, + hidden: true, + type: 'string' + } + ]; + + prompt.get(questions, function(err, results) { + nconf.set('bcrypt_rounds', 12); + User.create(results.username, results.password, results.email, function(err, uid) { + Groups.getGidFromName('Administrators', function(err, gid) { + if (gid) Groups.join(gid, uid, callback); + else { + Groups.create('Administrators', 'Forum Administrators', function(err, groupObj) { + Groups.join(groupObj.gid, uid, callback); + }); + } + }); + }); }); }, save: function(server_conf, client_conf, callback) { @@ -109,10 +200,7 @@ var async = require('async'), }); } ], function(err) { - process.stdout.write( - "\n\nConfiguration Saved OK\n\n" - ); - + winston.info('Configuration Saved OK'); callback(err); }); } diff --git a/src/login.js b/src/login.js index f4ada02e5f..f7aea087f3 100644 --- a/src/login.js +++ b/src/login.js @@ -8,7 +8,6 @@ var user = require('./user.js'), (function(Login){ Login.loginViaLocal = function(username, password, next) { - if (!username || !password) { return next({ status: 'error', @@ -26,8 +25,7 @@ var user = require('./user.js'), } user.getUserFields(uid, ['password', 'banned'], function(err, userData) { - if(err) - return next(err); + if(err) return next(err); if(userData.banned && userData.banned === '1') { return next({ @@ -38,7 +36,7 @@ var user = require('./user.js'), bcrypt.compare(password, userData.password, function(err, res) { if(err) { - winston.err(err); + winston.err(err.message); next({ status: "error", message: 'bcrypt compare error' diff --git a/src/routes/api.js b/src/routes/api.js index 84de81e518..0a96239c0b 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -4,13 +4,14 @@ var user = require('./../user.js'), categories = require('./../categories.js') utils = require('./../../public/src/utils.js'), pkg = require('../../package.json'), - meta = require('./../meta.js'); + meta = require('./../meta.js'), + path = require('path'); (function(Api) { Api.create_routes = function(app) { app.get('/api/get_templates_listing', function(req, res) { - utils.walk(global.configuration.ROOT_DIRECTORY + '/public/templates', function(err, data) { + utils.walk(path.join(__dirname, '../../', 'public/templates'), function(err, data) { res.json(data); }); }); diff --git a/src/routes/user.js b/src/routes/user.js index f05aed6207..856266fbdb 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -131,7 +131,7 @@ var user = require('./../user.js'), return; } - var absolutePath = path.join(global.configuration['ROOT_DIRECTORY'], global.nconf.get('upload_path'), path.basename(oldpicture)); + var absolutePath = path.join(__dirname, '../', global.nconf.get('upload_path'), path.basename(oldpicture)); fs.unlink(absolutePath, function(err) { if(err) { @@ -152,7 +152,7 @@ var user = require('./../user.js'), } var filename = uid + '-profileimg' + extension; - var uploadPath = path.join(global.configuration['ROOT_DIRECTORY'], global.nconf.get('upload_path'), filename); + var uploadPath = path.join(__dirname, '../', global.nconf.get('upload_path'), filename); winston.info('Attempting upload to: '+ uploadPath); diff --git a/src/user.js b/src/user.js index d087cd4102..271b6874e0 100644 --- a/src/user.js +++ b/src/user.js @@ -84,7 +84,7 @@ var utils = require('./../public/src/utils.js'), RDB.incr('usercount', function(err, count) { RDB.handle(err); - io.sockets.emit('user.count', {count: count}); + if (typeof io !== 'undefined') io.sockets.emit('user.count', {count: count}); }); RDB.zadd('users:joindate', timestamp, uid); @@ -93,15 +93,14 @@ var utils = require('./../public/src/utils.js'), userSearch.index(username, uid); - io.sockets.emit('user.latest', {userslug: userslug, username: username}); + if (typeof io !== 'undefined') io.sockets.emit('user.latest', {userslug: userslug, username: username}); if (password !== undefined) { - User.hashPassword(password, function(hash) { + User.hashPassword(password, function(err, hash) { User.setUserField(uid, 'password', hash); + callback(null, uid); }); - } - - callback(null, uid); + } else callback(null, uid); }); }); }; @@ -309,7 +308,7 @@ var utils = require('./../public/src/utils.js'), } if (res) { - User.hashPassword(data.newPassword, function(hash) { + User.hashPassword(data.newPassword, function(err, hash) { User.setUserField(uid, 'password', hash); callback({err:null}); @@ -382,9 +381,7 @@ var utils = require('./../public/src/utils.js'), } bcrypt.genSalt(nconf.get('bcrypt_rounds'), function(err, salt) { - bcrypt.hash(password, salt, function(err, hash) { - callback(hash); - }); + bcrypt.hash(password, salt, callback); }); } @@ -906,7 +903,7 @@ var utils = require('./../public/src/utils.js'), RDB.handle(err); } - User.hashPassword(password, function(hash) { + User.hashPassword(password, function(err, hash) { User.setUserField(uid, 'password', hash); });