Skip to content

Repository files navigation

ANDLER

Android Linux Emulator & Runtime

Rust-native VM orchestration for Linux/KVM — paravirtualized 3D graphics, Android guests, external snapshot trees, live telemetry, and zero-root guest provisioning.



Release GPL-3.0 Linux + KVM Rust stable QEMU + OVMF Zero-root gRPC


Download Release  •  Quick Start  •  CLI Reference  •  Architecture  •  Roadmap  •  FAQ


Run Linux and Android VMs as managed systems — not as a pile of hypervisor flags.

One daemon, one CLI, one declarative instance.toml: lifecycle and supervision over gRPC, Venus/VirGL 3D over virtio-gpu, live overlay snapshots with branching, base images pulled from GitHub Releases, and /proc + vendor GPU telemetry streamed every second.


📑 Navigation

Table of Contents

⚡ At a Glance

Layer Technology What you get
Control plane andlerd daemon + gRPC + thin andler CLI One entry point for lifecycle, supervision, and automation
Hypervisor Linux KVM + QEMU + OVMF/UEFI Hardware-isolated guests with per-instance QMP + QGA sockets
3D graphics Venus (Vulkan) · VirGL (OpenGL) · Virtio-GPU · CPU Near-native host GPU rendering — no dedicated card required
Android layer Waydroid runtime on custom bootable images Android 11 & 13 (VANILLA / GAPPS) with ARM → x86 translation
Storage engine QCOW2 external overlay chains Live snapshots, non-destructive branching, OCI image export
State & config Declarative instance.toml per instance + SQLite snapshot metadata Hand-edited TOML is honored; crash-safe registry scan on restart
Interfaces Interactive wizard · flags · TOML · --json on every reader Scriptable end to end, plus a terminal-native wizard for humans
Security QGA online + the libguestfs appliance offline 100 % zero-root guest provisioning — no sudoers, no helper binary

📌 Project Facts

Guests Linux (ISO install) · Android 11 & 13 (Waydroid, VANILLA / GAPPS)
Host Linux x86_64 with KVM — ~/.andler/, no root for normal operation
Binaries andlerd (daemon) · andler (thin gRPC client)
Protocol gRPC + protobuf, version handshake on every command
Config instance.toml per instance — the file is the source of truth
Snapshots External QCOW2 overlay chains: live create, offline restore, branching
Metrics /proc + AMD sysfs / NVIDIA NVML / Intel i915-xe, 1 s cadence
License GPL-3.0 · Rust, stable toolchain

✦ Why ANDLER

Doing GPU-accelerated VMs on Linux by hand means long QEMU command lines, manual firmware paths, elevated NBD mounts, and separate monitoring scripts. ANDLER collapses the whole stack into one declarative workflow.

🎮 Hardware-Accelerated 3D

Venus (Vulkan) and VirGL (OpenGL) expose the host GPU to the guest over virtio-gpu with blob + hostmem shared memory. Desktop compositors, Vulkan apps, and Android 3D run at near-native rates.

🤖 Turnkey Android Runtime

Bootable Waydroid images with a Weston/GL compositor, GApps variants, boot-mode switching, and transparent ARM64 translation via libndk or libhoudini.

📸 Snapshot Trees & Branching

External QCOW2 overlay chains switched over QMP while the guest runs. Restore offline, branch with --branch, delete commits a layer into its parent — with clone-chain protection.

📊 Telemetry by Default

CPU %, resident RAM, disk and network throughput from /proc, plus VRAM and GPU load from AMD sysfs, NVIDIA NVML, and Intel i915/xe — one stream, one-second cadence.


⏱️ 30-Second Tour

Once installed — one command, see 📦 Installation:

$ andler image download --android-version 13 --variant gapps     # base image from Releases
$ andler create --kind android --name pixel --android-version 13 --arm-translator libndk
$ andler start pixel                                             # boots QEMU/KVM
$ andler metrics pixel                                           # CPU · RAM · disk · net · VRAM · GPU
$ andler snapshot create pixel --tag clean-install                # live overlay snapshot
$ andler connect pixel --level console                            # serial console, ssh, or adb
$ andler export-oci pixel ./oci-out                               # OCI image layout

Everything above is one gRPC request per command against andlerd; nothing needs root, and every reader accepts --json.


🧱 Architecture

flowchart TB
    CLI["andler CLI"] -->|gRPC| RPC["andler-rpc"]
    EXT["External gRPC client"] --> RPC
    RPC --> SUP["Per-instance supervisor<br/>(FSM · single writer)"]

    subgraph Daemon["andlerd"]
        RPC
        SUP
        REG["File registry<br/>instance.toml"]
        SUP --> REG
    end

    SUP --> CORE["andler-core<br/>domain · traits · config"]
    SUP --> QEMU["andler-qemu<br/>QMP · cmdline · /proc"]
    SUP --> DISK["andler-disk<br/>qcow2 · overlays · OCI"]
    SUP --> GUESTFS["andler-guestfs<br/>libguestfs appliance"]
    SUP --> NET["andler-net<br/>NAT · bridge · isolated netns"]
    SUP --> STORE["andler-store<br/>snapshot metadata"]

    QEMU -->|spawn / QMP| QEMUP["QEMU process"]
    QEMUP --> KVM["Linux KVM"]
    QEMUP --> DRM["Host DRM / Vulkan / NVML"]
    KVM --> GUEST["Linux OS · Android (Waydroid)"]
    QEMU -.->|*.qga.sock| QGA["QEMU guest agent"]
    QGA -.-> GUEST
Loading

Workspace map

andler/
├── core/andler-core/        Domain types, HypervisorBackend trait, 9-section config, FSM
├── backends/andler-qemu/    QEMU backend: QMP + QGA, cmdline builder, /proc metrics
├── services/
│   ├── andler-disk/         qemu-img, overlays, clones, zero-root guest ops, base-image downloads
│   ├── andler-guestfs/      GuestfsMutator — libguestfs appliance, zero root
│   ├── andler-net/          NAT, bridge and isolated (netns) networking via iproute2
│   ├── andler-store/        SQLite snapshot metadata + legacy config migration
│   ├── andler-firmware/     OVMF discovery, hardware auto-detect, AMD/NVIDIA/Intel GPU metrics
│   └── andler-rpc/          Protobuf definitions, gRPC, proto ↔ domain conversions
├── apps/daemon/             andlerd — supervisors, event bus, operations, health checks
├── apps/cli/                andler — wizard, diagnostics, terminal monitoring
├── docker/e2e/              Containerized unit / integration / E2E harness
├── docker/images/           Guest base-image pipelines (rootfs → bootable qcow2)
└── docs/                    Architecture, API, gRPC reference, changelog, roadmap

Three boundaries hold the design together: andler-core is the bottom layer and depends on no workspace crate; andler-cli talks only to andler-rpc; a new hypervisor means implementing HypervisorBackend and nothing else. Full detail in docs/ARCHITECTURE.md.


📦 Installation

Important

Prebuilt release archives are the recommended path. Building from source is only needed to work on ANDLER itself.

Each release publishes one artifact set per platform, with the target triple in every file name — what a file runs on is never a guess:

Asset Contains For
andler-<tag>-x86_64-unknown-linux-gnu.tar.gz andler + andlerd, LICENSE, README.md the default: a host that serves VMs and drives them
andler-<tag>-x86_64-unknown-linux-gnu the CLI alone, raw a client machine, talking to a daemon elsewhere
andlerd-<tag>-x86_64-unknown-linux-gnu the daemon alone, raw a host that only serves VMs
SHA256SUMS checksums of all three sha256sum -c SHA256SUMS verifies any download you make by hand

The archive name is the CLI's file name plus .tar.gz, so tar -xzf on a downloaded asset drops a runnable andler next to its daemon. Public releases carry a build-provenance attestation for every artifact.

1 · Install

scripts/install.sh                                                    # local build (PATH or target/release): daemon + CLI + user service
scripts/install.sh --from-release                                     # latest release, same three things
scripts/install.sh --with-optional                                    # …and install the optional dependency set too
scripts/install.sh --dry-run --with-optional                          # show what that would install, change nothing
scripts/install.sh --check-deps                                       # host dependency report only, nothing installed
scripts/install.sh --component cli --no-service --from-release v0.1.0 # a client machine: just the CLI, no systemd

daemon + CLI is the default on purpose: the two verify each other, so the pair is the only combination that cannot drift. The installer walks the host first — the same checks andler doctor runs, grouped by what this install actually covers — and for anything missing prints the command that installs it for your distribution:

▸ Host dependencies
  required for '--component both' + the systemd user service
  ✓ qemu-system-x86_64 · /usr/bin/qemu-system-x86_64
  ✓ qemu-img · /usr/bin/qemu-img
  ✓ OVMF/UEFI firmware · /usr/share/edk2/x64/OVMF_CODE.4m.fd + /usr/share/edk2/x64/OVMF_VARS.4m.fd
  ✓ /dev/kvm · accessible

  optional — a missing one disables only the feature named, everything else works
  ✓ ip (iproute2) · /usr/bin/ip
  ⚠ guestfish (guestfs-tools) · offline guest operations (install/remove, boot mode, ARM translators)
      → sudo pacman -S --needed libguestfs

  ✓ required dependencies · all present
  1 optional dependency is missing — the feature named above stays unavailable

A missing required dependency stops the install with the one command that fixes it (--skip-deps overrides, --check-deps reports and exits); a missing optional one only costs you the feature it names. Everything the tables below list is in this report.

--with-optional installs the missing optional packages through your own package manager (pacman/apt/dnf) and records the set, so scripts/uninstall.sh --optional removes exactly those again — nothing else is ever uninstalled. Two are deliberately left to you: the NVIDIA driver (a distribution-specific kernel-module package that wants a reboot) and oras (not packaged everywhere); the report prints their instructions instead.

Then it verifies the archive against the release's SHA256SUMS, installs into ~/.local/bin (--bin-dir to change it) and — for the daemon — writes and enables the per-user systemd unit. Installing is idempotent: it overwrites, re-checks and re-points the unit at the binary it just installed. Without --from-release the binaries come from a path you pass, from PATH, or from target/release after cargo build --release. When a daemon answers on this host the run closes with andler doctor, whose report is the authoritative version of the one it opened with. --dry-run prints the plan (and the exact package command) and changes nothing; --check-deps and --dry-run are also the two modes that accept being run as root, which is what containers and CI images need.

Download and install by hand
set -euo pipefail
tag=v0.1.0 triple=x86_64-unknown-linux-gnu
base="https://github.com/hateoff0/andler/releases/download/$tag"
bundle="andler-$tag-$triple"

curl -fSLO "$base/$bundle.tar.gz" && curl -fSLO "$base/SHA256SUMS"
sha256sum -c SHA256SUMS                        # the manifest covers the archive and both raw binaries
tar -xzf "$bundle.tar.gz"
install -m 0755 "$bundle"/{andler,andlerd} ~/.local/bin/   # or /usr/local/bin with sudo

The raw binaries install the same way: curl -fSLO "$base/andler-$tag-$triple" for the client alone, andlerd-… for the daemon alone. ~/.local/bin has to be on PATH; for the systemd user unit on top of a hand-installed daemon, run scripts/install.sh --component daemon --bin-dir ~/.local/bin from a checkout — it writes and enables the unit pointing at that binary.

Important

Install both sides from the same release. andler verifies the daemon's version before every command and refuses a daemon built from a different one — a mismatched pair fails at the first command instead of misbehaving quietly.

2 · Verify the host

andler --version      # andler 0.1.0
andlerd --version     # andlerd 0.1.0 — both sides must agree
andler doctor

doctor checks KVM access, QEMU binaries, OVMF firmware, CAP_NET_ADMIN, the zero-root offline prerequisites, daemon reachability, and the base-image cache — every failing line prints the command that fixes it.

3 · The daemon as a user service

scripts/install.sh already wrote and enabled the unit; to check or re-point it:

systemctl --user status andlerd
scripts/install.sh --component daemon --bin-dir ~/.local/bin    # (re)write the unit for that binary
scripts/uninstall.sh                                            # stop, disable, remove the unit
scripts/uninstall.sh --optional                                 # …plus the packages --with-optional installed
scripts/uninstall.sh --binaries --purge                         # …plus the binaries and the data root

uninstall.sh is scoped exactly like the installer: the user unit, the optional packages it recorded (only with --optional), and — with --binaries — the binaries in --bin-dir; ~/.andler — instances, disks, snapshots, database — survives unless --purge is passed, which asks for yes on a terminal (--yes answers for scripts and for the package removal, ANDLER_HOME moves what is deleted).

One daemon per ANDLER_HOME (flock on ~/.andler/andlerd.lock); default listen address 127.0.0.1:50051, overridable with ANDLERD_LISTEN_ADDR.

Build from source (contributors)
cargo build --release          # → target/release/andlerd, target/release/andler
cargo build --workspace
cargo test --workspace

Requires protoc for gRPC code generation. Reproducible builds and the full test harness run in containers — see docker/e2e/README.md.


🚀 Quick Start

0 · Install

git clone --depth 1 https://github.com/hateoff0/andler && cd andler
scripts/install.sh --from-release     # dependency report → daemon + CLI into ~/.local/bin → systemd user unit

The installer checks the host first (KVM, QEMU, OVMF) and prints the command that installs anything missing for your distribution; --with-optional installs the optional set too, --component cli --no-service installs just the client on a machine that talks to a daemon elsewhere, --check-deps only reports. Nothing is built: the binaries come from the latest release, checksum-verified. The archive-by-hand path and the full flag table are in 📦 Installation.

1 · Start the daemon

andlerd            # start by hand, or: systemctl --user status andlerd   (the installer enabled it)

2 · Create a VM — wizard or flags

andler create                                   # interactive wizard, hardware auto-detection
andler create --kind linux --name dev \
  --iso-path ~/isos/ubuntu.iso --disk-size-gib 128 --quick
andler create --template desktop --kind linux --name work --quick

Preview without touching the daemon: --dry-run prints the resolved config and the real QEMU command line; --verify prints a ✓/✗ pre-flight report and exits non-zero on failure.

3 · Create an Android VM — base image from the release catalog

andler image list
andler image download --android-version 13 --variant gapps
andler create --kind android --name pixel --android-version 13 --arm-translator libndk

Every part is checksum-verified against the release manifest before anything lands in the cache; an already-cached build is reused without a request.

4 · Boot, watch, and snapshot

andler start dev
andler metrics dev                 # CPU · RAM · disk · net · VRAM · GPU, every second
andler logs dev --follow
andler snapshot create dev --tag clean-install

🎯 Capabilities

3D Paravirtualized Graphics

[gpu]
render_backend = "venus"    # venus | virgl | virtiogpu | cpu
hostmem_bytes = 4294967296  # 4 GiB shared graphics memory
blob = true                 # virtio-gpu shared memory (required for Venus)
gl = true                   # expose GL contexts
Backend Protocol Best fit
venus Vulkan over virtio-gpu Wayland desktops, Vulkan apps, Android 3D
virgl OpenGL over virtio-gpu GL compositors, classic desktops
virtiogpu 2D virtio-gpu Lightweight VMs, CI nodes
cpu Software framebuffer Headless hosts with no usable DRM node
Display engine sdl (low overhead, default on NVIDIA) · gtk · spice · dbus · none
Resolution Applied inside the guest: passed as QEMU fw_cfg on boot, and switched live on a running VM with andler config set <id> display.resolution WxH through the guest agent.

Turnkey Android Guests

andler create --kind android --name pixel --android-version 13 --arm-translator libndk --gapps
andler guest boot-mode pixel android      # or: linux
andler config set pixel display.resolution 1920x1080
  • Versions & variants — Android 11 and 13, VANILLA or GAPPS, selected by --android-version / --variant.
  • ARM → x86 translation — libndk (Google prebuilt) or libhoudini (Intel), installed offline or online with andler guest install.
  • Compositor — Weston over GL/virgl, which presents on any host GPU; venus stays enabled for guest Vulkan apps.
  • Provenance — each Android instance records base_image_pin = { id, sha256 }; a swapped or tampered backing image is refused at creation.

External Snapshot Trees

base.qcow2 ──▶ snapshot layer ──▶ active disk.qcow2
                        └──▶ branch: experimental-branch
Operation Behaviour
snapshot create Live: the QEMU block graph is switched over QMP to a fresh overlay; the previous disk becomes a layer under disk.snapshots/. Free space is pre-checked with statvfs.
snapshot restore Offline (instance stopped): discards newer layers, or --branch archives the current chain as a branch you can switch back to.
snapshot delete Commits the layer into its parent and re-points children.
snapshot list Human-readable tree, or --json before the subcommand.

Linked clones protect their source chain — a restore or delete that would break a live clone is refused with remove the clones first.

Zero-Root Guest Management

Path Mechanism When
Online qemu-guest-agent over the private *.qga.sock chardev — guest-exec, file writes, resolution changes VM is Running
Maintenance The daemon auto-starts a stopped VM headless, installs through QGA, then stops it again — one cancellable supervisor operation guest install / guest remove on a stopped VM
Offline the libguestfs appliance runs the guest's own package manager as root inside its own QEMU VM --offline, or a VM that cannot boot

Tip

There is no privileged helper binary and no sudoers rule anywhere in ANDLER. andler doctor verifies the offline prerequisite (guestfish, from guestfs-tools) and prints the package that provides it.

andler guest install hello dev           # online or maintenance path, chosen automatically
andler guest apply dev                   # installs what the instance's own config selects
andler guest provision manifest.toml dev # declarative write/cp/mv/rm/chmod/symlink batch

Operations, Hot-plug & OCI Export

andler attach disk dev --path /data/extra.qcow2 --size 32G   # live block hot-plug
andler attach net  dev                                        # live NIC hot-plug
andler detach disk dev --path /data/extra.qcow2
andler op list --json        &&  andler op cancel <op-id>
andler snapshot restore dev --tag clean --idempotency-token deploy-42
andler export-oci dev ./oci-out --disk-format qcow2
  • Long operations run as supervisor sub-tasks with weighted phase progress, one operation per instance, cooperative cancellation, and an in-flight join key: retrying with the same --idempotency-token joins the running operation instead of starting a duplicate.
  • export-oci produces a spec-conformant OCI image layout (oci-layout, index.json manifest list, config.json, rootfs blob under blobs/sha256/).

⚙️ Configuration

Each instance is a directory ~/.andler/instances/<id>/ whose instance.toml is the source of truth — the daemon re-reads it on every state transition, so hand edits are honored. SQLite keeps only snapshot metadata.

name = "dev"
iso_path = "/isos/ubuntu.iso"
disk_path = "~/vm/disk.qcow2"
ovmf_vars_path = "/usr/share/OVMF/OVMF_VARS_4M.fd"
Full reference — 9 sections + extras
name = "workstation"
disk_size_gib = 256          # thin-provisioned qcow2 default
autostart = false            # start with the daemon
snapshot_timeout_secs = 30
compact_on_shutdown = false

[cpu]
cores = 8
sockets = 1
threads = 1
priority = "normal"
affinity = [0, 1, 2, 3]      # taskset pinning + cross-instance overlap gate

[memory]
size_bytes = 17179869184     # 16 GiB
mem_lock = false             # mlock guest RAM (-overcommit mem-lock=on)
hugepages = false            # back RAM with /dev/hugepages

[gpu]
render_backend = "venus"
hostmem_bytes = 4294967296
blob = true
gl = true

[display]
resolution = { width = 1920, height = 1080 }
engine = "sdl"       # sdl | gtk | spice | dbus | none

[network]
mode = "nat"                 # nat | bridge | isolated
nat_backend = "slirp"        # or "passt" when available
device_model = "virtio-net-pci"

[[network.port_forwards]]
protocol = "tcp"
host_port = 2222
guest_port = 22

[audio]
backend = "pipewire"
device = "virtiosound"

[input]
pointer_mode = "tablet"
hide_host_cursor = true
clipboard_enabled = true     # needs spice-vdagent inside the guest

# Android instances instead of iso_path:
# android_version = 13, base_image_path, overlay_size_gib = 128
# gapps = true, arm_translator = "libndk", boot_mode = "android"
Editing surface Behaviour
config view / config edit Prints or opens the real instance.toml in $VISUAL/$EDITOR; the file is authoritative.
config set <key> <value> Walks the full InstanceConfig key schema — every key is either settable or rejected with the reason it is not (e.g. cpu.affinity → edit instance.toml directly).
config status File-vs-memory diff: what instance.toml holds versus what the daemon loaded, plus any pending live resolution.
create --template Built-ins headless / desktop, or your own files under ~/.andler/templates/; precedence is defaults < template < flags.

Guard rails enforced at start: host-port conflicts, a disk already in use by another running instance, overlapping CPU pins, and guest RAM that would exceed host physical RAM.


📊 Observability

andler metrics dev            # stream   ·   --once   ·   --json
cpu=14.2%    rss=2.41 GiB   disk_r=32.4 MB/s  disk_w=1.2 MB/s  net_rx=840 KB/s  net_tx=120 KB/s  vram=1.42 GiB/8.00 GiB  gpu=42%
Metric Source Mechanism
CPU % /proc/<pid>/stat Delta of utime + stime over uptime and core count
Resident RAM /proc/<pid>/status VmRSS
Disk I/O /proc/<pid>/io read_bytes / write_bytes deltas (clamped, non-monotonic safe)
Network /proc/net/dev Delta throughput per interface
VRAM / GPU AMD sysfs · NVIDIA NVML + nvidia-smi fallback · Intel i915/xe First found vendor wins
andler events dev --follow --json     # lifecycle, operations, QMP events
andler logs daemon --follow           # the daemon's own log from an in-memory ring
andler doctor --metrics               # RPC latency p50/p99, errors by status, running guest RAM

Guest operations report where they are while they run: guest install, guest remove, guest apply, guest provision, guest list and guest boot-mode print the daemon's operation phase, its percentage and the elapsed seconds, and the ARM-translator switch is a tracked operation (op list, cancellable) with named stages. Only running instances are touched; installing into a stopped one goes through the appliance, which is why a cold translator switch takes minutes — and now says so.

Every CLI request carries a request_id metadata header, so a daemon log line reconstructs the path CLI → RPC → operation. Guest output is never logged at INFO/WARN — see the redaction policy in docs/ARCHITECTURE.md.


🧭 CLI Cheatsheet

# Lifecycle
andler create | wizard | start | stop [--graceful] | pause | resume | remove [--purge]
andler list [--state running] [--name <regex>] [--sort name] [--json] [-q]
andler status <id> [--json]

# Configuration
andler config view|edit|status <id>       &&      andler config set <id> <key> <value>

# Monitoring
andler metrics <id> [--once|--json]       &&      andler logs <id> [--follow|--grep <re>|--tail N]
andler events [<id>] [--follow|--json]    &&      andler logs daemon [--follow|--json|--since MS]

# Snapshots & operations
andler snapshot create|restore|delete <id> --tag <t> [--branch] [--idempotency-token <k>]
andler snapshot list <id>                 &&      andler op list|cancel [<id>]

# Storage
andler disk create|info|resize [--shrink]|compact <path>
andler clone <id> --name <n> --mode linked|full-standalone|shared-base
andler export <id> <dest>                 &&      andler export-oci <id> <dest> [--disk-format …]
andler attach|detach disk|net <id> [flags]

# Guest
andler connect <id> [--level auto|console|ssh|adb]   &&   andler exec <id> -- <cmd>
andler guest install|remove <pkg> <id> [--offline] [--idempotency-token <k>]
andler guest list|apply|boot-mode|provision <id|manifest>

# Host & images
andler doctor [--metrics]                 &&      andler image list|download [--android-version 13 --variant gapps]
andler cache list|clean [--dry-run|--json]        &&   andler completions <bash|zsh|fish>

Note

--json is accepted by status, list, metrics, config status, create (including --dry-run and --verify), clone, export, disk info, guest list, doctor, cache, image, exec, attach/detach, and op list. On failure it emits a single {"error": "…"} document on stderr.


🌦️ Environment Variables

Variable Applies to Purpose
ANDLERD_LISTEN_ADDR daemon Listen address (default 127.0.0.1:50051)
ANDLERD_STORE_PATH daemon SQLite path (default ~/.andler/andlerd.db)
ANDLERD_OVMF_CODE / ANDLERD_OVMF_VARS daemon Override the discovered OVMF pair
ANDLERD_LOG_FORMAT=json daemon Structured JSON logging (tee'd into the daemon log ring)
ANDLERD_HEALTH_CHECK_INTERVAL_SECS daemon Health-check interval (default 30, 0 disables)
ANDLERD_GUEST_AGENT_WAIT_SECS daemon How long a maintenance auto-start waits for QGA (default 120)
ANDLERD_GUEST_PACKAGE_TIMEOUT_SECS daemon How long one in-guest package-manager step (index refresh, install, remove) may run (default 600, minimum 30) — raise it when a guest's mirrors are slow; this is also the bound of the offline appliance session that runs them
ANDLERD_GUESTFS_TIMEOUT_SECS daemon How long one libguestfs appliance session may run (default 300, minimum 30)
ANDLERD_DEV_RESTART=1 daemon SIGTERM/Ctrl+C leaves VMs running; the next start adopts them
ANDLERD_IMAGE_REPO daemon owner/repo whose releases hold the base images (default hateoff0/andler)
ANDLERD_IMAGE_API_BASE daemon API base for the release catalog (default https://api.github.com)
ANDLERD_IMAGE_TOKEN daemon Token for a private catalog; falls back to GH_TOKEN / GITHUB_TOKEN
ANDLERD_ADDR CLI Daemon address; --daemon-addr overrides (needs a scheme: http://…)
ANDLER_HOME both Root of all ANDLER data (default ~/.andler)
ANDLER_WIZARD_NOT_TTY CLI Test override: force the non-interactive wizard path
RUST_LOG daemon tracing filter, overrides -v / -vv

🗃️ Data Layout

~/.andler/
├── andlerd.db                   SQLite: snapshot metadata only
├── andlerd.lock                 flock — one daemon per ANDLER_HOME
├── instances/<64-hex-id>/
│   ├── instance.toml            Single source of truth for config
│   ├── events.jsonl             Registry audit log (transitions, operations)
│   ├── disk.qcow2               Active writable volume
│   ├── disk.snapshots/          External immutable overlay layers
│   ├── VARS.fd                  Per-instance UEFI NVRAM copy
│   ├── console.log              Serial console transcript
│   └── qemu.log                 QEMU stdout/stderr history
├── cache/
│   ├── base-images/             <stem>.qcow2 + <stem>.manifest.json (flat or androidN-variant/)
│   └── arm-translators/         libndk / libhoudini prebuilts
└── templates/                   User VM templates (headless/desktop built-ins are shipped)

$XDG_RUNTIME_DIR/andler/qmp/
├── <id>.sock                    QMP command monitor
├── <id>.sock.events.sock        Dedicated QMP event monitor
└── <id>.qga.sock                Guest-agent chardev

Instance IDs are 64-hex; commands accept Docker-style prefixes, and list shows the first 12 characters (-q / --full-id for the whole id).


🖥️ Host Requirements & Dependencies

Two commands answer "will ANDLER work on this machine?" with the same verdict: scripts/install.sh --check-deps before installing, andler doctor afterwards. Both print one line per dependency and, for anything missing, the exact command that installs it on your distribution.

Required

Dependency Needed for Package (Arch · Debian/Ubuntu · Fedora)
Linux x86_64 with KVM — /dev/kvm readable/writable, your user in the kvm group every VM; without it QEMU falls back to software emulation, which is not a supported mode kernel kvm-intel/kvm-amd modules + sudo usermod -aG kvm $USER
qemu-system-x86_64 (a full QEMU build with UEFI support) spawning guests qemu-system-x86 · qemu-system-x86 · qemu-kvm
qemu-img disk create/clone/resize/compact, every snapshot layer qemu-img · qemu-utils · qemu-img
OVMF/UEFI firmware pair (OVMF_CODE* + OVMF_VARS*) guest boot; each instance's VARS.fd is copied from the template edk2-ovmf · ovmf · edk2-ovmf
systemd user instance only when installing the daemon as a user service — running andlerd by hand needs none systemd (skip with --no-service)
tar, sha256sum, curl only for the release install path (--from-release) tar, coreutils, curl

The OVMF pair is auto-discovered across the common distro layouts (/usr/share/edk2/x64/, /usr/share/OVMF/, /usr/share/edk2-ovmf/x64/, /usr/share/qemu/); pin your own with ANDLERD_OVMF_CODE / ANDLERD_OVMF_VARS when they live elsewhere.

Optional — each one unlocks exactly one feature

scripts/install.sh --with-optional installs whichever of these your package manager provides, and records the set so scripts/uninstall.sh --optional takes exactly those back out. Two rows are never installed for you — the NVIDIA driver, a distribution-specific kernel-module package that wants a reboot, and oras, which is not packaged everywhere; the report prints their own instructions instead.

Dependency Unlocks Package
ip (iproute2) network.mode = "bridge" and "isolated" — it creates the taps both hand to QEMU iproute2
unshare (util-linux) + unprivileged user namespaces + /dev/net/tun network.mode = "isolated": the guest's QEMU builds its tap inside its own user namespace, so no host capability is involved util-linux, modprobe tun
CAP_NET_ADMIN on andlerd network.mode = "bridge" sudo setcap cap_net_admin+ep $(command -v andlerd)
passt NAT through passt instead of the built-in slirp (auto-detected; slirp is the fallback, not an error) passt
guestfish (guestfs-tools) every offline guest operation: guest install/remove --offline, boot-mode and ARM-translator switches, guest apply, and the maintenance auto-start fallback libguestfs · libguestfs-tools · guestfs-tools
debugfs (e2fsprogs) reading build.prop out of the Waydroid system.img while staging an ARM translator e2fsprogs
lspci (pciutils) GPU vendor auto-detection when /sys/class/drm has no usable card node pciutils
glxinfo (mesa-utils) reporting the host Mesa version that decides Venus vs VirGL for a new instance mesa-utils · mesa-utils · mesa-demos
NVIDIA driver (nvidia-smi and/or NVML) NVIDIA VRAM and GPU-load metrics in andler metrics (AMD sysfs and Intel i915/xe need nothing) your driver package: nvidia-utils · nvidia-driver · akmod-nvidia
oras pushing an exported OCI layout to a registry — export-oci itself writes the layout and needs nothing upstream: https://oras.land (Arch: AUR oras)
host Mesa with the Venus/VirGL drivers 3D at all: Venus (Vulkan) and VirGL (OpenGL) are Mesa drivers on the host talking to the guest's virtio-gpu your distro's mesa packages
protoc (protobuf-compiler) building from source only protobuf · protobuf-compiler · protobuf-compiler

One-liners

# Arch / CachyOS / EndeavourOS / Manjaro
sudo pacman -S --needed qemu-system-x86 qemu-img edk2-ovmf \
    iproute2 util-linux e2fsprogs pciutils mesa-utils libguestfs passt

# Debian / Ubuntu
sudo apt install qemu-system-x86 qemu-utils ovmf \
    iproute2 util-linux e2fsprogs pciutils mesa-utils libguestfs-tools passt

# Fedora / RHEL family
sudo dnf install qemu-kvm qemu-img edk2-ovmf \
    iproute2 util-linux e2fsprogs pciutils mesa-demos guestfs-tools passt

# every distro: KVM access for your user, then a re-login
sudo usermod -aG kvm "$USER"

Nothing else runs as root: the daemon is unprivileged, offline guest work happens inside the libguestfs appliance (a QEMU VM of its own), and Bridge mode is the single feature that additionally wants CAP_NET_ADMIN. Build-time prerequisites — Rust toolchain, Docker for the test harness — live in docs/DEVELOPMENT.md.


🩺 Diagnostics

andler doctor            # read-only; works even with the daemon down
andler doctor --json     # {"overall": "ok" | "needs_attention", "checks": [...]}

Before anything is installed, scripts/install.sh --check-deps prints the host half of this report — KVM, QEMU, OVMF, and every optional tool below — with the per-distro install command for each gap.

andler doctor

Hypervisor
  ✓ /dev/kvm: accessible
  ✓ qemu-system-x86_64: /usr/bin/qemu-system-x86_64
  ✓ qemu-img: /usr/bin/qemu-img
  ✓ OVMF/UEFI firmware: /usr/share/OVMF/OVMF_CODE_4M.fd + /usr/share/OVMF/OVMF_VARS_4M.fd
  ⚠ CAP_NET_ADMIN: not held — bridge networking will fail
      → setcap on andlerd, or use network mode = "nat"

Offline guest operations (libguestfs appliance)
  ✓ oras: /usr/bin/oras
  ✓ guestfish: /usr/bin/guestfish

Daemon
  ✓ andlerd: reachable at http://127.0.0.1:50051
Base images
  ✓ 4 build(s) cached in ~/.andler/cache/base-images (12.4 GiB)
Symptom Fix
andlerd is not running The address needs a scheme — --daemon-addr http://127.0.0.1:50051, not a bare host:port.
/dev/kvm present but denied sudo usermod -aG kvm $USER, then re-login.
Instance stuck in Error andler status <id> records why (health check, backend loss, refused start); andler start <id> retries from Error.
A failed start, reason unclear Tail ~/.andler/instances/<id>/qemu.log and console.log — andler logs <id> streams the same.
Offline guest install fails andler doctor shows the missing prerequisite; retry with --offline only for a VM that cannot boot.
scripts/install.sh refuses to install A required dependency is missing: the report names it and prints the one command that installs everything missing on your distribution. --skip-deps installs anyway (the covered features will fail), --check-deps runs the report on its own.
Two daemons One daemon per ANDLER_HOME; point ANDLER_HOME elsewhere for a test run.

❓ FAQ

Do I need a second GPU?

No. Venus (Vulkan) and VirGL (OpenGL) share your existing host GPU over virtio-gpu; that is the supported 3D path. VFIO passthrough is reserved in the domain model but not implemented, and no physical card dedication is required for acceleration.

Is root required?

No. The daemon runs as your user and performs no privileged operations: online guest work goes over the guest agent, offline work inside the libguestfs appliance, a QEMU VM of its own. Bridge networking is the one feature that additionally wants CAP_NET_ADMIN on andlerd; nat mode needs nothing.

Does it work on NVIDIA hosts?

Yes. The Android session composites through Weston over GL rather than a Vulkan-only compositor, so frames present on any host GPU; sdl is the default display engine when an NVIDIA GPU is detected.

Can I run everything headless?

Yes — set display.engine = "none", or create from the built-in headless template. Metrics, logs, events, exec, and the guest agent all work with no display attached.

Where do base images come from?

The project publishes bootable Android images to GitHub Releases; andler image list / image download verify every part against the release manifest and install them into the cache. You can also build one locally with the pipeline in docker/images/.

Can snapshots be taken on a running VM?

Creating is live (the block graph is switched over QMP). Restoring and deleting are offline by design: they rewrite the chain, so the instance must be stopped. --branch restores without discarding the current chain.

Windows or macOS guests?

Not supported. ANDLER targets Linux and Android guests on x86_64 with KVM.

Why was my start refused?

Start-time gates refuse a VM before QEMU is touched: a host port already bound, a disk already in use by another running instance, a CPU pin overlapping another running VM, or guest RAM that would exceed host physical memory. The error names the conflicting instance and what to do.


🧪 Testing & Verification

Tier Command Needs KVM
Unit cargo test --workspace No
Integration cargo test --workspace -- --ignored Yes
gRPC round-trip cargo test -p daemon grpc_roundtrip No
E2E docker compose -f docker/e2e/compose.yaml run --rm e2e Yes

The E2E orchestrator starts a fresh daemon and store per suite under a wall-clock watchdog and runs every suite in docker/e2e/tests/NN_*.sh — lifecycle and FSM negatives, config, disk, external snapshots and chain reconciliation, clone/export, hot-plug, guest operations online and offline, preview/verify, persistence and adoption, operations, connect/exec, port forwards, boot mode, version handshake, events, daemon logs and metrics, autostart, the start-time conflict gates, affinity/mem-lock/hugepages, --json output, OCI export, and base-image downloads against a fixture release server.

Before any change lands: cargo build --workspace → cargo test --workspace → cargo clippy --workspace -- -D warnings → cargo fmt --all -- --check.


📈 Project Metrics

ANDLER repository card ANDLER repository card Most used languages Most used languages



Stars Forks Issues Repo size Code size

Cards by github-stats-extended · badges by Shields.io

Note

The cards and badges resolve once this repository is public; each card follows the reader's colour scheme (dark/light).


🤝 Contributing

  1. Read docs/DEVELOPMENT.md and the README of the crate you are touching.
  2. Branch from main; every commit keeps the gate green — a branch is not a WIP dump.
  3. Conventional Commits — feat(qemu): …, fix(disk): …, docs(cli): … — one logical change per commit.
  4. Run the full gate, then re-run it after any later edit:
cargo build --workspace
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --all -- --check

User-visible behavior also needs an E2E suite that exercises its positive path, and the owning doc updated in the same commit.


📚 Documentation

Document Owns
docs/ARCHITECTURE.md Architecture, crate responsibilities, FSM, snapshot mechanism, redaction policy
docs/API.md CLI reference with every flag, TOML config format
docs/GRPC_API.md Proto messages, RPC list, gRPC status-code table
docs/DEVELOPMENT.md Development workflow, crate layout, adding a config field or RPC
docs/CHANGELOG.md User-visible changes
docs/ROADMAP.md Now / next / later plans
docker/e2e/README.md Containerized build, test and E2E targets
Crate README.md files Crate-local behavior — e.g. QEMU wire schemas in backends/andler-qemu/README.md

🙏 Acknowledgments

ANDLER is mostly glue around excellent work by other people. Every link below is the source of truth for the component it names.

🧩 Hypervisor & kernel

  • QEMU — machine emulation; qemu-img drives every disk operation
  • Linux KVM — the hardware virtualization ANDLER targets
  • virtio — paravirtualized devices (blk, net, gpu, serial, sound)
  • OVMF / edk2 — UEFI firmware for guest boot
  • libguestfs — the appliance behind zero-root offline guest work

🎮 Paravirtualized graphics

  • Mesa Venus — Vulkan over virtio-gpu
  • VirGL — OpenGL over virtio-gpu
  • ANGLE — GL on Vulkan inside modern Android stacks

🤖 Android & Waydroid

  • Waydroid — the Android-in-container runtime ANDLER boots
  • waydroid-nvidia — GPU-accelerated Waydroid on the NVIDIA driver, container-native and without VFIO passthrough: Vulkan (Venus) is proxied over a unix socket to a host renderer, and buffers reach the compositor as native NVIDIA dmabufs. The reference for making Android 3D work on NVIDIA hardware.
  • waydroid_script — reference for translator pins, build.prop keys and binfmt registration
  • waydroid-helper — reference packaging for libndk / libhoudini
  • libndk_translation prebuilt · libhoudini prebuilt — ARM → x86 translation runtimes ANDLER installs
  • binfmt_misc — how the kernel hands ARM ELF binaries to those runtimes

🏛️ Architecture & design references

  • containerd — gRPC daemon shape: plugin services, snapshots, event streaming, thin client
  • Proxmox VE — typed parameter validation and error → status mapping
  • quickemu — host-capability-driven QEMU command-line building

README craft — awesome-readme · Best-README-Template · readme-md-generator

Rust ecosystem — tokio · tonic · prost · clap · serde · tracing · thiserror · rusqlite · nvml-wrapper


📄 License

ANDLER is licensed under the GNU General Public License v3.0 — see LICENSE.


Releases · Issues · Discussions · CHANGELOG · ROADMAP · Back to top

GPL-3.0 © the ANDLER contributors

About

A lightweight Rust daemon and CLI for QEMU/KVM delivering GPU acceleration for Linux/Android (Waydroid) guests, featuring declarative configs, live snapshot branching, and rootless provisioning.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages