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.
Download Release • Quick Start • CLI Reference • Architecture • Roadmap • FAQ
One daemon, one CLI, one declarative
instance.toml: lifecycle and supervision over gRPC, Venus/VirGL 3D overvirtio-gpu, live overlay snapshots with branching, base images pulled from GitHub Releases, and/proc+ vendor GPU telemetry streamed every second.
Table of Contents
- ⚡ At a Glance
- 📌 Project Facts
- ✦ Why ANDLER
- ⏱️ 30-Second Tour
- 🧱 Architecture
- 📦 Installation
- 🚀 Quick Start
- 🎯 Capabilities
- ⚙️ Configuration
- 📊 Observability
- 🧭 CLI Cheatsheet
- 🌦️ Environment Variables
- 🗃️ Data Layout
- 🖥️ Host Requirements & Dependencies
- 🩺 Diagnostics
- ❓ FAQ
- 🧪 Testing & Verification
- 📈 Project Metrics
- 🤝 Contributing
- 📚 Documentation
- 🙏 Acknowledgments
| 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 |
| 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 |
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.
|
Venus (Vulkan) and VirGL (OpenGL) expose the host GPU to the guest over |
Bootable Waydroid images with a Weston/GL compositor, |
|
External QCOW2 overlay chains switched over QMP while the guest runs. Restore offline, branch with |
CPU %, resident RAM, disk and network throughput from |
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.
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
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.
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.
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 systemddaemon + 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 sudoThe 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.
andler --version # andler 0.1.0
andlerd --version # andlerd 0.1.0 — both sides must agree
andler doctordoctor 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.
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 rootuninstall.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 --workspaceRequires protoc for gRPC code generation. Reproducible builds and the full test harness run in containers — see docker/e2e/README.md.
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 unitThe 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.
andlerd # start by hand, or: systemctl --user status andlerd (the installer enabled it)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 --quickPreview 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.
andler image list
andler image download --android-version 13 --variant gapps
andler create --kind android --name pixel --android-version 13 --arm-translator libndkEvery part is checksum-verified against the release manifest before anything lands in the cache; an already-cached build is reused without a request.
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[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. |
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,
VANILLAorGAPPS, selected by--android-version/--variant. - ARM → x86 translation —
libndk(Google prebuilt) orlibhoudini(Intel), installed offline or online withandler guest install. - Compositor — Weston over GL/virgl, which presents on any host GPU;
venusstays 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.
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.
| 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 batchandler 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-tokenjoins the running operation instead of starting a duplicate. export-ociproduces a spec-conformant OCI image layout (oci-layout,index.jsonmanifest list,config.json, rootfs blob underblobs/sha256/).
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.
andler metrics dev # stream · --once · --jsoncpu=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 RAMGuest 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.
# 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.
| 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 |
~/.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).
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.
| 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.
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 |
# 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.
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. |
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.
| 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.
Note
The cards and badges resolve once this repository is public; each card follows the reader's colour scheme (dark/light).
- Read
docs/DEVELOPMENT.mdand the README of the crate you are touching. - Branch from
main; every commit keeps the gate green — a branch is not a WIP dump. - Conventional Commits —
feat(qemu): …,fix(disk): …,docs(cli): …— one logical change per commit. - 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 -- --checkUser-visible behavior also needs an E2E suite that exercises its positive path, and the owning doc updated in the same commit.
| 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 |
ANDLER is mostly glue around excellent work by other people. Every link below is the source of truth for the component it names.
|
|
README craft — awesome-readme · Best-README-Template · readme-md-generator
Rust ecosystem — tokio · tonic · prost · clap · serde · tracing · thiserror · rusqlite · nvml-wrapper
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