Skip to content
bitstuffingPublic

About

Boot physical disks, partitions, and disk images as QEMU/KVM virtual machines or run installed Linux systems through chroot or systemd-nspawn; all from one interactive command-line tool

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ved — Virtual External Devices

Boot a physical disk, a partition or a disk image as a virtual machine, or enter an installed Linux system as a chroot / container — from one command.

ved is a single, modular Bash tool that wraps QEMU/KVM, chroot and systemd-nspawn behind a friendly, interactive menu. It auto-detects your disks, EFI loaders, GPUs, TPM and USB devices, installs missing packages for your distribution, and asks for the administrator password only once.


Table of contents


Features

  • One entry point: ./ved. Run it with no arguments for an interactive menu.
  • Physical disks, partitions or images: pass a whole disk (--disk), a Linux root partition (--partition) or a disk image (--image).
  • Three backends: QEMU/KVM, chroot, and systemd-nspawn.
  • Automatic detection of disks, EFI system partitions, EFI loaders, guest OS, DRM render nodes, hardware TPM and USB devices.
  • OpenGL acceleration through virtio-vga-gl and a selected DRM render node, with a safe fallback chain (spice-gl → spice → …).
  • TPM via hardware passthrough or a software TPM (swtpm), and optional Secure Boot OVMF firmware.
  • USB auto-passthrough: webcam and Bluetooth are passed automatically; Android phones are detected and offered. Includes a host-side Bluetooth reset trick so wireless earbuds (e.g. AirPods) work inside the VM.
  • Storage modes: temporary (discard changes on exit) or persistent.
  • Automatic dependency installation for your distribution, or --no-install.
  • Internationalization with English and Spanish out of the box.
  • Dry run (--dry-run) to print the exact command without running it.
  • Administrator password asked once, then re-executes through sudo cleanly.
  • Graphical desktop sessions in chroot/nspawn: launch a full desktop session (GNOME, KDE Plasma, Xfce, …) or a single app from an installed system, with GPU acceleration, Wayland/X11, audio and input devices.
  • GNOME 49+ aware: ved inspects the installed system and, when GNOME no longer supports X11, starts the shell as a nested Wayland window instead of Xephyr.
  • nspawn status bar (Ctrl+A prefix): when booting a full system with --boot, a persistent status bar shows machine name/PID and lets you Stop/Reboot/Poweroff/Quit without leaving the terminal.

Requirements

  • Linux with Bash 4.3+.
  • KVM (/dev/kvm) for hardware acceleration — falls back to TCG (slow) if absent.
  • A graphical session if you want a windowed VM (SPICE/GTK viewer) or a graphical chroot/nspawn session.

ved checks for its dependencies and can install them automatically. The logical dependencies are:

Purpose Packages (logical)
Virtualization qemu-system-x86 / qemu-full / qemu
UEFI firmware OVMF (edk2-ovmf, ovmf, …)
FAT tooling dosfstools (mkfs.vfat)
Core tooling util-linux, coreutils, findutils
SPICE viewer (optional) virt-viewer (remote-viewer, virt-viewer, spicy)
Software TPM (optional) swtpm
Containers (nspawn backend) systemd-container
Nested X11 (optional) Xephyr (only for guests that still support X11 sessions)

Requirements inside the installed (guest) system for a full graphical desktop:

Guest desktop What it needs
GNOME 49+ (Ubuntu 25.10+, Fedora 43+, …) mutter-devkit (Ubuntu: package mutter-dev-bin)
GNOME ≤ 48, KDE Plasma, Xfce, MATE, Cinnamon, … the usual session binary (gnome-session, startplasma-x11, startxfce4, …)
Any dbus-run-session (package dbus-daemon / dbus-x11)

Supported distributions

Package names are resolved automatically for these families:

Debian/Ubuntu/Mint/Pop/Kali · Fedora/RHEL/CentOS/Nobara/Rocky/AlmaLinux · Arch/Manjaro/EndeavourOS/CachyOS · openSUSE/SLES · Alpine · Void.

If the package manager is unknown, use --no-install and provide the tools yourself.

Installation

git clone https://github.com/bitstuffing/virtual-external-devices.git
cd virtual-external-devices
chmod +x ved
./ved

There is nothing to build. You can also symlink ved into your PATH:

sudo ln -s "$PWD/ved" /usr/local/bin/ved

Quick start

Run it with no options — the interactive menu guides you through everything:

./ved

The tool will:

  1. scan and list your physical disks, and ask which one to use;
  2. ask what to do with it (virtual machine, chroot or container);
  3. ask for the administrator password (once);
  4. mount the system, inspect it and detect the EFI loader, guest OS, desktop, memory, CPUs and storage mode;
  5. show a summary of exactly what it is about to launch, and start it.

Backends

Backend Flag What it does
QEMU/KVM -b qemu Runs a physical disk, a partition or an image as a full virtual machine (UEFI + SPICE).
chroot -b chroot Mounts an installed Linux system and enters it with chroot. Can bind the host display/audio with --graphical.
systemd-nspawn -b nspawn Mounts an installed Linux system and runs it as a container; --boot boots it with systemd.

Security: chroot is not a security boundary. It runs with host privileges and exposes host kernel interfaces to the installed system. Use it only with installations you trust; use QEMU for untrusted disks or images.

Interactive flow

Available disks:
  1) /dev/nvme0n1  ...
Which disk do you want to use? [1]:

What do you want to do?
  1) Start it as a virtual machine (QEMU/KVM)
  2) Enter an installed Linux system (chroot)
  3) Run an installed Linux system as a container (systemd-nspawn)
Option [1]:

What do you want to launch inside the system?
  1) An interactive shell
  2) A single graphical application
  3) A full desktop session
  4) Boot the whole system (init/systemd)
Option [1]:

Where should the graphical session be shown?
  1) On the current desktop (host screen)
  2) In a separate nested window (Xephyr)
Option [1]:

The last question is skipped automatically when the installed desktop cannot run inside Xephyr (see GNOME 49+).

USB devices (webcam, Bluetooth/AirPods, phones)

By default (--usb-auto auto) ved scans the USB bus and:

  • Webcams (USB video class) are passed to the VM automatically.
  • Bluetooth adapters are passed with guest-reset=true,guest-resets-all=true, and shortly after boot the host toggles the adapter's authorized flag 0 → 1 to recover it. This is what lets wireless earbuds such as AirPods work inside the guest.
  • Android phones (known vendors or MTP interface) are detected and you are asked whether to pass them.

Control the behavior with:

Option Effect
--usb-auto auto Pass webcam + Bluetooth, offer phones (default).
--usb-auto all Also pass detected phones without asking.
--usb-auto none / --no-usb Disable USB auto-detection.
--usb VID:PID Pass a specific device (repeatable).
--reset-bluetooth VID:PID Reset a device shortly after the VM starts (repeatable).

The network is provided by an emulated e1000e NIC (--network user), which avoids flaky vendor Wi-Fi/Bluetooth drivers inside the guest.

Display and OpenGL

Display modes: auto, spice-gl, spice, gtk-gl, gtk, sdl, vnc, none.

In auto mode, ved prefers a GPU-accelerated SPICE/GTK window and falls back gracefully if the viewer or render node is unavailable. Force options with --gl / --no-gl and --render-node /dev/dri/renderDXXX. When OpenGL is active, ved reports the render node in use.

TPM and Secure Boot

  • --tpm / --no-tpm: hardware TPM passthrough when /dev/tpm0 or /dev/tpmrm0 exists, otherwise a software TPM via swtpm.
  • --secure-boot: selects the Secure Boot OVMF firmware.

Languages

The interface ships in English and Spanish. Choose explicitly with --lang en / --lang es (-L), otherwise it follows your locale. Adding a language is a matter of copying locale/en.sh and translating the values.

Graphical desktop sessions (chroot / nspawn)

With --graphical (or --session desktop in the menu) you can launch a full desktop environment from an installed system:

# chroot backend (lightweight, no PID 1 isolation)
./ved --backend chroot --partition /dev/nvme0n1p2 --graphical --session desktop

# nspawn backend (container; no systemd user session unless --boot is used)
./ved --backend nspawn --partition /dev/nvme0n1p2 --graphical --session desktop

What gets bound automatically:

Resource chroot nspawn
X11 socket (/tmp/.X11-unix, read-write) ✓ ✓
Wayland socket (exported into the private runtime dir) ✓ ✓
GPU/DRM (/dev/dri, read-write) ✓ ✓
Audio (/dev/snd, PipeWire/Pulse) ✓ ✓
Input devices (/dev/input) ✓ ✓
System D-Bus (/run/dbus) ✓ ✓ (without --boot)
Session D-Bus ✓ (dbus-run-session) ✓ (dbus-run-session)
Private user runtime dir (/run/user/$UID) ✓ ✓
Xauthority ✓ ✓

The guest gets its own private XDG_RUNTIME_DIR, owned by the graphical user. Only the host sockets it actually needs are exported into it (the Wayland compositor socket for the host target, plus the PulseAudio/PipeWire audio sockets). The host's /run/user/$UID is never bind-mounted as a whole: sharing it would make the guest's systemd, keyring, gvfs, Remmina and D-Bus daemons collide with the host's live sockets.

The session is wrapped with dbus-run-session so GNOME/KDE can start their required services (gvfs, keyring, etc.). systemd-run --user is used only when a systemd user manager is actually running inside the container; a container started without --boot has no PID 1 and therefore no user manager, so the host's systemd socket must not be used.

The system root is mounted before the graphical questions are asked, so ved can inspect the installed desktop and offer only the options that will work.

GUI target (--gui / --gui-target):

  • host (default): render on your current Wayland/X11 session. GPU acceleration is available.
  • nested: start a nested Xephyr X server (isolated window). Xephyr has no DRI3, so the session is forced to software rendering (LIBGL_ALWAYS_SOFTWARE=1); GPU acceleration is only available with the host target. Only valid for desktops that still support X11 sessions.

GNOME 49 and newer

GNOME Shell 49 (for example Ubuntu 25.10) no longer supports X11 sessions. Running gnome-session inside Xephyr starts the background services (Remmina, Evolution, gvfs, …) but never starts the shell, which shows up as a permanent black screen. Leftover *-xorg.desktop entries under /usr/share/xsessions do not change that.

When ved detects GNOME ≥ 49 in a desktop session it:

  1. forces the host target (and skips the Xephyr question);

  2. starts the shell directly as a nested Wayland window on your desktop:

    dbus-run-session -- gnome-shell --devkit --wayland --wayland-display ved-$$
  3. warns if the guest has no mutter-devkit instead of leaving you with a black screen.

Trade-offs: this path does not go through gnome-session, so autostart applications are not launched. That is usually desirable for a rescue/inspection desktop. Passing your own command (--command) disables the automatic selection.

Status: the chroot backend has been tested with Ubuntu 25.10 (GNOME 49) guests. The nspawn path uses the same command and the bindings listed above; it is considered experimental for GNOME 49+.

nspawn status bar (Ctrl+A prefix)

When using --backend nspawn --boot (or selecting "Boot the whole system" in the menu), a persistent status bar appears at the bottom of your terminal:

 Machine: ved-nvme0n1p2-12345  PID: 67890  Ctrl+A then: [S]top [R]eboot [P]oweroff [Q]uit ved
  • Normal typing passes through to the container (no key conflicts)
  • Ctrl+A enters command mode — the bar changes to show available actions
  • In command mode: S = terminate, R = reboot, P = poweroff, Q = quit ved
  • Esc / Ctrl+C / Ctrl+A cancels command mode

This works like tmux's prefix key or vi's command mode — accessible, no accidental keypresses, and you never need another terminal to manage the container.

Options

Common options:
  -b, --backend NAME    qemu, chroot or nspawn (default: qemu)
  -L, --lang CODE       Language code: en, es (default: from the environment)
  -d, --disk DEVICE     Physical disk, for example /dev/nvme0n1
  -i, --image FILE      Disk image file (QEMU backend)
  -p, --partition DEV   Linux root partition (chroot/nspawn backend)
      --root-partition DEV
                        Alias for --partition
      --esp DEVICE      EFI system partition (auto-detected by default)
      --os TYPE         auto, linux or windows (default: auto)
      --efi-loader PATH EFI loader relative to the ESP (no `..`), or empty for the UEFI menu
      --storage MODE    temp or persistent (default: ask)
      --ram SIZE        Positive memory size, for example 8G or 8192M
      --cpus N          Number of virtual CPUs
      --accel MODE      auto, kvm or tcg (default: auto)
      --tpm             Enable TPM passthrough or a software TPM
      --no-tpm          Disable the TPM
      --secure-boot     Use the Secure Boot OVMF/AAVMF firmware
      --no-secure-boot  Disable Secure Boot
      --boot-menu       Show the QEMU boot menu (default)
      --no-boot-menu    Hide the QEMU boot menu
      --network MODE    user or none (default: user)
      --usb VID:PID     Pass a USB device to the VM; can be repeated
      --usb-auto MODE   auto, all or none (default: auto)
      --no-usb          Do not auto-detect USB devices
      --reset-bluetooth VID:PID
                        Reset a USB device shortly after the VM starts
      --display MODE    auto, spice-gl, spice, gtk-gl, gtk, sdl, vnc or none
      --gl              Force OpenGL acceleration
      --no-gl           Disable OpenGL acceleration
      --render-node DEV Force a DRM render node, for example /dev/dri/renderD128
      --user NAME       User to use inside a chroot or nspawn container
  -c, --command CMD     Command to run in the chroot or nspawn backend
      --graphical       Bind the host display and audio into chroot/nspawn
      --boot            Boot the guest with systemd-nspawn instead of a command
      --session MODE    shell, app, desktop or system (default: ask)
      --app CMD         Run one graphical application (implies --session app)
      --gui TARGET      Host or nested Xephyr display: host or nested (default: ask)
      --gui-target TARGET
                        Alias for --gui
      --nested          Shortcut for --gui nested
      --host-display    Shortcut for --gui host
      --arch ARCH       QEMU guest architecture: auto, x86_64 or aarch64

Other options:
      --config FILE     Load a configuration file before applying options (trusted Bash code)
      --no-install      Do not install missing packages
  -y, --yes             Answer yes to confirmation prompts
  -n, --dry-run         Print the command instead of running it
      --keep-temp       Keep temporary files after exit
  -v, --verbose         Show debugging output
  -h, --help            Show this help
  -V, --version         Show the version

Configuration security: --config sources the file as Bash, including after privilege escalation. Only load configuration files you control and trust.

Examples

# Interactive menu
./ved

# Boot a physical disk as a VM with 16 GB of RAM and 10 CPUs
./ved --disk /dev/nvme0n1 --ram 16G --cpus 10

# Boot a Windows disk with TPM and Secure Boot
./ved --disk /dev/nvme0n1 --os windows --tpm --secure-boot

# Boot a disk image, force software rendering
./ved --image ~/win11.qcow2 --no-gl

# Print the QEMU command without running it
./ved --disk /dev/nvme0n1 --dry-run

# Enter an installed Linux system with chroot, sharing the host display
./ved --backend chroot --partition /dev/nvme0n1p2 --graphical

# Launch a full desktop from an installed system (chroot).
# GNOME 49+ is detected automatically and started with gnome-shell --devkit.
./ved --backend chroot --partition /dev/nvme0n1p2 --graphical --session desktop

# Same with nspawn (experimental for GNOME 49+)
./ved --backend nspawn --partition /dev/nvme0n1p2 --graphical --session desktop

# Inspect the exact command that would be used for a desktop session
./ved --backend chroot --partition /dev/nvme0n1p2 --graphical --session desktop --dry-run

# Boot a Linux install as a container with systemd (status bar with Ctrl+A)
./ved --backend nspawn --partition /dev/nvme0n1p2 --boot

# Run a single graphical app (e.g. Firefox) from an installed system
./ved --backend nspawn --partition /dev/nvme0n1p2 --graphical --session app --command firefox

# Force a specific desktop command instead of the automatic selection
./ved --backend chroot --partition /dev/nvme0n1p2 --graphical --session desktop \
  --command 'dbus-run-session -- gnome-shell --devkit --wayland --wayland-display ved-0'

# Pass a specific USB device and reset the Bluetooth adapter
./ved --disk /dev/nvme0n1 --usb 413c:301a --reset-bluetooth 8087:0033

Project layout

ved                     single entry point
lib/
  core.sh               globals, CLI parsing, privilege handling, dispatch
  i18n.sh               locale loading and message helpers
  platform.sh           distribution detection and dependency installation
  ui.sh                 menus, prompts, configuration summary
  disks.sh              disk/partition/EFI-loader/guest-OS detection
  firmware.sh           OVMF, TPM, audio, render nodes, USB detection
  display.sh            display mode selection and viewer launch
  session.sh            guest desktop probing and session setup
  backends/
    qemu.sh             QEMU/KVM backend
    chroot.sh           chroot backend
    nspawn.sh           systemd-nspawn backend
locale/
  en.sh                 English messages
  es.sh                 Spanish messages
tests/
  cli_test.sh           non-privileged CLI regression tests
.github/workflows/
  ci.yml                GitHub Actions validation workflow

Development and testing

The checks do not mount devices, request sudo, start QEMU, or modify guest systems. Run them locally from the repository root:

make syntax  # Parse every Bash file
make test    # Run non-privileged CLI regression tests
make lint    # Run ShellCheck (requires shellcheck)
make check   # Run syntax, tests and ShellCheck

GitHub Actions runs make check for every push and pull request. On Fedora/Nobara, install ShellCheck locally with sudo dnf install ShellCheck; on Debian/Ubuntu use sudo apt install shellcheck.

Troubleshooting

  • "Hardware virtualization is not available" — enable VT-x/AMD-V in your BIOS/UEFI (/dev/kvm must exist). Without it ved uses TCG, which is very slow.
  • No SPICE viewer found — install virt-viewer (or use spicy), or use --display none for a headless run.
  • OpenGL context errors — try --no-gl, or force a specific GPU with --render-node /dev/dri/renderD128.
  • Bluetooth disappears after the VM exits — ved resets the adapter automatically; if it is still missing, re-run or pass --reset-bluetooth VID:PID.
  • Run ./ved --dry-run … to inspect the exact command and diagnose flags.
  • Black screen with a nested Xephyr window and GNOME 49+ (Ubuntu 25.10+) — GNOME Shell 49 no longer supports X11, so gnome-session in Xephyr starts services but no shell. ved detects this and uses gnome-shell --devkit on the host display. The guest needs mutter-devkit (Ubuntu: mutter-dev-bin). Check the guest's GNOME version with chroot <root> gnome-shell --version.
  • GNOME 49+ needs mutter-devkit in the guest — install the package that provides /usr/libexec/mutter-devkit in the installed system (Ubuntu: mutter-dev-bin; dpkg -S /usr/libexec/mutter-devkit tells you which one).
  • Failed to start X Wayland: Directory "/tmp/.X11-unix" is not writable (nspawn) — the X11 socket directory must be bound read-write; the shell creates its own Xwayland socket there. ved binds it read-write.
  • Created surfaceless renderer without GPU (nspawn) — /dev/dri was bound read-only or the container is not allowed to use the DRM devices. ved binds it read-write; if the GPU is still missing, bind the render node explicitly and allow it with --property=DeviceAllow.
  • Couldn't connect to system bus / endSessionDialog.js exception, then could not connect to wayland server (nspawn) — the guest shell needs the system D-Bus (accounts service). Without --boot there is no system bus in the container, so ved binds the host's /run/dbus.
  • Mixed GNOME versions in the guest (e.g. gnome-shell 49~beta with mutter 49.0) — happens on systems installed from a beta ISO and never fully upgraded. Run apt full-upgrade (or the equivalent) inside the installed system before reporting desktop problems.
  • GNOME/KDE falls back to "Something has gone wrong" screen — ensure your user exists in the guest (UID 1000), use --gui host, or check journalctl --user -f inside the session for gnome-shell errors. For GNOME < 49, --gui nested (Xephyr) can help to isolate Wayland issues.
  • Failed to connect to user scope bus via local transport: No data available — systemd-run --user was used in a container that was not booted with --boot. There is no systemd user manager inside it; ved falls back to dbus-run-session.
  • DRI3 error / GL Helper exited with code 512 in Xephyr — expected with a nested server. ved forces LIBGL_ALWAYS_SOFTWARE=1 for the nested target. Use --gui host if you need GPU acceleration.
  • ERROR: NVIDIA driver is not loaded — the guest ships NVIDIA userspace libraries but the NVIDIA devices are not available inside the chroot/container. Harmless; the session falls back to Mesa on the other GPU.
  • couldn't connect to control socket ... /keyring/control or fuse: failed to access mountpoint .../gvfs — the guest was reusing the host's /run/user/$UID. ved creates a private runtime directory for the guest instead.
  • nspawn status bar not appearing — requires a real TTY (-t); it won't show in scripts or pipes. Use --dry-run to verify the command.

License

This project is released under the GNU General Public License v3.0 (GPLv3). See the full text at https://www.gnu.org/licenses/gpl-3.0.html.

You are free to use, study, share and improve it; derivative works must remain under the same license, keeping these community freedoms for everyone.


Made by @bitstuffing with love.

About

Boot physical disks, partitions, and disk images as QEMU/KVM virtual machines or run installed Linux systems through chroot or systemd-nspawn; all from one interactive command-line tool

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages