English | 中文
在已 root 的 Android 设备上,用宿主内核运行一个 Linux rootfs。它使用私有 mount namespace、pivot_root、全新的 /proc /sys /dev、可选的 bind 设备节点,并默认把 rootfs 放在 loop 挂载的 ext4 镜像中,同时共享宿主网络命名空间。
- 提供真实的 Linux 根文件系统,而不是
proot的假象;df这类感知挂载的工具能正确看到容器根。 - 在不改 Android 和内核的前提下,运行普通发行版工具与服务,例如 SSH、Tailscale、Samba、Podman、Python。
- 通过 ext4 loop 镜像承载 rootfs,避免 Android FBE/fscrypt 导致有状态服务出现
ENOKEY。 - 为 Alpine/Debian guest 补上 Android paranoid networking 所需的 Android gid。
- 默认只 bind
tun、fuse等通用节点,避免机型专属硬件依赖。
- 已 root 的 Android(Magisk),以及一个由用户自己选择的可写目录(例如
/data下任意位置);脚本会从自身路径推导CTDIR,不固定存放位置。 - 内核支持 mount namespace、UTS namespace、
TMPFS;默认 loop rootfs 还需要BLK_DEV_LOOP和EXT4_FS。TUN/FUSE可选。 - SELinux 策略允许 root shell 执行
unshare、bind mount、losetup、pivot_root。 - rootfs 需匹配手机架构,并提供
/bin/sh与基础工具(mount、umount、mknod、ln、env)。Alpine 和 Debian 由lib/guest-init-*处理。
./init.sh
./start.sh
./enter.sh
./stop.sh
./backup.sh
./restore.sh可选开机自启:./setup_magisk_module.sh 会为当前目录安装一个可被 Magisk 管理的模块;./setup_magisk_module.sh remove 标记重启后移除,disable/enable 可开关。移动目录后需重跑。若 SELinux 阻止 loop rootfs(内核对 rootfs.img 的写操作被拒绝),./init.sh 会尽力执行 setenforce 0,./setup_selinux_magisk_module.sh 则安装一个开机重复该操作的 Magisk 模块,同样支持 remove/disable/enable 子命令。
ROOTFS_URL=... ./init.sh 会在 rootfs/bin/sh 缺失时下载并解压 rootfs 归档。start.sh 不依赖固定安装路径,可从任意当前目录启动;CTDIR 由脚本路径推导。启动时会同步 DNS,之后由宿主侧看门狗按 DNS_SYNC_INTERVAL 秒轮询保持同步(默认 30,设为 0 关闭)。
loop rootfs 默认值:
ROOTFS_URL=... LOOP_SIZE=80G ROOTFS_BACKUP=0 ./init.sh
ROOTFS_IMG= ./init.sh # 关闭 loop 镜像,直接使用 rootfs/ 目录
ROOTFS_SPARSE=1 ./init.sh # 以稀疏文件方式创建镜像(默认关闭)
ROOTFS_LOOP_DETACH=1 ./stop.sh # stop 时同时 detach loop 设备ROOTFS_IMG默认是$CTDIR/rootfs.img;LOOP_SIZE默认40G。- 如果已存在
rootfs/但还没有rootfs.img,init.sh会把它迁移进新的 ext4 镜像。旧目录默认移动为rootfs.bak.<时间戳>;ROOTFS_BACKUP=0会直接删除。 - 默认
stop.sh只卸载 rootfs,保留 loop attach,方便下次快速启动。
备份与恢复:
backup.sh通过tar | gzip流式管道把 rootfs(已挂载的镜像或纯目录)打包为$BACKUP_DIR/rootfs.<时间戳>.tar.gz。容器运行中也可执行;镜像未挂载时会临时挂载、结束后卸载;只保留最新ROOTFS_BACKUP_KEEP份(默认2;BACKUP_DIR默认$CTDIR/backup)。restore.sh [归档]恢复最新(或指定)备份:需要先 stop 容器,恢复会清空 rootfs 再解包。
诊断:
- 启动/停止各阶段会记录到
run/container.log;日志超过LOG_MAX_KBKiB(默认256)后自动截断保留较新的一半。
- Android paranoid networking 需要 guest 内存在对应 Android gid。guest 初始化脚本会创建
aid_inet=3003、aid_net_raw=3004并把root加入。 enter.sh和容器启动都使用干净环境(env -i,只带PATH、HOME=/root、TMPDIR=/tmp),不继承 adb/Android 变量。- 主容器不做权限降级:guest 内 root 保留完整 capabilities,并与宿主共享 pid/network namespace,可以看到并影响宿主进程。这是便利容器,不是安全边界。
pivot_root后会尽量把旧 Android 根从容器 mount namespace 分离,避免df被/.oldroot条目污染。- 默认只 bind 通用设备节点:
/dev/tun、存在时的/dev/net/tun,以及/dev/fuse。GPU/相机/音频节点默认不包含。 scripts/start-fore.sh受CT_FORE=1保护;直接运行可能破坏当前 mount namespace。