diff --git a/index.js b/index.js old mode 100644 new mode 100755 index b26d655b..8f54ee46 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +#!/usr/bin/env node + if (/^v0/.test(process.version)) { console.error('node.js ' + process.version + ' is not supported. ' + 'For more information, visit ' + @@ -5,14 +7,45 @@ if (/^v0/.test(process.version)) { process.exit(1); } -try { - require("./lib/logger"); - var Server = require("./lib/server"); -} catch (err) { - console.error('FATAL: Failed to require() lib/server.js'); +const args = parseArgs(); + +if (args.has('--daemonize')) { + fork(); +} else { + try { + require('./lib/main'); + } catch (err) { + console.error('FATAL: Failed to require() lib/main.js'); + handleStartupError(err); + } +} + +function fork() { + try { + console.log('Warning: --daemonize support is experimental. Use with caution.'); + + const { spawn } = require('child_process'); + const path = require('path'); + const main = path.resolve(__dirname, 'lib', 'main.js'); + + const child = spawn(process.argv[0], [main], { + detached: true, + stdio: 'ignore' // TODO: support setting stdout/stderr logfile + }); + + child.unref(); + console.log('Forked with PID ' + child.pid); + } catch (error) { + console.error('FATAL: Failed to fork lib/main.js'); + handleStartupError(error); + } +} + +function handleStartupError(err) { if (/module version mismatch/i.test(err.message)) { - console.error('Module version mismatch, try running `npm rebuild` or removing ' + - 'the node_modules folder and re-running `npm install`'); + console.error('Module version mismatch, try running `npm rebuild` or ' + + 'removing the node_modules folder and re-running ' + + '`npm install`'); } else { console.error('Possible causes:\n' + ' * You haven\'t run `npm run build-server` to regenerate ' + @@ -22,120 +55,22 @@ try { ' * A dependency failed to install correctly (check the output ' + 'of `npm install` next time)'); } + console.error(err.stack); process.exit(1); } -var Config = require("./lib/config"); -var Logger = require("./lib/logger"); -const Switches = require("./lib/switches"); -require("source-map-support").install(); -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); - }); +function parseArgs() { + const args = new Map(); + for (let i = 2; i < process.argv.length; i++) { + if (/^--/.test(process.argv[i])) { + let val; + if (i+1 < process.argv.length) val = process.argv[i+1]; + else val = null; - process.on("SIGINT", function () { - sv.shutdown(); - }); -} - -var stdinbuf = ""; -process.stdin.on("data", function (data) { - stdinbuf += data; - if (stdinbuf.indexOf("\n") !== -1) { - var line = stdinbuf.substring(0, stdinbuf.indexOf("\n")); - stdinbuf = stdinbuf.substring(stdinbuf.indexOf("\n") + 1); - handleLine(line); + args.set(process.argv[i], val); + } } -}); -var validIP = require('net').isIP; -function handleLine(line) { - if (line === "/reload") { - Logger.syslog.log("Reloading config"); - Config.load("config.yaml"); - } else if (line === "/gc") { - if (global && global.gc) { - Logger.syslog.log("Running GC"); - global.gc(); - } else { - Logger.syslog.log("Failed to invoke GC: node started without --expose-gc"); - } - } else if (line === "/delete_old_tables") { - require("./lib/database/update").deleteOldChannelTables(function (err) { - if (!err) { - Logger.syslog.log("Deleted old channel tables"); - } - }); - } else if (line.indexOf("/switch") === 0) { - var args = line.split(" "); - args.shift(); - if (args.length === 1) { - Logger.syslog.log("Switch " + args[0] + " is " + - (Switches.isActive(args[0]) ? "ON" : "OFF")); - } else if (args.length === 2) { - Switches.setActive(args[0], args[1].toLowerCase() === "on" ? true : false); - Logger.syslog.log("Switch " + args[0] + " is now " + - (Switches.isActive(args[0]) ? "ON" : "OFF")); - } - } else if (line.indexOf("/reload-partitions") === 0) { - sv.reloadPartitionMap(); - } else if (line.indexOf("/globalban") === 0) { - var args = line.split(/\s+/); args.shift(); - if (args.length >= 2 && validIP(args[0]) !== 0) { - var ip = args.shift(); - var comment = args.join(' '); - require("./lib/database").globalBanIP(ip, comment, function (err, res) { - if (!err) { - Logger.eventlog.log("[acp] " + "SYSTEM" + " global banned " + ip); - } - }) - } - } else if (line.indexOf("/unglobalban") === 0) { - var args = line.split(/\s+/); args.shift(); - if (args.length >= 1 && validIP(args[0]) !== 0) { - var ip = args.shift(); - require("./lib/database").globalUnbanIP(ip, function (err, res) { - if (!err) { - Logger.eventlog.log("[acp] " + "SYSTEM" + " un-global banned " + ip); - } - }) - } - } else if (line.indexOf("/unloadchan") === 0) { - var args = line.split(/\s+/); args.shift(); - if(args.length){ - var name = args.shift(); - var chan = sv.getChannel(name); - var users = Array.prototype.slice.call(chan.users); - chan.emit("empty"); - users.forEach(function (u) { - u.kick("Channel shutting down"); - }); - Logger.eventlog.log("[acp] " + "SYSTEM" + " forced unload of " + name); - } - } else if (line.indexOf("/reloadcert") === 0) { - sv.reloadCertificateData(); - } + return args; } - -// Go Go Gadget Service Socket -if (Config.get("service-socket.enabled")) { - Logger.syslog.log("Opening service socket"); - var ServiceSocket = require('./lib/servsock'); - var server = new ServiceSocket; - server.init(handleLine, Config.get("service-socket.socket")); -} - -// Hi I'm Mr POSIX! Look at me! -process.on('SIGUSR2', () => { - sv.reloadCertificateData(); -}); - -require("bluebird"); -process.on("unhandledRejection", function (reason, promise) { - Logger.errlog.log("[SEVERE] Unhandled rejection: " + reason.stack); -}); diff --git a/package.json b/package.json index c8091fa4..4cf70334 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.46.1", + "version": "3.46.2", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/src/main.js b/src/main.js new file mode 100644 index 00000000..85d49c37 --- /dev/null +++ b/src/main.js @@ -0,0 +1,123 @@ +import Config from './config'; +import Switches from './switches'; +import { isIP as validIP } from 'net'; +import { eventlog } from './logger'; +require('source-map-support').install(); + +const LOGGER = require('@calzoneman/jsli')('main'); + +Config.load('config.yaml'); + +const sv = require('./server').init(); + +if (!Config.get('debug')) { + process.on('uncaughtException', error => { + LOGGER.fatal('Uncaught exception: %s', error.stack); + }); + + process.on('SIGINT', () => { + LOGGER.info('Caught SIGINT; shutting down'); + sv.shutdown(); + }); +} + +// TODO: this can probably just be part of servsock.js +// servsock should also be refactored to send replies instead of +// relying solely on tailing logs +function handleLine(line) { + if (line === '/reload') { + LOGGER.info('Reloading config'); + Config.load('config.yaml'); + } else if (line === '/gc') { + if (global && global.gc) { + LOGGER.info('Running GC'); + global.gc(); + } else { + LOGGER.info('Failed to invoke GC: node started without --expose-gc'); + } + } else if (line === '/delete_old_tables') { + require('./database/update').deleteOldChannelTables(function (err) { + if (!err) { + LOGGER.info('Deleted old channel tables'); + } + }); + } else if (line.indexOf('/switch') === 0) { + const args = line.split(' '); + args.shift(); + if (args.length === 1) { + LOGGER.info('Switch ' + args[0] + ' is ' + + (Switches.isActive(args[0]) ? 'ON' : 'OFF')); + } else if (args.length === 2) { + Switches.setActive(args[0], args[1].toLowerCase() === 'on' ? true : false); + LOGGER.info('Switch ' + args[0] + ' is now ' + + (Switches.isActive(args[0]) ? 'ON' : 'OFF')); + } + } else if (line.indexOf('/reload-partitions') === 0) { + sv.reloadPartitionMap(); + } else if (line.indexOf('/globalban') === 0) { + const args = line.split(/\s+/); args.shift(); + if (args.length >= 2 && validIP(args[0]) !== 0) { + const ip = args.shift(); + const comment = args.join(' '); + // TODO: this is broken by the knex refactoring + require('./database').globalBanIP(ip, comment, function (err, res) { + if (!err) { + eventlog.log('[acp] ' + 'SYSTEM' + ' global banned ' + ip); + } + }) + } + } else if (line.indexOf('/unglobalban') === 0) { + var args = line.split(/\s+/); args.shift(); + if (args.length >= 1 && validIP(args[0]) !== 0) { + var ip = args.shift(); + // TODO: this is broken by the knex refactoring + require('./database').globalUnbanIP(ip, function (err, res) { + if (!err) { + eventlog.log('[acp] ' + 'SYSTEM' + ' un-global banned ' + ip); + } + }) + } + } else if (line.indexOf('/unloadchan') === 0) { + const args = line.split(/\s+/); args.shift(); + if (args.length) { + const name = args.shift(); + const chan = sv.getChannel(name); + const users = Array.prototype.slice.call(chan.users); + chan.emit('empty'); + users.forEach(function (u) { + u.kick('Channel shutting down'); + }); + eventlog.log('[acp] ' + 'SYSTEM' + ' forced unload of ' + name); + } + } else if (line.indexOf('/reloadcert') === 0) { + sv.reloadCertificateData(); + } +} + +// Go Go Gadget Service Socket +if (Config.get('service-socket.enabled')) { + LOGGER.info('Opening service socket'); + const ServiceSocket = require('./servsock'); + const sock = new ServiceSocket(); + sock.init(handleLine, Config.get('service-socket.socket')); +} + +let stdinbuf = ''; +process.stdin.on('data', function (data) { + stdinbuf += data; + if (stdinbuf.indexOf('\n') !== -1) { + let line = stdinbuf.substring(0, stdinbuf.indexOf('\n')); + stdinbuf = stdinbuf.substring(stdinbuf.indexOf('\n') + 1); + handleLine(line); + } +}); + +// Hi I'm Mr POSIX! Look at me! +process.on('SIGUSR2', () => { + sv.reloadCertificateData(); +}); + +require('bluebird'); +process.on('unhandledRejection', function (reason, promise) { + LOGGER.fatal('Unhandled rejection: %s', reason.stack); +});