Skip to content

Instantly share code, notes, and snippets.

@genbtc
Created January 7, 2023 02:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save genbtc/384e3fbe29bb8889bf097ace72985f94 to your computer and use it in GitHub Desktop.
Save genbtc/384e3fbe29bb8889bf097ace72985f94 to your computer and use it in GitHub Desktop.
chroot.bash
#!/usr/bin/env bash
#
# Shamelessly "borrowed" from Arch to make Gentoo development in chroots easier;
# Thanks for doing all of the hard work! -@Kangie, 2022
# subsequently modified by @genr8eofl, 2023
#
#
# Options:
# -d | --debug
# -f | --no-bind-distfiles | NO_MOUNT_DISTFILES
# -k | --no-bind-kernel | NO_MOUNT_KERNEL_SOURCE
# -n | --no-bind-bashrc | NO_MOUNT_BASHRC
# -b | --bind-binpkgs | MOUNT_BINPKGS
# -m | --bind-makeconf | MOUNT_MAKECONF
# -r | --bind-hostrepos | MOUNT_HOSTREPOS
# -u | --user | USERSPEC=user[:group]
usage() {
cat <<EOF
Usage: ${0##*/} [-options] /path/to/chroot [command]
Description:
Provide it with the directory path that you want to chroot into,
and it will handle all of the pre-requisites, plus advanced features!
Such as the following options. Along with the appropriate ENVVARS set
Options:
[ -d | --debug ], [ -u | --user <user>[:group] ],
[ -f | --no-bind-distfiles ], [ -k | --no-bind-kernel, [ -n | --no-bind-bashrc ],
[ -b | --bind-binpkgs ], [ -m | --bind-makeconf ], [ -r | --bind-hostrepos ]
/path/to/chroot is REQUIRED, the obvious choice is /mnt/gentoo
command is optional. If unspecified, ${0##*/} will launch /bin/bash by default.
Note, the target chroot directory *SHOULD* be a mountpoint.
-This ensures that tools such as findmnt(8) have an accurate hierarchy of
the mounted filesystems within the chroot.
-Tip, you can bind mount the directory on itself to make it a mountpoint:
'mount --bind /your/chroot /your/chroot' (as a workaround)
EOF
exit 2
}
shopt -s extglob
#include utility functions, option handling, etc.
#source chroot-include.bash
out() { printf '%s %s %s\n' "$1" "$2" "${@:3}"; }
error() { out "==> \033[1;31mERROR\033[0m:" "$@"; } >&2
warning() { out "==> \033[1;33mWARNING\033[0m:" "$@"; } >&2
msg() { out "==>" "$@"; }
msg2() { out " ->" "$@";}
die() { error "$@"; exit 1; }
ignore_error() {
"$@" 2>/dev/null
return 0
}
chroot_add_mount() {
mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}")
}
chroot_maybe_add_mount() {
local cond=$1; shift
if eval "$cond"; then
chroot_add_mount "$@"
fi
}
# meat
chroot_setup() {
CHROOT_ACTIVE_MOUNTS=()
[[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap'
trap 'chroot_teardown' EXIT
chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev &&
chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro &&
ignore_error chroot_maybe_add_mount "[[ -d '$1/sys/firmware/efi/efivars' ]]" \
efivarfs "$1/sys/firmware/efi/efivars" -t efivarfs -o nosuid,noexec,nodev &&
chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid &&
chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec &&
chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev &&
chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 &&
chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid
}
chroot_teardown() {
if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then
umount "${CHROOT_ACTIVE_MOUNTS[@]}"
fi
unset CHROOT_ACTIVE_MOUNTS
}
chroot_add_mount_lazy() {
mount "$@" && CHROOT_ACTIVE_LAZY=("$2" "${CHROOT_ACTIVE_LAZY[@]}")
}
chroot_bind_device() {
local DEVICE="${CHROOT_DIR}/$1"
touch "${DEVICE}" && CHROOT_ACTIVE_FILES=("${DEVICE}" "${CHROOT_ACTIVE_FILES[@]}")
chroot_add_mount "$1" "${DEVICE}" --bind
}
chroot_add_link() {
ln -sf "$1" "$2" && CHROOT_ACTIVE_FILES=("$2" "${CHROOT_ACTIVE_FILES[@]}")
}
resolve_link() {
local target=$1 root=$2
# If a root was given, make sure it ends in a slash.
[[ -n $root && $root != */ ]] && root=$root/
while [[ -L $target ]]; do
target=$(readlink -m "$target")
# If a root was given, make sure the target is under it.
# Make sure to strip any leading slash from target first.
[[ -n $root && $target != $root* ]] && target=$root${target#/}
done
printf %s "$target"
}
# potato
# Mount arbitrary files into your chroot
# usage: chroot_add_file src-file[:dst-file]
# (gets CHROOT_DIR from environment)
chroot_add_file() {
local SRC_FILE DST_FILE
if [[ "${1}" == *:* ]]; then
# Bind src -> dest
SRC_FILE=${1%:*}
DST_FILE=${1#*:}
SRC_FILE=$(resolve_link "${SRC_FILE}")
DST_FILE=$(resolve_link "${CHROOT_DIR}/${DST_FILE}" "${CHROOT_DIR}")
else
SRC_FILE=$(resolve_link "${1}")
DST_FILE=$(resolve_link "${CHROOT_DIR}/${1}" "${CHROOT_DIR}")
fi
# If we don't have a source file, there's nothing useful we can do.
[[ -e ${SRC_FILE} ]] || return 0
if [[ ! -e $DST_FILE ]]; then
# $DST_FILE may not exist because:
#
# 1. The file truly does not exist, so the variable becomes equal to $CHROOT_DIR/$1.
# 2. $1/$2 is (or resolves to) a broken link.
# The environment clearly intends for there to be a file here, but something's wrong.
# Maybe it normally creates the target at boot time?
# In either case we'll (try to) take care of it by creating a dummy file at the target,
# so that we have something to bind to.
#
# Case 1.
#[[ ${DST_FILE} = "${CHROOT_DIR}/${1}" ]] && return 0
# Case 2.
install -Dm644 /dev/null "${DST_FILE}" || die "${DST_FILE} does not exist in chroot and a dummy file to bind to could not be created"
fi
chroot_add_mount "${SRC_FILE}" "${DST_FILE}" --bind
}
# Relevant paths
dir_vardbrepos="/var/db/repos"
dir_vardbgentoo="/var/db/repos/gentoo"
dir_binpkgs="/var/cache/binpkgs"
dir_distfiles="/var/cache/distfiles"
dir_usrclinux="/usr/src/linux"
file_makeconf="/etc/portage/make.conf"
file_reposconf="/etc/portage/repos.conf"
file_resolvconf="/etc/resolv.conf"
gentoo_bashrc="/tmp/gentoo-bashrc"
# the core program
gentoo-chroot() {
local CHROOT_FILES=( )
# required preliminary checks
(( EUID == 0 )) || die "This script must be run with root privileges"
[[ -d $CHROOT_DIR ]] || die "Can't create chroot on non-directory %s" "$CHROOT_DIR"
# actually setup chroot (basic mounts)
chroot_setup "$CHROOT_DIR" || die "failed to setup chroot %s" "$CHROOT_DIR"
# warn if somehow its still not mounted, NonFatal
if ! mountpoint -q "$CHROOT_DIR"; then
warning "$CHROOT_DIR is not a mountpoint. This may have undesirable side effects."
#TODO: can bind-mount itself to itself in the script but leave it to the user for now
warning "-Tip, you can bind mount the directory on itself to make it a mountpoint:"
warning " 'mount --bind /your/chroot /your/chroot' (as a workaround)"
fi
# copy host's /etc/resolv.conf for network DNS server settings
CHROOT_FILES+=( "${file_resolvconf}" )
# Create our bashrc which does useful gentoo functions like env-update and setting $PS1 (chroot)
if [[ -z ${NO_MOUNT_BASHRC+x} ]]; then
if [ ! -r ${gentoo_bashrc} ]; then
cat > ${gentoo_bashrc} <<EOF
#!/usr/bin/env bash
source /etc/profile && env-update
export PS1="(chroot) \$PS1"
EOF
fi #^ backslash to escape the $ so it doesnt expand the variable
CHROOT_FILES+=( "${gentoo_bashrc}:/root/.bashrc" ) # map .bashrc to /root, inside the chroot
fi
# re-use the host system's Make.conf
if [[ -n ${MOUNT_MAKECONF+x} ]]; then
CHROOT_FILES+=( "${file_makeconf}" )
fi
# Allow the use of $ADDITIONAL_CHROOT_FILES to mount arbirary files from a running env
if (( ${#ADDITIONAL_CHROOT_FILES[@]} )); then
CHROOT_FILES+=( "${ADDITIONAL_CHROOT_FILES}" )
fi
for FILE in "${CHROOT_FILES[@]}"; do
chroot_add_file "${FILE}" || die "failed to setup ${FILE}"
done
# Distfiles, Try and avoid redundant downloads
if [[ -z ${NO_MOUNT_DISTFILES+x} ]]; then
chroot_bind_device $dir_distfiles "${CHROOT_DIR}${dir_distfiles}"
fi
# Kernel Source dir
if [[ -z ${NO_MOUNT_KERNEL_SOURCE+x} ]]; then
chroot_bind_device $dir_usrclinux "${CHROOT_DIR}${dir_usrclinux}"
fi
# Repository DB and repos.conf, optional
if [[ -n ${MOUNT_HOSTREPOS+x} ]]; then
chroot_bind_device $file_reposconf "${CHROOT_DIR}${file_reposconf}"
chroot_bind_device $dir_vardbrepos "${CHROOT_DIR}${dir_vardbrepos}"
else
# A fresh stage3 may not have /var/db/repos/gentoo, create it.
if [ ! -d "${CHROOT_DIR}${dir_vardbgentoo}" ]; then
ignore_error rm "${CHROOT_DIR}${dir_vardbgentoo}"
mkdir -p "${CHROOT_DIR}${dir_vardbgentoo}"
fi
chroot_bind_device $dir_vardbgentoo "${CHROOT_DIR}${dir_vardbgentoo}"
fi
# Binpkgs, optional
if [[ -d "${dir_binpkgs}" ]] && [[ -n ${MOUNT_BINPKGS+x} ]]; then
chroot_bind_device $dir_binpkgs "${CHROOT_DIR}${dir_binpkgs}"
fi
# Run it
CHROOT_ARGS=()
[[ $USERSPEC ]] && CHROOT_ARGS+=(--userspec "$USERSPEC")
# If arguments = 0, run a shell
if [ ${#ARGS[@]} -eq 0 ]; then
SHELL=/bin/bash chroot "${CHROOT_ARGS[@]}" -- "$CHROOT_DIR"
# else run the command specified in the @ARGS
else
#shellcheck disable=2145 # "Argument mixes string and array. Use * or separate argument." ----->|
chroot "${CHROOT_ARGS[@]}" -- "$CHROOT_DIR" /bin/bash -c "source /etc/profile && env-update && ${ARGS[@]}"
fi
}
#argument parsing
PARSED_ARGS=$(getopt -a -n "${0##*/}" -o bfdhkmnNru: --long bind-binpkgs,no-bind-distfiles,debug,help,no-bind-kernel,bind-makeconf,no-bind-bashrc,unshare,bind-hostrepos,user: -- "$@")
eval set -- "$PARSED_ARGS"
while :
do
case "$1" in
-b | --bind-binpkgs ) MOUNT_BINPKGS=1 ; shift ;;
-f | --no-bind-distfiles ) NO_MOUNT_DISTFILES=1 ; shift ;;
-d | --debug ) DEBUG=1; echo -e "\e[1;35mEnabling very-basic debug!\e[0m"; set -x; shift ;;
-h | --help ) usage; exit 0 ;;
-k | --no-bind-kernel) NO_MOUNT_KERNEL_SOURCE=1; shift ;;
-m | --bind-makeconf ) MOUNT_MAKECONF=1 ; shift ;;
-n | --no-bind-bashrc ) NO_MOUNT_BASHRC=1 ; shift ;;
-r | --bind-hostrepos ) MOUNT_HOSTREPOS=1 ; shift ;;
-u | --user ) USERSPEC="${2}"; shift 2 ;;
-- ) shift ; break ;;
esac
done
if [ $# -eq 0 ]; then
usage || die "missing parameters - specify a chroot directory"
fi
CHROOT_DIR=$1
shift
ARGS=("$@")
#run program
$DEBUG gentoo-chroot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment