English | 中文
Run a Linux rootfs on a rooted Android device with the host kernel. It uses a private mount namespace, pivot_root, fresh /proc /sys /dev, optional bind-mounted device nodes, and by default a loop-mounted ext4 rootfs image while sharing the host network namespace.
- Provides a real Linux root filesystem instead of a
prootillusion, so mount-aware tools such asdfsee the container root correctly. - Runs normal distro tools and services such as SSH, Tailscale, Samba, Podman, or Python while keeping Android and the kernel unchanged.
- Avoids Android FBE/fscrypt
ENOKEYfailures for stateful services by putting the rootfs on an ext4 loop image. - Patches Alpine/Debian guests with the Android group IDs needed for Android paranoid networking.
- Avoids model-specific hardware dependencies by binding only generic nodes such as
tunandfuse.
- Rooted Android with Magisk and a writable directory chosen by the user (for example under
/data); the scripts deriveCTDIRfrom their own path. - Kernel with mount and UTS namespaces,
TMPFS; for the default loop rootfs alsoBLK_DEV_LOOPandEXT4_FS.TUN/FUSEare optional. - SELinux policy that allows the root shell to
unshare, bind-mount,losetup, andpivot_root. - A rootfs matching the phone architecture with
/bin/shand basic tools (mount,umount,mknod,ln,env). Alpine and Debian are handled bylib/guest-init-*.
./init.sh
./start.sh
./enter.sh
./stop.shOptional boot autostart: ./setup_magisk_module.sh installs a manageable Magisk module for the current directory; ./setup_magisk_module.sh remove schedules removal, and disable/enable toggles it. Re-run after moving the directory.
ROOTFS_URL=... ./init.sh downloads and extracts a rootfs archive when rootfs/bin/sh is missing. start.sh can be run from any current directory; CTDIR is derived from the script path. DNS is synced at start; while running, rerun scripts/dns-sync.sh after network changes.
Loop rootfs defaults:
ROOTFS_URL=... LOOP_SIZE=80G ROOTFS_BACKUP=0 ./init.sh
ROOTFS_IMG= ./init.sh # disable the loop image and use rootfs/ directly
ROOTFS_LOOP_DETACH=1 ./stop.sh # also detach the loop device at stopROOTFS_IMGdefaults to$CTDIR/rootfs.img;LOOP_SIZEdefaults to40G.- If
rootfs/already exists androotfs.imgis missing,init.shmigrates it into a new ext4 image. The old directory is moved torootfs.bak.<timestamp>unlessROOTFS_BACKUP=0, which deletes it. - By default
stop.shunmounts the rootfs but keeps the loop device attached for a fast next start.
- Android paranoid networking needs Android group IDs in the guest. The guest init scripts create
aid_inet=3003andaid_net_raw=3004and addrootto them. enter.shand container boot start with a clean environment (env -iwithPATH,HOME=/root,TMPDIR=/tmp) instead of inheriting adb/Android variables.- The guest is not privilege-isolated: guest root keeps the full capability set and shares the pid/network namespaces with the host, so it can see and affect host processes. This is a convenience container, not a security boundary.
- After
pivot_root, the old Android root is detached from the container mount namespace when possible, sodfis not polluted by/.oldrootentries. - Only generic device nodes are bound by default:
/dev/tun,/dev/net/tunwhen present, and/dev/fuse. GPU/camera/audio nodes are intentionally not included. scripts/start-fore.shis guarded byCT_FORE=1; running it directly can break the current mount namespace.