Quick start in Node

Boot Alpine Linux to a shell from the command line, then drive the same machine from JavaScript.

Get a kernel

Alpine's netboot kernel and initramfs are the quickest way to a shell:

curl -LO https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/aarch64/netboot/vmlinuz-virt
curl -LO https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/aarch64/netboot/initramfs-virt

Boot it from the command line

wemu-system-aarch64 -m 1G -kernel vmlinuz-virt -initrd initramfs-virt -append console=ttyAMA0 -nographic

The kernel boots, and a ~ # prompt appears in your terminal. Try uname -a. Press Ctrl+A then c for the monitor, where info status and info cpus work, and Ctrl+A then x to quit.

If you've used qemu-system-aarch64, this is the same command line, and so is everything you'd add to it.

Boot it from JavaScript

The same command line runs from code:

boot.mjs
import { WEMU } from "warm64";
import { nodeWEMUHost } from "warm64/wemu-node";

const vm = await WEMU.launch(
  "-m 1G -S -display none -kernel vmlinuz-virt -initrd initramfs-virt -append console=ttyAMA0",
  nodeWEMUHost(),
);

// print the console, and answer the shell's prompt once
let typed = false;
vm.serial[0].onData((bytes) => {
  process.stdout.write(bytes);
  if (!typed && new TextDecoder().decode(bytes).includes("~ # ")) {
    typed = true;
    vm.serial[0].write("uname -a; poweroff\n");
  }
});

await vm.qmp("cont");
const { code } = await vm.wait();
console.log(`\nexited with ${code}`);
node boot.mjs

What's going on:

  • -S starts the machine paused, so your console listener is attached before the first byte of output. cont starts it.
  • -display none and no -nographic: the console is vm.serial[0], not the terminal.
  • poweroff in the guest ends the machine; wait() resolves with its exit code.

Control it

While it runs, anything QEMU's QMP or monitor can do:

await vm.qmp("stop");
console.log(await vm.hmp("info registers"));
await vm.hmp("savevm booted");
await vm.qmp("cont");

Next steps