From 4a2366eb06ee429d7db4c2084e2575a5b62aae9b Mon Sep 17 00:00:00 2001 From: calzoneman Date: Wed, 22 Jan 2014 17:11:26 -0600 Subject: [PATCH] Switch config to YAML --- .gitignore | 1 + config.template.yaml | 66 ++++++++++++++ index.js | 24 +++-- lib/bgtask.js | 11 +-- lib/config.js | 206 ++++++++++++++++++++++++++----------------- lib/database.js | 11 +-- lib/get-info.js | 16 ++-- lib/io/ioserver.js | 11 +-- lib/server.js | 30 +++---- lib/user.js | 6 +- package.json | 3 +- 11 files changed, 251 insertions(+), 134 deletions(-) create mode 100644 config.template.yaml diff --git a/.gitignore b/.gitignore index 90872276..5d088858 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.swp cfg.json +config.yaml chandump chanlogs *.log diff --git a/config.template.yaml b/config.template.yaml new file mode 100644 index 00000000..b96a7c4a --- /dev/null +++ b/config.template.yaml @@ -0,0 +1,66 @@ +# MySQL server details +# server: domain or IP of MySQL server +# database: a MySQL database that the user specified has read/write access to +# user: username to authenticate as +# password: password for user +mysql: + server: 'localhost' + database: 'cytube3' + user: 'cytube3' + password: 'pickles' + +# HTTP server details +http: + host: '' + port: 8080 + domain: 'http://localhost' + +# HTTPS server details +https: + enabled: false + port: 8443 + domain: 'https://localhost' + keyfile: 'localhost.key' + passphrase: '' + certfile: 'localhost.cert' + +# Socket.IO server details +io: + port: 1337 + # limit the number of concurrent socket connections per IP address + ip-connection-limit: 10 + +# Mailer details (used for sending password reset links) +# see https://github.com/andris9/Nodemailer +mail: + enabled: false + transport: 'SMTP' + config: + service: 'Gmail' + auth: + user: 'some.user@gmail.com' + password: 'supersecretpassword' + from-address: 'some.user@gmail.com' + +# GData API v2 developer key (for non-anonymous youtube requests) +youtube-v2-key: '' +# Minutes between saving channel state to disk +channel-save-interval: 5 +# Minimum number of seconds between guest logins from the same IP +guest-login-delay: 60 +# Block known Tor IP addresses +enable-tor-blocker: true + +# Configure statistics tracking +stats: + # Interval (in milliseconds) between data points - default 1h + interval: 3600000 + # Maximum age of a datapoint (ms) before it is deleted - default 24h + max-age: 86400000 + +# Configure periodic clearing of old alias data +aliases: + # Interval (in milliseconds) between subsequent runs of clearing + purge-interval: 3600000 + # Maximum age of an alias (in milliseconds) - default 1 month + max-age: 2592000000 diff --git a/index.js b/index.js index c35fb13d..71f8f6ca 100644 --- a/index.js +++ b/index.js @@ -13,17 +13,15 @@ var Server = require("./lib/server"); var Config = require("./lib/config"); var Logger = require("./lib/logger"); -Config.load("cfg.json", function (cfg) { - cfg["debug"] = true; - var sv = Server.init(cfg); - if(!cfg["debug"]) { - process.on("uncaughtException", function (err) { - Logger.errlog.log("[SEVERE] Uncaught Exception: " + err); - Logger.errlog.log(err.stack); - }); +Config.load("config.yaml"); +var sv = Server.init(); +if (!Config.get("debug")) { + process.on("uncaughtException", function (err) { + Logger.errlog.log("[SEVERE] Uncaught Exception: " + err); + Logger.errlog.log(err.stack); + }); - process.on("SIGINT", function () { - sv.shutdown(); - }); - } -}); + process.on("SIGINT", function () { + sv.shutdown(); + }); +} diff --git a/lib/bgtask.js b/lib/bgtask.js index 5734bd09..8b8d86c3 100644 --- a/lib/bgtask.js +++ b/lib/bgtask.js @@ -18,13 +18,14 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ var Logger = require("./logger"); +var Config = require("./config"); var init = null; /* Stats */ function initStats(Server) { - var STAT_INTERVAL = Server.cfg["stat-interval"]; - var STAT_EXPIRE = Server.cfg["stat-max-age"]; + var STAT_INTERVAL = Config.get("stats.interval"); + var STAT_EXPIRE = Config.get("stats.max-age"); setInterval(function () { var db = Server.db; @@ -44,8 +45,8 @@ function initStats(Server) { /* Alias cleanup */ function initAliasCleanup(Server) { - var CLEAN_INTERVAL = Server.cfg["alias-purge-interval"]; - var CLEAN_EXPIRE = Server.cfg["alias-max-age"]; + var CLEAN_INTERVAL = Config.get("aliases.purge-interval"); + var CLEAN_EXPIRE = Config.get("aliases.max-age"); setInterval(function () { Server.db.cleanOldAliases(CLEAN_EXPIRE, function (err) { @@ -68,7 +69,7 @@ function initIpThrottleCleanup(Server) { } function initChannelDumper(Server) { - var CHANNEL_SAVE_INTERVAL = Server.cfg["channel-save-interval"] * 60000; + var CHANNEL_SAVE_INTERVAL = Config.get("channel-save-interval") * 60000; setInterval(function () { for (var i = 0; i < Server.channels.length; i++) { var chan = Server.channels[i]; diff --git a/lib/config.js b/lib/config.js index 13bdea26..ab6a33f7 100644 --- a/lib/config.js +++ b/lib/config.js @@ -10,99 +10,145 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ var fs = require("fs"); +var path = require("path"); var Logger = require("./logger"); var nodemailer = require("nodemailer"); +var YAML = require("yamljs"); var defaults = { - "mysql-server" : "localhost", - "mysql-db" : "cytube", - "mysql-user" : "cytube", - "mysql-pw" : "supersecretpass", - "express-host" : "0.0.0.0", - "io-host" : "0.0.0.0", - "enable-ssl" : false, - "ssl-keyfile" : "", - "ssl-passphrase" : "", - "ssl-certfile" : "", - "ssl-port" : 443, - "asset-cache-ttl" : 0, - "web-port" : 8080, - "io-port" : 1337, - "ip-connection-limit" : 10, - "guest-login-delay" : 60, - "channel-save-interval" : 5, - "enable-mail" : false, - "mail-transport" : "SMTP", - "mail-config" : { - "service" : "Gmail", - "auth" : { - "user" : "some.user@gmail.com", - "pass" : "supersecretpassword" - } + mysql: { + server: "localhost", + database: "cytube3", + user: "cytube3", + password: "" }, - "mail-from" : "some.user@gmail.com", - "domain" : "http://localhost", - "ytv3apikey" : "", - "enable-ytv3" : false, - "ytv2devkey" : "", - "stat-interval" : 3600000, - "stat-max-age" : 86400000, - "alias-purge-interval" : 3600000, - "alias-max-age" : 2592000000, - "tor-blocker" : false -} - -function save(cfg, file) { - if(!cfg.loaded) - return; - var x = {}; - for(var k in cfg) { - if(k !== "nodemailer" && k !== "loaded") - x[k] = cfg[k]; + http: { + host: "", + port: 8080, + domain: "http://localhost" + }, + https: { + enabled: false, + port: 8443, + domain: "https://localhost:8443", + keyfile: "localhost.key", + passphrase: "", + certfile: "localhost.cert" + }, + io: { + port: 1337, + "ip-connection-limit": 10 + }, + mail: { + enabled: false, + transport: "SMTP", + /* the key "config" is omitted because the format depends on the + service the owner is configuring for nodemailer */ + "from-address": "some.user@gmail.com" + }, + "youtube-v2-key": "", + "channel-save-interval": 5, + "guest-login-delay": 60, + "enable-tor-blocker": true, + stats: { + interval: 3600000, + "max-age": 86400000 + }, + aliases: { + "purge-interval": 3600000, + "max-age": 2592000000 + } +}; + +/** + * Merges a config object with the defaults, warning about missing keys + */ +function merge(obj, def, path) { + for (var key in def) { + if (key in obj) { + if (typeof obj[key] === "object") { + merge(obj[key], def[key], path + "." + key); + } + } else { + Logger.syslog.log("[WARNING] Missing config key " + (path + "." + key) + + "; using default: " + JSON.stringify(def[key])); + obj[key] = def[key]; + } } - fs.writeFileSync(file, JSON.stringify(x, null, 4)); } -exports.load = function (file, callback) { - var cfg = {}; - for(var k in defaults) - cfg[k] = defaults[k]; +var cfg = defaults; - fs.readFile(file, function (err, data) { - if(err) { - if(err.code == "ENOENT") { - Logger.syslog.log("Config file not found, generating default"); - Logger.syslog.log("Edit cfg.json to configure"); - data = "{}"; +/** + * Initializes the configuration from the given YAML file + */ +exports.load = function (file) { + try { + cfg = YAML.load(path.join(__dirname, "..", file)); + } catch (e) { + if (e.code === "ENOENT") { + Logger.syslog.log(file + " does not exist, assuming default configuration"); + cfg = defaults; + return; + } else { + Logger.errlog.log("Error loading config file " + file + ": "); + if (e.stack) { + Logger.errlog.log(e.stack); } - else { - Logger.errlog.log("Config load failed"); - Logger.errlog.log(err); - return; - } - } - - try { - data = JSON.parse(data + ""); - } catch(e) { - Logger.errlog.log("Config JSON is invalid: "); - Logger.errlog.log(e); + cfg = defaults; return; } + } - for(var k in data) - cfg[k] = data[k]; + if (cfg == null) { + Logger.syslog.log(file + " is an Invalid configuration file, " + + "assuming default configuration"); + cfg = defaults; + return; + } - if(cfg["enable-mail"]) { - cfg["nodemailer"] = nodemailer.createTransport( - cfg["mail-transport"], - cfg["mail-config"] - ); + var mailconfig = {}; + if (cfg.mail && cfg.mail.config) { + mailconfig = cfg.mail.config; + delete cfg.mail.config; + } + + merge(cfg, defaults, "config"); + + cfg.mail.config = mailconfig; + cfg.mail.nodemailer = nodemailer.createTransport( + cfg.mail.transport, + cfg.mail.config + ); + + if (process.env.DEBUG === "1" || process.env.DEBUG === "true") { + cfg.debug = true; + } else { + cfg.debug = false; + } + + Logger.syslog.log("Loaded configuration from " + file); +}; + +/** + * Retrieves a configuration value with the given key + * + * Accepts a dot-separated key for nested values, e.g. "http.port" + * Throws an error if a nonexistant key is requested + */ +exports.get = function (key) { + var obj = cfg; + var keylist = key.split("."); + var current = keylist.shift(); + var path = current; + while (keylist.length > 0) { + if (!(current in obj)) { + throw new Error("Nonexistant config key '" + path + "." + current + "'"); } + obj = obj[current]; + current = keylist.shift(); + path += "." + current; + } - cfg["loaded"] = true; - - save(cfg, file); - callback(cfg); - }); -} + return obj[current]; +}; diff --git a/lib/database.js b/lib/database.js index d36c18f1..8ee76d55 100644 --- a/lib/database.js +++ b/lib/database.js @@ -2,16 +2,17 @@ var mysql = require("mysql"); var bcrypt = require("bcrypt"); var $util = require("./utilities"); var Logger = require("./logger"); +var Config = require("./config"); var pool = null; var global_ipbans = {}; -module.exports.init = function (cfg) { +module.exports.init = function () { pool = mysql.createPool({ - host: cfg["mysql-server"], - user: cfg["mysql-user"], - password: cfg["mysql-pw"], - database: cfg["mysql-db"], + host: Config.get("mysql.server"), + user: Config.get("mysql.user"), + password: Config.get("mysql.password"), + database: Config.get("mysql.database"), multipleStatements: true }); diff --git a/lib/get-info.js b/lib/get-info.js index ccb14eb2..70626eb8 100644 --- a/lib/get-info.js +++ b/lib/get-info.js @@ -16,6 +16,7 @@ var Logger = require("./logger.js"); var Media = require("./media.js").Media; var CustomEmbedFilter = require("./customembed").filter; var Server = require("./server"); +var Config = require("./config"); var urlRetrieve = function (transport, options, callback) { // Catch any errors that crop up along the way of the request @@ -48,10 +49,12 @@ var Getters = { /* youtube.com */ yt: function (id, callback) { var sv = Server.getServer(); + /* if (sv.cfg["enable-ytv3"] && sv.cfg["ytv3apikey"]) { Getters["ytv3"](id, callback); return; } + */ var m = id.match(/([\w-]+)/); if (m) { @@ -70,9 +73,9 @@ var Getters = { timeout: 1000 }; - if(sv.cfg["ytv2devkey"]) { + if(Config.get("youtube-v2-key")) { options.headers = { - "X-Gdata-Key": "key=" + sv.cfg["ytv2devkey"] + "X-Gdata-Key": "key=" + Config.get("youtube-v2-key") }; } @@ -147,6 +150,7 @@ var Getters = { }, /* youtube.com API v3 (requires API key) */ + // DEPRECATED ytv3: function (id, callback) { var sv = Server.getServer(); var m = id.match(/([\w-]+)/); @@ -226,9 +230,9 @@ var Getters = { timeout: 1000 }; - if(sv.cfg["ytv2devkey"]) { + if(Config.get("youtube-v2-key")) { options.headers = { - "X-Gdata-Key": "key=" + sv.cfg["ytv2devkey"] + "X-Gdata-Key": "key=" + Config.get("youtube-v2-key") }; } @@ -291,9 +295,9 @@ var Getters = { timeout: 1000 }; - if(sv.cfg["ytv2devkey"]) { + if(Config.get("youtube-v2-key")) { options.headers = { - "X-Gdata-Key": "key=" + sv.cfg["ytv2devkey"] + "X-Gdata-Key": "key=" + Config.get("youtube-v2-key") }; } diff --git a/lib/io/ioserver.js b/lib/io/ioserver.js index a0d678e8..a66dd1b4 100644 --- a/lib/io/ioserver.js +++ b/lib/io/ioserver.js @@ -4,6 +4,7 @@ var Logger = require("../logger"); var db = require("../database"); var User = require("../user"); var Server = require("../server"); +var Config = require("../config"); var $util = require("../utilities"); var CONNECT_RATE = { @@ -89,7 +90,7 @@ function handleConnection(sock) { } ipCount[ip]++; - if (ipCount[ip] > srv.cfg["ip-connection-limit"]) { + if (ipCount[ip] > Config.get("io.ip-connection-limit")) { sock.emit("kick", { reason: "Too many connections from your IP address" }); @@ -117,11 +118,11 @@ function handleConnection(sock) { module.exports = { init: function (srv) { - var ioport = srv.cfg["io-port"]; - var webport = srv.cfg["web-port"]; + var ioport = Config.get("io.port"); + var webport = Config.get("http.port"); var app; if (ioport !== webport) { - app = require("express")().listen(ioport, srv.cfg["express-host"]); + app = require("express")().listen(ioport, Config.get("http.host")); srv.ioWeb = app; } else { app = srv.express; @@ -132,7 +133,7 @@ module.exports = { srv.io.set("authorization", handleAuth); srv.io.on("connection", handleConnection); - if (srv.cfg["enable-ssl"]) { + if (Config.get("https.enabled")) { srv.ioSecure = sio.listen(srv.https); srv.ioSecure.set("log level", 1); srv.ioSecure.set("authorization", handleAuth); diff --git a/lib/server.js b/lib/server.js index da25ba51..57f3f3f2 100644 --- a/lib/server.js +++ b/lib/server.js @@ -9,11 +9,12 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const VERSION = "2.4.5"; +const VERSION = "3.0.0-alpha"; var singleton = null; +var Config = require("./config"); module.exports = { - init: function (cfg) { + init: function () { Logger.syslog.log("Starting CyTube v" + VERSION); var chanlogpath = path.join(__dirname, "../chanlogs"); fs.exists(chanlogpath, function (exists) { @@ -24,7 +25,7 @@ module.exports = { fs.exists(chandumppath, function (exists) { exists || fs.mkdir(chandumppath); }); - singleton = new Server(cfg); + singleton = new Server(); return singleton; }, @@ -38,17 +39,14 @@ var fs = require("fs"); var http = require("http"); var https = require("https"); var express = require("express"); -var Config = require("./config"); var Logger = require("./logger"); var Channel = require("./channel-new"); var User = require("./user"); var $util = require("./utilities"); var ActionLog = require("./actionlog"); -var Server = function (cfg) { +var Server = function () { var self = this; - - self.cfg = cfg; self.channels = [], self.express = null; self.http = null; @@ -66,7 +64,7 @@ var Server = function (cfg) { // database init ------------------------------------------------------ var Database = require("./database"); self.db = Database; - self.db.init(self.cfg); + self.db.init(); // webserver init ----------------------------------------------------- self.httplog = new Logger.Logger(path.join(__dirname, @@ -155,23 +153,23 @@ var Server = function (cfg) { */ // http/https/sio server init ----------------------------------------- - if (self.cfg["enable-ssl"]) { + if (Config.get("https.enabled")) { var key = fs.readFileSync(path.resolve(__dirname, "..", - self.cfg["ssl-keyfile"])); + Config.get("https.keyfile"))); var cert = fs.readFileSync(path.resolve(__dirname, "..", - self.cfg["ssl-certfile"])); + Config.get("https.certfile"))); var opts = { key: key, cert: cert, - passphrase: self.cfg["ssl-passphrase"] + passphrase: Config.get("https.passphrase") }; self.https = https.createServer(opts, self.express) - .listen(self.cfg["ssl-port"]); + .listen(Config.get("https.port")); } - self.http = self.express.listen(self.cfg["web-port"], - self.cfg["express-host"]); + self.http = self.express.listen(Config.get("http.port"), + Config.get("http.host") || undefined); /* self.ioWeb = express().listen(self.cfg["io-port"], self.cfg["io-host"]); self.io = require("socket.io").listen(self.ioWeb); @@ -186,7 +184,7 @@ var Server = function (cfg) { require("./bgtask")(self); // tor blocker init --------------------------------------------------- - if (self.cfg["tor-blocker"]) { + if (Config.get("enable-tor-blocker")) { self.torblocker = require("./torblocker")(); } }; diff --git a/lib/user.js b/lib/user.js index 97a11749..b5d8cf83 100644 --- a/lib/user.js +++ b/lib/user.js @@ -4,6 +4,7 @@ var util = require("./utilities"); var MakeEmitter = require("./emitter"); var db = require("./database"); var InfoGetter = require("./get-info"); +var Config = require("./config"); function User(socket) { var self = this; @@ -387,15 +388,14 @@ User.prototype.whenLoggedIn = function (fn) { var lastguestlogin = {}; User.prototype.guestLogin = function (name) { var self = this; - var srv = Server.getServer(); if (self.ip in lastguestlogin) { var diff = (Date.now() - lastguestlogin[self.ip]) / 1000; - if (diff < srv.cfg["guest-login-delay"]) { + if (diff < Config.get("guest-login-delay")) { self.socket.emit("login", { success: false, error: "Guest logins are restricted to one per IP address per " + - srv.cfg["guest-login-delay"] + " seconds." + Config.get("guest-login-delay") + " seconds." }); return; } diff --git a/package.json b/package.json index e7d8a983..5a465e61 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "jade": "~1.1.5", "socket.io": "~0.9.16", "nodemailer": "~0.6.0", - "cookie": "~0.1.0" + "cookie": "~0.1.0", + "yamljs": "~0.1.4" } }