Omahub
← All plugins
L

libvirt control

by Leyanora

libvirt frontend for the bar: a green/red state light per domain, play/stop controls, click-to-open console, keyboard navigation, and an expandable row for pause, reboot, save state and snapshots.

Security review

Review recommended · 6 findings

Deterministic scan — not a security guarantee

Low
Risk level
Low
Analyzed commit
6674b80
Scanned
1 month ago

Flagged patterns appear only in documentation files (README / docs) — descriptive examples, not executable code.

  • Docs persistence README.md:175

    Registers scheduled or boot-time system tasks.

    systemctl enable --now virtqemud.socket virtnetworkd.socket virtstoraged.socket
  • Docs sudo README.md:173

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo usermod -aG libvirt $USER           # then log out and back in
  • Docs sudo README.md:151

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo pacman -S qemu-full libvirt virt-manager virt-viewer edk2-ovmf
  • Docs sudo README.md:174

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo pacman -S dnsmasq                   # the default NAT network needs it
  • Docs sudo README.md:175

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo systemctl enable --now virtqemud.socket virtnetworkd.socket virtstoraged.socket
  • Docs sudo README.md:176

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo virsh net-start default

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
6674b80
Reviewed
1 month ago

The plugin is a libvirt frontend that drives domains via virsh with the user's own privileges; it never runs sudo, installs packages, or enables system services. The deterministic scan flagged README commands (sudo pacman, systemctl enable) but those are documentation for the user to set up libvirt, not executed by the plugin. The only system modification is an optional user-level systemd drop-in to suppress crash toasts, which is scoped, reversible, and defaults to off.

  • The plugin can issue destructive virsh commands (destroy, snapshot delete, revert) but these are user-initiated and protected by arm-then-confirm.
  • The crash-watch drop-in writes to $XDG_RUNTIME_DIR/systemd/user, a user-scoped runtime location that does not persist across reboots; it only sets an environment variable for a user service.
  • The plugin writes settings to shell.json, which is normal configuration persistence, not malicious.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/Leyanora/omarchy-libvirt --enable
System #launcher #system

Omarchy libvirt

A libvirt frontend for the Omarchy bar. The bar item is a glyph, dimmed when nothing is running; the popup lists every domain on the connection with a state light, play/stop controls, and a click-to-open console. Expand a row for pause, reboot, save state and snapshots.

It drives domains and nothing else: it never starts libvirt, never touches networks, and never opens a connection to a guest. Creating, cloning and deleting domains stay with virt-manager, which the popup's footer opens.

Claude code was heavily involved in the creation of this widget.

The popup, listing a paused, a running and a shut off domain

Using it

Left click the bar item for the popup, right click to open virt-manager, middle click or scroll to refresh. The glyph turns amber while a domain is suspended and red while the connection is down; its tooltip names what is running.

  • Green running, amber paused, red shut off. A row only shows the buttons its state allows.
  • The play arrow starts a shut off domain, or resumes a paused one.
  • The stop square is virsh shutdown — a polite ACPI request the guest can ignore.
  • The lightning bolt is virsh destroy. It cuts power and loses unwritten guest state, so it takes two clicks: the first arms it, the second does it, and it disarms itself after four seconds.
  • Clicking a domain's name opens its console in virt-viewer, only while the domain is up.
  • The chevron expands the row for everything else.
  • A shutdown the guest ignores turns the lightning bolt red after twenty seconds, and its tooltip says how long it has been waiting. virsh shutdown is only a request, and until now a guest with no ACPI handler looked exactly like a button that did nothing.
  • With eight or more domains a filter field appears above the list.

Keyboard

The popup takes keyboard focus, so it can be driven without the mouse — bind omarchy-shell leyanora.libvirt toggle and never touch the pointer.

Key Does
↑ ↓, k j Move the row cursor
→ ←, l h Expand and collapse the row
Enter, Space Start, resume or shut down — whichever the row's state means
x Force off, or delete a snapshot. Still takes the second press
s Snapshots for the row
c Console for the row
r Refresh
, Settings
/ Jump to the filter field
Tab Step through the settings controls
Esc Leave the field, then the sub-view, then the popup

The cursor is shared with the mouse: hovering a row moves it, so there is only ever one highlight on screen.

In the expanded row

Expanding a row draws a border around it and fills it, so the name and its buttons read as one object; collapsing it takes both away again. The actions are a single line of icon pills — hover one to see what it does.

  • Pause (virsh suspend) freezes a running domain; the play arrow resumes it.
  • Reboot (virsh reboot) is an ACPI request, the same class as shut down — a guest with no handler will ignore it.
  • Save state (virsh managedsave) writes the memory image out and stops the domain. Its light goes hollow to say it is shut off but not blank, and the play arrow becomes a restore arrow: starting it puts the guest back exactly where it was.
  • Discard saved state throws that memory image away, so the domain boots cold next time. Two clicks, like force off — the armed pill turns red, and its hover label says what the second click will do.
  • Snapshots opens the snapshot list for that domain.

Above the pills, the row reports the domain's vCPU count and memory, and for a domain that is off, why — crashed, destroyed or shutdown. Those come from virsh dominfo and virsh domstate --reason, asked only when the row is expanded, never on the poll tick.

Snapshots

The back arrow returns to the domain list. A check mark against a name is the current snapshot — the one the domain is sitting on. Each snapshot has revert and delete; both take two clicks, because reverting throws away everything the domain has done since the snapshot was taken. The armed button draws the four-second window down as a bar, so the second click is not on a hidden timer.

The two fields at the bottom take a snapshot: an optional description, and the name — leave the name empty and libvirt names it after the epoch second.

Typed names are tidied first: whitespace and / collapse to single hyphens and surrounding whitespace is dropped, so my snap is stored as my-snap. libvirt itself only refuses / and a leading ., but it will happily store my snap verbatim, and a name you cannot retype is a name you cannot easily revert to. A name that tidies away to nothing gets libvirt's automatic one. Existing snapshots are never renamed — only what you type is cleaned.

Internal snapshots need qcow2 disks. On a raw disk, or a domain with no disk at all, virsh refuses and the popup shows you why.

The settings view

The cogwheel next to refresh opens a settings view, with the back arrow to return. It holds the settings worth changing from the bar rather than from a config file, in three boxes.

Connection — the libvirt URI, as a picker between qemu:///session and qemu:///system (plus whatever you hand-edited into shell.json, if it is neither), and the poll interval. Switching connection empties the list and re-polls; the widget still never starts libvirt, so a connection that is not running comes back Disconnected.

Behaviour — the running count on the bar glyph, the snapshot button in the expanded row, the three confirm switches, and suppressing QEMU crash toasts.

State light colours — the three lights. Each colour row shows a swatch, the value in effect, and a palette button that expands the row into a colour picker: drag in the saturation/value square, pick a hue on the strip below it, or click one of the preset swatches. The value is shown as hex and as R/G/B, and both are editable — type a hex value (#rgb, #rrggbb or #aarrggbb) and press Enter, and anything else is refused with a message rather than being stored. The circular arrow puts that one colour back to its default.

Only the end of a drag is saved, so the state lights in the list settle when you let go while the picker itself tracks the pointer.

Whatever you change here is written into the widget's shell.json entry, so it survives a restart; everything else in Settings below stays hand-edited. The URI is the one value the picker will not let you type: it ends up in every virsh call, so it is chosen from a list rather than entered.

Under the title: Connected when libvirt answered the last poll, Disconnected in red with the error below it when it did not. The URI is on hover. The widget only reads and drives domains — it never starts libvirt, so a dead connection is yours to bring up from a terminal.

virsh shutdown and friends return when the request is queued, not when the guest acts on it, so expect a row to change state a beat after you click.

Prerequisites

sudo pacman -S qemu-full libvirt virt-manager virt-viewer edk2-ovmf
Package Why
libvirt Required. Provides virsh, the entire backend
qemu-full The hypervisor, plus every guest architecture and its firmware
virt-viewer Required for consoles — it is what clicking a domain name opens
virt-manager Only the popup's footer button. Set "manager": "" to hide that row
edk2-ovmf UEFI firmware for guests. Optional, but a modern guest usually wants it

KVM needs no group membership on Arch — /dev/kvm is world-writable by udev rule. Check with test -w /dev/kvm && echo ok.

The default connection, qemu:///session, needs no further setup: virsh spawns virtqemud under your own user on first contact, guests get QEMU user-mode networking, and definitions live in ~/.config/libvirt/. The trade-off is no inbound connections to guests and no networking between them.

For host-wide qemu:///system instead:

sudo usermod -aG libvirt $USER           # then log out and back in
sudo pacman -S dnsmasq                   # the default NAT network needs it
sudo systemctl enable --now virtqemud.socket virtnetworkd.socket virtstoraged.socket
sudo virsh net-autostart default && sudo virsh net-start default

dnsmasq is only an optional dependency of libvirt, but without it the default NAT network cannot hand out DHCP and refuses to start. Hosts on the monolithic daemon use libvirtd.socket in place of the three units above.

Install

omarchy plugin add https://github.com/Leyanora/omarchy-libvirt.git --enable

The widget expects the connection to work without a password prompt. If virsh -c <uri> list --all works in a terminal it works here; if it does not, the popup shows you the same error.

Uninstall

omarchy plugin remove leyanora.libvirt

That is the whole thing. It asks once (--yes skips), unloads the widget, deletes ~/.config/omarchy/plugins/leyanora.libvirt, and drops the widget's entry — and with it every setting you put there — out of ~/.config/omarchy/shell.json.

Nothing of yours is touched: the widget only ever shells out to virsh, so your domains, disks, networks and libvirt config are exactly as you left them. The packages above stay installed.

The one thing that outlives the command is the crash-toast drop-in — and only if you turned it on, and only until you next log in, since it lives in $XDG_RUNTIME_DIR, which is wiped with the session. To be rid of it right now, either switch it off before removing the plugin, which deletes it on the spot, or clean up by hand afterwards:

rm -f "$XDG_RUNTIME_DIR/systemd/user/omarchy-crash-watch.service.d/50-leyanora.libvirt.conf"
systemctl --user daemon-reload
systemctl --user restart omarchy-crash-watch.service

Silencing the shutdown crash toast

Stopping a VM tends to raise an Omarchy "Process crashed: qemu-system-x86_64" notification. That is not this plugin, and not virt-viewer either: libvirt SIGTERMs QEMU, and QEMU segfaults inside its own SPICE teardown on the way out. The guest has already stopped by then, so nothing is lost — it is an upstream bug with no consequence beyond the toast. But you press the button that triggers it here, so this is where it gets handled.

It is therefore offered as a setting, reachable from the cogwheel in the popup header — or as suppressCrashToasts in shell.json, which is the same key. Flipping the switch writes it back to shell.json, so it sticks.

It is off by default. Turned on, the widget writes a drop-in at

$XDG_RUNTIME_DIR/systemd/user/omarchy-crash-watch.service.d/50-leyanora.libvirt.conf

that sets OMARCHY_CRASH_IGNORE for Omarchy's crash watcher — the supported filter, nothing patched — then reloads and restarts it. An unchanged file is left alone.

That path is the runtime unit directory, not ~/.config, deliberately: Omarchy has no uninstall hook for plugins, so a drop-in under ~/.config would outlive the plugin and go on silencing QEMU crashes on a system that no longer has the widget. In the runtime directory it is rewritten on every start and gone at the next login. (Upgrading from an earlier version also sweeps the old ~/.config drop-in away.)

Turn it on from the cogwheel, or with:

{ "id": "leyanora.libvirt", "suppressCrashToasts": true }

Two things worth knowing:

  • The watcher filters on the executable name only, so this also silences a QEMU crash that happens mid-run — a VM dying under you goes unannounced. That is the reason it is off by default.
  • The widget will only ever delete a drop-in carrying its own marker comment, so a file you wrote by hand at either path is left alone.

omarchy-toggle-crash-capture remains the way to turn crash announcements off wholesale, if you would rather not filter per-binary.

Settings

Every key goes in the widget's entry in ~/.config/omarchy/shell.json, keyed by the plugin id — not the display name:

{
  "bar": {
    "layout": {
      "right": [
        { "id": "leyanora.libvirt", "uri": "qemu:///system", "interval": 5 }
      ]
    }
  }
}
Key Default What it does
uri qemu:///session libvirt connection URI. Also selectable from the cogwheel
interval 10 Poll seconds; floored at 2. Also editable from the cogwheel
icon 󰒋 Bar glyph
showCount false Show the running count next to the glyph. Also editable from the cogwheel
confirmForceOff true Require the second click on force off. Also editable from the cogwheel
confirmDiscardSaved true Require the second click on discard saved state. Also editable from the cogwheel
confirmSnapshotRevert true Require the second click on snapshot revert and delete. Also editable from the cogwheel
showSnapshots true Show the snapshots button in the expanded row. Also editable from the cogwheel
console virt-viewer --connect {uri} {name} Console command; {uri} and {name} are substituted shell-quoted
manager virt-manager -c <uri> The popup's footer action; "" hides the row
onRightClick same as manager Right click on the bar item
colorRunning #3fb950 State light, running. Also editable from the cogwheel
colorPaused #d29922 State light, paused. Also editable from the cogwheel
colorStopped #f85149 State light, shut off. Also editable from the cogwheel
suppressCrashToasts false Filter QEMU crash toasts, see above. The one key the popup can also write, from its cogwheel
crashIgnore ^qemu-system- Regex of executable names to filter when the above is on. A % is passed through literally; a value containing a quote or newline, or ending in a backslash, is refused with an error in the popup, because it cannot survive the systemd drop-in intact

IPC

omarchy-shell leyanora.libvirt toggle     # also: open, close, show, hide
omarchy-shell leyanora.libvirt refresh    # re-poll on every monitor

Bind either in ~/.config/hypr/bindings.lua.

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.