为三星 Note20 Ultra 编译支持 Docker 的内核
前提:
使用 Ubuntu ,是否虚拟机,是否有图形界面均可。
已经在 Samsung Open Source 下载了 三星 Note20 Ultra(SM-N9860)的内核源码,并把其中的 Kernel 解压了出来。
假定源码在 ~/Project/Kernel 目录下,编译依赖放在 ~/Project/toolchains 目录下。
安装编译依赖
安装基本编译工具:
sudo apt install -y build-essential bc bison flex libssl-dev libncurses5-dev libelf-dev git python-is-python3
进入 toolchains 目录
-
克隆 linux-x86(clang-llvm)
git clone --branch android12-release --single-branch --depth 1 https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86
-
克隆 aarch64-linux-android-4.9
git clone --branch android12-release --single-branch --depth 1 https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9
-
克隆 arm-linux-androideabi-4.9
git clone --branch android12-release --single-branch --depth 1 https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9
changes
编辑 ~/Project/Kernel/arch/arm64/configs/vendor/c2q_chn_hkx_defconfig
comment all Control Flow Protection section
#
# Control Flow Protection
#
# CONFIG_CFP=y
# CONFIG_CFP_JOPP=y
# CONFIG_CFP_JOPP_MAGIC=0x00be7bad
# CONFIG_CFP_ROPP=y
# CONFIG_CFP_ROPP_SYSREGKEY=y
# CONFIG_CFP_ROPP_RANDKEY is not set
# CONFIG_CFP_ROPP_FIXKEY is not set
# CONFIG_CFP_ROPP_ZEROKEY is not set
# CONFIG_CFP_TEST is not set
-Werror 改为 -Wno-error
显示所有的 -Werror : grep -r --include="Makefile" -e "-Werror" drivers
把所有的 -Werror 替换为 -Wno-error:
修改 drivers 目录下所有 Makefile 中的 -Werror 为 -Wno-error
find drivers -name "Makefile" -print0 | xargs -0 sed -i 's/-Werror/-Wno-error/g'
可选修改 techpack 目录(高通驱动重灾区)下所有 Makefile 中的 -Werror 为 -Wno-error
find techpack -name "Makefile" -print0 | xargs -0 sed -i 's/-Werror/-Wno-error/g'
build.sh
创建 ~/Project/Kernel/build.sh
#!/bin/bash
# ==========================================
# Samsung Note 20 Ultra (SM-N9860)
# Build Script
# ==========================================
# 1. 路径配置
# ------------------------------------------
PROJECT_ROOT="$HOME/Project"
CLANG_DIR="$PROJECT_ROOT/toolchains/linux-x86/clang-r416183b1/bin"
GCC64_DIR="$PROJECT_ROOT/toolchains/aarch64-linux-android-4.9/bin"
GCC32_DIR="$PROJECT_ROOT/toolchains/arm-linux-androideabi-4.9/bin"
# 2. 检查环境
# ------------------------------------------
if ! command -v make &> /dev/null; then
echo "错误: 系统未安装 'make'。"
echo "请执行: sudo apt install build-essential"
exit 1
fi
if [ ! -d "$CLANG_DIR" ]; then
echo "错误: Clang 路径不对 -> $CLANG_DIR"
exit 1
fi
# 3. 设置环境变量
# ------------------------------------------
export PATH=$CLANG_DIR:$GCC64_DIR:$GCC32_DIR:$PATH
export ARCH=arm64
# 指定项目代号,避免 techpack 编译所有设备的驱动
export PROJECT_NAME=c2q
# 强行注入:关闭错误提示,忽略原型检查
export KCFLAGS="-Wno-error -Wno-error=strict-prototypes -Wno-error=implicit-function-declaration -Wno-strict-prototypes -fno-builtin-stpcpy"
export HOSTCFLAGS="-Wno-strict-prototypes -Wno-error -fcommon"
export SUBARCH=arm64
export KBUILD_BUILD_USER="builder"
export KBUILD_BUILD_HOST="localhost"
# 4. 核心配置 (精准匹配你的 c2q_chn_hkx)
# ------------------------------------------
# 你提供的 tree 显示配置文件在 arch/arm64/configs/vendor/c2q_chn_hkx_defconfig
# Make 会自动在 arch/arm64/configs 下寻找,所以我们要传给它 vendor/c2q_chn_hkx_defconfig
DEFCONFIG="vendor/c2q_chn_hkx_defconfig"
echo "-------------------------------------"
echo "机型: Note 20 Ultra (Snapdragon 865+)"
echo "目标: SM-N9860 (国行/港版)"
echo "配置: $DEFCONFIG"
echo "-------------------------------------"
# 高通版 techpack 检查
if [ ! -d "techpack" ]; then
echo "!!!! 严重警告 !!!!"
echo "根目录下未检测到 'techpack' 文件夹。"
echo "如果编译报错提示 qc_ 找不到,请去源码包里找 techpack 并解压过来。"
echo "-------------------------------------"
sleep 3
fi
# 5. 定义编译器参数
# ------------------------------------------
ARGS="O=out \
ARCH=arm64 \
CC=clang \
CLANG_TRIPLE=aarch64-linux-gnu- \
CROSS_COMPILE=aarch64-linux-android- \
CROSS_COMPILE_ARM32=arm-linux-androideabi-"
# 6. 执行编译
# ------------------------------------------
echo ">> [1/3] 清理构建环境..."
make clean
make mrproper
rm -rf out
echo ">> [2/3] 加载配置: $DEFCONFIG"
mkdir -p out
make $ARGS $DEFCONFIG
if [ $? -ne 0 ]; then
echo "错误: 配置文件加载失败!"
echo "请确认 arch/arm64/configs/$DEFCONFIG 真的存在。"
exit 1
fi
echo ">> [3/3] 开始编译..."
CORES=$(nproc)
echo "cores: $CORES"
make -j$CORES $ARGS Image.gz-dtb
# make -j1 KCFLAGS="$KCFLAGS" $ARGS Image.gz-dtb
# make -j32 KCFLAGS="$KCFLAGS" $ARGS Image.gz-dtb
# 7. 结果反馈
# ------------------------------------------
TARGET="out/arch/arm64/boot/Image.gz-dtb"
if [ -f "$TARGET" ]; then
echo "=================================================="
echo " 编译成功 (BUILD SUCCESS)"
echo "=================================================="
echo "内核文件位置: $PWD/$TARGET"
else
echo "=================================================="
echo " 编译失败 (BUILD FAILED)"
echo "=================================================="
fi
先在 ~/Project/Kernel 目录下执行 ./build.sh 看能否编译官方内核。
AnyKernel3
如果能编译出官方内核,则尝试用 AnyKernel3 刷入,看是否能开机
cd ~/Project/
git clone --depth 1 https://github.com/osm0sis/AnyKernel3.git
cd AnyKernel3
cp $PWD/$TARGET .
上面的 cp $PWD/$TARGET . 的意思是把编译出来的 Image.gz-dtb 复制到 AnyKernel3 文件夹下
edit global properties and boot shell variables
# global properties
properties() { '
kernel.string=Note20U-Snapdragon
do.devicecheck=1
do.modules=0
do.systemless=1
do.cleanup=1
do.cleanuponabort=0
device.name1=c2q
device.name2=c2q_chn_hkx
device.name3=SM-N9860
device.name4=sm8250
device.name5=kona
supported.versions=11.0-14.0
supported.patchlevels=
supported.vendorpatchlevels=
'; } # end properties
# boot shell variables
# 三星高通机型的标准 Boot 分区路径
BLOCK=/dev/block/bootdevice/by-name/boot;
IS_SLOT_DEVICE=0;
RAMDISK_COMPRESSION=auto;
PATCH_VBMETA_FLAG=auto;
full version:
### AnyKernel3 Ramdisk Mod Script
## osm0sis @ xda-developers
### AnyKernel setup
# global properties
properties() { '
kernel.string=Note20U-Snapdragon
do.devicecheck=1
do.modules=0
do.systemless=1
do.cleanup=1
do.cleanuponabort=0
device.name1=c2q
device.name2=c2q_chn_hkx
device.name3=SM-N9860
device.name4=sm8250
device.name5=kona
supported.versions=11.0-14.0
supported.patchlevels=
supported.vendorpatchlevels=
'; } # end properties
### AnyKernel install
## boot files attributes
boot_attributes() {
set_perm_recursive 0 0 755 644 $RAMDISK/*;
set_perm_recursive 0 0 750 750 $RAMDISK/init* $RAMDISK/sbin;
} # end attributes
# boot shell variables
# 三星高通机型的标准 Boot 分区路径
BLOCK=/dev/block/bootdevice/by-name/boot;
IS_SLOT_DEVICE=0;
RAMDISK_COMPRESSION=auto;
PATCH_VBMETA_FLAG=auto;
# import functions/variables and setup patching - see for reference (DO NOT REMOVE)
. tools/ak3-core.sh;
# boot install
dump_boot; # use split_boot to skip ramdisk unpack, e.g. for devices with init_boot ramdisk
# init.rc
backup_file init.rc;
replace_string init.rc "cpuctl cpu,timer_slack" "mount cgroup none /dev/cpuctl cpu" "mount cgroup none /dev/cpuctl cpu,timer_slack";
# init.tuna.rc
backup_file init.tuna.rc;
insert_line init.tuna.rc "nodiratime barrier=0" after "mount_all /fstab.tuna" "\tmount ext4 /dev/block/platform/omap/omap_hsmmc.0/by-name/userdata /data remount nosuid nodev noatime nodiratime barrier=0";
append_file init.tuna.rc "bootscript" init.tuna;
# fstab.tuna
backup_file fstab.tuna;
patch_fstab fstab.tuna /system ext4 options "noatime,barrier=1" "noatime,nodiratime,barrier=0";
patch_fstab fstab.tuna /cache ext4 options "barrier=1" "barrier=0,nomblk_io_submit";
patch_fstab fstab.tuna /data ext4 options "data=ordered" "nomblk_io_submit,data=writeback";
append_file fstab.tuna "usbdisk" fstab;
write_boot; # use flash_boot to skip ramdisk repack, e.g. for devices with init_boot ramdisk
## end boot install
## init_boot files attributes
#init_boot_attributes() {
#set_perm_recursive 0 0 755 644 $RAMDISK/*;
#set_perm_recursive 0 0 750 750 $RAMDISK/init* $RAMDISK/sbin;
#} # end attributes
# init_boot shell variables
#BLOCK=init_boot;
#IS_SLOT_DEVICE=1;
#RAMDISK_COMPRESSION=auto;
#PATCH_VBMETA_FLAG=auto;
# reset for init_boot patching
#reset_ak;
# init_boot install
#dump_boot; # unpack ramdisk since it is the new first stage init ramdisk where overlay.d must go
#write_boot;
## end init_boot install
## vendor_kernel_boot shell variables
#BLOCK=vendor_kernel_boot;
#IS_SLOT_DEVICE=1;
#RAMDISK_COMPRESSION=auto;
#PATCH_VBMETA_FLAG=auto;
# reset for vendor_kernel_boot patching
#reset_ak;
# vendor_kernel_boot install
#split_boot; # skip unpack/repack ramdisk, e.g. for dtb on devices with hdr v4 and vendor_kernel_boot
#flash_boot;
## end vendor_kernel_boot install
## vendor_boot files attributes
#vendor_boot_attributes() {
#set_perm_recursive 0 0 755 644 $RAMDISK/*;
#set_perm_recursive 0 0 750 750 $RAMDISK/init* $RAMDISK/sbin;
#} # end attributes
# vendor_boot shell variables
#BLOCK=vendor_boot;
#IS_SLOT_DEVICE=1;
#RAMDISK_COMPRESSION=auto;
#PATCH_VBMETA_FLAG=auto;
# reset for vendor_boot patching
#reset_ak;
# vendor_boot install
#dump_boot; # use split_boot to skip ramdisk unpack, e.g. for dtb on devices with hdr v4 but no vendor_kernel_boot
#write_boot; # use flash_boot to skip ramdisk repack, e.g. for dtb on devices with hdr v4 but no vendor_kernel_boot
## end vendor_boot install
get AnyKernel3.zip
zip -r9 UPDATE-AnyKernel3.zip * -x .git README.md *placeholder
将 UPDATE-AnyKernel3.zip 用 Recovery 或者 Kernel Flasher 刷入,看是否能开机,能正常开机则进入下一步
docker
创建 ~/Project/Kernel/docker.sh :
# 定义配置文件路径
DEFCONFIG="arch/arm64/configs/vendor/c2q_chn_hkx_defconfig"
# 1. 备份原文件 (后悔药)
cp $DEFCONFIG $DEFCONFIG.bak_nodocker
# 2. 追加 Docker 核心支持配置
cat <<EOT >> $DEFCONFIG
# ==============================================
# Docker / Container Support
# ==============================================
# 1. 基本 Cgroup 支持
CONFIG_CGROUPS=y
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
CONFIG_CGROUP_SCHED=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CPUSETS=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_BPF=y
# 2. Namespaces (命名空间)
CONFIG_NAMESPACES=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y
# 3. 网络虚拟化 (veth, bridge, iptables)
CONFIG_NETFILTER=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_BRIDGE=y
CONFIG_BRIDGE_NETFILTER=y
CONFIG_VETH=y
CONFIG_NET_CORE=y
CONFIG_IP_NF_FILTER=y
CONFIG_IP_NF_TARGET_MASQUERADE=y
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
CONFIG_NETFILTER_XT_MATCH_IPVS=y
CONFIG_IP_NF_NAT=y
CONFIG_NF_NAT=y
CONFIG_POSIX_MQUEUE=y
CONFIG_NETFILTER_XT_TARGET_CHECKSUM=y
CONFIG_IP_NF_MANGLE=y
# 4. 文件系统 (OverlayFS 是 Docker 分层存储的核心)
CONFIG_OVERLAY_FS=y
# 5. 其他杂项
CONFIG_KEYS=y
CONFIG_BINFMT_MISC=y
CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y
# 关闭可能干扰容器的 Android 限制 (可选,但推荐)
# CONFIG_ANDROID_PARANOID_NETWORK is not set
EOT
echo "Docker 配置已追加到 $DEFCONFIG"
再次构建
cd ~/Project/Kernel/
./docker.sh
./build.sh
再次做成 AnyKernel3 刷机包刷入
run docker in termux
太棒了!你已经完成了 99% 的核心工作。既然你的内核已经支持了 Docker(开启了 Cgroups, Namespace, OverlayFS 等),在 Termux 中运行 Docker 就只剩下最后的“软件配置”了。
由于 Android 的系统架构和标准 Linux 发行版(如 Ubuntu)不同,直接运行 dockerd 通常会报错(主要是 Cgroups 挂载点的问题)。你需要按照以下步骤来“欺骗” Docker,让它以为自己跑在标准 Linux 上。
第一步:安装 Docker 和必要工具
打开 Termux,执行以下命令安装 Docker 和 Root 权限工具:
# 1. 更新源
pkg update && pkg upgrade
# 2. 安装 tsu (用于在 Termux 中获取 root 权限)
pkg install tsu
# 3. 启用 root 仓库
pkg install root-repo
# 4. 安装 docker 软件包
pkg install docker docker-compose
第二步:编写启动脚本 (关键)
Android 的 Cgroups 挂载点和 Docker 预期的不一样(Android 在 /dev/cg2_bpf 或其他位置,Docker 想要 /sys/fs/cgroup)。我们需要写一个脚本来手动挂载它们。
在 Termux 中创建一个启动脚本,比如叫 run_docker.sh:
复制粘贴以下内容(这是适配 Android 12/13/14 的通用挂载脚本):
#!/data/data/com.termux/files/usr/bin/bash
# ================= 配置区域 =================
# Docker 默认网段范围 (覆盖 172.16.x.x ~ 172.31.x.x)
DOCKER_SUBNET="172.16.0.0/12"
# EasyTier 虚拟网段设置
# 如果你需要 EasyTier,请填入你的虚拟网段 CIDR (例如 10.144.144.0/24)
# 如果你不需要或想禁用此功能,请将引号内容留空,例如 EASYTIER_SUBNET=""
EASYTIER_SUBNET="10.144.144.0/24"
# 自定义路由表 ID (防止污染系统表)
DOCKER_TABLE_ID=100
DOCKER_REPLY_TABLE_ID=101
# 路由规则优先级
RULE_PREF=2500
# ===========================================
# 0. Root 权限检查
if [ "$(id -u)" != "0" ]; then
sudo "$0" "$@"
exit $?
fi
echo "=== 🚀 Android Docker 智能启动 (完全版) ==="
# ----------------------------------------------------------------
# 1. 自动配置 daemon.json
# ----------------------------------------------------------------
CONF_DIR="/data/data/com.termux/files/usr/etc/docker"
CONF_FILE="$CONF_DIR/daemon.json"
if [ ! -f "$CONF_FILE" ]; then
echo "[1/7] Creating daemon.json..."
mkdir -p "$CONF_DIR"
cat > "$CONF_FILE" <<'DAEMON_EOF'
{
"data-root": "/data/data/com.termux/files/usr/lib/docker",
"exec-root": "/data/data/com.termux/files/usr/var/run/docker",
"pidfile": "/data/data/com.termux/files/usr/var/run/docker.pid",
"dns": ["223.5.5.5", "119.29.29.29", "8.8.8.8"],
"userland-proxy": false,
"ip-masq": true,
"iptables": true,
"bridge": "docker0",
"hosts": ["unix:///data/data/com.termux/files/usr/var/run/docker.sock"],
"storage-driver": "overlay2"
}
DAEMON_EOF
else
echo "[1/7] daemon.json exists, skipping overwrite."
fi
# 如果网络不行,daemon.json 中可以设置 "mtu": 1280, 来排除这个可能
# ----------------------------------------------------------------
# 2. 基础环境挂载 (Cgroups)
# ----------------------------------------------------------------
echo "[2/7] Mounting Cgroups..."
if [ ! -d /sys/fs/cgroup ]; then mkdir -p /sys/fs/cgroup; fi
if ! mountpoint -q /sys/fs/cgroup; then mount -t tmpfs -o mode=755 tmpfs /sys/fs/cgroup; fi
for subsys in cpu cpuacct memory devices freezer blkio perf_event pids cpuset; do
mkdir -p /sys/fs/cgroup/$subsys
if ! mountpoint -q /sys/fs/cgroup/$subsys; then
mount -t cgroup -o $subsys cgroup /sys/fs/cgroup/$subsys 2>/dev/null || true
fi
done
# ----------------------------------------------------------------
# 3. 内核参数优化
# ----------------------------------------------------------------
echo "[3/7] Enabling IP Forwarding & Fixing rp_filter..."
sysctl -w net.ipv4.ip_forward=1 > /dev/null
sysctl -w net.ipv4.conf.all.forwarding=1 > /dev/null
for file in /proc/sys/net/ipv4/conf/*/rp_filter; do
echo 0 > "$file"
done
# ----------------------------------------------------------------
# 4. 智能路由配置
# ----------------------------------------------------------------
echo "[4/7] Configuring Routing Strategy..."
# A. 清理旧规则
while ip rule del from all lookup main pref 1 2>/dev/null; do true; done
while ip rule del from all lookup main pref 30000 2>/dev/null; do true; done
while ip rule del from $DOCKER_SUBNET lookup $DOCKER_TABLE_ID 2>/dev/null; do true; done
while ip rule del to $DOCKER_SUBNET lookup main 2>/dev/null; do true; done
while ip rule del fwmark 0x20233 lookup main 2>/dev/null; do true; done
while ip rule del fwmark 0x999 lookup main 2>/dev/null; do true; done
while ip rule del from 172.17.0.0/16 lookup $DOCKER_REPLY_TABLE_ID 2>/dev/null; do true; done
# B. 探测当前外网出口
ROUTE_INFO=$(ip route get 223.5.5.5 2>/dev/null)
INTERFACE=$(echo "$ROUTE_INFO" | grep -oP 'dev \K\S+')
GATEWAY=$(echo "$ROUTE_INFO" | grep -oP 'via \K\S+')
if [ -n "$INTERFACE" ]; then
echo " -> 当前系统主出口: $INTERFACE (网关: ${GATEWAY:-直连})"
# C. 为 Docker 出站建立独立路由表
ip route flush table $DOCKER_TABLE_ID
if [ -n "$GATEWAY" ]; then
ip route add default via "$GATEWAY" dev "$INTERFACE" table $DOCKER_TABLE_ID
else
ip route add default dev "$INTERFACE" table $DOCKER_TABLE_ID
fi
# D. Docker 出站策略
ip rule add from $DOCKER_SUBNET lookup $DOCKER_TABLE_ID pref $RULE_PREF
echo " -> 策略已生效: Docker 出站流量 -> Table $DOCKER_TABLE_ID -> $INTERFACE"
# E. Docker 入站策略(去往 Docker 的流量走 main 表)
ip rule add to $DOCKER_SUBNET lookup main pref $((RULE_PREF - 1))
echo " -> 回程路由: 去往 Docker -> main table"
# F. fwmark 绕过 Android VPN
ip rule add fwmark 0x20233 lookup main pref 100
echo " -> fwmark 规则: 0x20233 -> main table"
else
echo "⚠️ 警告: 未检测到网络连接"
fi
# G. 修复 EasyTier
if [ -n "$EASYTIER_SUBNET" ]; then
echo "检测到 EasyTier 配置..."
while ip rule del to "$EASYTIER_SUBNET" lookup main pref 1 2>/dev/null; do true; done
ip rule add to "$EASYTIER_SUBNET" lookup main pref 1
echo " -> EasyTier 路由: to $EASYTIER_SUBNET -> main table"
fi
# ----------------------------------------------------------------
# 5. 防火墙与 NAT
# ----------------------------------------------------------------
echo "[5/7] Applying Firewall & NAT Rules..."
# 清理规则
iptables -t nat -F POSTROUTING 2>/dev/null
iptables -t mangle -F OUTPUT 2>/dev/null
iptables -t mangle -F POSTROUTING 2>/dev/null
# A. 允许转发
iptables -P FORWARD ACCEPT
iptables -I FORWARD 1 -j ACCEPT
# B. Docker 基础 NAT
iptables -t nat -A POSTROUTING -s "$DOCKER_SUBNET" ! -d "$DOCKER_SUBNET" -j MASQUERADE
# C. 高通硬件校验和修复
iptables -t mangle -A POSTROUTING -p tcp -j CHECKSUM --checksum-fill 2>/dev/null
iptables -t mangle -A POSTROUTING -p udp -j CHECKSUM --checksum-fill 2>/dev/null
# D. fwmark 标记
iptables -t mangle -A OUTPUT -d $DOCKER_SUBNET -j MARK --set-mark 0x20233
echo " -> iptables 标记: 去往 Docker -> fwmark 0x20233"
# ----------------------------------------------------------------
# 6. 启动 Docker
# ----------------------------------------------------------------
echo "[6/7] Starting Dockerd..."
pkill dockerd 2>/dev/null
sleep 2
pkill containerd 2>/dev/null
sleep 1
dockerd > /dev/null 2>&1 &
IS_RUNNING=0
for i in {1..10}; do
if [ -S "/data/data/com.termux/files/usr/var/run/docker.sock" ]; then
echo "✅ Docker Daemon is Running!"
export DOCKER_HOST="unix:///data/data/com.termux/files/usr/var/run/docker.sock"
IS_RUNNING=1
break
fi
sleep 1
done
if [ "$IS_RUNNING" -eq 1 ]; then
if docker ps >/dev/null 2>&1; then
echo " 服务响应正常 (docker ps OK)"
fi
else
echo "❌ 启动失败"
exit 1
fi
# ----------------------------------------------------------------
# 7. 配置局域网访问(动态检测)
# ----------------------------------------------------------------
echo "[7/7] Configuring LAN Access..."
# 等待 Docker 网络就绪
sleep 2
# 动态检测物理网络接口和 IP
detect_lan_interface() {
# 优先级:wlan0 > eth0 > rndis0 > usb0
for iface in wlan0 eth0 rndis0 usb0; do
if ip link show $iface 2>/dev/null | grep -q "state UP"; then
local lan_ip=$(ip -4 addr show $iface 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1)
if [ -n "$lan_ip" ]; then
echo "$iface:$lan_ip"
return 0
fi
fi
done
return 1
}
LAN_INFO=$(detect_lan_interface)
if [ -n "$LAN_INFO" ]; then
LAN_IFACE=$(echo $LAN_INFO | cut -d: -f1)
LAN_IP=$(echo $LAN_INFO | cut -d: -f2)
LAN_SUBNET=$(echo $LAN_IP | cut -d. -f1-3).0/24
echo " -> 检测到局域网接口: $LAN_IFACE ($LAN_IP)"
# A. 为 Docker 回程创建路由表
ip route flush table $DOCKER_REPLY_TABLE_ID 2>/dev/null
ip route add $LAN_SUBNET dev $LAN_IFACE src $LAN_IP table $DOCKER_REPLY_TABLE_ID
# 尝试添加默认路由(如果有网关)
LAN_GW=$(ip route | grep "default.*$LAN_IFACE" | grep -oP 'via \K\S+' | head -1)
if [ -n "$LAN_GW" ]; then
ip route add default via $LAN_GW dev $LAN_IFACE table $DOCKER_REPLY_TABLE_ID 2>/dev/null
else
ip route add default dev $LAN_IFACE table $DOCKER_REPLY_TABLE_ID 2>/dev/null
fi
# B. 添加策略路由(Docker 回程走专用表)
ip rule add from 172.17.0.0/16 lookup $DOCKER_REPLY_TABLE_ID pref 98
echo " -> 回程路由表: 172.17.0.0/16 -> table $DOCKER_REPLY_TABLE_ID"
# C. FORWARD 链允许
iptables -I FORWARD 1 -i $LAN_IFACE -o docker0 -j ACCEPT
iptables -I FORWARD 1 -i docker0 -o $LAN_IFACE -j ACCEPT
echo " -> FORWARD: $LAN_IFACE <-> docker0"
# D. SNAT(回程地址转换)
iptables -t nat -I POSTROUTING 1 -s 172.17.0.0/16 -o $LAN_IFACE -j SNAT --to-source $LAN_IP
echo " -> SNAT: 172.17.0.0/16 -> $LAN_IP"
# E. INPUT 链允许(可选,更安全的方式)
iptables -I INPUT 1 -i $LAN_IFACE -p tcp --dport 1024:65535 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
echo " -> INPUT: 允许 $LAN_IFACE 访问高位端口"
echo "✅ 局域网访问已配置完成"
echo " 局域网设备可通过 http://$LAN_IP:<端口> 访问容器"
else
echo "⚠️ 未检测到活跃的局域网接口,跳过局域网访问配置"
echo " (仅支持本机访问 http://172.17.0.1:<端口>)"
fi
echo ""
echo "=== 🎉 Docker 启动完成 ==="
echo "本机访问: http://172.17.0.1:<端口>"
if [ -n "$LAN_IP" ]; then
echo "局域网访问: http://$LAN_IP:<端口>"
fi
保存并退出 (Ctrl+O -> Enter -> Ctrl+X)。
第三步 配置 docker 相关文件
如果完整执行了上一步,则不需要此步。
配置 DOCKER_HOST
告诉 docker 客户端:别去 /var/run 找了,来这里找
tsu
echo export DOCKER_HOST=\"unix:///data/data/com.termux/files/usr/var/run/docker.sock\" >> ~/.bashrc
source ~/.bashrc
修改 /data/data/com.termux/files/usr/etc/docker/daemon.json 为以下内容
{
"data-root": "/data/data/com.termux/files/usr/lib/docker",
"exec-root": "/data/data/com.termux/files/usr/var/run/docker",
"pidfile": "/data/data/com.termux/files/usr/var/run/docker.pid",
"dns": ["223.5.5.5", "119.29.29.29", "8.8.8.8"],
"userland-proxy": false,
"ip-masq": true,
"iptables": true,
"bridge": "docker0",
"hosts": ["unix:///data/data/com.termux/files/usr/var/run/docker.sock"],
"storage-driver": "overlay2"
}
第四步:运行 Docker
-
赋予脚本执行权限:
tsu
chmod +x run_docker.sh
-
启动!
你会看到很多日志输出,只要没有红色的 Fatal Error,并且最后停在日志滚动状态,就说明守护进程启动成功了。
-
测试 Docker (保持上面的窗口不动,从左侧滑出菜单,新建一个 Termux 窗口):
# 在新窗口中获取 root
tsu
# 测试 hello-world
docker run hello-world
第五步:常见故障排除
如果 docker run 报错,请检查以下几点:
-
devices cgroup isn't mounted:
- 说明脚本里的
mount -t cgroup -o devices ... 失败了。
- 检查你的内核配置:
zcat /proc/config.gz | grep CONFIG_CGROUP_DEVICE,必须是 =y。如果之前编译时漏了,得重编内核。
-
iptables failed / 无法联网:
- 在启动脚本的
dockerd 命令后面加上 --iptables=false。
- Docker 容器默认用 Google DNS (8.8.8.8),如果你在墙内,可能需要在运行容器时指定 DNS:
docker run --dns 223.5.5.5 hello-world
-
OverlayFS not supported:
- Docker 会回退到
vfs 存储驱动,非常慢且占用空间。
- 检查内核配置
CONFIG_OVERLAY_FS=y。
- 注意:Docker 数据通常存在
/data/docker 下,这里是 ext4/f2fs 文件系统,支持 OverlayFS。不要试图在 /sdcard (FAT/FUSE) 上跑 Docker。
现在,你的 Note 20 Ultra 应该已经变成一台运行 Docker 的微型 Linux 服务器了!你可以跑 HomeAssistant, QingLong, 甚至 Linux 桌面版容器。
ln
第三步配置了 DOCKER_HOST ,则可省略此步骤
use MT manager
sudo ln -s /data/data/com.termux/files/usr/var/run/docker.sock /var/run/docker.sock
keep alive
这是一个在 Termux/Android 上运行 Docker 最常见的问题。
虽然你之前运行 hello-world 成功了,但现在报错 Cannot connect to the Docker daemon at unix:///var/run/docker.sock,通常意味着 后台的 Docker 服务进程 (dockerd) 已经挂了(被杀后台了),或者你忘记加 sudo 了。
对于三星 Note 20 Ultra (Android 12/13/14),有一个名为 "Phantom Process Killer" (幽灵进程杀手) 的系统机制是 Docker 的头号大敌。
请按照以下步骤逐步排查并彻底修复:
1. 最简单的检查:你加 sudo 了吗?
在 Termux 中,Docker 的守护进程是 Root 权限运行的,所以 Docker 客户端命令也必须加 sudo。
- 错误写法:
docker run xxx
- 正确写法:
sudo docker run xxx
如果加了 sudo 还是报这个错,说明 dockerd 服务确实已经停止了。
2. 核心修复:禁用三星幽灵进程杀手 (必做!)
三星 Android 12+ 系统有一个极其激进的机制,会监控 Termux 的子进程。一旦 Docker 开始拉取镜像(如 openlist 或 openwrt)或者消耗较多资源,系统会判定它是“耗电的幽灵进程”并直接杀死 dockerd。
你需要连接电脑 ADB,执行以下两条命令来禁用这个机制(重启后可能失效,建议保存):
# 1. 将幽灵进程上限调整到最大值 (约 20 亿)
adb shell "/system/bin/device_config put activity_manager max_phantom_processes 2147483647"
# 2. 禁用配置同步 (防止系统自动改回去)
adb shell "/system/bin/device_config set_sync_disabled_for_tests persistent"
执行完这两条命令后,不需要重启手机,Docker 就会变得非常稳定。
3. 重启 Docker 守护进程
既然它已经挂了,你需要重新启动它。
-
杀掉残留进程 (防止端口占用):
sudo pkill dockerd
sudo pkill containerd
-
重新运行启动脚本:
切换回你运行 run_docker.sh 的那个窗口,或者重新运行它:
确保你看到日志显示 "API listen on /var/run/docker.sock" 并且没有报错退出。
-
再试一次 (在新窗口):
sudo docker pull openwrt/openwrt # 举例
4. 进阶技巧:让 Docker 自动复活
如果你不想每次都手动敲命令重启,可以在 Termux 安装一个进程管理工具,或者简单地写一个循环脚本来“守护”它。
创建一个 keep_docker_alive.sh:
#!/bin/bash
while true; do
echo "启动 Dockerd..."
# 这里的参数根据你的实际情况调整
sudo dockerd --iptables=false >> docker.log 2>&1
echo "Dockerd 挂了! 5秒后重启..."
sleep 5
done
运行这个脚本,即使 Docker 被系统杀了,它也会立刻复活。
总结
- ADB 命令 是解决三星手机 Docker 不稳定的关键,一定要做。
- 永远记得用
sudo docker。
- 保持
run_docker.sh 的窗口一直开着,不要关闭它。