gns3-web-ui/local-server.js

321 lines
7.2 KiB
JavaScript

const { spawn } = require('child_process');
const kill = require('tree-kill');
const path = require('path');
const fs = require('fs');
const ini = require('ini');
const { ipcMain } = require('electron')
const { app } = require('electron')
const isWin = /^win/.test(process.platform);
let runningServers = {};
exports.getLocalServerPath = async () => {
let binary = isWin ? 'gns3server.exe': 'gns3server';
return findBinary('exe.', binary);
}
exports.getUbridgePath = async () => {
let binary = isWin ? 'ubridge.exe': 'ubridge';
return findBinary('ubridge', binary);
}
exports.startLocalServer = async (server) => {
return await run(server, {
logStdout: true
});
}
exports.stopLocalServer = async (server) => {
return await stop(server.name);
}
exports.getRunningServers = () => {
return Object.keys(runningServers);
}
exports.stopAllLocalServers = async () => {
return await stopAll();
}
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) {
const distDirectory = path.join(baseDirectory, 'dist');
if (!fs.existsSync(distDirectory)) {
return;
}
const files = fs.readdirSync(distDirectory);
let binaryPath = null;
files.forEach((directory) => {
if(directory.startsWith(binaryDirectory)) {
binaryPath = path.join(baseDirectory, 'dist', directory, filename);
}
});
if(binaryPath !== null && fs.existsSync(binaryPath)) {
return binaryPath;
}
return;
}
function getServerArguments(server, overrides, configPath) {
let serverArguments = [];
if(server.host) {
serverArguments.push('--host');
serverArguments.push(server.host);
}
if(server.port) {
serverArguments.push('--port');
serverArguments.push(server.port);
}
serverArguments.push('--local');
if(configPath) {
serverArguments.push('--config');
serverArguments.push(configPath);
}
return serverArguments;
}
function getChannelForServer(server) {
return `local-server-run-${server.name}`;
}
function notifyStatus(status) {
ipcMain.emit('local-server-status-events', status);
}
function filterOutput(line) {
const index = line.search('CRITICAL');
if(index > -1) {
return {
isCritical: true,
errorMessage: line.substr(index)
};
}
return {
isCritical: false
}
}
async function stopAll() {
for(var serverName in runningServers) {
let result, error = await stop(serverName);
}
console.log(`Stopped all servers`);
}
async function stop(serverName) {
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`);
delete runningServers[serverName];
return;
}
kill(pid, (error) => {
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'`
});
}
});
});
return stopped;
}
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;
}
if(server.ubridge_path) {
config.ubridge_path = server.ubridge_path;
}
fs.writeFileSync(configPath, ini.stringify(config, { section: 'Server' }));
}
async function setPATHEnv() {
const vpcsLookup = [
path.join(__dirname, 'dist', 'vpcs'),
path.join(path.dirname(app.getPath('exe')), 'dist', 'vpcs')
];
const dynamipsLookup = [
path.join(__dirname, 'dist', 'dynamips'),
path.join(path.dirname(app.getPath('exe')), 'dist', 'dynamips')
];
// prevent adding duplicates
let extra = [
...vpcsLookup,
...dynamipsLookup
].filter((dir) => {
return process.env.PATH.indexOf(dir) < 0;
});
extra.push(process.env.PATH);
process.env.PATH = extra.join(";");
}
async function run(server, options) {
if(!options) {
options = {};
}
const logStdout = options.logStdout || false;
const logSterr = options.logSterr || false;
console.log(`Configuring`);
const configPath = await getIniFile(server);
await configure(configPath, server);
console.log(`Setting up PATH`);
await setPATHEnv();
console.log(`Running '${server.path}'`);
let serverProcess = spawn(server.path, getServerArguments(server, {}, configPath));
notifyStatus({
serverName: server.name,
status: 'started',
message: `Server '${server.name}' started'`
});
runningServers[server.name] = {
process: serverProcess
};
serverProcess.stdout.on('data', function(data) {
const line = data.toString();
const { isCritical, errorMessage } = filterOutput(line);
if(isCritical) {
notifyStatus({
serverName: server.name,
status: 'stderr',
message: `Server reported error: '${errorMessage}`
});
}
if(logStdout) {
console.log(data.toString());
}
});
serverProcess.stderr.on('data', function(data) {
if(logSterr) {
console.log(data.toString());
}
});
serverProcess.on('exit', (code, signal) => {
notifyStatus({
serverName: server.name,
status: 'errored',
message: `Server '${server.name}' has exited with status='${code}'`
});
});
serverProcess.on('error', (err) => {
notifyStatus({
serverName: server.name,
status: 'errored',
message: `Server errored: '${err}`
});
});
}
async function main() {
await run({
name: 'my-local',
path: 'c:\\Program Files\\GNS3\\gns3server.EXE',
port: 3080
}, {
logStdout: true
});
}
if(ipcMain) {
ipcMain.on('local-server-run', async function (event, server) {
const responseChannel = getChannelForServer();
await run(server);
event.sender.send(responseChannel, {
success: true
});
});
}
if (require.main === module) {
process.on('SIGINT', function() {
console.log("Caught interrupt signal");
stopAll();
});
process.on('unhandledRejection', (reason, promise) => {
console.log(`UnhandledRejection occured '${reason}'`);
process.exit(1);
});
main();
}