Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@
are absent), so shell regressions are caught locally before the push.

### Bug fixes
- **`beetroot modes` / `doctor` / `build --vm-kernel` no longer advertise the x86_64-only `binder: vm` KVM path (or the native redroid host path) as usable on a non-x86_64 host (#190).** The whole guest stack is x86_64 and KVM only virtualizes the host's native architecture, so `qemu.detect_accel` is now host-arch-aware: on a non-x86_64 host an explicit `vm.accel: kvm` is a cross-arch error and `auto` resolves straight to TCG regardless of `/dev/kvm` (fixing both the `beetroot modes` KVM probe and the `beetroot doctor` `vm.accel` row). `beetroot modes` reports `redroid (binder: vm, KVM accel)` and `redroid (binder: host / auto)` as `unsupported` (arch mismatch) while keeping the TCG row reachable with an honest cross-arch note (`qemu-system-x86_64` under TCG boots the x86_64 guest cross-arch, just even slower). `beetroot build --vm-kernel --check` surfaces a non-x86_64 problem for the **local rootfs bake** (which stages x86_64 libs, pins the x86_64 loader, and copies `arch/x86/boot/bzImage`) — the prebuilt-**fetch** path is left cross-arch functional and is not gated.
- **`beetroot modes` no longer reports the redroid host/auto mode as `supported` when the Docker CLI is present but the daemon is stopped (#179).** The verdict now gates on a live `docker info` probe (relocated from `builder` into a shared `capabilities.docker_daemon_responsive()`): a ready host binder + installed CLI but an unresponsive `dockerd` reports `needs-setup` with the remedy "start the Docker daemon" instead of falsely advertising a mode that can't boot a container.
- **`beetroot doctor` on a `binder: vm` instance no longer reports `pass` when the QEMU binary or the guest kernel/rootfs artifacts are missing (#191).** `health()` gained a `vm.qemu` row (QEMU on `PATH`, reusing the same install remedy `beetroot modes` prints) and a `vm.artifacts` row (kernel + rootfs resolve, else a `beetroot build --vm-kernel` hint), so a green `vm.accel` alone can no longer imply a bootable VM.
- **`beetroot doctor` no longer false-fails a healthy micro-VM by reusing the USB-style `adb.serial` row (#164).** The vm backend now emits an `adb.connect` (connect-then-verify) row instead, matching the redroid path — a TCP adb target only appears after an explicit `adb connect`, so the always-listed-serial assumption was wrongly failing a fresh adb-server lifetime and making doctor exit non-zero.
Expand Down
9 changes: 8 additions & 1 deletion docs/design/binderless-hosts-qemu-tcg.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,14 @@ expensive step.
5. **KVM fast path** — ✅ done (issue #44): `detect_accel` probes
`/dev/kvm` (read+write) and prefers `-accel kvm` (`-cpu host`); `auto`
falls back to TCG, an explicit `kvm` request on a host without `/dev/kvm`
errors loudly.
errors loudly. The whole guest stack is x86_64, and KVM only virtualizes
the host's **native** architecture, so `detect_accel` is host-arch-aware
(issue #190): on a non-x86_64 host an explicit `kvm` is a cross-arch error
and `auto` resolves to TCG regardless of `/dev/kvm` (a stray native
`/dev/kvm` can't accelerate the x86_64 guest). `beetroot modes` reports the
KVM path — and the native `binder: host/auto` redroid path — as
`unsupported` on a non-x86_64 host, while the TCG path stays reachable
cross-arch (even slower).

### Usage

Expand Down
16 changes: 10 additions & 6 deletions docs/how-it-works/binder-and-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,28 @@ adb binaries, then classifies each mode:

| Verdict | When |
|---------|------|
| `supported` | binder is **ready** (`/dev/binder*` nodes exist, or `binder` is in `/proc/filesystems`) **and** the Docker CLI is present. |
| `supported` | host is **x86_64**, binder is **ready** (`/dev/binder*` nodes exist, or `binder` is in `/proc/filesystems`) **and** the Docker CLI is present. |
| `needs-setup` | binder is **loadable** (`CONFIG_ANDROID_BINDER_IPC=m`/`=y` but not loaded) → `sudo modprobe binder_linux devices=binder,hwbinder,vndbinder`; **or** binder is ready but Docker isn't installed. |
| `unsupported` | the kernel has binder **compiled out** (`# CONFIG_ANDROID_BINDER_IPC is not set`) — no Docker flag can fix this. |
| `unsupported` | the kernel has binder **compiled out** (`# CONFIG_ANDROID_BINDER_IPC is not set`) — no Docker flag can fix this; **or** the host is **not x86_64** — Beetroot's redroid image is the x86_64 `*_houdini_magisk` build and runs natively against the host kernel (no emulation), so it can't boot on, e.g., arm64 (issue [#190](https://github.com/Xiddoc/Beetroot/issues/190)) — use `binder: vm` (TCG cross-arch) instead. |
| `unknown` | binder isn't present and the kernel config couldn't be read (e.g. macOS, locked-down `/proc`). |

### `redroid (binder: vm, KVM accel)`

The guest is x86_64, and KVM only virtualizes the host's **native** architecture, so this row is **x86_64-only** — on a non-x86_64 host KVM can never accelerate the x86_64 guest (issue [#190](https://github.com/Xiddoc/Beetroot/issues/190)).

| Verdict | When |
|---------|------|
| `supported` | `/dev/kvm` is usable **and** QEMU is installed. (You still build the guest once: `beetroot build --vm-kernel`.) |
| `needs-setup` | `/dev/kvm` usable but QEMU missing → install `qemu-system-x86`. |
| `unsupported` | no usable `/dev/kvm` use the TCG row instead, or move to a KVM-capable host/runner. |
| `supported` | host is **x86_64**, `/dev/kvm` is usable, **and** QEMU is installed. (You still build the guest once: `beetroot build --vm-kernel`.) |
| `needs-setup` | host is x86_64, `/dev/kvm` usable, but QEMU missing → install `qemu-system-x86`. |
| `unsupported` | no usable `/dev/kvm` (use the TCG row, or move to a KVM-capable host/runner); **or** the host is **not x86_64** (KVM can't accelerate the x86_64 guest cross-arch — use the TCG row). |

### `redroid (binder: vm, TCG accel)`

Software emulation, so this row is reachable **cross-arch**: `qemu-system-x86_64` under TCG boots the x86_64 guest even on a non-x86_64 host (just even slower).

| Verdict | When |
|---------|------|
| `supported` | QEMU is installed. Works with **no** host binder and **no** KVM. Build the guest once with `beetroot build --vm-kernel`. |
| `supported` | QEMU is installed. Works with **no** host binder and **no** KVM. On a non-x86_64 host the reason flags the extra **cross-arch** cost (even slower than native-arch TCG). Build the guest once with `beetroot build --vm-kernel`. |
| `needs-setup` | QEMU missing → install `qemu-system-x86`, then `beetroot build --vm-kernel`. |

### `adb backend (adopt remote device)`
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ binder: host
```

!!! tip "`binder: vm` boots an emulated micro-VM"
Selecting `vm` dispatches `beetroot up` to a QEMU micro-VM that ships its own binder-enabled kernel. Build the guest artifacts once with `beetroot build --vm-kernel`, point `vm.kernel` / `vm.rootfs` at them (or set `BEETROOT_VM_KERNEL` / `BEETROOT_VM_ROOTFS`), and run `beetroot apply` then `beetroot up`. On a host with `/dev/kvm` this is near-native; without it the backend falls back to TCG (~5-20x slower — a slow first boot is expected, not a hang). The slow path is **never** engaged automatically; `binder: vm` is always an explicit opt-in. See [Binderless hosts (QEMU/TCG)](../design/binderless-hosts-qemu-tcg.md).
Selecting `vm` dispatches `beetroot up` to a QEMU micro-VM that ships its own binder-enabled kernel. Build the guest artifacts once with `beetroot build --vm-kernel`, point `vm.kernel` / `vm.rootfs` at them (or set `BEETROOT_VM_KERNEL` / `BEETROOT_VM_ROOTFS`), and run `beetroot apply` then `beetroot up`. On an **x86_64** host with `/dev/kvm` this is near-native; without it the backend falls back to TCG (~5-20x slower — a slow first boot is expected, not a hang). The whole guest stack is x86_64, so **KVM only accelerates on an x86_64 host** — on a non-x86_64 host (e.g. arm64) KVM cannot virtualize the x86_64 guest and `beetroot modes` / `doctor` report the KVM path as `unsupported`; the guest still boots there under TCG cross-arch emulation (even slower). The slow path is **never** engaged automatically; `binder: vm` is always an explicit opt-in. See [Binderless hosts (QEMU/TCG)](../design/binderless-hosts-qemu-tcg.md).

!!! warning "Frida is not yet supported under `binder: vm`"
The micro-VM guest is network-isolated, so the `vm` backend is scoped to ADB forwarding (`beetroot shell`) only. `beetroot frida-addr <vm-instance>` raises a friendly "not yet supported on the 'vm' backend" error (exit 2) instead of emitting an address, `beetroot doctor` omits the `frida.handshake` row, and `ls` / `status` report the Frida address as `unsupported`. Any `frida:` block in a `binder: vm` config is ignored (no `frida-server` is staged). For Frida, use `binder: auto` / `host` (redroid) or `beetroot adopt` an external rooted device. Tracked as a follow-up to [#44](https://github.com/Xiddoc/Beetroot/issues/44).
Expand All @@ -362,7 +362,7 @@ QEMU micro-VM tunables. Consulted **only** when `binder: vm`; ignored otherwise.
|-------|------|---------|-------------|
| `vm.kernel` | string \| null | `null` | Host path to the guest `bzImage`. `null` defers to `BEETROOT_VM_KERNEL`. |
| `vm.rootfs` | string \| null | `null` | Host path to the guest ext4 root image. `null` defers to `BEETROOT_VM_ROOTFS`. |
| `vm.accel` | string | `auto` | QEMU accelerator: `auto` (probe `/dev/kvm`, prefer KVM, else TCG), `kvm` (force; errors if `/dev/kvm` is absent), or `tcg` (force software emulation). |
| `vm.accel` | string | `auto` | QEMU accelerator: `auto` (probe `/dev/kvm`, prefer KVM, else TCG), `kvm` (force; errors if `/dev/kvm` is absent — or, on a non-x86_64 host, with a cross-arch error, since KVM can't accelerate the x86_64 guest there), or `tcg` (force software emulation). On a non-x86_64 host `auto` resolves straight to TCG regardless of `/dev/kvm`. |
| `vm.smp` | int \| `auto` | `auto` | Guest vCPUs (`-smp`). `auto` pins `-smp` to the host's **physical** core count (HyperThread siblings collapsed, capped by CPU affinity so a cgroup-limited CI runner is respected) — the vm-rnd-log §B.5 measured optimum, since more vCPUs than physical cores oversubscribe the emulator. An explicit integer (>= 1) pins it. |
| `vm.memory_mib` | int | `8192` | Guest RAM in MiB (`-m`). Must be >= 256. Authoritative for `binder: vm`; `resources.mem` is the Docker cap used by `binder: auto` / `host`. Both knobs kept by decision in [#104](https://github.com/Xiddoc/Beetroot/issues/104). |
| `vm.boot_cache` | bool | `false` | Warm-start boot cache. When `true`, the first `up` cold-boots through a qcow2 overlay and checkpoints the running machine state with QEMU `savevm`; every later `up` *resumes* that checkpoint (`-loadvm`) instead of cold-booting — **~10 s vs ~3-4 min under TCG**. Resume reverts the guest to the checkpoint each time (a fast *known-good boot*, not persistence) — so `/data` writes made after the first boot (installed apps, logins, flashed-module state) are discarded on every warm `up`, and Beetroot prints a runtime warning on each resume so that reset is never silent. Set `vm.boot_cache: false` if you need `/data` to persist across restarts. The checkpoint lives at `<instance>/vm-overlay.qcow2` (~2 GiB) and auto-invalidates when the kernel/rootfs changes (a digest is recorded beside it); delete it by hand to force a reset otherwise. Requires `qemu-img`. See [Warm-start boot cache](#warm-start-boot-cache-vmboot_cache). |
Expand Down
21 changes: 21 additions & 0 deletions src/beetroot/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import fcntl
import hashlib
import os
import platform
import shlex
import shutil
import subprocess
Expand All @@ -38,6 +39,7 @@

from . import capabilities, config, console, kernel_download, paths, rootfs_download
from .settings import settings
from .vm import qemu

# The patcher CLI flag each GApps vendor needs. Keyed by the *resolved* vendor
# (config.GappsVendor) rather than the user-facing intent, so the build matches
Expand Down Expand Up @@ -1459,6 +1461,25 @@ def vm_bake_preflight(*, redroid_tar: Path | None = None) -> list[PreflightProbl
"""
cfg = _RootfsConfig.from_env(out_image=Path("preflight"), vm_dir=Path("preflight"))
problems: list[PreflightProblem] = []
# The local bake is genuinely x86_64-only: it stages x86_64-linux-gnu libs,
# pins /lib64/ld-linux-x86-64.so.2, and ``cp``s arch/x86/boot/bzImage — all
# of which assume an x86_64 build host. A non-x86_64 host can't bake, but it
# CAN still *fetch* the prebuilt artifacts (which run under TCG cross-arch),
# so this is a bake-only problem, never a fetch-path one (issue #190).
if not qemu.host_is_guest_arch():
problems.append(
PreflightProblem(
requirement="x86_64 build host",
detail=(
f"the local rootfs bake only runs on an x86_64 build host "
f"(this host is {platform.machine()})"
),
fix=(
"fetch the prebuilt artifacts instead of forcing a local bake "
"(the default `beetroot build --vm-kernel`), or bake on an x86_64 host"
),
)
)
for attr, pkg in _VM_STATIC_BINS:
path: Path = getattr(cfg, attr)
if not path.is_file():
Expand Down
Loading