proot multistrap best - hanyong/note GitHub Wiki

proot multistrap 最佳实践

更新:2017-12-15 环境:ubuntu 16.04

基本观念:

  • 先搭建一个最小的可用环境, 其他软件包使用 apt-get 按需安装。 配合 ubuntu 的 command-not-found,很容易知道需要安装的软件包。

  • 为安全考虑,可使用 proot 尽量避免使用真实 root 权限。

最小软件包整理

许多软件包的安装配置都依赖一些基础软件包,但是其依赖说明上没有包含这些依赖,可能它们假设这些依赖必定是已经存在的。 如许多软件包的安装配置都会使用 sed, sh, bash 等,反过来 bash 又依赖这些软件包,如果严格声明的话将会产生依赖循环。 multistrap 等默认会将 Priority: required 的软件包全部装上,所以通常不会有问题。 但其中包含许多我们不需要的软件包。 经过不断试错整理,以下为必须最小软件包集合:

libc-bin dash coreutils findutils mawk sed grep bash base-passwd base-files

我们可以在此基础上添加 dpkg 或 apt 得到具备安装软件包能力的最小基础环境。

  • libc-bin 到 grep 为 dpkg 必须的基本包。

  • 许多软件配置脚本和正常运行还依赖 bash base-passwd base-files 。

  • locales 也是很基本的软件包,其又依赖 gzip,这些软件包可以不加入最小环境,稍后使用 apt 安装。

  • 稍后可使用如下命令查询 required 软件包列表:

aptitude search '?Priority(required) ?Architecture(amd64)' -F '%p' ```

fakeinit 脚本

ubuntu 14.04 测试,其他版本未测试。

一些软件包安装过程会调用 initctl 等,可设置 fakeinit 。 最小包包含 initscripts(?),第一阶段后执行:

TARGET=. sbin/fakeinit.sh

添加 fakeinit 脚本:

cat > sbin/fakeinit.sh <<'EOF'
#!/bin/sh

if ! [ -e "$TARGET/usr/sbin/policy-rc.d.REAL" ]; then
	test -e "$TARGET/usr/sbin/policy-rc.d" || touch "$TARGET/usr/sbin/policy-rc.d"
	mv -T "$TARGET/usr/sbin/policy-rc.d" "$TARGET/usr/sbin/policy-rc.d.REAL"
	echo \
"#!/bin/sh
exit 101" > "$TARGET/usr/sbin/policy-rc.d"
	chmod 755 "$TARGET/usr/sbin/policy-rc.d"
fi

if ! [ -e "$TARGET/sbin/start-stop-daemon.REAL" ]; then
	mv "$TARGET/sbin/start-stop-daemon" "$TARGET/sbin/start-stop-daemon.REAL"
	echo \
"#!/bin/sh
echo
echo \"Warning: Fake start-stop-daemon called, doing nothing\"" > "$TARGET/sbin/start-stop-daemon"
	chmod 755 "$TARGET/sbin/start-stop-daemon"
fi

if ! [ -e "$TARGET/sbin/initctl.REAL" ]; then
	if [ -x "$TARGET/sbin/initctl" ]; then
	  mv "$TARGET/sbin/initctl" "$TARGET/sbin/initctl.REAL"
	  echo \
"#!/bin/sh
if [ \"\$1\" = version ]; then exec /sbin/initctl.REAL \"\$@\"; fi
echo
echo \"Warning: Fake initctl called, doing nothing\"" > "$TARGET/sbin/initctl"
	  chmod 755 "$TARGET/sbin/initctl"
	fi
fi
EOF
chmod 755 sbin/fakeinit.sh

cat > sbin/unfakeinit.sh <<'EOF'
#!/bin/sh

	if [ -x "$TARGET/sbin/initctl.REAL" ]; then
		mv "$TARGET/sbin/initctl.REAL" "$TARGET/sbin/initctl"
	fi

if [ -x "$TARGET/sbin/start-stop-daemon.REAL" ]; then
	mv "$TARGET/sbin/start-stop-daemon.REAL" "$TARGET/sbin/start-stop-daemon"
fi

if [ -e "$TARGET/usr/sbin/policy-rc.d.REAL" ]; then
	mv -T "$TARGET/usr/sbin/policy-rc.d.REAL" "$TARGET/usr/sbin/policy-rc.d"
	test -z "$(cat "$TARGET/usr/sbin/policy-rc.d")" && rm -f "$TARGET/usr/sbin/policy-rc.d"
fi
EOF
chmod 755 sbin/unfakeinit.sh

multistrap 安装

准备一个最小配置文件 ubu1604.conf 如下:

[General]
bootstrap=Ubuntu
aptsources=Ubuntu
unpack=true
omitrequired=true
omitpreinst=true
ignorenativearch=true
allowrecommends=false
arch=
multiarch=

[Ubuntu]
#packages=libc-bin dash coreutils findutils mawk sed grep bash base-passwd base-files dpkg
packages=libc-bin dash coreutils findutils mawk sed grep bash base-passwd base-files apt
source=http://cn.archive.ubuntu.com/ubuntu/
keyring=
suite=xenial
omitdebsrc=true
components=main
  • 配置名必须全部为小写字母,读起来有点吃力。
  • 初始安装尽量少的包,配置 omitrequired,手动指定包列表。
  • 宿主机上正常已安装 ubuntu-keyring,apt 已依赖 ubuntu-keyring,不必配置 keyring,否则 multistrap 会在宿主机上请求 root 权限进行下载 keyring 等操作。
  • multistrap 执行 preinst 或安装本地包需要 root 权限执行 chroot,配置 omitpreinstignorenativearch 避免此操作。
  • multistrap 未执行 preinst,则必须手动执行。参考 man multistrap 相关说明。
  • multiarch 影响安装后的系统 dpkg 配置,参考 dpkg --print-foreign-architectures
  • multistrap 支持配置 cleanup 清理下载的软件包,但我们稍后依赖 dpkg -i 安装软件包,不要清理。事后可执行 apt-get clean 清理。

第一阶段:

rm -rf ubu1604/ && env LANG=C LANGUAGE= multistrap -d ubu1604/ -f ubu1604.conf | tee multistrap.log && ln multistrap-config.sh packages.txt ubu1604/

第二阶段:

env LANG=C LANGUAGE= PATH=/usr/sbin:/usr/bin:/sbin:/bin proot -r ubu1604/ -b /proc -b /sys -b /dev -b /dev/pts -w / -0 bash multistrap-config.sh
  • 参考 man multistrap 说明,可手动执行 preinst 后调用 dpkg --configure -a。 但多个 preinst 需要确定顺序,configure 也因为顺序和依赖问题失败,所以考虑写一个脚本完成第二阶段配置。

  • 如何决定配置软件包的顺序?主要依赖下载包的顺序,有失败的地方再手动调整。 从 multistrap 操作标准输出日志解析下载包顺序:

cat multistrap.log | sed '1,/^After this operation/ d' | grep -P '^Get:\d+' | awk '{print $5}' > packages.txt ```

首先尝试配置 dpkg 最小环境:

  • 配置 libc6 时报错其依赖 libgcc1,先配置 libgcc1 又报错其依赖 libc6,循环依赖。两个包同时配置可解决此问题。

  • 配置 base-files 时报错:

Setting up base-files (9.4ubuntu4) ... chown: invalid user: 'root:root' dpkg: error processing package base-files (--configure): subprocess installed post-installation script returned error exit status 1 Errors were encountered while processing: base-files ```

用户信息 `root:root` 等应该是 `base-passwd` 提供的,将其移动到 base-passwd 之后。
  • 配置 bash 时报错依赖 base-files,将其移动到 base-files 之后。

然后尝试配置 apt 最小环境:

  • 执行 debconf 的 preinst 时报错:

dpkg-maintscript-helper: error: couldn't identify the package ```

看了下这个包在 dpkg 之后,dpkg 环境应该已经完全安装配置完成。

dpkg 之后的软件包尝试使用 dpkg -i 直接从 deb 包安装。

  • dpkg -i 安装 passwd 时报错如下:

Setting up passwd (1:4.2-3.1ubuntu5) ... chown: cannot access '/etc/shadow': No such file or directory chown: cannot access '/etc/gshadow': No such file or directory chmod: cannot access '/etc/shadow': No such file or directory chmod: cannot access '/etc/gshadow': No such file or directory Please correct the error and rerun `/sbin/shadowconfig on' dpkg: error processing package passwd (--install): subprocess installed post-installation script returned error exit status 1 Errors were encountered while processing: passwd ```

查了一下 `/etc/shadow` 等文件应该就是由 passwd 软件包管理的。

看日志提示以上错误发生在 /sbin/shadowconfig on,这个命令应该就是生成 shadow 等文件。 看了下这个文件是个脚本,其中调用了 pwconv,而看 man pwconv 其就是生成 shadow 文件的。

```

The pwconv command creates shadow from passwd and an optionally existing shadow. ```

测了一下果然 pwconv 执行失败(退出码1),看文档其有使用 `/etc/login.defs` 中的配置,此文件来自包 `login`,尝试添加此软件包。
  • 安装 login 时也发生报错 chown: invalid user: 'root:root',尝试将其移动到 base-files 之后,安装成功。

总结:

  • libc6, libgcc1 同时 configure 。
  • 调整 base-filesbase-passwd 之后。
  • 调整 bashbase-files 之后。
  • apt 最小环境需手动添加 login 软件包,调整到 base-files 之后。

最终配置脚本 multistrap-config.sh 如下:

#!/bin/bash
# multistrap target environment second stage configure
set -e
export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true
export LC_ALL=C LANGUAGE=C LANG=C
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
#cat multistrap.log | sed '1,/^After this operation/ d' | grep -P '^Get:\d+' | awk '{print $5}' > packages.txt
afterDpkg=false
cat packages.txt | while read line ; do
	if test -z "${line}" ; then
		continue
	fi
	if ! "${afterDpkg}" ; then
		echo ">>>> process ${line}"
		for e in ${line} ; do
			if test -e /var/lib/dpkg/info/"${e}".preinst ; then
				echo ">> preinst ${e}"
				/var/lib/dpkg/info/"${e}".preinst install
			fi
			if test "${e}" = "dpkg" ; then
				afterDpkg=true
			fi
		done
		echo ">> configure ${line}"
		dpkg --configure ${line}
	else
		echo ">>>> install ${line}"
		debList=()
		for e in ${line} ; do
			debList=( "${debList[@]}" /var/cache/apt/archives/"${e}"_*.deb )
		done
		dpkg -i "${debList[@]}"
	fi
done
#dpkg --configure -a

拷贝 resolv.conf,使 proot 到目标环境后可访问网络。

```

rsync -avi -L /etc/resolv.conf ubu1604/etc/ ```

目标环境可移动 multistrap 设置的 apt sources 为默认配置:

mv /etc/apt/sources.list.d/multistrap-ubuntu.list /etc/apt/sources.list

基本配置和软件包安装

apt-get update
apt-get install locales gzip bzip2 xz-utils -y
locale-gen en_US.UTF-8 zh_CN.UTF-8 zh_CN.GBK zh_CN.GB18030
apt-get install aptitude -y
aptitude install command-not-found -y