458 lines
17 KiB
JavaScript
458 lines
17 KiB
JavaScript
/* jslint node: true */
|
|
/* eslint-disable no-console */
|
|
'use strict';
|
|
|
|
// ENiGMA½
|
|
const conf = require('./config.js');
|
|
const logger = require('./logger.js');
|
|
const database = require('./database.js');
|
|
const resolvePath = require('./misc_util.js').resolvePath;
|
|
const UserProps = require('./user_property.js');
|
|
const SysProps = require('./system_property.js');
|
|
const SysLogKeys = require('./system_log.js');
|
|
const UserLogNames = require('./user_log_name');
|
|
|
|
// deps
|
|
const async = require('async');
|
|
const util = require('util');
|
|
const _ = require('lodash');
|
|
const mkdirs = require('fs-extra').mkdirs;
|
|
const fs = require('graceful-fs');
|
|
const paths = require('path');
|
|
const moment = require('moment');
|
|
|
|
// our main entry point
|
|
exports.main = main;
|
|
|
|
// object with various services we want to de-init/shutdown cleanly if possible
|
|
const initServices = {};
|
|
|
|
// only include bbs.js once @ startup; this should be fine
|
|
const COPYRIGHT = fs
|
|
.readFileSync(paths.join(__dirname, '../LICENSE.TXT'), 'utf8')
|
|
.split(/\r?\n/g)[0];
|
|
|
|
const FULL_COPYRIGHT = `ENiGMA½ ${COPYRIGHT}`;
|
|
const HELP = `${FULL_COPYRIGHT}
|
|
usage: main.js <args>
|
|
eg : main.js --config /enigma_install_path/config/
|
|
|
|
valid args:
|
|
--version : display version
|
|
--help : displays this help
|
|
--config PATH : override default config path
|
|
`;
|
|
|
|
function printHelpAndExit() {
|
|
console.info(HELP);
|
|
process.exit();
|
|
}
|
|
|
|
function printVersionAndExit() {
|
|
console.info(require('../package.json').version);
|
|
}
|
|
|
|
function main() {
|
|
let errorDisplayed = false;
|
|
|
|
async.waterfall(
|
|
[
|
|
function processArgs(callback) {
|
|
const argv = require('minimist')(process.argv.slice(2));
|
|
|
|
if (argv.help) {
|
|
return printHelpAndExit();
|
|
}
|
|
|
|
if (argv.version) {
|
|
return printVersionAndExit();
|
|
}
|
|
|
|
const configOverridePath = argv.config;
|
|
|
|
return callback(
|
|
null,
|
|
configOverridePath || conf.Config.getDefaultPath(),
|
|
_.isString(configOverridePath)
|
|
);
|
|
},
|
|
function initConfig(configPath, configPathSupplied, callback) {
|
|
const configFile = configPath + 'config.hjson';
|
|
|
|
conf.Config.create(resolvePath(configFile), err => {
|
|
//
|
|
// If the user supplied a path and we can't read/parse it
|
|
// then it's a fatal error
|
|
//
|
|
if (err) {
|
|
if ('ENOENT' === err.code) {
|
|
if (configPathSupplied) {
|
|
console.error(
|
|
'Configuration file does not exist: ' + configFile
|
|
);
|
|
} else {
|
|
configPathSupplied = null; // make non-fatal; we'll go with defaults
|
|
}
|
|
} else {
|
|
errorDisplayed = true;
|
|
console.error(`Configuration error: ${err.message}`); // eslint-disable-line no-console
|
|
if (err.hint) {
|
|
console.error(`Hint: ${err.hint}`);
|
|
}
|
|
if (err.configPath) {
|
|
console.error(`Note: ${err.configPath}`);
|
|
}
|
|
}
|
|
}
|
|
return callback(err);
|
|
});
|
|
},
|
|
function initSystem(callback) {
|
|
initialize(function init(err) {
|
|
if (err) {
|
|
console.error('Error initializing: ' + util.inspect(err));
|
|
}
|
|
return callback(err);
|
|
});
|
|
},
|
|
],
|
|
function complete(err) {
|
|
if (!err) {
|
|
// note this is escaped:
|
|
fs.readFile(
|
|
paths.join(__dirname, '../misc/startup_banner.asc'),
|
|
'utf8',
|
|
(err, banner) => {
|
|
console.info(FULL_COPYRIGHT);
|
|
if (!err) {
|
|
console.info(banner);
|
|
}
|
|
console.info('System started!');
|
|
}
|
|
);
|
|
}
|
|
|
|
if (err && !errorDisplayed) {
|
|
console.error('Error initializing: ' + util.inspect(err));
|
|
return process.exit();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
function shutdownSystem() {
|
|
const msg = 'Process interrupted. Shutting down...';
|
|
console.info(msg);
|
|
logger.log.info(msg);
|
|
|
|
async.series(
|
|
[
|
|
function closeConnections(callback) {
|
|
const ClientConns = require('./client_connections.js');
|
|
const activeConnections = ClientConns.getActiveConnections(
|
|
ClientConns.AllConnections
|
|
);
|
|
let i = activeConnections.length;
|
|
while (i--) {
|
|
const activeTerm = activeConnections[i].term;
|
|
if (activeTerm) {
|
|
activeTerm.write(
|
|
'\n\nServer is shutting down NOW! Disconnecting...\n\n'
|
|
);
|
|
}
|
|
ClientConns.removeClient(activeConnections[i]);
|
|
}
|
|
callback(null);
|
|
},
|
|
function stopListeningServers(callback) {
|
|
return require('./listening_server.js').shutdown(() => {
|
|
return callback(null); // ignore err
|
|
});
|
|
},
|
|
function stopEventScheduler(callback) {
|
|
if (initServices.eventScheduler) {
|
|
return initServices.eventScheduler.shutdown(() => {
|
|
return callback(null); // ignore err
|
|
});
|
|
} else {
|
|
return callback(null);
|
|
}
|
|
},
|
|
function stopFileAreaWeb(callback) {
|
|
require('./file_area_web.js').startup(() => {
|
|
return callback(null); // ignore err
|
|
});
|
|
},
|
|
function stopMsgNetwork(callback) {
|
|
require('./msg_network.js').shutdown(callback);
|
|
},
|
|
],
|
|
() => {
|
|
console.info('Goodbye!');
|
|
return process.exit();
|
|
}
|
|
);
|
|
}
|
|
|
|
function initialize(cb) {
|
|
async.series(
|
|
[
|
|
function createMissingDirectories(callback) {
|
|
const Config = conf.get();
|
|
async.each(
|
|
Object.keys(Config.paths),
|
|
function entry(pathKey, next) {
|
|
mkdirs(Config.paths[pathKey], function dirCreated(err) {
|
|
if (err) {
|
|
console.error(
|
|
'Could not create path: ' +
|
|
Config.paths[pathKey] +
|
|
': ' +
|
|
err.toString()
|
|
);
|
|
}
|
|
return next(err);
|
|
});
|
|
},
|
|
function dirCreationComplete(err) {
|
|
return callback(err);
|
|
}
|
|
);
|
|
},
|
|
function basicInit(callback) {
|
|
logger.init();
|
|
logger.log.info(
|
|
{
|
|
version: require('../package.json').version,
|
|
nodeVersion: process.version,
|
|
},
|
|
'**** ENiGMA½ Bulletin Board System Starting Up! ****'
|
|
);
|
|
|
|
process.on('SIGINT', shutdownSystem);
|
|
|
|
require('@breejs/later').date.localTime(); // use local times for later.js/scheduling
|
|
|
|
return callback(null);
|
|
},
|
|
function initDatabases(callback) {
|
|
return database.initializeDatabases(callback);
|
|
},
|
|
function initMimeTypes(callback) {
|
|
return require('./mime_util.js').startup(callback);
|
|
},
|
|
function initStatLog(callback) {
|
|
return require('./stat_log.js').init(callback);
|
|
},
|
|
function initMenusAndThemes(callback) {
|
|
const { ThemeManager } = require('./theme');
|
|
return ThemeManager.create(callback);
|
|
},
|
|
function loadSysOpInformation(callback) {
|
|
//
|
|
// Copy over some +op information from the user DB -> system properties.
|
|
// * Makes this accessible for MCI codes, easy non-blocking access, etc.
|
|
// * We do this every time as the op is free to change this information just
|
|
// like any other user
|
|
//
|
|
const User = require('./user.js');
|
|
|
|
// :TODO: use User.getUserInfo() for this!
|
|
|
|
const propLoadOpts = {
|
|
names: [
|
|
UserProps.RealName,
|
|
UserProps.Sex,
|
|
UserProps.EmailAddress,
|
|
UserProps.Location,
|
|
UserProps.Affiliations,
|
|
],
|
|
};
|
|
|
|
async.waterfall(
|
|
[
|
|
function getOpUserName(next) {
|
|
return User.getUserName(User.RootUserID, next);
|
|
},
|
|
function getOpProps(opUserName, next) {
|
|
User.loadProperties(
|
|
User.RootUserID,
|
|
propLoadOpts,
|
|
(err, opProps) => {
|
|
return next(err, opUserName, opProps);
|
|
}
|
|
);
|
|
},
|
|
],
|
|
(err, opUserName, opProps) => {
|
|
const StatLog = require('./stat_log.js');
|
|
|
|
if (err) {
|
|
propLoadOpts.names.concat('username').forEach(v => {
|
|
StatLog.setNonPersistentSystemStat(`sysop_${v}`, 'N/A');
|
|
});
|
|
} else {
|
|
opProps.username = opUserName;
|
|
|
|
_.each(opProps, (v, k) => {
|
|
StatLog.setNonPersistentSystemStat(`sysop_${k}`, v);
|
|
});
|
|
}
|
|
|
|
return callback(null);
|
|
}
|
|
);
|
|
},
|
|
function initSystemLogStats(callback) {
|
|
const StatLog = require('./stat_log.js');
|
|
|
|
const filter = {
|
|
logName: SysLogKeys.UserLoginHistory,
|
|
resultType: 'count',
|
|
date: moment(),
|
|
};
|
|
|
|
StatLog.findSystemLogEntries(filter, (err, callsToday) => {
|
|
if (!err) {
|
|
StatLog.setNonPersistentSystemStat(
|
|
SysProps.LoginsToday,
|
|
callsToday
|
|
);
|
|
}
|
|
return callback(null);
|
|
});
|
|
},
|
|
function initUserLogStats(callback) {
|
|
const StatLog = require('./stat_log');
|
|
|
|
const entries = [
|
|
[UserLogNames.UlFiles, [SysProps.FileUlTodayCount, 'count']],
|
|
[UserLogNames.UlFileBytes, [SysProps.FileUlTodayBytes, 'obj']],
|
|
[UserLogNames.DlFiles, [SysProps.FileDlTodayCount, 'count']],
|
|
[UserLogNames.DlFileBytes, [SysProps.FileDlTodayBytes, 'obj']],
|
|
[UserLogNames.NewUser, [SysProps.NewUsersTodayCount, 'count']],
|
|
];
|
|
|
|
async.each(
|
|
entries,
|
|
(entry, nextEntry) => {
|
|
const [logName, [sysPropName, resultType]] = entry;
|
|
|
|
const filter = {
|
|
logName,
|
|
resultType,
|
|
date: moment(),
|
|
};
|
|
|
|
StatLog.findUserLogEntries(filter, (err, stat) => {
|
|
if (!err) {
|
|
if (resultType === 'obj') {
|
|
stat = stat.reduce(
|
|
(bytes, entry) =>
|
|
bytes + parseInt(entry.log_value) || 0,
|
|
0
|
|
);
|
|
}
|
|
|
|
StatLog.setNonPersistentSystemStat(sysPropName, stat);
|
|
}
|
|
return nextEntry(null);
|
|
});
|
|
},
|
|
() => {
|
|
return callback(null);
|
|
}
|
|
);
|
|
},
|
|
function initLastLogin(callback) {
|
|
const StatLog = require('./stat_log');
|
|
StatLog.getSystemLogEntries(
|
|
SysLogKeys.UserLoginHistory,
|
|
'timestamp_desc',
|
|
1,
|
|
(err, lastLogin) => {
|
|
if (err) {
|
|
return callback(null);
|
|
}
|
|
|
|
let loginObj;
|
|
try {
|
|
loginObj = JSON.parse(lastLogin[0].log_value);
|
|
loginObj.timestamp = moment(lastLogin[0].timestamp);
|
|
} catch (e) {
|
|
return callback(null);
|
|
}
|
|
|
|
// For live stats we want to resolve user ID -> name, etc.
|
|
const User = require('./user');
|
|
User.getUserInfo(loginObj.userId, (err, props) => {
|
|
const stat = Object.assign({}, props, loginObj);
|
|
StatLog.setNonPersistentSystemStat(SysProps.LastLogin, stat);
|
|
return callback(null);
|
|
});
|
|
}
|
|
);
|
|
},
|
|
function initUserCount(callback) {
|
|
const User = require('./user.js');
|
|
User.getUserCount((err, count) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
const StatLog = require('./stat_log');
|
|
StatLog.setNonPersistentSystemStat(SysProps.TotalUserCount, count);
|
|
return callback(null);
|
|
});
|
|
},
|
|
function initMessageStats(callback) {
|
|
return require('./message_area.js').startup(callback);
|
|
},
|
|
function initMCI(callback) {
|
|
return require('./predefined_mci.js').init(callback);
|
|
},
|
|
function readyMessageNetworkSupport(callback) {
|
|
return require('./msg_network.js').startup(callback);
|
|
},
|
|
function readyEvents(callback) {
|
|
return require('./events.js').startup(callback);
|
|
},
|
|
function genericModulesInit(callback) {
|
|
return require('./module_util.js').initializeModules(callback);
|
|
},
|
|
function listenConnections(callback) {
|
|
return require('./listening_server.js').startup(callback);
|
|
},
|
|
function readyFileBaseArea(callback) {
|
|
return require('./file_base_area.js').startup(callback);
|
|
},
|
|
function readyFileAreaWeb(callback) {
|
|
return require('./file_area_web.js').startup(callback);
|
|
},
|
|
function readyPasswordReset(callback) {
|
|
const WebPasswordReset =
|
|
require('./web_password_reset.js').WebPasswordReset;
|
|
return WebPasswordReset.startup(callback);
|
|
},
|
|
function ready2FA_OTPRegister(callback) {
|
|
const User2FA_OTPWebRegister = require('./user_2fa_otp_web_register.js');
|
|
return User2FA_OTPWebRegister.startup(callback);
|
|
},
|
|
function readyEventScheduler(callback) {
|
|
const EventSchedulerModule =
|
|
require('./event_scheduler.js').EventSchedulerModule;
|
|
EventSchedulerModule.loadAndStart((err, modInst) => {
|
|
initServices.eventScheduler = modInst;
|
|
return callback(err);
|
|
});
|
|
},
|
|
function listenUserEventsForStatLog(callback) {
|
|
return require('./stat_log.js').initUserEvents(callback);
|
|
},
|
|
],
|
|
function onComplete(err) {
|
|
return cb(err);
|
|
}
|
|
);
|
|
}
|