cvmts: Use npm versions of superqemu/nodejs-rfb.

We publish them now, so let's use them in cvmts!

Additionally, this removes the 'shared' module entirely, since it has little purpose anymore. The logger is replaced with pino (because superqemu uses pino for logging itself).
This commit is contained in:
modeco80
2024-07-16 08:29:52 -04:00
parent cf9f11819e
commit 432e75d42a
32 changed files with 469 additions and 1131 deletions

View File

@@ -11,12 +11,14 @@
"author": "Elijah R, modeco80",
"license": "GPL-3.0",
"dependencies": {
"@computernewb/nodejs-rfb": "^0.3.0",
"@computernewb/superqemu": "^0.1.0",
"@cvmts/cvm-rs": "*",
"@cvmts/qemu": "*",
"@maxmind/geoip2-node": "^5.0.0",
"execa": "^8.0.1",
"mnemonist": "^0.39.5",
"msgpackr": "^1.10.2",
"pino": "^9.3.1",
"sharp": "^0.33.3",
"toml": "^3.0.0",
"ws": "^8.14.1"
@@ -24,6 +26,7 @@
"devDependencies": {
"@types/node": "^20.12.5",
"@types/ws": "^8.5.5",
"pino-pretty": "^11.2.1",
"prettier": "^3.2.5",
"typescript": "^5.4.4"
}

View File

@@ -1,12 +1,9 @@
import { Logger } from '@cvmts/shared';
import { Rank, User } from './User.js';
export default class AuthManager {
apiEndpoint: string;
secretKey: string;
private logger = new Logger('CVMTS.AuthMan');
constructor(apiEndpoint: string, secretKey: string) {
this.apiEndpoint = apiEndpoint;
this.secretKey = secretKey;

View File

@@ -6,18 +6,20 @@ import * as cvm from '@cvmts/cvm-rs';
import CircularBuffer from 'mnemonist/circular-buffer.js';
import Queue from 'mnemonist/queue.js';
import { createHash } from 'crypto';
import { VMState, QemuVM, QemuVmDefinition } from '@cvmts/qemu';
import { VMState, QemuVM, QemuVmDefinition } from '@computernewb/superqemu';
import { IPDataManager } from './IPData.js';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import AuthManager from './AuthManager.js';
import { Size, Rect, Logger } from '@cvmts/shared';
import { JPEGEncoder } from './JPEGEncoder.js';
import VM from './VM.js';
import { ReaderModel } from '@maxmind/geoip2-node';
import * as msgpack from 'msgpackr';
import { CollabVMProtocolMessage, CollabVMProtocolMessageType } from '@cvmts/collab-vm-1.2-binary-protocol';
import { Size, Rect } from './VMDisplay.js';
import pino from 'pino';
// Instead of strange hacks we can just use nodejs provided
// import.meta properties, which have existed since LTS if not before
const __dirname = import.meta.dirname;
@@ -36,6 +38,7 @@ type VoteTally = {
no: number;
};
export default class CollabVMServer {
private Config: IConfig;
@@ -87,7 +90,7 @@ export default class CollabVMServer {
// Geoip
private geoipReader: ReaderModel | null;
private logger = new Logger('CVMTS.Server');
private logger = pino({ name: 'CVMTS.Server' });
constructor(config: IConfig, vm: VM, auth: AuthManager | null, geoipReader: ReaderModel | null) {
this.Config = config;
@@ -121,7 +124,7 @@ export default class CollabVMServer {
if (config.vm.type == 'qemu') {
(vm as QemuVM).on('statechange', (newState: VMState) => {
if (newState == VMState.Started) {
self.logger.Info('VM started');
self.logger.info('VM started');
// well aware this sucks but whatever
self.VM.GetDisplay().on('resize', (size: Size) => self.OnDisplayResized(size));
self.VM.GetDisplay().on('rect', (rect: Rect) => self.OnDisplayRectangle(rect));
@@ -129,7 +132,7 @@ export default class CollabVMServer {
if (newState == VMState.Stopped) {
setTimeout(async () => {
self.logger.Info('restarting VM');
self.logger.info('restarting VM');
await self.VM.Start();
}, kRestartTimeout);
}
@@ -154,7 +157,7 @@ export default class CollabVMServer {
try {
user.countryCode = this.geoipReader!.country(user.IP.address).country!.isoCode;
} catch (error) {
this.logger.Warning(`Failed to get country code for ${user.IP.address}: ${(error as Error).message}`);
this.logger.warn(`Failed to get country code for ${user.IP.address}: ${(error as Error).message}`);
}
}
user.socket.on('msg', (msg: string) => this.onMessage(user, msg));
@@ -179,7 +182,7 @@ export default class CollabVMServer {
this.clients.splice(clientIndex, 1);
this.logger.Info(`Disconnect From ${user.IP.address}${user.username ? ` with username ${user.username}` : ''}`);
this.logger.info(`Disconnect From ${user.IP.address}${user.username ? ` with username ${user.username}` : ''}`);
if (!user.username) return;
if (this.TurnQueue.toArray().indexOf(user) !== -1) {
var hadturn = this.TurnQueue.peek() === user;
@@ -204,7 +207,7 @@ export default class CollabVMServer {
try {
let res = await this.auth!.Authenticate(msgArr[1], client);
if (res.clientSuccess) {
this.logger.Info(`${client.IP.address} logged in as ${res.username}`);
this.logger.info(`${client.IP.address} logged in as ${res.username}`);
client.sendMsg(cvm.guacEncode('login', '1'));
let old = this.clients.find((c) => c.username === res.username);
if (old) {
@@ -236,7 +239,7 @@ export default class CollabVMServer {
}
}
} catch (err) {
this.logger.Error(`Error authenticating client ${client.IP.address}: ${(err as Error).message}`);
this.logger.error(`Error authenticating client ${client.IP.address}: ${(err as Error).message}`);
// for now?
client.sendMsg(cvm.guacEncode('login', '0', 'There was an internal error while authenticating. Please let a staff member know as soon as possible'));
}
@@ -679,7 +682,7 @@ export default class CollabVMServer {
}
} catch (err) {
// No
this.logger.Error(`User ${user?.IP.address} ${user?.username ? `with username ${user?.username}` : ''} sent broken Guacamole: ${err as Error}`);
this.logger.error(`User ${user?.IP.address} ${user?.username ? `with username ${user?.username}` : ''} sent broken Guacamole: ${err as Error}`);
user?.kick();
}
}
@@ -721,10 +724,10 @@ export default class CollabVMServer {
client.sendMsg(cvm.guacEncode('rename', '0', status, client.username!, client.rank.toString()));
if (hadName) {
this.logger.Info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`);
this.logger.info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`);
if (announce) this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('rename', '1', oldname, client.username!, client.rank.toString())));
} else {
this.logger.Info(`Rename ${client.IP.address} to ${client.username}`);
this.logger.info(`Rename ${client.IP.address} to ${client.username}`);
if (announce)
this.clients.forEach((c) => {
c.sendMsg(cvm.guacEncode('adduser', '1', client.username!, client.rank.toString()));

View File

@@ -1,4 +1,3 @@
import { Logger } from '@cvmts/shared';
import { Reader, ReaderModel } from '@maxmind/geoip2-node';
import * as fs from 'fs/promises';
import * as path from 'node:path';
@@ -6,102 +5,101 @@ import { Readable } from 'node:stream';
import { ReadableStream } from 'node:stream/web';
import { finished } from 'node:stream/promises';
import { execa } from 'execa';
import pino from 'pino';
export default class GeoIPDownloader {
private directory: string;
private accountID: string;
private licenseKey: string;
private logger: Logger
constructor(filename: string, accountID: string, licenseKey: string) {
this.directory = filename;
if (!this.directory.endsWith('/')) this.directory += '/';
this.accountID = accountID;
this.licenseKey = licenseKey;
this.logger = new Logger('CVMTS.GeoIPDownloader');
}
private directory: string;
private accountID: string;
private licenseKey: string;
private logger = pino({ name: 'CVMTS.GeoIPDownloader' });
constructor(filename: string, accountID: string, licenseKey: string) {
this.directory = filename;
if (!this.directory.endsWith('/')) this.directory += '/';
this.accountID = accountID;
this.licenseKey = licenseKey;
}
private genAuthHeader(): string {
return `Basic ${Buffer.from(`${this.accountID}:${this.licenseKey}`).toString('base64')}`;
}
private genAuthHeader(): string {
return `Basic ${Buffer.from(`${this.accountID}:${this.licenseKey}`).toString('base64')}`;
}
private async ensureDirectoryExists(): Promise<void> {
let stat;
try {
stat = await fs.stat(this.directory);
}
catch (e) {
var error = e as NodeJS.ErrnoException;
if (error.code === 'ENOTDIR') {
this.logger.Warning('File exists at GeoIP directory path, unlinking...');
await fs.unlink(this.directory.substring(0, this.directory.length - 1));
} else if (error.code !== 'ENOENT') {
this.logger.Error('Failed to access GeoIP directory: {0}', error.message);
process.exit(1);
}
this.logger.Info('Creating GeoIP directory: {0}', this.directory);
await fs.mkdir(this.directory, { recursive: true });
return;
}
}
private async ensureDirectoryExists(): Promise<void> {
let stat;
try {
stat = await fs.stat(this.directory);
} catch (e) {
var error = e as NodeJS.ErrnoException;
if (error.code === 'ENOTDIR') {
this.logger.warn('File exists at GeoIP directory path, unlinking...');
await fs.unlink(this.directory.substring(0, this.directory.length - 1));
} else if (error.code !== 'ENOENT') {
this.logger.error('Failed to access GeoIP directory: {0}', error.message);
process.exit(1);
}
this.logger.info('Creating GeoIP directory: {0}', this.directory);
await fs.mkdir(this.directory, { recursive: true });
return;
}
}
async getGeoIPReader(): Promise<ReaderModel> {
await this.ensureDirectoryExists();
let dbpath = path.join(this.directory, (await this.getLatestVersion()).replace('.tar.gz', ''), 'GeoLite2-Country.mmdb');
try {
await fs.access(dbpath, fs.constants.F_OK | fs.constants.R_OK);
this.logger.Info('Loading cached GeoIP database: {0}', dbpath);
} catch (ex) {
var error = ex as NodeJS.ErrnoException;
if (error.code === 'ENOENT') {
await this.downloadLatestDatabase();
} else {
this.logger.Error('Failed to access GeoIP database: {0}', error.message);
process.exit(1);
}
}
return await Reader.open(dbpath);
}
async getGeoIPReader(): Promise<ReaderModel> {
await this.ensureDirectoryExists();
let dbpath = path.join(this.directory, (await this.getLatestVersion()).replace('.tar.gz', ''), 'GeoLite2-Country.mmdb');
try {
await fs.access(dbpath, fs.constants.F_OK | fs.constants.R_OK);
this.logger.info('Loading cached GeoIP database: {0}', dbpath);
} catch (ex) {
var error = ex as NodeJS.ErrnoException;
if (error.code === 'ENOENT') {
await this.downloadLatestDatabase();
} else {
this.logger.error('Failed to access GeoIP database: {0}', error.message);
process.exit(1);
}
}
return await Reader.open(dbpath);
}
async getLatestVersion(): Promise<string> {
let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', {
redirect: 'follow',
method: "HEAD",
headers: {
"Authorization": this.genAuthHeader()
}
});
let disposition = res.headers.get('Content-Disposition');
if (!disposition) {
this.logger.Error('Failed to get latest version of GeoIP database: No Content-Disposition header');
process.exit(1);
}
let filename = disposition.match(/filename=(.*)$/);
if (!filename) {
this.logger.Error('Failed to get latest version of GeoIP database: Could not parse version from Content-Disposition header');
process.exit(1);
}
return filename[1];
}
async getLatestVersion(): Promise<string> {
let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', {
redirect: 'follow',
method: 'HEAD',
headers: {
Authorization: this.genAuthHeader()
}
});
let disposition = res.headers.get('Content-Disposition');
if (!disposition) {
this.logger.error('Failed to get latest version of GeoIP database: No Content-Disposition header');
process.exit(1);
}
let filename = disposition.match(/filename=(.*)$/);
if (!filename) {
this.logger.error('Failed to get latest version of GeoIP database: Could not parse version from Content-Disposition header');
process.exit(1);
}
return filename[1];
}
async downloadLatestDatabase(): Promise<void> {
let filename = await this.getLatestVersion();
this.logger.Info('Downloading latest GeoIP database: {0}', filename);
let dbpath = path.join(this.directory, filename);
let file = await fs.open(dbpath, fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY);
let stream = file.createWriteStream();
let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', {
redirect: 'follow',
headers: {
"Authorization": this.genAuthHeader()
}
});
await finished(Readable.fromWeb(res.body as ReadableStream<any>).pipe(stream));
await file.close();
this.logger.Info('Finished downloading latest GeoIP database: {0}', filename);
this.logger.Info('Extracting GeoIP database: {0}', filename);
// yeah whatever
await execa('tar', ['xzf', filename], {cwd: this.directory});
this.logger.Info('Unlinking GeoIP tarball');
await fs.unlink(dbpath);
}
}
async downloadLatestDatabase(): Promise<void> {
let filename = await this.getLatestVersion();
this.logger.info('Downloading latest GeoIP database: {0}', filename);
let dbpath = path.join(this.directory, filename);
let file = await fs.open(dbpath, fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY);
let stream = file.createWriteStream();
let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', {
redirect: 'follow',
headers: {
Authorization: this.genAuthHeader()
}
});
await finished(Readable.fromWeb(res.body as ReadableStream<any>).pipe(stream));
await file.close();
this.logger.info('Finished downloading latest GeoIP database: {0}', filename);
this.logger.info('Extracting GeoIP database: {0}', filename);
// yeah whatever
await execa('tar', ['xzf', filename], { cwd: this.directory });
this.logger.info('Unlinking GeoIP tarball');
await fs.unlink(dbpath);
}
}

View File

@@ -1,4 +1,4 @@
import { Logger } from '@cvmts/shared';
import pino from 'pino';
export class IPData {
tempMuteExpireTimeout?: NodeJS.Timeout;
@@ -22,7 +22,7 @@ export class IPData {
export class IPDataManager {
static ipDatas = new Map<string, IPData>();
static logger = new Logger('CVMTS.IPDataManager');
static logger = pino({ name: 'CVMTS.IPDataManager' });
static GetIPData(address: string) {
if (IPDataManager.ipDatas.has(address)) {
@@ -64,7 +64,7 @@ export class IPDataManager {
setInterval(() => {
for (let tuple of IPDataManager.ipDatas) {
if (tuple[1].refCount == 0) {
IPDataManager.logger.Info('Deleted IPData for IP {0}', tuple[0]);
IPDataManager.logger.info(`Deleted IPData for IP ${tuple[0]}`);
IPDataManager.ipDatas.delete(tuple[0]);
}
}

View File

@@ -1,4 +1,4 @@
import { Size, Rect } from '@cvmts/shared';
import { Size, Rect } from './VMDisplay.js';
import sharp from 'sharp';
import * as cvm from '@cvmts/cvm-rs';

View File

@@ -2,20 +2,19 @@ import EventEmitter from 'events';
import NetworkServer from '../NetworkServer.js';
import { Server, Socket } from 'net';
import IConfig from '../IConfig.js';
import { Logger } from '@cvmts/shared';
import TCPClient from './TCPClient.js';
import { IPDataManager } from '../IPData.js';
import { User } from '../User.js';
import pino from 'pino';
export default class TCPServer extends EventEmitter implements NetworkServer {
listener: Server;
Config: IConfig;
logger: Logger;
logger = pino({name: 'CVMTS.TCPServer'});
clients: TCPClient[];
constructor(config: IConfig) {
super();
this.logger = new Logger('CVMTS.TCPServer');
this.Config = config;
this.listener = new Server();
this.clients = [];
@@ -23,7 +22,7 @@ export default class TCPServer extends EventEmitter implements NetworkServer {
}
private onConnection(socket: Socket) {
this.logger.Info(`New TCP connection from ${socket.remoteAddress}`);
this.logger.info(`New TCP connection from ${socket.remoteAddress}`);
var client = new TCPClient(socket);
this.clients.push(client);
this.emit('connect', new User(client, IPDataManager.GetIPData(client.getIP()), this.Config));
@@ -31,7 +30,7 @@ export default class TCPServer extends EventEmitter implements NetworkServer {
start(): void {
this.listener.listen(this.Config.tcp.port, this.Config.tcp.host, () => {
this.logger.Info(`TCP server listening on ${this.Config.tcp.host}:${this.Config.tcp.port}`);
this.logger.info(`TCP server listening on ${this.Config.tcp.host}:${this.Config.tcp.port}`);
});
}
stop(): void {

View File

@@ -4,9 +4,9 @@ import { IPData } from './IPData.js';
import IConfig from './IConfig.js';
import RateLimiter from './RateLimiter.js';
import { execa, execaCommand, ExecaSyncError } from 'execa';
import { Logger } from '@cvmts/shared';
import NetworkClient from './NetworkClient.js';
import { CollabVMCapabilities } from '@cvmts/collab-vm-1.2-binary-protocol';
import pino from 'pino';
export class User {
socket: NetworkClient;
@@ -31,7 +31,7 @@ export class User {
TurnRateLimit: RateLimiter;
VoteRateLimit: RateLimiter;
private logger = new Logger('CVMTS.User');
private logger = pino({ name: 'CVMTS.User' });
constructor(socket: NetworkClient, ip: IPData, config: IConfig, username?: string, node?: string) {
this.IP = ip;
@@ -148,7 +148,7 @@ export class User {
await execa(args.shift()!, args, { stdout: process.stdout, stderr: process.stderr });
this.kick();
} else {
this.logger.Error(`Failed to ban ${this.IP.address} (${this.username}): Empty command`);
this.logger.error(`Failed to ban ${this.IP.address} (${this.username}): Empty command`);
}
} else if (typeof this.Config.collabvm.bancmd == 'string') {
let cmd: string = this.banCmdArgs(this.Config.collabvm.bancmd);
@@ -156,11 +156,11 @@ export class User {
await execaCommand(cmd, { stdout: process.stdout, stderr: process.stderr });
this.kick();
} else {
this.logger.Error(`Failed to ban ${this.IP.address} (${this.username}): Empty command`);
this.logger.error(`Failed to ban ${this.IP.address} (${this.username}): Empty command`);
}
}
} catch (e) {
this.logger.Error(`Failed to ban ${this.IP.address} (${this.username}): ${(e as ExecaSyncError).shortMessage}`);
this.logger.error(`Failed to ban ${this.IP.address} (${this.username}): ${(e as ExecaSyncError).shortMessage}`);
}
}

View File

@@ -1,5 +1,5 @@
import { VMState } from '@cvmts/qemu';
import VMDisplay from './VMDisplay.js';
import { VMState } from '@computernewb/superqemu';
import { VMDisplay } from './VMDisplay.js';
export default interface VM {
Start(): Promise<void>;

View File

@@ -1,7 +1,20 @@
import { Size } from '@cvmts/shared';
import EventEmitter from 'node:events';
export default interface VMDisplay extends EventEmitter {
// not great but whatever
// nodejs-rfb COULD probably export them though.
export type Size = {
width: number;
height: number;
};
export type Rect = {
x: number;
y: number;
width: number;
height: number;
};
export interface VMDisplay extends EventEmitter {
Connect(): void;
Disconnect(): void;
Connected(): boolean;

View File

@@ -1,15 +1,23 @@
import EventEmitter from 'events';
import VNCVMDef from './VNCVMDef';
import VM from '../VM';
import VMDisplay from '../VMDisplay';
import { Clamp, Logger, Rect, Size, Sleep } from '@cvmts/shared';
import { Size, Rect, VMDisplay } from '../VMDisplay';
import { VncClient } from '@computernewb/nodejs-rfb';
import { BatchRects, VMState } from '@cvmts/qemu';
import { BatchRects, VMState } from '@computernewb/superqemu';
import { execaCommand } from 'execa';
import pino from 'pino';
function Clamp(input: number, min: number, max: number) {
return Math.min(Math.max(input, min), max);
}
async function Sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export default class VNCVM extends EventEmitter implements VM, VMDisplay {
def: VNCVMDef;
logger: Logger;
logger;
private displayVnc = new VncClient({
debug: false,
fps: 60,
@@ -20,7 +28,8 @@ export default class VNCVM extends EventEmitter implements VM, VMDisplay {
constructor(def: VNCVMDef) {
super();
this.def = def;
this.logger = new Logger(`CVMTS.VNCVM/${this.def.vncHost}:${this.def.vncPort}`);
// TODO: Now that we're using an actual structured logger can we please
this.logger = pino({ name: `CVMTS.VNCVM/${this.def.vncHost}:${this.def.vncPort}` });
this.displayVnc.on('connectTimeout', () => {
this.Reconnect();
@@ -31,7 +40,7 @@ export default class VNCVM extends EventEmitter implements VM, VMDisplay {
});
this.displayVnc.on('disconnect', () => {
this.logger.Info('Disconnected');
this.logger.info('Disconnected');
this.Reconnect();
});
@@ -40,7 +49,7 @@ export default class VNCVM extends EventEmitter implements VM, VMDisplay {
});
this.displayVnc.on('firstFrameUpdate', () => {
this.logger.Info('Connected');
this.logger.info('Connected');
// apparently this library is this good.
// at least it's better than the two others which exist.
this.displayVnc.changeFps(60);
@@ -101,13 +110,13 @@ export default class VNCVM extends EventEmitter implements VM, VMDisplay {
}
async Start(): Promise<void> {
this.logger.Info('Connecting');
this.logger.info('Connecting');
if (this.def.startCmd) await execaCommand(this.def.startCmd, { shell: true });
this.Connect();
}
async Stop(): Promise<void> {
this.logger.Info('Disconnecting');
this.logger.info('Disconnecting');
this.Disconnect();
if (this.def.stopCmd) await execaCommand(this.def.stopCmd, { shell: true });
}

View File

@@ -1,7 +1,6 @@
import { WebSocket } from 'ws';
import NetworkClient from '../NetworkClient.js';
import EventEmitter from 'events';
import { Logger } from '@cvmts/shared';
export default class WSClient extends EventEmitter implements NetworkClient {
socket: WebSocket;

View File

@@ -8,20 +8,19 @@ import { isIP } from 'net';
import { IPDataManager } from '../IPData.js';
import WSClient from './WSClient.js';
import { User } from '../User.js';
import { Logger } from '@cvmts/shared';
import pino from 'pino';
export default class WSServer extends EventEmitter implements NetworkServer {
private httpServer: http.Server;
private wsServer: WebSocketServer;
private clients: WSClient[];
private Config: IConfig;
private logger: Logger;
private logger = pino({ name: 'CVMTS.WSServer' });
constructor(config: IConfig) {
super();
this.Config = config;
this.clients = [];
this.logger = new Logger('CVMTS.WSServer');
this.httpServer = http.createServer();
this.wsServer = new WebSocketServer({ noServer: true });
this.httpServer.on('upgrade', (req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) => this.httpOnUpgrade(req, socket, head));
@@ -34,7 +33,7 @@ export default class WSServer extends EventEmitter implements NetworkServer {
start(): void {
this.httpServer.listen(this.Config.http.port, this.Config.http.host, () => {
this.logger.Info(`WebSocket server listening on ${this.Config.http.host}:${this.Config.http.port}`);
this.logger.info(`WebSocket server listening on ${this.Config.http.host}:${this.Config.http.port}`);
});
}
@@ -94,7 +93,7 @@ export default class WSServer extends EventEmitter implements NetworkServer {
// Make sure x-forwarded-for is set
if (req.headers['x-forwarded-for'] === undefined) {
killConnection();
this.logger.Error('X-Forwarded-For header not set. This is most likely a misconfiguration of your reverse proxy.');
this.logger.error('X-Forwarded-For header not set. This is most likely a misconfiguration of your reverse proxy.');
return;
}
try {
@@ -102,7 +101,7 @@ export default class WSServer extends EventEmitter implements NetworkServer {
ip = req.headers['x-forwarded-for']?.toString().replace(/\ /g, '').split(',')[0];
} catch {
// If we can't get the IP, kill the connection
this.logger.Error('Invalid X-Forwarded-For header. This is most likely a misconfiguration of your reverse proxy.');
this.logger.error('Invalid X-Forwarded-For header. This is most likely a misconfiguration of your reverse proxy.');
killConnection();
return;
}
@@ -135,10 +134,10 @@ export default class WSServer extends EventEmitter implements NetworkServer {
this.emit('connect', user);
ws.on('error', (e) => {
this.logger.Error(`${e} (caused by connection ${ip})`);
this.logger.error(`${e} (caused by connection ${ip})`);
ws.close();
});
this.logger.Info(`New WebSocket connection from ${user.IP.address}`);
this.logger.info(`New WebSocket connection from ${user.IP.address}`);
}
}

View File

@@ -3,9 +3,8 @@ import IConfig from './IConfig.js';
import * as fs from 'fs';
import CollabVMServer from './CollabVMServer.js';
import { QemuVM, QemuVmDefinition } from '@cvmts/qemu';
import { QemuVM, QemuVmDefinition } from '@computernewb/superqemu';
import * as Shared from '@cvmts/shared';
import AuthManager from './AuthManager.js';
import WSServer from './WebSocket/WSServer.js';
import { User } from './User.js';
@@ -13,24 +12,25 @@ import TCPServer from './TCP/TCPServer.js';
import VM from './VM.js';
import VNCVM from './VNCVM/VNCVM.js';
import GeoIPDownloader from './GeoIPDownloader.js';
import pino from 'pino';
let logger = new Shared.Logger('CVMTS.Init');
let logger = pino();
logger.Info('CollabVM Server starting up');
logger.info('CollabVM Server starting up');
// Parse the config file
let Config: IConfig;
if (!fs.existsSync('config.toml')) {
logger.Error('Fatal error: Config.toml not found. Please copy config.example.toml and fill out fields');
logger.error('Fatal error: Config.toml not found. Please copy config.example.toml and fill out fields');
process.exit(1);
}
try {
var configRaw = fs.readFileSync('config.toml').toString();
Config = toml.parse(configRaw);
} catch (e) {
logger.Error('Fatal error: Failed to read or parse the config file: {0}', (e as Error).message);
logger.error('Fatal error: Failed to read or parse the config file: {0}', (e as Error).message);
process.exit(1);
}
@@ -54,15 +54,6 @@ async function start() {
let auth = Config.auth.enabled ? new AuthManager(Config.auth.apiEndpoint, Config.auth.secretKey) : null;
switch (Config.vm.type) {
case 'qemu': {
// Print a warning if qmpSockDir is set
// and the host OS is Windows, as this
// configuration will very likely not work.
if (process.platform === 'win32' && Config.qemu.qmpSockDir !== null) {
logger.Warning("You appear to have the option 'qmpSockDir' enabled in the config.");
logger.Warning('This is not supported on Windows, and you will likely run into issues.');
logger.Warning('To remove this warning, use the qmpHost and qmpPort options instead.');
}
// Fire up the VM
let def: QemuVmDefinition = {
id: Config.collabvm.node,
@@ -78,7 +69,7 @@ async function start() {
break;
}
default: {
logger.Error('Invalid VM type in config: {0}', Config.vm.type);
logger.error(`Invalid VM type in config: ${Config.vm.type}`);
process.exit(1);
return;
}