Linux Guest (UML)
Create a connection with the Linux Guest (UML) transport and Haven boots a
whole Linux kernel — user-mode Linux — as one of its own processes. Unlike the
Local Shell (PRoot), which emulates a root filesystem by
intercepting syscalls, the guest runs a real kernel: real ptrace, real
/proc, real block devices, and its own network stack. The connection editor
asks only for a name; the kernel arguments are fixed.
What it’s good for
- A normal Linux box in your pocket. The guest runs an Alpine aarch64
userland with apk
and the standard Alpine 3.22 repositories, so
apk add python3 gcc git nodejs(and thousands of other packages) install straight into the guest. Everything runs as root inside the guest, and the guest’s filesystem is its own ext4 image that survives reboots. - When PRoot isn’t enough. PRoot emulates a root filesystem by intercepting
syscalls, and some software notices or breaks under that: process tracers and
debuggers, programs that inspect
/procin unusual ways, and anything that needs to mount filesystems. The guest sidesteps all of it by running an actual kernel, with no interception layer between your programs and Linux. - A scratch box for untrusted commands. The guest sees only its own disk. It cannot read the phone’s storage, contacts, or anything else Haven can reach, and it holds no privileges on the device — closing the tab powers it off. That makes it a reasonable place to run scripts you don’t fully trust.
- An agent workbench. Haven’s MCP tools (terminal input, snapshot, scrollback) work on a guest session like any other terminal, so an agent can drive a full root Linux on the phone: install packages, run builds, iterate on scripts — while you watch the same console.
Getting started
- Connections → + → Linux Guest (UML). The editor asks for a name only. The picker only offers the guest on arm64 devices running the full flavour; if it’s missing, your build doesn’t carry the payload.
- Connect. The first connect unpacks a ~512 MB rootfs image into app storage (needs ~600 MB free) and then boots; later connects skip the unpack.
- Wait for the boot messages to settle into a
uml:~#prompt — typically 10–20 s on a recent phone, most of it the network bring-up. - Update the package index once:
apk update.
Closing the guest’s tab (or tapping the stop button) sends poweroff into the
guest. Files you created stay on the image for next time.
What to expect
- Fixed resources: 384 MB of RAM and a ~536 MB ext4 disk (which shares the
space
apk addfills up). There is no snapshotting and no way to enlarge either from the UI. - One shared folder, one direction. The guest can mount Haven’s private
uml/sharefolder at/host(mount -t hostfs none /host). That is the only phone path the guest can see — the kernel confines every hostfs mount to it — and it exists so the guest has somewhere to write output files (the USB card rescue console puts rescued images there). Nothing is shared into the guest automatically; the phone’s other files stay invisible. - Memory adds up. Each running guest occupies its 384 MB for as long as its tab is open, and you can have several open at once. Close guest tabs you aren’t using.
- A console, not a desktop. The guest surface is a serial-style console. For graphical Linux, use the Local Desktops manager instead.
How it compares
- Local Shell (PRoot) is the lighter tool: ~4 MB download, runs on Android 8+, and starts instantly. It emulates rather than runs a kernel, which is where its limits come from. Use PRoot for everyday shell work; use the guest when you hit those limits.
- SSH to a server gives you a machine that stays up when you close the app and usually more CPU and RAM, but needs the server and a connection. The guest is on-device and needs nothing but the phone.
- Android Terminal VM (Pixel 8 and newer) virtualizes with pKVM and is a more complete VM. The guest runs on any arm64 Android device and needs no virtualization support from the SoC, because user-mode Linux is just a process.
Requirements
- arm64 device. Only
arm64-v8abuilds ship the guest payload. - The full flavour. The payload is four native files, ~13 MB in the APK — the
kernel (
libvmlinux.so) is ~79 MB as built but AGP’s debug-symbol strip drops it to ~8 MB (the bytes outside the loadable segment are ELF metadata only; the loaded image is unchanged). The connection picker hides the guest transport when any of the four is missing. - ~600 MB of free app storage. The rootfs image is unpacked (~512 MB) into app storage on first connect. It stays there afterwards; deleting the connection does not delete it.
How it works
- Kernel: a bionic-static UML kernel built from the Linux UML tree (branch
um-arm64) with thestub-execve-fallback.patchandandroid-app-compat.patchon top, so the kernel runs as an ordinary Android app process — no root, no/dev/kvm, no privileged setup. - Network: passt runs as a sibling of the kernel, connected over a
SOCK_SEQPACKETsocketpair; the kernel’s UML vector transport (vec0) uses that pair as its NIC, and passt forwards to the app’s own network context. DNS is set to 1.1.1.1 by default. No VPN permission is needed because everything stays inside the app. - Console: the guest’s stdio console is the terminal tab’s pty, so the boot messages and shell appear as they happen.
- Boot time: the rootfs is a ~536 MB ext4 image (a minimal aarch64 rootfs with an init, busybox, and a network bring-up). The kernel prints its first boot messages within a couple of seconds; the shell prompt appears once the inittab’s
ifup -afinishes its DHCP round onvec0, which adds a few more seconds on top (measured ~10–20 s total on an OPPO CPH2655, MCP round-trips included). - Rootfs image versions: the staged image carries a version marker
(
uml/rootfs.version). When a Haven update changes the image contents, the next connect re-unpacks it once — anything stored inside the guest image is replaced, so keep anything you care about in/hostor over the network.
Closing
Closing the guest’s terminal tab sends poweroff into the guest and waits up
to 5 s for it to exit before killing the process. The ext4 rootfs is
journaled, and this keeps unmounts clean even if Haven is killed outright.
The guest does not persist state across poweroff beyond what is written into
the image; there is no snapshotting, and Haven does not ship multiple rootfs
variants.
Source availability
The guest kernel and passt are GPL-2.0 (passt under its upstream
GPL-2.0-or-later identifier). Their complete corresponding source, the two
kernel patches, the passt patch and the build recipes are published in the
uml-transport repository;
the binaries Haven ships are the pinned uml-guest-4 release of that
project. The pinned binaries and their sha256 checksums are in
core/local/fetch-uml.sh;
the fetch fails the build loudly if a pinned artifact disappears, and each
artifact’s checksum is verified before it is placed in the APK.
The rootfs image ships GPL binaries too. It is Alpine 3.22.5 (aarch64) with
these packages baked in, all fetched from the official Alpine 3.22 repositories
with their sources: busybox 1.37.0-r20, apk-tools 2.14.10-r0, e2fsprogs
1.47.2-r2, util-linux 2.41.6-r1, ddrescue (GNU ddrescue) 1.29-r0, nbd 3.26.1-r0,
mtools 4.0.47-r0, and the Alpine base packages. Each package’s source is its
upstream project (via Alpine’s aports
recipes). The one custom file in the image, the haven-recover init hook, is
a plain shell script shipped in source form inside the image itself
(/sbin/haven-recover).