Skip to content

chicohaager/ZVM-Extra

Repository files navigation

zima-vm-extras

Community add-on for ZimaOS's built-in ZVM (Zima Virtual Machine) module. It adds the day-to-day operational features the official UI lacks, without touching the ZVM frontend — it installs as a separate zima_vm_extras.raw sysext with its own VM Extras tile on the ZimaOS dashboard.

Status: v0.7.0 — verified on ZimaOS 1.7.0-beta1 (kernel 6.18.9, virtqemud / libvirt 10, qemu 9).

v0.7.0 is a bug-fix and quality-of-life release from community feedback on v0.6.3. Fixed: snapshots whose name contains a space could be created and then neither deleted nor reverted; the Metrics tab reported every VM at 100 % memory, because it divided the balloon's target size by itself; the snapshot storage target vanished for a shut-off VM instead of saying why; and the external target directory did not follow the VM picker, so an external snapshot of VM B could land in VM A's directory. Added: power controls on the Autostart tab, remembered backup and snapshot target directories, a short history sparkline per metric, a PCIe list that hides the two thirds of a host's devices that can never be passed through, and a VNC console that can be restricted to localhost instead of the whole LAN. Details in the sections below.

v0.6.x adds the TPM & Secure Boot tab. Windows 11 refuses to install without a TPM 2.0. ZVM has a Windows-11-specific code path for this — its backend carries a function named EnableTPMForWin11 and references /usr/share/qemu/edk2-x86_64-secure-code-win11.fd — and it works: a Windows 11 VM created by ZVM comes out with pc-q35 + smm=on, the Secure Boot firmware and a tpm-tis 2.0 device, and Windows installs. But the path is automatic and invisible — there is no control for it anywhere in the ZVM UI, so it can be neither forced nor turned off — and it fires for Windows 11 only. A ZVM-created Windows 10 VM gets no TPM and the plain edk2-x86_64-code.fd loader, which rules out BitLocker in TPM mode with no way to change it. Linux guests get nothing either.

This tab makes the device explicit and operator-controlled: it adds <tpm model='tpm-crb'><backend type='emulator' version='2.0'/></tpm> to any VM and pins it, so a ZVM re-save cannot silently drop it again, and it shows whether the running guest actually has one.

It also reports something ZVM's own path leaves in a misleading state. ZimaOS pairs its Windows 11 firmware with the empty edk2-i386-vars.fd and ships no NVRAM template with keys enrolled, so Secure Boot is capable but has nothing to validate against: the firmware stays in setup mode and reports Secure Boot as off to the guest (msinfo32Secure Boot State: Off). Windows 11 installs regardless, because setup checks for capability. The tab shows that as its own state rather than as a green "secure boot".

v0.5.x adds the VNC security tab. ZVM generates every VM's console as <graphics type='vnc' listen='::'> with no password, so anyone on the LAN can take over a VM's screen and keyboard with a plain VNC viewer. This tab sets a console password on the domain and a reconciler keeps it there when ZVM re-saves the domain and strips it. The password applies at the VM's next startvirsh define rewrites the persistent config and never disturbs a running VM.

v0.5.1 fixes the read-back of that password: virsh dumpxml masks the passwd attribute unless it is given --security-info, so a protected console reported itself as unprotected — the tab showed the wrong state and the reconciler re-defined the domain once a minute forever, because the password it had just written kept reading back as absent.

v0.5.2 stops the tab from reporting a console as safe while it is still open. The password goes into the persistent config, which the running qemu never re-reads, so between setting it and the next VM start the config says "protected" while the live console still lets anyone in. The tab now reads the running domain separately and shows three states: exposed (no password anywhere), restart required (saved, but the running console started without it), and password set (the live console really does ask for one).

Features

Tab What it does
Autostart Starts VMs on host boot, orchestrated by the daemon — per-VM order and delay so VMs come up in sequence. An optional per-VM watchdog restarts a VM if it stops or crashes. Also carries the power controls: start, ACPI shut down, reboot, resume, and a confirmed force-off.
Snapshots Create / revert / delete per VM. Running VMs get a full external snapshot (disk + memory state) so it is genuinely revertable; shut-off VMs get a quick internal snapshot. An optional schedule takes periodic snapshots with retention.
USB passthrough Pass a host USB device into a VM from the GUI — no manual XML. Persistent devices are re-applied automatically if the ZVM UI strips them on re-save, and across host reboots.
PCIe passthrough Pass a host PCI device through via VFIO. Shows IOMMU groups and the current host driver. Bridges and devices without an IOMMU group cannot be passed and are hidden behind a toggle, so the list shows candidates rather than the whole bus. Same persistence/reconcile as USB.
VNC security Shows each VM's console listen address and whether it is password-protected, sets a console password, and controls reachability — the listen address (all interfaces vs. localhost only) and the port. A console restricted to 127.0.0.1 can only be opened through an SSH tunnel or a reverse proxy on the box; libvirt applies the address to ZVM's websocket port too, so both surfaces close together. Putting a console back on all interfaces requires a password. ZVM leaves consoles open to the whole LAN with no authentication; a reconciler re-applies the password whenever ZVM strips it. Distinguishes a saved password from an effective one — a console keeps running without it until the VM restarts, and the tab says so instead of showing green.
TPM & Secure Boot Adds a TPM 2.0 emulator device so Windows 11 will install, and pins it against ZVM re-saves. Reports the saved device and the running one separately — a TPM added to a running VM only counts after a restart. Secure Boot status is shown but deliberately not switched: the firmware and its NVRAM file are a matched pair, and repointing an existing VM can leave it unbootable.
Metrics Live per-VM CPU, memory, disk and network, sampled from libvirt, each with a short in-page history sparkline. Memory is reported as in-guest usage where the guest's virtio-balloon driver provides it, and honestly labelled allocated where it does not — the balloon's target size equals the configured maximum on an uninflated balloon, which is why v0.6.3 showed every VM at 100 %.
Backup Export a VM — domain XML + disk image(s) — as a standalone compact qcow2, asynchronously. The target directory is remembered on the appliance between visits.
Network Switch a VM's NIC to another libvirt network and change its model — config-only, never touches host bridges.
Remote storage Mount NFS / SMB-CIFS shares; mounted paths appear as snapshot/backup storage targets.

Screenshots

Dashboard — VM overview, power control & autostart

Dashboard

Snapshots

Snapshots

USB passthrough

USB passthrough

PCIe passthrough — bridges and devices without an IOMMU group are hidden by default; on this ZimaCube that is 24 of 41 entries. One checkbox brings them back.

PCIe passthrough

Metrics — memory is the guest's own figure, not the balloon's target size, with a short history per value.

Metrics

VNC security — a console the way ZVM leaves it: reachable from the whole LAN, no password. The second card restricts it to localhost.

VNC security

TPM & Secure Boot — a TPM 2.0 device added and pinned against ZVM re-saves. Secure Boot is reported, never switched.

TPM & Secure Boot

Security model

The daemon binds 127.0.0.1 only and registers a reverse-proxy route (/v2/vm_extras) with the ZimaOS gateway. The Web UI reaches it same-origin through port 80 — exactly the pattern the zima_cron module uses. The daemon is never exposed on the LAN on a port of its own.

Requirements

  • ZimaOS 1.6.1+ with a working ZVM install. Tested on 1.6.1 and 1.7.0-beta1.
  • For PCIe passthrough: intel_iommu=on (or amd_iommu=on) on the kernel cmdline — ZimaCube images ship with this already set.

Install

# On a build host (Linux, amd64) — needs go 1.22+ and squashfs-tools:
./build.sh                       # produces dist/zima_vm_extras.raw

# Copy it to the device. ZimaOS disables root SSH login (`PermitRootLogin no`),
# so this goes through your admin account and sudo:
scp dist/zima_vm_extras.raw <user>@<zimaos>:/tmp/
ssh <user>@<zimaos> 'sudo install -m 644 /tmp/zima_vm_extras.raw /var/lib/extensions/ && \
                     sudo systemd-sysext refresh && \
                     sudo systemctl daemon-reload && \
                     sudo systemctl enable --now zima-vm-extras.service'

Or run sudo ./install.sh on the device after build.sh. Then open the VM Extras tile on the ZimaOS dashboard (or http://<zimaos>/modules/zima_vm_extras/).

On first start the daemon installs a small watchdog timer into /etc/systemd/system/ — this guarantees the service comes up after a reboot even though its unit lives inside a sysext (sysext units can otherwise lose the multi-user.target boot race).

Architecture

Standard ZimaOS module layout: a manifest under /usr/share/casaos/modules/, a static UI under /usr/share/casaos/www/modules/, and a systemd service.

  • Daemon — Go, ~6 MB, no cgo, no external dependencies. Binds 127.0.0.1:8473.
  • Frontend — vanilla JS, no build step.
  • libvirt access — subprocess to /usr/bin/virsh (stable interface, no header-coupled cgo).
  • State/DATA/AppData/zima-vm-extras/, survives reboots and OS upgrades: autostart.json, usb.json, pci.json, vnc.json, tpm.json, schedule.json, mounts.json, settings.json, snapshots/.
  • Reconcilers — four background loops, each on a 60-second tick, keep the pinned USB and PCI passthroughs, the VNC console password and the TPM device present in each VM's persistent config, because the ZVM UI drops them on a re-save.

API

All endpoints are served under /api (reachable via the gateway at /v2/vm_extras/api/...).

Method Path Purpose
GET /api/health liveness + version
GET /api/vms combined virsh + autostart view
GET /api/autostart list autostart entries
GET/PUT/DELETE /api/autostart/<vm> per-VM autostart (enabled, order, delay)
GET /api/snapshot/<vm> list snapshots + capability info
POST /api/snapshot/<vm> create snapshot
POST /api/snapshot/<vm>/<snap>/revert revert to snapshot
DELETE /api/snapshot/<vm>/<snap> delete snapshot (?children=1 recursive)
GET /api/usb/host host USB devices
GET /api/usb/<vm>/pinned pinned USB devices of a VM
POST /api/usb/<vm> attach USB device
DELETE /api/usb/<vm>/<vendor>:<product> detach USB device
GET /api/pci/host host PCI devices + IOMMU groups
GET /api/pci/<vm>/pinned pinned PCI devices of a VM
POST /api/pci/<vm> attach PCI device
DELETE /api/pci/<vm>/<address> detach PCI device
GET /api/vnc/<vm> console state: saved password, live password, listen address, pinned
POST /api/vnc/<vm> set the console password (1–8 chars), optionally listen (127.0.0.1|0.0.0.0) and port, and pin them
DELETE /api/vnc/<vm> remove the console password and the pin
GET /api/tpm/<vm> TPM state (saved + running) and firmware / Secure Boot info
POST /api/tpm/<vm> add a TPM 2.0 emulator device and pin it
DELETE /api/tpm/<vm> remove the TPM device and the pin
GET /api/storage/targets writable filesystems for snapshot storage
GET/POST /api/mounts list / create remote mounts
GET/PUT/DELETE /api/mounts/<id> manage a remote mount
POST /api/mounts/<id>/mount mount
POST /api/mounts/<id>/unmount unmount
GET /api/metrics/<vm> live VM stats sample (CPU, mem, disk, net)
POST /api/power/<vm> start, shutdown, reboot, force-off, suspend, resume
GET/PUT /api/settings remembered backup / snapshot target directories
GET /api/schedule list snapshot schedules
GET/PUT/DELETE /api/schedule/<vm> per-VM snapshot schedule
GET /api/backup list backup jobs
POST /api/backup/<vm> start a backup
GET /api/net/networks list libvirt networks
GET /api/net/<vm> list a VM's NICs
PUT /api/net/<vm>/<mac> switch a NIC's network / model

Configuration

The systemd unit sets these; override via the environment if needed:

Variable Default
BIND_ADDR 127.0.0.1
PORT 8473
DATA_DIR /DATA/AppData/zima-vm-extras
STATIC_DIR /usr/share/casaos/www/modules/zima_vm_extras
VIRSH_BIN /usr/bin/virsh
ROUTE_PATH /v2/vm_extras
GATEWAY_URL_FILE /var/run/casaos/management.url

Building

./build.sh        # go vet + go test + compile + pack squashfs

build.sh reads the version from the VERSION file (the release workflow overrides it from the git tag). Output: dist/zima_vm_extras.raw + .sha256.

Uninstall

sudo ./uninstall.sh

Removes the sysext, the watchdog units and the gateway route. State under /DATA/AppData/zima-vm-extras/ is preserved (rm -rf it to wipe).

Roadmap

The original roadmap — live metrics, a VM watchdog, scheduled snapshots, backup/export and network switching — all shipped in v0.4.0; console password protection shipped in v0.5.x, the TPM 2.0 device in v0.6.x. Possible future additions: compressed backup archives, an optional JWT auth layer in front of the API, and further reconcilers for the defaults ZVM gets wrong on Linux guests (it stamps every VM os_type=windows, which drags in clock offset='localtime' and Hyper-V enlightenments, and it leaves the installer ISO attached and bootable).

License

MIT

About

Community add-on for ZimaOS's built-in ZVM module — autostart, revertable snapshots, USB & PCIe passthrough, backup/export, live metrics, NIC switching and NFS/SMB storage

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages