add VNCVM
This commit is contained in:
@@ -11,6 +11,11 @@ origin = false
|
|||||||
# Origins to accept connections from.
|
# Origins to accept connections from.
|
||||||
originAllowedDomains = ["computernewb.com"]
|
originAllowedDomains = ["computernewb.com"]
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
enabled = false
|
||||||
|
host = "0.0.0.0"
|
||||||
|
port = 6014
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
enabled = false
|
enabled = false
|
||||||
apiEndpoint = ""
|
apiEndpoint = ""
|
||||||
@@ -24,6 +29,9 @@ vote = true
|
|||||||
|
|
||||||
|
|
||||||
[vm]
|
[vm]
|
||||||
|
type = "qemu"
|
||||||
|
|
||||||
|
[qemu]
|
||||||
qemuArgs = "qemu-system-x86_64"
|
qemuArgs = "qemu-system-x86_64"
|
||||||
vncPort = 5900
|
vncPort = 5900
|
||||||
snapshots = true
|
snapshots = true
|
||||||
@@ -33,6 +41,14 @@ snapshots = true
|
|||||||
# Comment out qmpSockDir if you're using Windows.
|
# Comment out qmpSockDir if you're using Windows.
|
||||||
qmpSockDir = "/tmp/"
|
qmpSockDir = "/tmp/"
|
||||||
|
|
||||||
|
[vncvm]
|
||||||
|
vncHost = "127.0.0.1"
|
||||||
|
vncPort = 5900
|
||||||
|
# startCmd = ""
|
||||||
|
# stopCmd = ""
|
||||||
|
# rebootCmd = ""
|
||||||
|
# restoreCmd = ""
|
||||||
|
|
||||||
[collabvm]
|
[collabvm]
|
||||||
node = "acoolvm"
|
node = "acoolvm"
|
||||||
displayname = "A <b>Really</b> Cool CollabVM Instance"
|
displayname = "A <b>Really</b> Cool CollabVM Instance"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { WebSocketServer, WebSocket } from 'ws';
|
|
||||||
import * as http from 'http';
|
|
||||||
import IConfig from './IConfig.js';
|
import IConfig from './IConfig.js';
|
||||||
import * as Utilities from './Utilities.js';
|
import * as Utilities from './Utilities.js';
|
||||||
import { User, Rank } from './User.js';
|
import { User, Rank } from './User.js';
|
||||||
@@ -8,19 +6,17 @@ import * as guacutils from './guacutils.js';
|
|||||||
import CircularBuffer from 'mnemonist/circular-buffer.js';
|
import CircularBuffer from 'mnemonist/circular-buffer.js';
|
||||||
import Queue from 'mnemonist/queue.js';
|
import Queue from 'mnemonist/queue.js';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { isIP } from 'node:net';
|
|
||||||
import { QemuVM, QemuVmDefinition } from '@cvmts/qemu';
|
import { QemuVM, QemuVmDefinition } from '@cvmts/qemu';
|
||||||
import { IPData, IPDataManager } from './IPData.js';
|
import { IPDataManager } from './IPData.js';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import AuthManager from './AuthManager.js';
|
import AuthManager from './AuthManager.js';
|
||||||
import { Size, Rect, Logger } from '@cvmts/shared';
|
import { Size, Rect, Logger } from '@cvmts/shared';
|
||||||
|
|
||||||
import { JPEGEncoder } from './JPEGEncoder.js';
|
import { JPEGEncoder } from './JPEGEncoder.js';
|
||||||
|
import VM from './VM.js';
|
||||||
|
|
||||||
// Instead of strange hacks we can just use nodejs provided
|
// Instead of strange hacks we can just use nodejs provided
|
||||||
// import.meta properties, which have existed since LTS if not before
|
// import.meta properties, which have existed since LTS if not before
|
||||||
const __filename = import.meta.filename;
|
|
||||||
const __dirname = import.meta.dirname;
|
const __dirname = import.meta.dirname;
|
||||||
|
|
||||||
const kCVMTSAssetsRoot = path.resolve(__dirname, '../../assets');
|
const kCVMTSAssetsRoot = path.resolve(__dirname, '../../assets');
|
||||||
@@ -78,14 +74,14 @@ export default class CollabVMServer {
|
|||||||
// Indefinite turn
|
// Indefinite turn
|
||||||
private indefiniteTurn: User | null;
|
private indefiniteTurn: User | null;
|
||||||
private ModPerms: number;
|
private ModPerms: number;
|
||||||
private VM: QemuVM;
|
private VM: VM;
|
||||||
|
|
||||||
// Authentication manager
|
// Authentication manager
|
||||||
private auth: AuthManager | null;
|
private auth: AuthManager | null;
|
||||||
|
|
||||||
private logger = new Logger('CVMTS.Server');
|
private logger = new Logger('CVMTS.Server');
|
||||||
|
|
||||||
constructor(config: IConfig, vm: QemuVM, auth: AuthManager | null) {
|
constructor(config: IConfig, vm: VM, auth: AuthManager | null) {
|
||||||
this.Config = config;
|
this.Config = config;
|
||||||
this.ChatHistory = new CircularBuffer<ChatHistory>(Array, this.Config.collabvm.maxChatHistoryLength);
|
this.ChatHistory = new CircularBuffer<ChatHistory>(Array, this.Config.collabvm.maxChatHistoryLength);
|
||||||
this.TurnQueue = new Queue<User>();
|
this.TurnQueue = new Queue<User>();
|
||||||
@@ -214,7 +210,7 @@ export default class CollabVMServer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.connectedToNode = true;
|
client.connectedToNode = true;
|
||||||
client.sendMsg(guacutils.encode('connect', '1', '1', this.Config.vm.snapshots ? '1' : '0', '0'));
|
client.sendMsg(guacutils.encode('connect', '1', '1', this.VM.SnapshotsSupported() ? '1' : '0', '0'));
|
||||||
if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg());
|
if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg());
|
||||||
if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode('chat', '', this.Config.collabvm.motd));
|
if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode('chat', '', this.Config.collabvm.motd));
|
||||||
if (this.screenHidden) {
|
if (this.screenHidden) {
|
||||||
@@ -247,7 +243,7 @@ export default class CollabVMServer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.sendMsg(guacutils.encode('connect', '1', '1', this.Config.vm.snapshots ? '1' : '0', '0'));
|
client.sendMsg(guacutils.encode('connect', '1', '1', this.VM.SnapshotsSupported() ? '1' : '0', '0'));
|
||||||
if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg());
|
if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg());
|
||||||
if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode('chat', '', this.Config.collabvm.motd));
|
if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode('chat', '', this.Config.collabvm.motd));
|
||||||
|
|
||||||
@@ -361,7 +357,7 @@ export default class CollabVMServer {
|
|||||||
this.VM.GetDisplay()!.KeyboardEvent(keysym, down === 1 ? true : false);
|
this.VM.GetDisplay()!.KeyboardEvent(keysym, down === 1 ? true : false);
|
||||||
break;
|
break;
|
||||||
case 'vote':
|
case 'vote':
|
||||||
if (!this.Config.vm.snapshots) return;
|
if (!this.VM.SnapshotsSupported()) return;
|
||||||
if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && client.rank !== Rank.Admin && client.rank !== Rank.Moderator && client.rank !== Rank.Turn) return;
|
if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && client.rank !== Rank.Admin && client.rank !== Rank.Moderator && client.rank !== Rank.Turn) return;
|
||||||
if (!client.connectedToNode) return;
|
if (!client.connectedToNode) return;
|
||||||
if (msgArr.length !== 2) return;
|
if (msgArr.length !== 2) return;
|
||||||
@@ -461,7 +457,7 @@ export default class CollabVMServer {
|
|||||||
// Reboot
|
// Reboot
|
||||||
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.reboot)) return;
|
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.reboot)) return;
|
||||||
if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return;
|
if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return;
|
||||||
this.VM.MonitorCommand('system_reset');
|
await this.VM.Reboot();
|
||||||
break;
|
break;
|
||||||
case '12':
|
case '12':
|
||||||
// Ban
|
// Ban
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import VNCVMDef from "./VNCVM/VNCVMDef";
|
||||||
|
|
||||||
export default interface IConfig {
|
export default interface IConfig {
|
||||||
http: {
|
http: {
|
||||||
host: string;
|
host: string;
|
||||||
@@ -24,6 +26,9 @@ export default interface IConfig {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
vm: {
|
vm: {
|
||||||
|
type: "qemu" | "vncvm";
|
||||||
|
};
|
||||||
|
qemu: {
|
||||||
qemuArgs: string;
|
qemuArgs: string;
|
||||||
vncPort: number;
|
vncPort: number;
|
||||||
snapshots: boolean;
|
snapshots: boolean;
|
||||||
@@ -31,6 +36,7 @@ export default interface IConfig {
|
|||||||
qmpPort: number | null;
|
qmpPort: number | null;
|
||||||
qmpSockDir: string | null;
|
qmpSockDir: string | null;
|
||||||
};
|
};
|
||||||
|
vncvm: VNCVMDef;
|
||||||
collabvm: {
|
collabvm: {
|
||||||
node: string;
|
node: string;
|
||||||
displayname: string;
|
displayname: string;
|
||||||
|
|||||||
11
cvmts/src/VM.ts
Normal file
11
cvmts/src/VM.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import VMDisplay from "./VMDisplay.js";
|
||||||
|
|
||||||
|
export default interface VM {
|
||||||
|
Start(): Promise<void>;
|
||||||
|
Stop(): Promise<void>;
|
||||||
|
Reboot(): Promise<void>;
|
||||||
|
Reset(): Promise<void>;
|
||||||
|
MonitorCommand(command: string): Promise<any>;
|
||||||
|
GetDisplay(): VMDisplay;
|
||||||
|
SnapshotsSupported(): boolean;
|
||||||
|
}
|
||||||
12
cvmts/src/VMDisplay.ts
Normal file
12
cvmts/src/VMDisplay.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Size } from "@cvmts/shared";
|
||||||
|
import EventEmitter from "node:events";
|
||||||
|
|
||||||
|
export default interface VMDisplay extends EventEmitter {
|
||||||
|
Connect(): void;
|
||||||
|
Disconnect(): void;
|
||||||
|
Connected(): boolean;
|
||||||
|
Buffer(): Buffer;
|
||||||
|
Size(): Size;
|
||||||
|
MouseEvent(x: number, y: number, buttons: number): void;
|
||||||
|
KeyboardEvent(keysym: number, pressed: boolean): void;
|
||||||
|
}
|
||||||
174
cvmts/src/VNCVM/VNCVM.ts
Normal file
174
cvmts/src/VNCVM/VNCVM.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
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 { VncClient } from '@computernewb/nodejs-rfb';
|
||||||
|
import { BatchRects } from "@cvmts/qemu";
|
||||||
|
import { execaCommand } from "execa";
|
||||||
|
|
||||||
|
export default class VNCVM extends EventEmitter implements VM, VMDisplay {
|
||||||
|
def : VNCVMDef;
|
||||||
|
logger: Logger;
|
||||||
|
private displayVnc = new VncClient({
|
||||||
|
debug: false,
|
||||||
|
fps: 60,
|
||||||
|
encodings: [
|
||||||
|
VncClient.consts.encodings.raw,
|
||||||
|
VncClient.consts.encodings.pseudoDesktopSize
|
||||||
|
]
|
||||||
|
});
|
||||||
|
private vncShouldReconnect: boolean = false;
|
||||||
|
|
||||||
|
constructor(def : VNCVMDef) {
|
||||||
|
super();
|
||||||
|
this.def = def;
|
||||||
|
this.logger = new Logger(`CVMTS.VNCVM/${this.def.vncHost}:${this.def.vncPort}`);
|
||||||
|
|
||||||
|
this.displayVnc.on('connectTimeout', () => {
|
||||||
|
this.Reconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.displayVnc.on('authError', () => {
|
||||||
|
this.Reconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.displayVnc.on('disconnect', () => {
|
||||||
|
this.logger.Info('Disconnected');
|
||||||
|
this.Reconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.displayVnc.on('closed', () => {
|
||||||
|
this.Reconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.displayVnc.on('firstFrameUpdate', () => {
|
||||||
|
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);
|
||||||
|
this.emit('connected');
|
||||||
|
|
||||||
|
this.emit('resize', { width: this.displayVnc.clientWidth, height: this.displayVnc.clientHeight });
|
||||||
|
//this.emit('rect', { x: 0, y: 0, width: this.displayVnc.clientWidth, height: this.displayVnc.clientHeight });
|
||||||
|
this.emit('frame');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.displayVnc.on('desktopSizeChanged', (size: Size) => {
|
||||||
|
this.emit('resize', size);
|
||||||
|
});
|
||||||
|
|
||||||
|
let rects: Rect[] = [];
|
||||||
|
|
||||||
|
this.displayVnc.on('rectUpdateProcessed', (rect: Rect) => {
|
||||||
|
rects.push(rect);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.displayVnc.on('frameUpdated', (fb: Buffer) => {
|
||||||
|
// use the cvmts batcher
|
||||||
|
let batched = BatchRects(this.Size(), rects);
|
||||||
|
this.emit('rect', batched);
|
||||||
|
|
||||||
|
// unbatched (watch the performace go now)
|
||||||
|
//for(let rect of rects)
|
||||||
|
// this.emit('rect', rect);
|
||||||
|
|
||||||
|
rects = [];
|
||||||
|
|
||||||
|
this.emit('frame');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async Reset(): Promise<void> {
|
||||||
|
if (this.def.restoreCmd) await execaCommand(this.def.restoreCmd, {shell: true});
|
||||||
|
else {
|
||||||
|
await this.Stop();
|
||||||
|
await Sleep(1000);
|
||||||
|
await this.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Reconnect() {
|
||||||
|
if (this.displayVnc.connected) return;
|
||||||
|
|
||||||
|
if (!this.vncShouldReconnect) return;
|
||||||
|
|
||||||
|
// TODO: this should also give up after a max tries count
|
||||||
|
// if we fail after max tries, emit a event
|
||||||
|
|
||||||
|
this.displayVnc.connect({
|
||||||
|
host: this.def.vncHost,
|
||||||
|
port: this.def.vncPort,
|
||||||
|
path: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Start(): Promise<void> {
|
||||||
|
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.Disconnect();
|
||||||
|
if (this.def.stopCmd) await execaCommand(this.def.stopCmd, {shell: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Reboot(): Promise<void> {
|
||||||
|
if (this.def.rebootCmd) await execaCommand(this.def.rebootCmd, {shell: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
async MonitorCommand(command: string): Promise<any> {
|
||||||
|
// TODO: This can maybe run a specified command?
|
||||||
|
return "This VM does not support monitor commands.";
|
||||||
|
}
|
||||||
|
|
||||||
|
GetDisplay(): VMDisplay {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnapshotsSupported(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connect(): void {
|
||||||
|
this.vncShouldReconnect = true;
|
||||||
|
this.Reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
Disconnect(): void {
|
||||||
|
this.vncShouldReconnect = false;
|
||||||
|
this.displayVnc.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connected(): boolean {
|
||||||
|
return this.displayVnc.connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer(): Buffer {
|
||||||
|
return this.displayVnc.fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size(): Size {
|
||||||
|
if (!this.displayVnc.connected)
|
||||||
|
return {
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: this.displayVnc.clientWidth,
|
||||||
|
height: this.displayVnc.clientHeight
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseEvent(x: number, y: number, buttons: number): void {
|
||||||
|
if (this.displayVnc.connected) this.displayVnc.sendPointerEvent(Clamp(x, 0, this.displayVnc.clientWidth), Clamp(y, 0, this.displayVnc.clientHeight), buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardEvent(keysym: number, pressed: boolean): void {
|
||||||
|
if (this.displayVnc.connected) this.displayVnc.sendKeyEvent(keysym, pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
cvmts/src/VNCVM/VNCVMDef.ts
Normal file
8
cvmts/src/VNCVM/VNCVMDef.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default interface VNCVMDef {
|
||||||
|
vncHost : string;
|
||||||
|
vncPort : number;
|
||||||
|
startCmd : string | null;
|
||||||
|
stopCmd : string | null;
|
||||||
|
rebootCmd : string | null;
|
||||||
|
restoreCmd : string | null;
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import AuthManager from './AuthManager.js';
|
|||||||
import WSServer from './WebSocket/WSServer.js';
|
import WSServer from './WebSocket/WSServer.js';
|
||||||
import { User } from './User.js';
|
import { User } from './User.js';
|
||||||
import TCPServer from './TCP/TCPServer.js';
|
import TCPServer from './TCP/TCPServer.js';
|
||||||
|
import VM from './VM.js';
|
||||||
|
import VNCVM from './VNCVM/VNCVM.js';
|
||||||
|
|
||||||
let logger = new Shared.Logger('CVMTS.Init');
|
let logger = new Shared.Logger('CVMTS.Init');
|
||||||
|
|
||||||
@@ -31,28 +33,53 @@ try {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function start() {
|
let exiting = false;
|
||||||
// Print a warning if qmpSockDir is set
|
let VM : VM;
|
||||||
// and the host OS is Windows, as this
|
|
||||||
// configuration will very likely not work.
|
|
||||||
if (process.platform === 'win32' && Config.vm.qmpSockDir) {
|
|
||||||
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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async function stop() {
|
||||||
|
if (exiting) return;
|
||||||
|
exiting = true;
|
||||||
|
await VM.Stop();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start() {
|
||||||
// Init the auth manager if enabled
|
// Init the auth manager if enabled
|
||||||
let auth = Config.auth.enabled ? new AuthManager(Config.auth.apiEndpoint, Config.auth.secretKey) : null;
|
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
|
// Fire up the VM
|
||||||
let def: QemuVmDefinition = {
|
let def: QemuVmDefinition = {
|
||||||
id: Config.collabvm.node,
|
id: Config.collabvm.node,
|
||||||
command: Config.vm.qemuArgs
|
command: Config.qemu.qemuArgs
|
||||||
};
|
};
|
||||||
|
|
||||||
|
VM = new QemuVM(def);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "vncvm": {
|
||||||
|
VM = new VNCVM(Config.vncvm);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
logger.Error('Invalid VM type in config: {0}', Config.vm.type);
|
||||||
|
process.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.on('SIGINT', async () => await stop());
|
||||||
|
process.on('SIGTERM', async () => await stop());
|
||||||
|
|
||||||
var VM = new QemuVM(def);
|
|
||||||
await VM.Start();
|
await VM.Start();
|
||||||
|
|
||||||
// Start up the server
|
// Start up the server
|
||||||
var CVM = new CollabVMServer(Config, VM, auth);
|
var CVM = new CollabVMServer(Config, VM, auth);
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ export class QemuVM extends EventEmitter {
|
|||||||
await this.StartQemu(cmd);
|
await this.StartQemu(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SnapshotsSupported() : boolean {
|
||||||
|
return gVMShouldSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Reboot() : Promise<void> {
|
||||||
|
await this.MonitorCommand('system_reset');
|
||||||
|
}
|
||||||
|
|
||||||
async Stop() {
|
async Stop() {
|
||||||
// This is called in certain lifecycle places where we can't safely assert state yet
|
// This is called in certain lifecycle places where we can't safely assert state yet
|
||||||
//this.AssertState(VMState.Started, 'cannot use QemuVM#Stop on a non-started VM');
|
//this.AssertState(VMState.Started, 'cannot use QemuVM#Stop on a non-started VM');
|
||||||
|
|||||||
Reference in New Issue
Block a user