nodegame-openshift
Version:
Prepares a nodeGame instance to run on the openshift cloud.
431 lines (356 loc) • 12.3 kB
JavaScript
/**
* # Launcher file for nodeGame Server
* Copyright(c) 2016 Stefano Balietti
* MIT Licensed
*
* Load configuration options and start the server
*
* http://www.nodegame.org
*/
// Modules.
var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
// Load commander.
var program = require('commander');
// Load the ServerNode class.
var ServerNode = require('nodegame-server').ServerNode;
// nodeGame version.
var version = require('./package.json').version;
// ServerNode object.
var sn;
// ServerNode options.
var options;
// Other local options.
var confDir, logDir, debug, infoQuery;
var nClients, clientType, killServer;
// Warn message.
var ignoredOptions;
// Helper variables.
// Split input parameters.
function list(val) {
return val.split(',');
}
// Go!
ignoredOptions = [];
// Defaults.
confFile = null;
confDir = './conf';
logDir = './log';
gamesDir = './games';
debug = false;
infoQuery = false;
nClients = 4;
clientType = 'autoplay';
runTests = false;
killServer = false;
// Commander.
program
.version(version)
// Specify a configuration file (other inline-options will be ignored).
.option('-C, --config [confFile]',
'Specifies a configuration file to load')
// Specify inline options (more limited than a conf file, but practical).
.option('-c, --confDir [confDir]',
'Sets the configuration directory')
.option('-l, --logDir [logDir]',
'Sets the log directory')
.option('-g, --gamesDir [gamesDir]',
'Sets the games directory')
.option('-d, --debug',
'Enables the debug mode')
.option('-i, --infoQuery',
'Enables getting information via query-string ?q=')
.option('-b, --build [components]',
'Rebuilds the specified components', list)
.option('-s, --ssl [path-to-ssl-dir]',
'Starts the server with SSL encryption')
// Connect phantoms.
.option('-p, --phantoms [channel]',
'Connect phantoms to the specified channel')
.option('-n, --nClients [n]',
'Sets the number of clients phantoms to connect')
.option('-t, --clientType [t]',
'Sets the client type of connecting phantoms')
.option('-T, --runTests',
'Run tests after all phantoms have reached game over ' +
'(overwrites settings.js in test/ folder')
.option('-k, --killServer',
'Kill server after all phantoms have reached game over')
.parse(process.argv);
if (program.confFile) {
if (!fs.existsSync(progam.ConfFile)) {
return printErr('--confFile ' + program.confFile + ' not found.');
}
options = require(program.confFile);
if ('object' !== typeof options) {
return printErr('--confFile ' + program.confFile + ' did not return ' +
'a configuration object.');
}
if (program.confDir) ignoredOptions.push('--confDir');
if (program.logDir) ignoredOptions.push('--logDir');
if (program.gamesDir) ignoredOptions.push('--gamesDir');
if (program.debug) ignoredOptions.push('--debug');
if (program.infoQuery) ignoredOptions.push('--infoQuery');
}
else {
options = {
// Additional conf directory.
confDir: confDir,
// Log Dir
logDir: logDir,
servernode: function(servernode) {
// Special configuration for the ServerNode object.
// Adds a new game directory (Default is nodegame-server/games).
servernode.gamesDirs.push(gamesDir);
// Sets the debug mode, exceptions will be thrown.
servernode.debug = debug;
// Can get information from /?q=
servernode.enableInfoQuery = infoQuery;
// Basepath (without trailing slash).
// servernode.basepath = '/mybasepath';
return true;
},
http: function(http) {
// Special configuration for Express goes here.
return true;
},
sio: function(sio) {
// Special configuration for Socket.Io goes here here.
// Might not work in Socket.IO 1.x (check).
// sio.set('transports', ['xhr-polling']);
// sio.set('transports', ['jsonp-polling']);
// sio.set('transports', [
// 'websocket'
// , 'flashsocket'
// , 'htmlfile'
// , 'xhr-polling'
// , 'jsonp-polling'
// ]);
return true;
}
};
// Validate other options.
if (program.confDir) {
if (!fs.existsSync(program.confDir)) {
return printErr('--confDir ' + program.confDir + ' not found.');
}
confDir = program.confDir;
}
if (program.logDir) {
if (!fs.existsSync(program.logDir)) {
return printErr('--logDir ' + program.logDir + ' not found.');
}
logDir = program.logDir;
}
if (program.gamesDir) {
if (!fs.existsSync(program.gamesDir)) {
return printErr('--gamesDir ' + program.gamesDir + ' not found.');
}
gamesDir = program.gamesDir;
}
if (program.debug) debug = true;
if (program.infoQuery) infoQuery = true;
}
// Rebuild server files as needed.
if (program.build) {
(function() {
var i, len, opts, modules;
var dir, J, info, module, out;
out = 'nodegame-full.js';
J = require('JSUS').JSUS;
modules = {
window: 'window',
client: 'client',
widgets: 'widgets',
JSUS: 'JSUS',
NDDB: 'NDDB'
};
info = require(J.resolveModuleDir('nodegame-server', __dirname) +
'bin/info.js');
len = program.build.length;
if (!len) {
program.build = [ 'client' ];
}
else if (len === 1 && program.build[0] === 'all') {
// Client will be done anyway.
program.build = [ 'window', 'widgets', 'JSUS', 'NDDB' ];
}
// Starting build.
i = -1, len = program.build.length;
for ( ; ++i < len ; ) {
module = program.build[i];
if (!modules[module]) {
throw new Error('unknown build component: ' + module);
}
// Will be done last anyway.
if (module === 'client') continue;
else if (module === 'NDDB') {
console.log('NDDB does not require build.');
continue;
}
opts = { all: true, clean: true };
info.build[module](opts);
console.log('');
}
// Do client last.
info.build.client({
clean: true,
all: true,
output: out
});
J.copyFile(info.modulesDir.client + 'build/' + out,
info.serverDir.build + out);
console.log(info.serverDir.build + out + ' rebuilt.');
console.log('');
})(program.build);
}
// Validate general options.
if ('boolean' === typeof program.ssl) {
options.ssl = true;
}
else if ('string' === typeof program.ssl) {
program.ssl = (function(dir) {
var ssl;
dir = path.resolve(dir) + '/';
if (!fs.existsSync(dir)) {
printErr('ssl directory not found: ' + dir);
return;
}
key = dir + 'private.key';
if (!fs.existsSync(key)) {
printErr('ssl private key not found: ' + key);
return;
}
cert = dir + 'certificate.pem';
if (!fs.existsSync(cert)) {
printErr('ssl certificate not found: ' + cert);
return;
}
ssl = {};
ssl.key = fs.readFileSync(key, 'utf8');
ssl.cert = fs.readFileSync(cert, 'utf8');
return ssl;
})(program.ssl);
if (!program.ssl) return;
}
if (program.nClients) {
if (!program.phantoms) ignoredOptions.push('--nClients');
else {
nClients = parseInt(program.nClients, 10);
if (isNaN(nClients)) {
return printErr('--nClients ' + program.nClients +
' is invalid.');
}
}
}
if (program.clientType) {
if (!program.phantoms) ignoredOptions.push('--clientType');
else clientType = program.clientType;
}
if (program.runTests) {
if (!program.runTests) ignoredOptions.push('--runTests');
else runTests = program.runTests;
}
if (program.killServer) {
if (!program.phantoms) ignoredOptions.push('--killServer');
else killServer = true;
}
// Print warnings, if any.
printIgnoredOptions();
// Start server, options parameter is optional.
sn = new ServerNode(options);
sn.ready(function() {
var i, phantoms, handleGameover;
var gameName, gameDir, queryString;
var numFinished;
// If there are not bots to add returns.
if (!program.phantoms) return;
gameName = program.phantoms;
if (!sn.channels[gameName]) {
printErr('channel ' + gameName + ' was not found.');
if (killServer) process.exit();
return;
}
gameDir = sn.channels[gameName].getGameDir();
if (clientType) {
queryString = '?clientType=' + clientType;
}
phantoms = [];
for (i = 0; i < nClients; ++i) {
console.log('Connecting phantom #', i+1, '/', nClients);
phantoms[i] = sn.channels[gameName].connectPhantom({
queryString: queryString
});
}
// TODO: Listen for room creation instead of timeout.
//setTimeout(function() {
// var node;
// node = sn.channels.ultimatum.gameRooms["ultimatum1"].node;
// node.events.ee.ng.on(
// 'GAME_OVER', function() {console.log('The game is over now.');});
//}, 5000);
if (killServer || runTests) {
handleGameover = function() {
var command;
console.log();
console.log(gameName + ' game has run successfully.');
console.log();
if (runTests) {
command = gameDir + 'node_modules/.bin/mocha';
if (fs.existsSync(command)) {
// Write and backup settings file.
writeSettingsFile(gameDir);
command += ' ' + gameDir + 'test/ --colors';
testProcess = exec(command, function(err, stdout, stderr) {
if (stdout) console.log(stdout);
if (stderr) console.log(stderr);
testProcess.kill();
if (killServer) process.exit();
});
}
else {
printErr('Cannot run tests, mocha executable not found: ',
command);
}
}
else if (killServer) process.exit();
};
// Wait for all PhantomJS processes to exit, then stop the server.
numFinished = 0;
for (i = 0; i < nClients; ++i) {
phantoms[i].on('exit', function(code) {
numFinished ++;
if (numFinished == nClients) {
handleGameover();
}
});
}
}
});
// ## Helper functions.
function printIgnoredOptions() {
if (ignoredOptions.length) {
console.log(' ignored options:', ignoredOptions.join(', '));
console.log();
console.log();
}
}
function printErr(err) {
console.log(' Check the input parameters.');
console.log(' Error: ' + err);
}
function writeSettingsFile(gameDir) {
var testProcess, settings, settingsFile, bak;
settings = 'module.exports = { numPlayers: ' + nClients + ' };';
settingsFile = gameDir + 'test/settings.js';
// Make a backup of existing settings file, if found.
if (fs.existsSync(settingsFile)) {
bak = fs.readFileSync(settingsFile).toString();
fs.writeFileSync(settingsFile + '.bak', bak);
}
// Write updated settings file.
fs.writeFileSync(gameDir + 'test/settings.js', settings);
}
// Exports the ServerNode instance.
module.exports = sn;