Chroot

来自 Alpine Linux

“更改根目录”或 “chroot” 是一种聚焦于文件系统一部分的方法,例如,/path 将指向以前在 /mnt/path 访问的内容。“chroot” 表达式中的 “root” 指的是根文件系统 /,而不是 root 用户。(虽然通常您需要 root 用户权限才能执行 chroot。)

执行 chroot 的一个常见原因是维护现有系统,在这些系统中,启动和/或登录不再起作用。必须以某种方式启动硬件,例如使用安装或救援 CD 或 USB;然后挂载损坏的系统并 chroot 进入其中并执行修复。常见的例子有

  • 重新安装 引导加载程序
  • 重建 initramfs 镜像
  • 升级或降级软件包
  • 重置遗忘的密码

Chroot 还可以用于创建和托管系统的独立虚拟化安装。这对于以下情况很有用:

  • 测试和开发,使用在生产系统上部署风险太高的软件。
  • 可以在仅填充其预期依赖项的 chroot 中开发、构建和测试软件。
  • 可以运行旧版软件或其他无法在主机系统上安装其支持库或数据文件的软件。
  • 打开的文件描述符(用于文件、管道和网络连接)可以通过运行程序 “携带到” chroot 中,从而使这些程序的工作文件不必在 chroot 目录中可见。这有助于分离权限或设计沙箱。请注意,chroot 不是包含具有 root 权限的恶意进程的安全方法。请参阅维基百科关于 chroot 限制的一些讨论

使用 chroot 安装 Alpine Linux 的脚本


准备工作

  • 本指南中的所有步骤都必须以 root 用户身份执行。
  • 如果您正在使用 chroot 来修复现有的 Linux 系统,则需要先挂载它。如果您不知道它所在的磁盘的位置和/或文件类型,请阅读 fdisk -llsblk 的输出。我们假设损坏的系统位于 /dev/sdx1,其文件类型为 ext3。创建一个目录来挂载此磁盘,并挂载它

    mkdir -p $CHROOT
    mount -t ext3 /dev/sdx1 $CHROOT

    其中 $CHROOT 评估为您要 chroot 进入的路径(例如,/mnt)。

    如果系统目录已加密或位于 LVM 组中或类似情况,我们将留给您自己去弄清楚采取哪些步骤来准备和挂载它。

  • 如果其他目录(例如 /boot/var/usr/home)有单独的文件系统,也请挂载它们

    mount /dev/sdx2 $CHROOT/boot
    mount /dev/sdx3 $CHROOT/var
    mount /dev/sdx4 $CHROOT/usr

    对于许多修复任务,您可能不需要挂载损坏系统的 /home 目录。

    如果您的 /boot 在单独的分区上,则在处理 GRUB、执行内核升级或任何此类操作之前,必须先挂载它。也可以在您 chroot 之后 挂载其中一些文件系统,在某些情况下,在 chroot 后挂载 /boot 在 GRUB 上效果更好。但是,如果可以,最好事先挂载它们。原因是,如果您从 chroot 内部执行此操作,则外部/主机环境将不知道已挂载的文件系统,因此,如果您在退出 chroot 之前忘记卸载它们,则系统在关闭时也不会知道要卸载它们。这可能会损坏这些文件系统。

  • 下面提供的脚本将负责生成或重新挂载其他系统目录,如 /dev/proc/sys/tmp 等。
  • 您启动进入的主机系统的架构(例如,如果是 32 位 LiveCD,则为 x86)和您要 chroot 进入的系统的架构必须匹配。您可以使用 uname -m 确定您启动进入的架构。
  • 在 chroot 之前,请确保在 chroot 内部工作时所需的所有内核模块都已加载。例如

    modprobe dm-mod
    modprobe dm-crypt

  • 如果您需要在 chroot 内部设置网络,例如安装更新的软件包。下面提供的脚本将复制主机系统的 /etc/resolv.conf,但在覆盖 chroot 内部现有的 etc/resolv.conf 之前会提示。如果您愿意,您可以从头开始生成一个,方法是执行

    echo 'nameserver 8.8.8.8' > $CHROOT/etc/resolv.conf

执行 chroot

最可靠的 chroot 方法是使用以下脚本

/root/start-chroot 的内容

#!/bin/sh -e if [ 0 -ne `id -u` ]; then echo "This script needs root access" >&2 exit 1 fi if ! [ -d "$1" ] || [ x-h = x"$*" ] || [ x--help = x"$*" ]; then echo "Usage: ${0##*/} <chroot_directory>" >&2 exit 1 fi cd "$1" if ! [ -d ./etc ]; then echo "No etc directory inside $1" >&2 exit 1 fi shift MOUNTED= umount_all() { case $MOUNTED in shm\ *) if [ -L ./dev/shm ]; then umount ./`readlink ./dev/shm` else umount ./dev/shm fi MOUNTED=${MOUNTED#shm };; esac case $MOUNTED in run\ *) umount ./run MOUNTED=${MOUNTED#run };; esac case $MOUNTED in tmp\ *) umount ./tmp MOUNTED=${MOUNTED#tmp };; esac case $MOUNTED in proc\ *) umount ./proc MOUNTED=${MOUNTED#proc };; esac case $MOUNTED in sys\ *) umount ./sys MOUNTED=${MOUNTED#sys };; esac case $MOUNTED in pts\ *) umount ./dev/pts MOUNTED=${MOUNTED#pts };; esac case $MOUNTED in dev\ *) umount ./dev MOUNTED=${MOUNTED#dev };; esac } trap 'umount_all' EXIT #mkdir -p ./etc ./dev/pts ./sys ./proc ./tmp ./run ./boot ./root cp -iL /etc/resolv.conf ./etc/ || true # if ^C, will cancel script mount --bind /dev ./dev MOUNTED="dev $MOUNTED" mount -t devpts devpts ./dev/pts -o nosuid,noexec MOUNTED="pts $MOUNTED" mount -t sysfs sys ./sys -o nosuid,nodev,noexec,ro MOUNTED="sys $MOUNTED" mount -t proc proc ./proc -o nosuid,nodev,noexec MOUNTED="proc $MOUNTED" mount -t tmpfs tmp ./tmp -o mode=1777,nosuid,nodev,strictatime MOUNTED="tmp $MOUNTED" mount -t tmpfs run ./run -o mode=0755,nosuid,nodev MOUNTED="run $MOUNTED" if [ -L ./dev/shm ]; then mkdir -p ./`readlink ./dev/shm` mount -t tmpfs shm ./`readlink ./dev/shm` -o mode=1777,nosuid,nodev else #mkdir -p ./dev/shm mount -t tmpfs shm ./dev/shm -o mode=1777,nosuid,nodev fi MOUNTED="shm $MOUNTED" case $1 in -l) shift;; -l*) one=${1#-l}; shift; set -- -"$one" "$@";; esac chroot . /usr/bin/env -i SHELL=/bin/sh HOME=/root TERM="$TERM" \ PATH=/usr/sbin:/usr/bin:/sbin:/bin PS1='chroot # ' /bin/sh -l "$@" # FIXME # are USER and LOGNAME set automatically? # perhaps: source /etc/profile && export PS1="chroot $PS1"


将该脚本复制到系统上的某个位置,例如 /root/start-chroot,运行 chmod +x 使其可执行,然后像这样调用它

/root/start-chroot $CHROOT

待办事项:关于环境变量的注释,start-chroot 的额外参数


当然可以手动执行 chroot,而无需使用此脚本。对于某些目的,您可以采用比脚本更简单的过程。但是很难提前列出何时走捷径会给您带来麻烦,因此最好养成使用此脚本(或执行与其相同操作)的习惯。当您在各种 wiki(可能包括此 wiki)中阅读时,您会遇到各种关于如何手动 chroot 的说明。这些说明通常在彼此之间的小细节上有所不同,并且通常不如上述脚本那么详尽。


故障排除

如果您看到错误

  • chroot: cannot run command '/bin/sh': Exec format error,则很可能是主机环境和 chroot 环境的架构不匹配。例如,您使用 32 位 Live CD 启动,并尝试 chroot 进入 x86_64 系统。
  • chroot: '/bin/sh': Permission denied,尝试使用 exec 权限重新挂载系统:mount -o remount,exec $CHROOT

在 chroot 内部

此时,您仍然运行着您启动的内核,但是所有路径 /path 将指向以前的 $CHROOT/path

如果您在运行系统上打开或连接了多个终端,则 chroot 仅在您执行它的终端中有效。

现在您可以执行所需的任何故障排除(或进行任何开发工作)

  • 将 GRUB 重新安装到磁盘的 MBR
  • 重建您的 initramfs 镜像
  • 修复您的 /etc/fstab
  • 执行内核升级(或降级)
  • 使用您的软件包管理器(重新)安装其他软件包
  • 重置遗忘的密码

或者任何其他操作。

如果您要对 GRUB 执行任何操作,则应首先确保您的 /etc/mtab 文件是最新的

grep -v rootfs /proc/mounts > /etc/mtab


从 chroot 运行图形应用程序

如果您的外部/主机系统上正在运行 X 服务器,则可以从 chroot 内部启动图形应用程序。要允许 chroot 环境连接到 X 服务器,您必须在主机系统中执行此操作(在 chroot 之前,或在执行 chroot 以外的终端中)

xhost +local

然后,要将来自 chroot 的应用程序定向到 X 服务器,请在 chroot 内部设置 DISPLAY 环境变量,使其值与主机环境中(拥有 X 服务器的用户)的值匹配。例如,在 chroot 内部,您可能会说

export DISPLAY=:0

清理

完成后,确保所有正在运行的程序都已停止。然后退出 chroot

exit

如果您使用 start-chroot 脚本启动 chroot,它将负责卸载 /dev/proc/sys/tmp 等。否则,您应该手动执行此操作。在任何情况下,您都应该卸载您自己挂载的任何分区,例如 /boot,然后卸载 $CHROOT 处的系统分区(如果适用)。

如果您收到错误消息,指出 $CHROOT(或其中的任何分区)正忙,则可能意味着以下两种情况之一

  • 程序仍在 chroot 内部运行。
  • 或者更常见的情况是,此挂载点上仍然存在挂载点。例如,在尝试卸载 $CHROOT 时,$CHROOT/usr 仍处于挂载状态。

在后一种情况下,只需先卸载有问题的挂载点即可。要提醒您当前的所有挂载点,请在不带参数的情况下运行 mount


其他资源