CyTube has been crashing recently due to things attempting to release the reference after the channel was already closed (apparently the uncaughtException handler isn't called for this?). This newer implementation keeps track of what is ref'ing and unref'ing it, so it can log an error if it detects a discrepancy. Also changed the server to not delete the refCounter field from the channel when it's unloaded, so that should reduce the number of errors stemming from it being null/undefined.
260 lines
7.6 KiB
JavaScript
260 lines
7.6 KiB
JavaScript
const VERSION = require("../package.json").version;
|
|
var singleton = null;
|
|
var Config = require("./config");
|
|
var Promise = require("bluebird");
|
|
import * as ChannelStore from './channel-storage/channelstore';
|
|
|
|
module.exports = {
|
|
init: function () {
|
|
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);
|
|
});
|
|
|
|
var gdvttpath = path.join(__dirname, "../google-drive-subtitles");
|
|
fs.exists(gdvttpath, function (exists) {
|
|
exists || fs.mkdir(gdvttpath);
|
|
});
|
|
singleton = new Server();
|
|
return singleton;
|
|
},
|
|
|
|
getServer: function () {
|
|
return singleton;
|
|
}
|
|
};
|
|
|
|
var path = require("path");
|
|
var fs = require("fs");
|
|
var http = require("http");
|
|
var https = require("https");
|
|
var express = require("express");
|
|
var Logger = require("./logger");
|
|
var Channel = require("./channel/channel");
|
|
var User = require("./user");
|
|
var $util = require("./utilities");
|
|
var db = require("./database");
|
|
var Flags = require("./flags");
|
|
var sio = require("socket.io");
|
|
import LocalChannelIndex from './web/localchannelindex';
|
|
import IOConfiguration from './configuration/ioconfig';
|
|
import WebConfiguration from './configuration/webconfig';
|
|
import NullClusterClient from './io/cluster/nullclusterclient';
|
|
import session from './session';
|
|
|
|
var Server = function () {
|
|
var self = this;
|
|
self.channels = [],
|
|
self.express = null;
|
|
self.db = null;
|
|
self.api = null;
|
|
self.announcement = null;
|
|
self.infogetter = null;
|
|
self.servers = {};
|
|
|
|
// database init ------------------------------------------------------
|
|
var Database = require("./database");
|
|
self.db = Database;
|
|
self.db.init();
|
|
ChannelStore.init();
|
|
|
|
// webserver init -----------------------------------------------------
|
|
const ioConfig = IOConfiguration.fromOldConfig(Config);
|
|
const webConfig = WebConfiguration.fromOldConfig(Config);
|
|
const clusterClient = new NullClusterClient(ioConfig);
|
|
const channelIndex = new LocalChannelIndex();
|
|
self.express = express();
|
|
require("./web/webserver").init(self.express,
|
|
webConfig,
|
|
ioConfig,
|
|
clusterClient,
|
|
channelIndex,
|
|
session);
|
|
|
|
// http/https/sio server init -----------------------------------------
|
|
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")));
|
|
}
|
|
}
|
|
|
|
var opts = {
|
|
key: key,
|
|
cert: cert,
|
|
passphrase: Config.get("https.passphrase"),
|
|
ca: ca,
|
|
ciphers: Config.get("https.ciphers"),
|
|
honorCipherOrder: true
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
if (bind.https && Config.get("https.enabled")) {
|
|
self.servers[id] = https.createServer(opts, self.express)
|
|
.listen(bind.port, bind.ip);
|
|
self.servers[id].on("clientError", function (err, socket) {
|
|
try {
|
|
socket.destroy();
|
|
} catch (e) {
|
|
}
|
|
});
|
|
} else if (bind.http) {
|
|
self.servers[id] = self.express.listen(bind.port, bind.ip);
|
|
self.servers[id].on("clientError", function (err, socket) {
|
|
try {
|
|
socket.destroy();
|
|
} catch (e) {
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
require("./io/ioserver").init(self, webConfig);
|
|
|
|
// background tasks init ----------------------------------------------
|
|
require("./bgtask")(self);
|
|
|
|
// setuid
|
|
require("./setuid");
|
|
};
|
|
|
|
Server.prototype.getHTTPIP = function (req) {
|
|
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;
|
|
}
|
|
}
|
|
return ip;
|
|
};
|
|
|
|
Server.prototype.getSocketIP = function (socket) {
|
|
var raw = socket.handshake.address.address;
|
|
if (raw === "127.0.0.1" || raw === "::1") {
|
|
var fwd = socket.handshake.headers["x-forwarded-for"];
|
|
if (fwd && typeof fwd === "string") {
|
|
return fwd;
|
|
}
|
|
}
|
|
return raw;
|
|
};
|
|
|
|
Server.prototype.isChannelLoaded = function (name) {
|
|
name = name.toLowerCase();
|
|
for (var i = 0; i < this.channels.length; i++) {
|
|
if (this.channels[i].uniqueName == name)
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
Server.prototype.getChannel = function (name) {
|
|
var self = this;
|
|
var cname = name.toLowerCase();
|
|
for (var i = 0; i < self.channels.length; i++) {
|
|
if (self.channels[i].uniqueName === cname)
|
|
return self.channels[i];
|
|
}
|
|
|
|
var c = new Channel(name);
|
|
c.on("empty", function () {
|
|
self.unloadChannel(c);
|
|
});
|
|
self.channels.push(c);
|
|
return c;
|
|
};
|
|
|
|
Server.prototype.unloadChannel = function (chan) {
|
|
if (chan.dead) {
|
|
return;
|
|
}
|
|
|
|
chan.saveState();
|
|
|
|
chan.logger.log("[init] Channel shutting down");
|
|
chan.logger.close();
|
|
|
|
chan.notifyModules("unload", []);
|
|
Object.keys(chan.modules).forEach(function (k) {
|
|
chan.modules[k].dead = true;
|
|
});
|
|
|
|
for (var i = 0; i < this.channels.length; i++) {
|
|
if (this.channels[i].uniqueName === chan.uniqueName) {
|
|
this.channels.splice(i, 1);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
Logger.syslog.log("Unloaded channel " + chan.name);
|
|
// Empty all outward references from the channel
|
|
var keys = Object.keys(chan);
|
|
for (var i in keys) {
|
|
if (keys[i] !== "refCounter") {
|
|
delete chan[keys[i]];
|
|
}
|
|
}
|
|
chan.dead = true;
|
|
};
|
|
|
|
Server.prototype.packChannelList = function (publicOnly, isAdmin) {
|
|
var channels = this.channels.filter(function (c) {
|
|
if (!publicOnly) {
|
|
return true;
|
|
}
|
|
|
|
return c.modules.options && c.modules.options.get("show_public");
|
|
});
|
|
|
|
var self = this;
|
|
return channels.map(function (c) {
|
|
return c.packInfo(isAdmin);
|
|
});
|
|
};
|
|
|
|
Server.prototype.announce = function (data) {
|
|
if (data == null) {
|
|
this.announcement = null;
|
|
db.clearAnnouncement();
|
|
} else {
|
|
this.announcement = data;
|
|
db.setAnnouncement(data);
|
|
sio.instance.emit("announcement", data);
|
|
}
|
|
};
|
|
|
|
Server.prototype.shutdown = function () {
|
|
Logger.syslog.log("Unloading channels");
|
|
Promise.map(this.channels, channel => {
|
|
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);
|
|
}).catch(err => {
|
|
Logger.errlog.log(`Caught error while saving channels: ${err.stack}`);
|
|
process.exit(1);
|
|
});
|
|
};
|