2015-07-06 18:16:02 +00:00
|
|
|
const VERSION = require("../package.json").version;
|
2013-10-11 21:31:40 +00:00
|
|
|
var singleton = null;
|
2014-01-22 23:11:26 +00:00
|
|
|
var Config = require("./config");
|
2015-09-21 06:17:06 +00:00
|
|
|
var Promise = require("bluebird");
|
2015-10-02 05:02:59 +00:00
|
|
|
import * as ChannelStore from './channel-storage/channelstore';
|
2013-10-11 21:31:40 +00:00
|
|
|
|
|
|
|
module.exports = {
|
2014-01-22 23:11:26 +00:00
|
|
|
init: function () {
|
2013-10-11 21:31:40 +00:00
|
|
|
Logger.syslog.log("Starting CyTube v" + VERSION);
|
|
|
|
var chanlogpath = path.join(__dirname, "../chanlogs");
|
|
|
|
fs.exists(chanlogpath, function (exists) {
|
|
|
|
exists || fs.mkdir(chanlogpath);
|
|
|
|
});
|
|
|
|
|
|
|
|
var chandumppath = path.join(__dirname, "../chandump");
|
|
|
|
fs.exists(chandumppath, function (exists) {
|
|
|
|
exists || fs.mkdir(chandumppath);
|
|
|
|
});
|
2015-07-23 04:23:50 +00:00
|
|
|
|
|
|
|
var gdvttpath = path.join(__dirname, "../google-drive-subtitles");
|
|
|
|
fs.exists(gdvttpath, function (exists) {
|
|
|
|
exists || fs.mkdir(gdvttpath);
|
|
|
|
});
|
2014-01-22 23:11:26 +00:00
|
|
|
singleton = new Server();
|
2013-10-11 21:31:40 +00:00
|
|
|
return singleton;
|
|
|
|
},
|
|
|
|
|
|
|
|
getServer: function () {
|
|
|
|
return singleton;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-07-19 20:27:21 +00:00
|
|
|
var path = require("path");
|
2013-07-28 23:47:55 +00:00
|
|
|
var fs = require("fs");
|
2013-09-09 22:16:41 +00:00
|
|
|
var http = require("http");
|
|
|
|
var https = require("https");
|
2013-04-17 18:42:29 +00:00
|
|
|
var express = require("express");
|
2013-07-15 22:57:33 +00:00
|
|
|
var Logger = require("./logger");
|
2014-05-21 02:30:14 +00:00
|
|
|
var Channel = require("./channel/channel");
|
2013-07-15 22:57:33 +00:00
|
|
|
var User = require("./user");
|
2013-10-09 23:10:26 +00:00
|
|
|
var $util = require("./utilities");
|
2014-02-01 18:41:06 +00:00
|
|
|
var db = require("./database");
|
2014-05-21 02:30:14 +00:00
|
|
|
var Flags = require("./flags");
|
2014-08-24 18:18:15 +00:00
|
|
|
var sio = require("socket.io");
|
2015-10-27 05:56:53 +00:00
|
|
|
import LocalChannelIndex from './web/localchannelindex';
|
|
|
|
import IOConfiguration from './configuration/ioconfig';
|
2015-10-28 05:04:21 +00:00
|
|
|
import WebConfiguration from './configuration/webconfig';
|
2015-10-27 05:56:53 +00:00
|
|
|
import NullClusterClient from './io/cluster/nullclusterclient';
|
2015-11-02 01:42:20 +00:00
|
|
|
import session from './session';
|
2016-02-05 05:43:20 +00:00
|
|
|
import { LegacyModule } from './legacymodule';
|
2016-06-07 04:54:49 +00:00
|
|
|
import { PartitionModule } from './partition/partitionmodule';
|
2016-04-24 02:53:18 +00:00
|
|
|
import * as Switches from './switches';
|
2013-04-22 19:42:39 +00:00
|
|
|
|
2014-01-22 23:11:26 +00:00
|
|
|
var Server = function () {
|
2013-10-09 23:10:26 +00:00
|
|
|
var self = this;
|
|
|
|
self.channels = [],
|
|
|
|
self.express = null;
|
|
|
|
self.db = null;
|
|
|
|
self.api = null;
|
|
|
|
self.announcement = null;
|
|
|
|
self.infogetter = null;
|
2014-04-11 05:14:52 +00:00
|
|
|
self.servers = {};
|
2013-10-09 23:10:26 +00:00
|
|
|
|
2016-02-05 05:43:20 +00:00
|
|
|
// backend init
|
|
|
|
var initModule;
|
2016-02-10 06:59:48 +00:00
|
|
|
if (Config.get("new-backend")) {
|
2016-04-24 02:53:18 +00:00
|
|
|
if (Config.get("dual-backend")) {
|
|
|
|
Switches.setActive(Switches.DUAL_BACKEND, true);
|
|
|
|
}
|
2016-02-16 01:21:28 +00:00
|
|
|
const BackendModule = require('./backend/backendmodule').BackendModule;
|
2016-02-05 05:43:20 +00:00
|
|
|
initModule = new BackendModule();
|
2016-06-07 04:54:49 +00:00
|
|
|
} else if (Config.get('enable-partition')) {
|
|
|
|
initModule = new PartitionModule();
|
2016-02-05 05:43:20 +00:00
|
|
|
} else {
|
|
|
|
initModule = new LegacyModule();
|
|
|
|
}
|
|
|
|
|
2013-10-09 23:10:26 +00:00
|
|
|
// database init ------------------------------------------------------
|
|
|
|
var Database = require("./database");
|
2013-12-12 22:28:30 +00:00
|
|
|
self.db = Database;
|
2014-01-22 23:11:26 +00:00
|
|
|
self.db.init();
|
2015-10-02 05:02:59 +00:00
|
|
|
ChannelStore.init();
|
2013-10-11 23:15:44 +00:00
|
|
|
|
|
|
|
// webserver init -----------------------------------------------------
|
2015-10-27 05:56:53 +00:00
|
|
|
const ioConfig = IOConfiguration.fromOldConfig(Config);
|
2015-10-28 05:04:21 +00:00
|
|
|
const webConfig = WebConfiguration.fromOldConfig(Config);
|
2016-02-05 05:43:20 +00:00
|
|
|
const clusterClient = initModule.getClusterClient();
|
2015-10-27 05:56:53 +00:00
|
|
|
const channelIndex = new LocalChannelIndex();
|
2013-10-09 23:10:26 +00:00
|
|
|
self.express = express();
|
2015-10-27 05:56:53 +00:00
|
|
|
require("./web/webserver").init(self.express,
|
2015-10-28 05:04:21 +00:00
|
|
|
webConfig,
|
2015-10-27 05:56:53 +00:00
|
|
|
ioConfig,
|
|
|
|
clusterClient,
|
2015-11-02 01:42:20 +00:00
|
|
|
channelIndex,
|
|
|
|
session);
|
2013-10-09 23:10:26 +00:00
|
|
|
|
|
|
|
// http/https/sio server init -----------------------------------------
|
2014-04-13 02:52:06 +00:00
|
|
|
var key = "", cert = "", ca = undefined;
|
|
|
|
if (Config.get("https.enabled")) {
|
|
|
|
key = fs.readFileSync(path.resolve(__dirname, "..",
|
|
|
|
Config.get("https.keyfile")));
|
|
|
|
cert = fs.readFileSync(path.resolve(__dirname, "..",
|
|
|
|
Config.get("https.certfile")));
|
|
|
|
if (Config.get("https.cafile")) {
|
|
|
|
ca = fs.readFileSync(path.resolve(__dirname, "..",
|
|
|
|
Config.get("https.cafile")));
|
|
|
|
}
|
2013-10-09 23:10:26 +00:00
|
|
|
}
|
|
|
|
|
2014-04-11 05:14:52 +00:00
|
|
|
var opts = {
|
|
|
|
key: key,
|
|
|
|
cert: cert,
|
|
|
|
passphrase: Config.get("https.passphrase"),
|
2015-03-05 04:31:45 +00:00
|
|
|
ca: ca,
|
2015-03-06 22:29:21 +00:00
|
|
|
ciphers: Config.get("https.ciphers"),
|
2015-03-06 21:59:34 +00:00
|
|
|
honorCipherOrder: true
|
2014-04-11 05:14:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Config.get("listen").forEach(function (bind) {
|
|
|
|
var id = bind.ip + ":" + bind.port;
|
|
|
|
if (id in self.servers) {
|
|
|
|
Logger.syslog.log("[WARN] Ignoring duplicate listen address " + id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-04-11 15:52:51 +00:00
|
|
|
if (bind.https && Config.get("https.enabled")) {
|
2014-04-11 05:14:52 +00:00
|
|
|
self.servers[id] = https.createServer(opts, self.express)
|
|
|
|
.listen(bind.port, bind.ip);
|
2015-01-06 15:54:14 +00:00
|
|
|
self.servers[id].on("clientError", function (err, socket) {
|
|
|
|
try {
|
|
|
|
socket.destroy();
|
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
});
|
2014-04-11 05:14:52 +00:00
|
|
|
} else if (bind.http) {
|
|
|
|
self.servers[id] = self.express.listen(bind.port, bind.ip);
|
2015-01-06 15:54:14 +00:00
|
|
|
self.servers[id].on("clientError", function (err, socket) {
|
|
|
|
try {
|
|
|
|
socket.destroy();
|
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
});
|
2014-04-11 05:14:52 +00:00
|
|
|
}
|
|
|
|
});
|
2014-01-23 03:12:43 +00:00
|
|
|
|
2015-12-13 00:59:58 +00:00
|
|
|
require("./io/ioserver").init(self, webConfig);
|
2013-10-09 23:10:26 +00:00
|
|
|
|
|
|
|
// background tasks init ----------------------------------------------
|
|
|
|
require("./bgtask")(self);
|
2015-02-21 08:12:26 +00:00
|
|
|
|
|
|
|
// setuid
|
|
|
|
require("./setuid");
|
2016-02-05 05:43:20 +00:00
|
|
|
|
|
|
|
initModule.onReady();
|
2013-10-09 23:10:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Server.prototype.getHTTPIP = function (req) {
|
2013-11-25 00:13:58 +00:00
|
|
|
var ip = req.ip;
|
|
|
|
if (ip === "127.0.0.1" || ip === "::1") {
|
|
|
|
var fwd = req.header("x-forwarded-for");
|
|
|
|
if (fwd && typeof fwd === "string") {
|
|
|
|
return fwd;
|
|
|
|
}
|
2013-06-04 03:56:06 +00:00
|
|
|
}
|
2013-11-25 00:13:58 +00:00
|
|
|
return ip;
|
2013-10-09 23:10:26 +00:00
|
|
|
};
|
2013-06-04 03:56:06 +00:00
|
|
|
|
2013-10-09 23:10:26 +00:00
|
|
|
Server.prototype.getSocketIP = function (socket) {
|
2013-07-06 17:00:02 +00:00
|
|
|
var raw = socket.handshake.address.address;
|
2013-11-25 00:13:58 +00:00
|
|
|
if (raw === "127.0.0.1" || raw === "::1") {
|
|
|
|
var fwd = socket.handshake.headers["x-forwarded-for"];
|
|
|
|
if (fwd && typeof fwd === "string") {
|
|
|
|
return fwd;
|
2013-07-06 17:00:02 +00:00
|
|
|
}
|
|
|
|
}
|
2013-07-15 22:57:33 +00:00
|
|
|
return raw;
|
2013-10-09 23:10:26 +00:00
|
|
|
};
|
2013-07-15 22:57:33 +00:00
|
|
|
|
2013-10-09 23:10:26 +00:00
|
|
|
Server.prototype.isChannelLoaded = function (name) {
|
|
|
|
name = name.toLowerCase();
|
|
|
|
for (var i = 0; i < this.channels.length; i++) {
|
2014-02-02 21:50:05 +00:00
|
|
|
if (this.channels[i].uniqueName == name)
|
2013-10-09 23:10:26 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
2013-09-09 22:16:41 +00:00
|
|
|
|
2013-10-09 23:10:26 +00:00
|
|
|
Server.prototype.getChannel = function (name) {
|
2014-01-23 21:53:53 +00:00
|
|
|
var self = this;
|
2013-10-09 23:10:26 +00:00
|
|
|
var cname = name.toLowerCase();
|
2014-01-23 21:53:53 +00:00
|
|
|
for (var i = 0; i < self.channels.length; i++) {
|
|
|
|
if (self.channels[i].uniqueName === cname)
|
|
|
|
return self.channels[i];
|
2013-10-09 23:10:26 +00:00
|
|
|
}
|
2013-09-09 22:16:41 +00:00
|
|
|
|
2013-10-11 21:31:40 +00:00
|
|
|
var c = new Channel(name);
|
2014-01-23 21:53:53 +00:00
|
|
|
c.on("empty", function () {
|
|
|
|
self.unloadChannel(c);
|
|
|
|
});
|
|
|
|
self.channels.push(c);
|
2013-10-09 23:10:26 +00:00
|
|
|
return c;
|
|
|
|
};
|
2013-09-09 22:16:41 +00:00
|
|
|
|
2013-10-09 23:10:26 +00:00
|
|
|
Server.prototype.unloadChannel = function (chan) {
|
2014-02-02 21:50:05 +00:00
|
|
|
if (chan.dead) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-05-21 02:30:14 +00:00
|
|
|
chan.saveState();
|
2013-09-09 22:16:41 +00:00
|
|
|
|
2014-03-04 17:57:05 +00:00
|
|
|
chan.logger.log("[init] Channel shutting down");
|
2013-10-09 23:10:26 +00:00
|
|
|
chan.logger.close();
|
2014-05-21 02:30:14 +00:00
|
|
|
|
|
|
|
chan.notifyModules("unload", []);
|
|
|
|
Object.keys(chan.modules).forEach(function (k) {
|
|
|
|
chan.modules[k].dead = true;
|
2016-01-31 03:42:55 +00:00
|
|
|
/*
|
|
|
|
* Automatically clean up any timeouts/intervals assigned
|
|
|
|
* to properties of channel modules. Prevents a memory leak
|
|
|
|
* in case of forgetting to clear the timer on the "unload"
|
|
|
|
* module event.
|
|
|
|
*/
|
|
|
|
Object.keys(chan.modules[k]).forEach(function (prop) {
|
|
|
|
if (chan.modules[k][prop] && chan.modules[k][prop]._onTimeout) {
|
|
|
|
Logger.errlog.log("Warning: detected non-null timer when unloading " +
|
|
|
|
"module " + k + ": " + prop);
|
|
|
|
try {
|
|
|
|
clearTimeout(chan.modules[k][prop]);
|
|
|
|
clearInterval(chan.modules[k][prop]);
|
|
|
|
} catch (error) {
|
|
|
|
Logger.errlog.log(error.stack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2014-05-21 02:30:14 +00:00
|
|
|
});
|
|
|
|
|
2013-10-09 23:10:26 +00:00
|
|
|
for (var i = 0; i < this.channels.length; i++) {
|
2014-01-23 21:53:53 +00:00
|
|
|
if (this.channels[i].uniqueName === chan.uniqueName) {
|
2013-10-09 23:10:26 +00:00
|
|
|
this.channels.splice(i, 1);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
2013-03-28 23:51:08 +00:00
|
|
|
|
2014-02-16 19:27:01 +00:00
|
|
|
Logger.syslog.log("Unloaded channel " + chan.name);
|
2013-10-09 23:10:26 +00:00
|
|
|
// Empty all outward references from the channel
|
|
|
|
var keys = Object.keys(chan);
|
|
|
|
for (var i in keys) {
|
2015-12-26 01:07:25 +00:00
|
|
|
if (keys[i] !== "refCounter") {
|
|
|
|
delete chan[keys[i]];
|
|
|
|
}
|
2013-10-09 23:10:26 +00:00
|
|
|
}
|
|
|
|
chan.dead = true;
|
|
|
|
};
|
2013-08-20 04:53:33 +00:00
|
|
|
|
2014-05-24 06:09:36 +00:00
|
|
|
Server.prototype.packChannelList = function (publicOnly, isAdmin) {
|
2014-01-20 23:52:36 +00:00
|
|
|
var channels = this.channels.filter(function (c) {
|
|
|
|
if (!publicOnly) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-05-21 02:30:14 +00:00
|
|
|
return c.modules.options && c.modules.options.get("show_public");
|
2014-01-20 23:52:36 +00:00
|
|
|
});
|
|
|
|
|
2014-05-24 05:40:35 +00:00
|
|
|
var self = this;
|
|
|
|
return channels.map(function (c) {
|
2014-05-24 06:09:36 +00:00
|
|
|
return c.packInfo(isAdmin);
|
2014-05-24 05:40:35 +00:00
|
|
|
});
|
2014-01-20 23:52:36 +00:00
|
|
|
};
|
|
|
|
|
2014-02-01 18:41:06 +00:00
|
|
|
Server.prototype.announce = function (data) {
|
|
|
|
if (data == null) {
|
|
|
|
this.announcement = null;
|
|
|
|
db.clearAnnouncement();
|
|
|
|
} else {
|
|
|
|
this.announcement = data;
|
|
|
|
db.setAnnouncement(data);
|
2014-08-24 18:18:15 +00:00
|
|
|
sio.instance.emit("announcement", data);
|
2014-02-01 18:41:06 +00:00
|
|
|
}
|
2013-10-09 23:10:26 +00:00
|
|
|
};
|
2013-03-28 23:51:08 +00:00
|
|
|
|
2013-10-09 23:10:26 +00:00
|
|
|
Server.prototype.shutdown = function () {
|
|
|
|
Logger.syslog.log("Unloading channels");
|
2016-05-26 01:56:10 +00:00
|
|
|
Promise.reduce(this.channels, (_, channel) => {
|
2015-09-21 06:17:06 +00:00
|
|
|
return channel.saveState().tap(() => {
|
|
|
|
Logger.syslog.log(`Saved /r/${channel.name}`);
|
|
|
|
}).catch(err => {
|
|
|
|
Logger.errlog.log(`Failed to save /r/${channel.name}: ${err.stack}`);
|
|
|
|
});
|
|
|
|
}).then(() => {
|
|
|
|
Logger.syslog.log("Goodbye");
|
|
|
|
process.exit(0);
|
2015-09-25 06:36:05 +00:00
|
|
|
}).catch(err => {
|
|
|
|
Logger.errlog.log(`Caught error while saving channels: ${err.stack}`);
|
|
|
|
process.exit(1);
|
2015-09-21 06:17:06 +00:00
|
|
|
});
|
2013-10-09 23:10:26 +00:00
|
|
|
};
|