Switch config to YAML

This commit is contained in:
calzoneman 2014-01-22 17:11:26 -06:00
parent 0a2dd6cbbe
commit 4a2366eb06
11 changed files with 251 additions and 134 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
*.swp *.swp
cfg.json cfg.json
config.yaml
chandump chandump
chanlogs chanlogs
*.log *.log

66
config.template.yaml Normal file
View File

@ -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

View File

@ -13,17 +13,15 @@ var Server = require("./lib/server");
var Config = require("./lib/config"); var Config = require("./lib/config");
var Logger = require("./lib/logger"); var Logger = require("./lib/logger");
Config.load("cfg.json", function (cfg) { Config.load("config.yaml");
cfg["debug"] = true; var sv = Server.init();
var sv = Server.init(cfg); if (!Config.get("debug")) {
if(!cfg["debug"]) { process.on("uncaughtException", function (err) {
process.on("uncaughtException", function (err) { Logger.errlog.log("[SEVERE] Uncaught Exception: " + err);
Logger.errlog.log("[SEVERE] Uncaught Exception: " + err); Logger.errlog.log(err.stack);
Logger.errlog.log(err.stack); });
});
process.on("SIGINT", function () { process.on("SIGINT", function () {
sv.shutdown(); sv.shutdown();
}); });
} }
});

View File

@ -18,13 +18,14 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
*/ */
var Logger = require("./logger"); var Logger = require("./logger");
var Config = require("./config");
var init = null; var init = null;
/* Stats */ /* Stats */
function initStats(Server) { function initStats(Server) {
var STAT_INTERVAL = Server.cfg["stat-interval"]; var STAT_INTERVAL = Config.get("stats.interval");
var STAT_EXPIRE = Server.cfg["stat-max-age"]; var STAT_EXPIRE = Config.get("stats.max-age");
setInterval(function () { setInterval(function () {
var db = Server.db; var db = Server.db;
@ -44,8 +45,8 @@ function initStats(Server) {
/* Alias cleanup */ /* Alias cleanup */
function initAliasCleanup(Server) { function initAliasCleanup(Server) {
var CLEAN_INTERVAL = Server.cfg["alias-purge-interval"]; var CLEAN_INTERVAL = Config.get("aliases.purge-interval");
var CLEAN_EXPIRE = Server.cfg["alias-max-age"]; var CLEAN_EXPIRE = Config.get("aliases.max-age");
setInterval(function () { setInterval(function () {
Server.db.cleanOldAliases(CLEAN_EXPIRE, function (err) { Server.db.cleanOldAliases(CLEAN_EXPIRE, function (err) {
@ -68,7 +69,7 @@ function initIpThrottleCleanup(Server) {
} }
function initChannelDumper(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 () { setInterval(function () {
for (var i = 0; i < Server.channels.length; i++) { for (var i = 0; i < Server.channels.length; i++) {
var chan = Server.channels[i]; var chan = Server.channels[i];

View File

@ -10,99 +10,145 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
*/ */
var fs = require("fs"); var fs = require("fs");
var path = require("path");
var Logger = require("./logger"); var Logger = require("./logger");
var nodemailer = require("nodemailer"); var nodemailer = require("nodemailer");
var YAML = require("yamljs");
var defaults = { var defaults = {
"mysql-server" : "localhost", mysql: {
"mysql-db" : "cytube", server: "localhost",
"mysql-user" : "cytube", database: "cytube3",
"mysql-pw" : "supersecretpass", user: "cytube3",
"express-host" : "0.0.0.0", password: ""
"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"
}
}, },
"mail-from" : "some.user@gmail.com", http: {
"domain" : "http://localhost", host: "",
"ytv3apikey" : "", port: 8080,
"enable-ytv3" : false, domain: "http://localhost"
"ytv2devkey" : "", },
"stat-interval" : 3600000, https: {
"stat-max-age" : 86400000, enabled: false,
"alias-purge-interval" : 3600000, port: 8443,
"alias-max-age" : 2592000000, domain: "https://localhost:8443",
"tor-blocker" : false keyfile: "localhost.key",
} passphrase: "",
certfile: "localhost.cert"
function save(cfg, file) { },
if(!cfg.loaded) io: {
return; port: 1337,
var x = {}; "ip-connection-limit": 10
for(var k in cfg) { },
if(k !== "nodemailer" && k !== "loaded") mail: {
x[k] = cfg[k]; 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 = defaults;
var cfg = {};
for(var k in defaults)
cfg[k] = defaults[k];
fs.readFile(file, function (err, data) { /**
if(err) { * Initializes the configuration from the given YAML file
if(err.code == "ENOENT") { */
Logger.syslog.log("Config file not found, generating default"); exports.load = function (file) {
Logger.syslog.log("Edit cfg.json to configure"); try {
data = "{}"; 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 { cfg = defaults;
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);
return; return;
} }
}
for(var k in data) if (cfg == null) {
cfg[k] = data[k]; Logger.syslog.log(file + " is an Invalid configuration file, " +
"assuming default configuration");
cfg = defaults;
return;
}
if(cfg["enable-mail"]) { var mailconfig = {};
cfg["nodemailer"] = nodemailer.createTransport( if (cfg.mail && cfg.mail.config) {
cfg["mail-transport"], mailconfig = cfg.mail.config;
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; return obj[current];
};
save(cfg, file);
callback(cfg);
});
}

View File

@ -2,16 +2,17 @@ var mysql = require("mysql");
var bcrypt = require("bcrypt"); var bcrypt = require("bcrypt");
var $util = require("./utilities"); var $util = require("./utilities");
var Logger = require("./logger"); var Logger = require("./logger");
var Config = require("./config");
var pool = null; var pool = null;
var global_ipbans = {}; var global_ipbans = {};
module.exports.init = function (cfg) { module.exports.init = function () {
pool = mysql.createPool({ pool = mysql.createPool({
host: cfg["mysql-server"], host: Config.get("mysql.server"),
user: cfg["mysql-user"], user: Config.get("mysql.user"),
password: cfg["mysql-pw"], password: Config.get("mysql.password"),
database: cfg["mysql-db"], database: Config.get("mysql.database"),
multipleStatements: true multipleStatements: true
}); });

View File

@ -16,6 +16,7 @@ var Logger = require("./logger.js");
var Media = require("./media.js").Media; var Media = require("./media.js").Media;
var CustomEmbedFilter = require("./customembed").filter; var CustomEmbedFilter = require("./customembed").filter;
var Server = require("./server"); var Server = require("./server");
var Config = require("./config");
var urlRetrieve = function (transport, options, callback) { var urlRetrieve = function (transport, options, callback) {
// Catch any errors that crop up along the way of the request // Catch any errors that crop up along the way of the request
@ -48,10 +49,12 @@ var Getters = {
/* youtube.com */ /* youtube.com */
yt: function (id, callback) { yt: function (id, callback) {
var sv = Server.getServer(); var sv = Server.getServer();
/*
if (sv.cfg["enable-ytv3"] && sv.cfg["ytv3apikey"]) { if (sv.cfg["enable-ytv3"] && sv.cfg["ytv3apikey"]) {
Getters["ytv3"](id, callback); Getters["ytv3"](id, callback);
return; return;
} }
*/
var m = id.match(/([\w-]+)/); var m = id.match(/([\w-]+)/);
if (m) { if (m) {
@ -70,9 +73,9 @@ var Getters = {
timeout: 1000 timeout: 1000
}; };
if(sv.cfg["ytv2devkey"]) { if(Config.get("youtube-v2-key")) {
options.headers = { 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) */ /* youtube.com API v3 (requires API key) */
// DEPRECATED
ytv3: function (id, callback) { ytv3: function (id, callback) {
var sv = Server.getServer(); var sv = Server.getServer();
var m = id.match(/([\w-]+)/); var m = id.match(/([\w-]+)/);
@ -226,9 +230,9 @@ var Getters = {
timeout: 1000 timeout: 1000
}; };
if(sv.cfg["ytv2devkey"]) { if(Config.get("youtube-v2-key")) {
options.headers = { 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 timeout: 1000
}; };
if(sv.cfg["ytv2devkey"]) { if(Config.get("youtube-v2-key")) {
options.headers = { options.headers = {
"X-Gdata-Key": "key=" + sv.cfg["ytv2devkey"] "X-Gdata-Key": "key=" + Config.get("youtube-v2-key")
}; };
} }

View File

@ -4,6 +4,7 @@ var Logger = require("../logger");
var db = require("../database"); var db = require("../database");
var User = require("../user"); var User = require("../user");
var Server = require("../server"); var Server = require("../server");
var Config = require("../config");
var $util = require("../utilities"); var $util = require("../utilities");
var CONNECT_RATE = { var CONNECT_RATE = {
@ -89,7 +90,7 @@ function handleConnection(sock) {
} }
ipCount[ip]++; ipCount[ip]++;
if (ipCount[ip] > srv.cfg["ip-connection-limit"]) { if (ipCount[ip] > Config.get("io.ip-connection-limit")) {
sock.emit("kick", { sock.emit("kick", {
reason: "Too many connections from your IP address" reason: "Too many connections from your IP address"
}); });
@ -117,11 +118,11 @@ function handleConnection(sock) {
module.exports = { module.exports = {
init: function (srv) { init: function (srv) {
var ioport = srv.cfg["io-port"]; var ioport = Config.get("io.port");
var webport = srv.cfg["web-port"]; var webport = Config.get("http.port");
var app; var app;
if (ioport !== webport) { if (ioport !== webport) {
app = require("express")().listen(ioport, srv.cfg["express-host"]); app = require("express")().listen(ioport, Config.get("http.host"));
srv.ioWeb = app; srv.ioWeb = app;
} else { } else {
app = srv.express; app = srv.express;
@ -132,7 +133,7 @@ module.exports = {
srv.io.set("authorization", handleAuth); srv.io.set("authorization", handleAuth);
srv.io.on("connection", handleConnection); srv.io.on("connection", handleConnection);
if (srv.cfg["enable-ssl"]) { if (Config.get("https.enabled")) {
srv.ioSecure = sio.listen(srv.https); srv.ioSecure = sio.listen(srv.https);
srv.ioSecure.set("log level", 1); srv.ioSecure.set("log level", 1);
srv.ioSecure.set("authorization", handleAuth); srv.ioSecure.set("authorization", handleAuth);

View File

@ -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. 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 singleton = null;
var Config = require("./config");
module.exports = { module.exports = {
init: function (cfg) { init: function () {
Logger.syslog.log("Starting CyTube v" + VERSION); Logger.syslog.log("Starting CyTube v" + VERSION);
var chanlogpath = path.join(__dirname, "../chanlogs"); var chanlogpath = path.join(__dirname, "../chanlogs");
fs.exists(chanlogpath, function (exists) { fs.exists(chanlogpath, function (exists) {
@ -24,7 +25,7 @@ module.exports = {
fs.exists(chandumppath, function (exists) { fs.exists(chandumppath, function (exists) {
exists || fs.mkdir(chandumppath); exists || fs.mkdir(chandumppath);
}); });
singleton = new Server(cfg); singleton = new Server();
return singleton; return singleton;
}, },
@ -38,17 +39,14 @@ var fs = require("fs");
var http = require("http"); var http = require("http");
var https = require("https"); var https = require("https");
var express = require("express"); var express = require("express");
var Config = require("./config");
var Logger = require("./logger"); var Logger = require("./logger");
var Channel = require("./channel-new"); var Channel = require("./channel-new");
var User = require("./user"); var User = require("./user");
var $util = require("./utilities"); var $util = require("./utilities");
var ActionLog = require("./actionlog"); var ActionLog = require("./actionlog");
var Server = function (cfg) { var Server = function () {
var self = this; var self = this;
self.cfg = cfg;
self.channels = [], self.channels = [],
self.express = null; self.express = null;
self.http = null; self.http = null;
@ -66,7 +64,7 @@ var Server = function (cfg) {
// database init ------------------------------------------------------ // database init ------------------------------------------------------
var Database = require("./database"); var Database = require("./database");
self.db = Database; self.db = Database;
self.db.init(self.cfg); self.db.init();
// webserver init ----------------------------------------------------- // webserver init -----------------------------------------------------
self.httplog = new Logger.Logger(path.join(__dirname, self.httplog = new Logger.Logger(path.join(__dirname,
@ -155,23 +153,23 @@ var Server = function (cfg) {
*/ */
// http/https/sio server init ----------------------------------------- // http/https/sio server init -----------------------------------------
if (self.cfg["enable-ssl"]) { if (Config.get("https.enabled")) {
var key = fs.readFileSync(path.resolve(__dirname, "..", var key = fs.readFileSync(path.resolve(__dirname, "..",
self.cfg["ssl-keyfile"])); Config.get("https.keyfile")));
var cert = fs.readFileSync(path.resolve(__dirname, "..", var cert = fs.readFileSync(path.resolve(__dirname, "..",
self.cfg["ssl-certfile"])); Config.get("https.certfile")));
var opts = { var opts = {
key: key, key: key,
cert: cert, cert: cert,
passphrase: self.cfg["ssl-passphrase"] passphrase: Config.get("https.passphrase")
}; };
self.https = https.createServer(opts, self.express) 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.http = self.express.listen(Config.get("http.port"),
self.cfg["express-host"]); Config.get("http.host") || undefined);
/* /*
self.ioWeb = express().listen(self.cfg["io-port"], self.cfg["io-host"]); self.ioWeb = express().listen(self.cfg["io-port"], self.cfg["io-host"]);
self.io = require("socket.io").listen(self.ioWeb); self.io = require("socket.io").listen(self.ioWeb);
@ -186,7 +184,7 @@ var Server = function (cfg) {
require("./bgtask")(self); require("./bgtask")(self);
// tor blocker init --------------------------------------------------- // tor blocker init ---------------------------------------------------
if (self.cfg["tor-blocker"]) { if (Config.get("enable-tor-blocker")) {
self.torblocker = require("./torblocker")(); self.torblocker = require("./torblocker")();
} }
}; };

View File

@ -4,6 +4,7 @@ var util = require("./utilities");
var MakeEmitter = require("./emitter"); var MakeEmitter = require("./emitter");
var db = require("./database"); var db = require("./database");
var InfoGetter = require("./get-info"); var InfoGetter = require("./get-info");
var Config = require("./config");
function User(socket) { function User(socket) {
var self = this; var self = this;
@ -387,15 +388,14 @@ User.prototype.whenLoggedIn = function (fn) {
var lastguestlogin = {}; var lastguestlogin = {};
User.prototype.guestLogin = function (name) { User.prototype.guestLogin = function (name) {
var self = this; var self = this;
var srv = Server.getServer();
if (self.ip in lastguestlogin) { if (self.ip in lastguestlogin) {
var diff = (Date.now() - lastguestlogin[self.ip]) / 1000; 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", { self.socket.emit("login", {
success: false, success: false,
error: "Guest logins are restricted to one per IP address per " + error: "Guest logins are restricted to one per IP address per " +
srv.cfg["guest-login-delay"] + " seconds." Config.get("guest-login-delay") + " seconds."
}); });
return; return;
} }

View File

@ -13,6 +13,7 @@
"jade": "~1.1.5", "jade": "~1.1.5",
"socket.io": "~0.9.16", "socket.io": "~0.9.16",
"nodemailer": "~0.6.0", "nodemailer": "~0.6.0",
"cookie": "~0.1.0" "cookie": "~0.1.0",
"yamljs": "~0.1.4"
} }
} }