Skip to content

SourceRegistry/node-qemu

Repository files navigation

node-qemu

QEMU management library for Node.js

npm CI License

Typed QMP client, process lifecycle manager, and qemu-img wrappers - all in pure TypeScript with no native bindings.

Features

  • QMP client - typed JSON-RPC over Unix socket or TCP, full capability negotiation, sequential command dispatch, typed async events
  • 60+ typed QMP commands - VM control, block/storage, device hotplug, migration, CPU/memory, display, introspection, QOM
  • Process manager - spawn, monitor, and gracefully stop QEMU processes; PID files; stdout/stderr streams
  • CLI argument builder - convert a typed QemuConfig into a validated string[] argv
  • Image tools - typed wrappers around qemu-img: create, resize, convert, info, check, snapshots
  • AsyncDisposable - await using support on QMPClient and QemuProcess
  • Automatic reconnect - exponential backoff on unexpected disconnect (opt-in)
  • Dual ESM + CJS output, TypeScript source maps, full .d.ts declarations

Installation

npm install node-qemu

Requires Node.js ≥ 18 and qemu-system-* / qemu-img binaries on the host.

Quick Start

QMP Client

import { QMPClient } from "node-qemu";

await using client = new QMPClient({ socketPath: "/run/qemu/vm1.sock" });
await client.connect();

// Typed command
const status = await client.queryStatus();
console.log(status.status); // "running" | "paused" | ...

// Typed events
client.on("SHUTDOWN", (e) => console.log("Guest shut down:", e.reason));
client.on("BLOCK_JOB_COMPLETED", (e) => console.log("Backup done:", e.device));
client.on("MIGRATION", (e) => console.log("Migration:", e.status));

// Pause and resume
await client.stop();
await client.cont();

// Graceful shutdown
await client.systemPowerdown();

Process Manager

import { QemuProcess, QMPClient } from "node-qemu";

const proc = new QemuProcess({
  binary: "/usr/bin/qemu-system-x86_64",
  config: {
    machine: { type: "q35", accel: "kvm" },
    cpu: "host",
    memory: { size: 2048 },
    drives: [{ file: "/var/lib/qemu/vm1.qcow2", format: "qcow2", virtio: true }],
    net: [{ type: "tap", ifname: "tap0", script: "no", vhost: true }],
    qmp: { socketPath: "/run/qemu/vm1.sock" },
    vnc: { display: "0" },
    enableKvm: true,
    noReboot: true,
    pidfile: "/run/qemu/vm1.pid",
  },
});

await proc.start();
console.log("QEMU PID:", proc.pid);

proc.on("stderr", (line) => console.error("[qemu]", line));
proc.on("exit", (code, signal) => console.log("Exited", { code, signal }));

// Connect QMP after process starts
await using client = new QMPClient({ socketPath: proc.socketPath! });
await client.connect();

// Graceful stop - SIGTERM with 5s SIGKILL fallback
await proc.stop();

Image Tools

import { QemuImg } from "node-qemu";

// Create a thin-provisioned overlay over a base image
await QemuImg.create({
  filename: "/var/lib/qemu/vm1.qcow2",
  format: "qcow2",
  size: "20G",
  backingFile: "/var/lib/qemu/base.qcow2",
});

// Resize in place
await QemuImg.resize({ filename: "/var/lib/qemu/vm1.qcow2", size: "+10G" });

// Convert raw → qcow2 with compression
await QemuImg.convert({
  src: "/var/lib/qemu/vm1.raw",
  dst: "/var/lib/qemu/vm1.qcow2",
  format: "qcow2",
  compress: true,
});

// Inspect
const info = await QemuImg.info({ filename: "/var/lib/qemu/vm1.qcow2" });
console.log(info.format, info.virtualSize, info.backingFile);

// Internal qcow2 snapshots
await QemuImg.snapshotCreate({ filename: "/var/lib/qemu/vm1.qcow2", tag: "clean" });
const snaps = await QemuImg.snapshotList({ filename: "/var/lib/qemu/vm1.qcow2" });
await QemuImg.snapshotApply({ filename: "/var/lib/qemu/vm1.qcow2", tag: "clean" });
await QemuImg.snapshotDelete({ filename: "/var/lib/qemu/vm1.qcow2", tag: "clean" });

TCP Connection

const client = new QMPClient({ host: "192.168.1.10", port: 4444 });
await client.connect();

Automatic Reconnect

const client = new QMPClient({
  socketPath: "/run/qemu/vm1.sock",
  reconnect: true,
  reconnectDelay: 1000,    // initial delay ms (doubles on each attempt)
  reconnectMaxDelay: 30_000,
});

client.on("disconnected", () => console.log("Lost connection, retrying..."));
client.on("connected", () => console.log("Reconnected"));

Incremental Backup with Dirty Bitmaps

// Track changed blocks since last backup
await client.blockDirtyBitmapAdd({ node: "hd0", name: "inc-backup" });

// ... time passes, guest writes data ...

// Incremental backup using the bitmap
await client.blockdevBackup({
  device: "hd0",
  target: "backup-target",
  sync: "incremental",
  bitmap: "inc-backup",
});

// Monitor completion
client.once("BLOCK_JOB_COMPLETED", async (e) => {
  console.log("Backup complete:", e.device);
  await client.blockDirtyBitmapClear({ node: "hd0", name: "inc-backup" });
});

Live Migration

await client.migrateSetCapabilities([
  { capability: "xbzrle", state: true },
  { capability: "zero-blocks", state: true },
]);

await client.migrate({ uri: "tcp:192.168.1.2:4444" });

client.on("MIGRATION", (e) => {
  if (e.status === "completed") console.log("Migration done");
  if (e.status === "failed")    console.error("Migration failed");
});

Error Handling

import { QmpError, QmpCommandError } from "node-qemu";

try {
  await client.blockdevBackup({ device: "hd0", target: "missing", sync: "full" });
} catch (err) {
  if (err instanceof QmpCommandError) {
    console.error(err.qmpClass, err.description); // "DeviceNotFound" "..."
  } else if (err instanceof QmpError) {
    console.error(err.code); // "CONNECTION_CLOSED" | "CLIENT_CLOSED" | ...
  }
}

Events

QMPClient extends EventEmitter with fully typed payloads:

Event Payload Description
SHUTDOWN ShutdownEvent Guest or host-initiated shutdown
RESET ResetEvent System reset
STOP - VM execution paused
RESUME - VM execution resumed
GUEST_PANICKED GuestPanicEvent Guest kernel panic
WATCHDOG WatchdogEvent Watchdog timer fired
BLOCK_IO_ERROR BlockIoErrorEvent Block device I/O error
BLOCK_JOB_COMPLETED BlockJobEvent Backup/mirror/stream finished
BLOCK_JOB_CANCELLED BlockJobEvent Block job cancelled
BLOCK_JOB_ERROR BlockJobErrorEvent Block job I/O error
BLOCK_JOB_READY BlockJobEvent Mirror ready to pivot
JOB_STATUS_CHANGE JobStatusChangeEvent Generic job status changed
DEVICE_ADDED DeviceEvent Hotplug device added
DEVICE_DELETED DeviceEvent Hotplug device removed
MIGRATION MigrationEvent Migration status update
MIGRATION_PASS MigrationPassEvent Migration pass counter
NIC_RX_FILTER_CHANGED NicRxFilterEvent NIC RX filter updated
VNC_CONNECTED VncEvent VNC client connected
VNC_DISCONNECTED VncEvent VNC client disconnected
MEMORY_FAILURE MemoryFailureEvent Hardware memory failure
SUSPEND - Guest suspended
WAKEUP - Guest woke up
connected - QMP handshake complete
disconnected - Socket closed
error Error Transport error

API Reference

Full API documentation is available at sourceregistry.github.io/node-qemu.

For the underlying QMP wire protocol, see the QEMU QMP Reference and qemu-img manual.

Related Libraries

Library Role
node-lxc LXC container lifecycle
node-ovsdb Open vSwitch / OVSDB networking
node-qemu QEMU VM lifecycle (this library)

License

Apache-2.0

About

QEMU management library for Node.js

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors