2019-02-06 08:12:14 +00:00
|
|
|
const { spawn } = require('child_process');
|
2019-02-07 12:35:07 +00:00
|
|
|
const kill = require('tree-kill');
|
2019-02-11 13:53:45 +00:00
|
|
|
const path = require('path');
|
|
|
|
const fs = require('fs');
|
2019-03-05 08:06:15 +00:00
|
|
|
const ini = require('ini');
|
2019-02-11 13:53:45 +00:00
|
|
|
const { ipcMain } = require('electron')
|
2019-02-28 14:07:06 +00:00
|
|
|
const { app } = require('electron')
|
2019-02-11 13:53:45 +00:00
|
|
|
|
|
|
|
const isWin = /^win/.test(process.platform);
|
2019-02-06 08:12:14 +00:00
|
|
|
|
2019-02-07 12:35:07 +00:00
|
|
|
let runningServers = {};
|
2019-02-06 08:12:14 +00:00
|
|
|
|
2019-02-11 13:53:45 +00:00
|
|
|
exports.getLocalServerPath = async () => {
|
2019-03-05 09:43:31 +00:00
|
|
|
let binary = isWin ? 'gns3server.exe': 'gns3server';
|
|
|
|
return findBinary('exe.', binary);
|
|
|
|
}
|
2019-02-11 13:53:45 +00:00
|
|
|
|
2019-03-05 09:43:31 +00:00
|
|
|
exports.getUbridgePath = async () => {
|
|
|
|
let binary = isWin ? 'ubridge.exe': 'ubridge';
|
|
|
|
return findBinary('ubridge', binary);
|
2019-02-11 13:53:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exports.startLocalServer = async (server) => {
|
2019-02-12 14:11:50 +00:00
|
|
|
return await run(server, {
|
|
|
|
logStdout: true
|
|
|
|
});
|
2019-02-11 13:53:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exports.stopLocalServer = async (server) => {
|
2019-02-12 14:11:50 +00:00
|
|
|
return await stop(server.name);
|
2019-02-11 13:53:45 +00:00
|
|
|
}
|
|
|
|
|
2019-02-12 14:34:18 +00:00
|
|
|
exports.getRunningServers = () => {
|
|
|
|
return Object.keys(runningServers);
|
|
|
|
}
|
|
|
|
|
2019-02-19 14:03:33 +00:00
|
|
|
exports.stopAllLocalServers = async () => {
|
|
|
|
return await stopAll();
|
|
|
|
}
|
|
|
|
|
2019-03-05 09:43:31 +00:00
|
|
|
async function findBinary(binaryDirectory, filename) {
|
|
|
|
const lookupDirectories = [
|
|
|
|
__dirname,
|
|
|
|
path.dirname(app.getPath('exe'))
|
|
|
|
];
|
|
|
|
|
|
|
|
for(var directory of lookupDirectories) {
|
|
|
|
const serverPath = await findBinaryInDirectory(directory, binaryDirectory, filename);
|
|
|
|
if(serverPath !== undefined) {
|
|
|
|
return serverPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function findBinaryInDirectory(baseDirectory, binaryDirectory, filename) {
|
2019-02-28 14:07:06 +00:00
|
|
|
const distDirectory = path.join(baseDirectory, 'dist');
|
|
|
|
|
|
|
|
if (!fs.existsSync(distDirectory)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const files = fs.readdirSync(distDirectory);
|
|
|
|
|
2019-03-05 09:43:31 +00:00
|
|
|
let binaryPath = null;
|
2019-02-28 14:07:06 +00:00
|
|
|
|
|
|
|
files.forEach((directory) => {
|
2019-03-05 09:43:31 +00:00
|
|
|
if(directory.startsWith(binaryDirectory)) {
|
|
|
|
binaryPath = path.join(baseDirectory, 'dist', directory, filename);
|
2019-02-28 14:07:06 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-03-05 09:43:31 +00:00
|
|
|
if(binaryPath !== null && fs.existsSync(binaryPath)) {
|
|
|
|
return binaryPath;
|
2019-02-28 14:07:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-05 09:43:31 +00:00
|
|
|
|
2019-03-05 08:06:15 +00:00
|
|
|
function getServerArguments(server, overrides, configPath) {
|
2019-02-19 14:03:33 +00:00
|
|
|
let serverArguments = [];
|
2019-02-26 15:16:54 +00:00
|
|
|
if(server.host) {
|
|
|
|
serverArguments.push('--host');
|
|
|
|
serverArguments.push(server.host);
|
|
|
|
}
|
|
|
|
if(server.port) {
|
|
|
|
serverArguments.push('--port');
|
|
|
|
serverArguments.push(server.port);
|
|
|
|
}
|
2019-03-05 08:06:15 +00:00
|
|
|
|
|
|
|
serverArguments.push('--local');
|
|
|
|
|
|
|
|
if(configPath) {
|
|
|
|
serverArguments.push('--config');
|
|
|
|
serverArguments.push(configPath);
|
|
|
|
}
|
|
|
|
|
2019-02-19 14:03:33 +00:00
|
|
|
return serverArguments;
|
2019-02-07 12:35:07 +00:00
|
|
|
}
|
2019-02-06 08:12:14 +00:00
|
|
|
|
2019-02-11 10:26:32 +00:00
|
|
|
function getChannelForServer(server) {
|
2019-02-19 14:03:33 +00:00
|
|
|
return `local-server-run-${server.name}`;
|
2019-02-11 10:26:32 +00:00
|
|
|
}
|
|
|
|
|
2019-02-12 14:11:50 +00:00
|
|
|
function notifyStatus(status) {
|
|
|
|
ipcMain.emit('local-server-status-events', status);
|
|
|
|
}
|
|
|
|
|
2019-02-20 10:32:54 +00:00
|
|
|
function filterOutput(line) {
|
|
|
|
const index = line.search('CRITICAL');
|
|
|
|
if(index > -1) {
|
|
|
|
return {
|
|
|
|
isCritical: true,
|
|
|
|
errorMessage: line.substr(index)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
isCritical: false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-07 12:35:07 +00:00
|
|
|
async function stopAll() {
|
2019-02-12 14:11:50 +00:00
|
|
|
for(var serverName in runningServers) {
|
2019-02-19 14:03:33 +00:00
|
|
|
let result, error = await stop(serverName);
|
2019-02-12 14:11:50 +00:00
|
|
|
}
|
|
|
|
console.log(`Stopped all servers`);
|
2019-02-07 12:35:07 +00:00
|
|
|
}
|
2019-02-06 08:12:14 +00:00
|
|
|
|
2019-02-07 12:35:07 +00:00
|
|
|
async function stop(serverName) {
|
2019-02-12 14:11:50 +00:00
|
|
|
let pid = undefined;
|
|
|
|
|
|
|
|
const runningServer = runningServers[serverName];
|
|
|
|
|
|
|
|
if(runningServer !== undefined && runningServer.process) {
|
|
|
|
pid = runningServer.process.pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(`Stopping '${serverName}' with PID='${pid}'`);
|
|
|
|
|
|
|
|
const stopped = new Promise((resolve, reject) => {
|
|
|
|
if(pid === undefined) {
|
|
|
|
resolve(`Server '${serverName} is already stopped`);
|
2019-02-12 14:34:18 +00:00
|
|
|
delete runningServers[serverName];
|
2019-02-12 14:11:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
kill(pid, (error) => {
|
2019-02-12 14:34:18 +00:00
|
|
|
if(error) {
|
|
|
|
console.error(`Error occured during stopping '${serverName}' with PID='${pid}'`);
|
|
|
|
reject(error);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
delete runningServers[serverName];
|
|
|
|
console.log(`Stopped '${serverName}' with PID='${pid}'`);
|
|
|
|
resolve(`Stopped '${serverName}' with PID='${pid}'`);
|
|
|
|
|
|
|
|
notifyStatus({
|
|
|
|
serverName: serverName,
|
|
|
|
status: 'stopped',
|
|
|
|
message: `Server '${serverName}' stopped'`
|
|
|
|
});
|
|
|
|
}
|
2019-02-06 08:12:14 +00:00
|
|
|
});
|
2019-02-12 14:11:50 +00:00
|
|
|
});
|
2019-02-07 15:02:25 +00:00
|
|
|
|
2019-02-12 14:11:50 +00:00
|
|
|
return stopped;
|
2019-02-07 12:35:07 +00:00
|
|
|
}
|
2019-02-06 08:12:14 +00:00
|
|
|
|
2019-03-05 08:06:15 +00:00
|
|
|
async function getIniFile(server) {
|
|
|
|
return path.join(app.getPath('userData'), `gns3_server_${server.id}.ini`);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function configure(configPath, server) {
|
|
|
|
if(!fs.existsSync(configPath)) {
|
|
|
|
fs.closeSync(fs.openSync(configPath, 'w'));
|
|
|
|
console.log(`Configuration file '${configPath}' has been created.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
var config = ini.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
|
|
|
|
|
|
if(server.path) {
|
|
|
|
config.path = server.path;
|
|
|
|
}
|
|
|
|
if(server.host) {
|
|
|
|
config.host = server.host;
|
|
|
|
}
|
|
|
|
if(server.port) {
|
|
|
|
config.port = server.port;
|
|
|
|
}
|
2019-03-05 09:43:31 +00:00
|
|
|
if(server.ubridge_path) {
|
|
|
|
config.ubridge_path = server.ubridge_path;
|
|
|
|
}
|
2019-03-05 08:06:15 +00:00
|
|
|
|
|
|
|
fs.writeFileSync(configPath, ini.stringify(config, { section: 'Server' }));
|
|
|
|
}
|
|
|
|
|
2019-03-05 11:01:24 +00:00
|
|
|
async function setPATHEnv() {
|
|
|
|
const vpcsLookup = [
|
|
|
|
path.join(__dirname, 'dist', 'vpcs'),
|
|
|
|
path.join(path.dirname(app.getPath('exe')), 'dist', 'vpcs')
|
|
|
|
];
|
|
|
|
|
|
|
|
// prevent adding duplicates
|
|
|
|
let extra = [
|
|
|
|
...vpcsLookup
|
|
|
|
].filter((dir) => {
|
|
|
|
return process.env.PATH.indexOf(dir) < 0;
|
|
|
|
});
|
|
|
|
extra.push(process.env.PATH);
|
|
|
|
process.env.PATH = extra.join(";");
|
|
|
|
}
|
|
|
|
|
2019-02-07 12:35:07 +00:00
|
|
|
async function run(server, options) {
|
2019-02-12 14:11:50 +00:00
|
|
|
if(!options) {
|
|
|
|
options = {};
|
|
|
|
}
|
2019-02-11 13:53:45 +00:00
|
|
|
|
2019-02-12 14:11:50 +00:00
|
|
|
const logStdout = options.logStdout || false;
|
|
|
|
const logSterr = options.logSterr || false;
|
2019-02-06 08:12:14 +00:00
|
|
|
|
2019-03-05 11:01:24 +00:00
|
|
|
console.log(`Configuring`);
|
2019-03-05 08:06:15 +00:00
|
|
|
|
|
|
|
const configPath = await getIniFile(server);
|
|
|
|
await configure(configPath, server);
|
|
|
|
|
2019-03-05 11:01:24 +00:00
|
|
|
console.log(`Setting up PATH`);
|
|
|
|
await setPATHEnv();
|
|
|
|
|
2019-02-12 14:11:50 +00:00
|
|
|
console.log(`Running '${server.path}'`);
|
2019-02-06 08:12:14 +00:00
|
|
|
|
2019-03-05 08:06:15 +00:00
|
|
|
let serverProcess = spawn(server.path, getServerArguments(server, {}, configPath));
|
2019-02-07 12:35:07 +00:00
|
|
|
|
2019-02-12 14:11:50 +00:00
|
|
|
notifyStatus({
|
|
|
|
serverName: server.name,
|
|
|
|
status: 'started',
|
|
|
|
message: `Server '${server.name}' started'`
|
|
|
|
});
|
|
|
|
|
|
|
|
runningServers[server.name] = {
|
|
|
|
process: serverProcess
|
|
|
|
};
|
|
|
|
|
|
|
|
serverProcess.stdout.on('data', function(data) {
|
2019-02-20 10:32:54 +00:00
|
|
|
const line = data.toString();
|
|
|
|
const { isCritical, errorMessage } = filterOutput(line);
|
|
|
|
if(isCritical) {
|
|
|
|
notifyStatus({
|
|
|
|
serverName: server.name,
|
|
|
|
status: 'stderr',
|
|
|
|
message: `Server reported error: '${errorMessage}`
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-02-12 14:11:50 +00:00
|
|
|
if(logStdout) {
|
|
|
|
console.log(data.toString());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
serverProcess.stderr.on('data', function(data) {
|
|
|
|
if(logSterr) {
|
|
|
|
console.log(data.toString());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
serverProcess.on('exit', (code, signal) => {
|
2019-02-20 10:32:54 +00:00
|
|
|
notifyStatus({
|
|
|
|
serverName: server.name,
|
|
|
|
status: 'errored',
|
|
|
|
message: `Server '${server.name}' has exited with status='${code}'`
|
|
|
|
});
|
2019-02-12 14:11:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
serverProcess.on('error', (err) => {
|
2019-02-20 10:32:54 +00:00
|
|
|
notifyStatus({
|
|
|
|
serverName: server.name,
|
|
|
|
status: 'errored',
|
2019-02-27 11:28:42 +00:00
|
|
|
message: `Server errored: '${err}`
|
2019-02-20 10:32:54 +00:00
|
|
|
});
|
2019-02-12 14:11:50 +00:00
|
|
|
});
|
|
|
|
|
2019-02-06 08:12:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function main() {
|
2019-02-19 14:03:33 +00:00
|
|
|
await run({
|
|
|
|
name: 'my-local',
|
|
|
|
path: 'c:\\Program Files\\GNS3\\gns3server.EXE',
|
|
|
|
port: 3080
|
|
|
|
}, {
|
|
|
|
logStdout: true
|
|
|
|
});
|
2019-02-06 08:12:14 +00:00
|
|
|
}
|
|
|
|
|
2019-03-05 11:01:24 +00:00
|
|
|
if(ipcMain) {
|
|
|
|
ipcMain.on('local-server-run', async function (event, server) {
|
|
|
|
const responseChannel = getChannelForServer();
|
|
|
|
await run(server);
|
|
|
|
event.sender.send(responseChannel, {
|
|
|
|
success: true
|
|
|
|
});
|
2019-02-19 14:03:33 +00:00
|
|
|
});
|
2019-03-05 11:01:24 +00:00
|
|
|
}
|
2019-02-19 14:03:33 +00:00
|
|
|
|
2019-02-06 08:12:14 +00:00
|
|
|
if (require.main === module) {
|
2019-02-19 14:03:33 +00:00
|
|
|
process.on('SIGINT', function() {
|
|
|
|
console.log("Caught interrupt signal");
|
|
|
|
stopAll();
|
|
|
|
});
|
2019-02-07 12:35:07 +00:00
|
|
|
|
2019-02-19 14:03:33 +00:00
|
|
|
process.on('unhandledRejection', (reason, promise) => {
|
2019-02-20 11:36:09 +00:00
|
|
|
console.log(`UnhandledRejection occured '${reason}'`);
|
2019-02-19 14:03:33 +00:00
|
|
|
process.exit(1);
|
|
|
|
});
|
2019-02-07 12:35:07 +00:00
|
|
|
|
2019-02-19 14:03:33 +00:00
|
|
|
main();
|
2019-02-06 08:12:14 +00:00
|
|
|
}
|