The Idea and Architecture
Part 1 of 4 — building Linxr, a single APK that runs Alpine Linux on non-rooted Android.
The Constraint
Android is a locked-down Linux system. For regular (non-root) apps:
-
execve()from app storage is blocked by SELinux W^X policy - Native Linux tools are unavailable without Termux
- The kernel disables
CONFIG_USER_NS— Docker rootless mode is impossible - No way to load kernel modules or create network namespaces
A QEMU virtual machine sidesteps all of this. The VM runs its own kernel with its own namespace — completely isolated from Android's restrictions.
Why Alpine?
Alpine Linux is the right guest OS for a mobile VM:
| Property | Alpine | Debian/Ubuntu |
|---|---|---|
| Compressed disk image | ~35 MB | ~300 MB+ |
| Boot time | ~5 s | ~30+ s |
| RAM at idle | ~30 MB | ~100 MB+ |
| Package manager | apk |
apt |
| Init system | OpenRC | systemd |
Small footprint, fast boot, full POSIX shell — ideal for embedding in an APK.
Architecture
Android OS (non-rooted)
└── APK (Flutter + Kotlin)
├── VmManager — asset extraction, QEMU lifecycle
├── VmService — ForegroundService (keeps QEMU alive)
└── Flutter UI — Home, Terminal, About tabs
└── TerminalScreen (dartssh2 + xterm)
QEMU process (libqemu.so)
└── Alpine Linux 3.19 VM
└── OpenRC init → sshd on :22
↑
SLIRP hostfwd tcp::2222-:22
↑
SSH on 127.0.0.1:2222 (from Android app)
Disk Layout
Linxr uses QCOW2 copy-on-write layering:
base.qcow2 (read-only — Alpine rootfs, openssh baked in)
└── user.qcow2 (writable overlay — your data lives here)
-
base.qcow2ships compressed in the APK assets. Extracted once on first install. -
user.qcow2is created fresh on first install. Persists across VM restarts — installed packages and files survive.
The overlay is only recreated when base.qcow2 changes (asset version marker bumps on app update).
SLIRP Networking
QEMU SLIRP requires no root and no kernel modules:
Android app
└── TCP to 127.0.0.1:2222
└── QEMU hostfwd: tcp::2222-:22
└── Alpine VM eth0 (10.0.2.15):22
└── sshd
Inside the VM:
eth0: 10.0.2.15/24
gateway: 10.0.2.2
DNS: 10.0.2.3 (SLIRP built-in resolver)
The VM has full outbound internet access. apk add, curl, git all work normally.
Token-Free Design
Pockr uses a UUID token injected via QEMU kernel cmdline to authenticate its HTTP API. Linxr doesn't need that — SSH handles authentication natively:
- Username:
root - Password:
alpine(baked into the base image)
No tokens, no custom API — standard SSH.
Next: Part 2 — Shipping QEMU in an APK
GitHub: github.com/AI2TH/Linxr
Linxr Series — Alpine Linux on Android
Linxr = Linux + r. A single Android APK that runs a full Alpine Linux shell on any Android phone — no root, no Termux, no PC required.
| # | Post | Topic |
|---|---|---|
| 📖 | Intro | What is Linxr? Start here |
| 1 | Part 1 | The Idea and Architecture |
| 2 | Part 2 | Shipping QEMU in an APK |
| 3 | Part 3 | SSH Terminal in Flutter |
| 4 | Part 4 | Test Results |
GitHub: github.com/AI2TH/Linxr
Website: ai2th.github.io
Top comments (0)