Created
April 16, 2020 09:46
-
-
Save bhelm/65283c37a0cb585089041214002df4f7 to your computer and use it in GitHub Desktop.
Chroot script from Archlinux (arch-chroot), works on any distri
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
shopt -s extglob | |
# generated from util-linux source: libmount/src/utils.c | |
declare -A pseudofs_types=([anon_inodefs]=1 | |
[autofs]=1 | |
[bdev]=1 | |
[bpf]=1 | |
[binfmt_misc]=1 | |
[cgroup]=1 | |
[cgroup2]=1 | |
[configfs]=1 | |
[cpuset]=1 | |
[debugfs]=1 | |
[devfs]=1 | |
[devpts]=1 | |
[devtmpfs]=1 | |
[dlmfs]=1 | |
[efivarfs]=1 | |
[fuse.gvfs-fuse-daemon]=1 | |
[fusectl]=1 | |
[hugetlbfs]=1 | |
[mqueue]=1 | |
[nfsd]=1 | |
[none]=1 | |
[pipefs]=1 | |
[proc]=1 | |
[pstore]=1 | |
[ramfs]=1 | |
[rootfs]=1 | |
[rpc_pipefs]=1 | |
[securityfs]=1 | |
[sockfs]=1 | |
[spufs]=1 | |
[sysfs]=1 | |
[tmpfs]=1) | |
# generated from: pkgfile -vbr '/fsck\..+' | awk -F. '{ print $NF }' | sort | |
declare -A fsck_types=([cramfs]=1 | |
[exfat]=1 | |
[ext2]=1 | |
[ext3]=1 | |
[ext4]=1 | |
[ext4dev]=1 | |
[jfs]=1 | |
[minix]=1 | |
[msdos]=1 | |
[reiserfs]=1 | |
[vfat]=1 | |
[xfs]=1) | |
out() { printf "$1 $2\n" "${@:3}"; } | |
error() { out "==> ERROR:" "$@"; } >&2 | |
warning() { out "==> WARNING:" "$@"; } >&2 | |
msg() { out "==>" "$@"; } | |
msg2() { out " ->" "$@";} | |
die() { error "$@"; exit 1; } | |
ignore_error() { | |
"$@" 2>/dev/null | |
return 0 | |
} | |
in_array() { | |
local i | |
for i in "${@:2}"; do | |
[[ $1 = "$i" ]] && return 0 | |
done | |
return 1 | |
} | |
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 | |
} | |
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" --bind && | |
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 | |
} | |
try_cast() ( | |
_=$(( $1#$2 )) | |
) 2>/dev/null | |
valid_number_of_base() { | |
local base=$1 len=${#2} i= | |
for (( i = 0; i < len; i++ )); do | |
try_cast "$base" "${2:i:1}" || return 1 | |
done | |
return 0 | |
} | |
mangle() { | |
local i= chr= out= | |
local {a..f}= {A..F}= | |
for (( i = 0; i < ${#1}; i++ )); do | |
chr=${1:i:1} | |
case $chr in | |
[[:space:]\\]) | |
printf -v chr '%03o' "'$chr" | |
out+=\\ | |
;; | |
esac | |
out+=$chr | |
done | |
printf '%s' "$out" | |
} | |
unmangle() { | |
local i= chr= out= len=$(( ${#1} - 4 )) | |
local {a..f}= {A..F}= | |
for (( i = 0; i < len; i++ )); do | |
chr=${1:i:1} | |
case $chr in | |
\\) | |
if valid_number_of_base 8 "${1:i+1:3}" || | |
valid_number_of_base 16 "${1:i+1:3}"; then | |
printf -v chr '%b' "${1:i:4}" | |
(( i += 3 )) | |
fi | |
;; | |
esac | |
out+=$chr | |
done | |
printf '%s' "$out${1:i}" | |
} | |
optstring_match_option() { | |
local candidate pat patterns | |
IFS=, read -ra patterns <<<"$1" | |
for pat in "${patterns[@]}"; do | |
if [[ $pat = *=* ]]; then | |
# "key=val" will only ever match "key=val" | |
candidate=$2 | |
else | |
# "key" will match "key", but also "key=anyval" | |
candidate=${2%%=*} | |
fi | |
[[ $pat = "$candidate" ]] && return 0 | |
done | |
return 1 | |
} | |
optstring_remove_option() { | |
local o options_ remove=$2 IFS=, | |
read -ra options_ <<<"${!1}" | |
for o in "${!options_[@]}"; do | |
optstring_match_option "$remove" "${options_[o]}" && unset 'options_[o]' | |
done | |
declare -g "$1=${options_[*]}" | |
} | |
optstring_normalize() { | |
local o options_ norm IFS=, | |
read -ra options_ <<<"${!1}" | |
# remove empty fields | |
for o in "${options_[@]}"; do | |
[[ $o ]] && norm+=("$o") | |
done | |
# avoid empty strings, reset to "defaults" | |
declare -g "$1=${norm[*]:-defaults}" | |
} | |
optstring_append_option() { | |
if ! optstring_has_option "$1" "$2"; then | |
declare -g "$1=${!1},$2" | |
fi | |
optstring_normalize "$1" | |
} | |
optstring_prepend_option() { | |
local options_=$1 | |
if ! optstring_has_option "$1" "$2"; then | |
declare -g "$1=$2,${!1}" | |
fi | |
optstring_normalize "$1" | |
} | |
optstring_get_option() { | |
local opts o | |
IFS=, read -ra opts <<<"${!1}" | |
for o in "${opts[@]}"; do | |
if optstring_match_option "$2" "$o"; then | |
declare -g "$o" | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
optstring_has_option() { | |
local "${2%%=*}" | |
optstring_get_option "$1" "$2" | |
} | |
dm_name_for_devnode() { | |
read dm_name <"/sys/class/block/${1#/dev/}/dm/name" | |
if [[ $dm_name ]]; then | |
printf '/dev/mapper/%s' "$dm_name" | |
else | |
# don't leave the caller hanging, just print the original name | |
# along with the failure. | |
print '%s' "$1" | |
error 'Failed to resolve device mapper name for: %s' "$1" | |
fi | |
} | |
fstype_is_pseudofs() { | |
(( pseudofs_types["$1"] )) | |
} | |
fstype_has_fsck() { | |
(( fsck_types["$1"] )) | |
} | |
usage() { | |
cat <<EOF | |
usage: ${0##*/} chroot-dir [command] | |
-h Print this help message | |
-u <user>[:group] Specify non-root user and optional group to use | |
If 'command' is unspecified, ${0##*/} will launch /bin/bash. | |
Note that when using arch-chroot, the target chroot directory *should* be a | |
mountpoint. This ensures that tools such as pacman(8) or findmnt(8) have an | |
accurate hierarchy of the mounted filesystems within the chroot. | |
If your chroot target is not a mountpoint, you can bind mount the directory on | |
itself to make it a mountpoint, i.e. 'mount --bind /your/chroot /your/chroot'. | |
EOF | |
} | |
chroot_add_resolv_conf() { | |
local chrootdir=$1 resolv_conf=$1/etc/resolv.conf | |
[[ -e /etc/resolv.conf ]] || return 0 | |
# Handle resolv.conf as a symlink to somewhere else. | |
if [[ -L $chrootdir/etc/resolv.conf ]]; then | |
# readlink(1) should always give us *something* since we know at this point | |
# it's a symlink. For simplicity, ignore the case of nested symlinks. | |
resolv_conf=$(readlink "$chrootdir/etc/resolv.conf") | |
if [[ $resolv_conf = /* ]]; then | |
resolv_conf=$chrootdir$resolv_conf | |
else | |
resolv_conf=$chrootdir/etc/$resolv_conf | |
fi | |
# ensure file exists to bind mount over | |
if [[ ! -f $resolv_conf ]]; then | |
install -Dm644 /dev/null "$resolv_conf" || return 1 | |
fi | |
elif [[ ! -e $chrootdir/etc/resolv.conf ]]; then | |
# The chroot might not have a resolv.conf. | |
return 0 | |
fi | |
chroot_add_mount /etc/resolv.conf "$resolv_conf" --bind | |
} | |
while getopts ':hu:' flag; do | |
case $flag in | |
h) | |
usage | |
exit 0 | |
;; | |
u) | |
userspec=$OPTARG | |
;; | |
:) | |
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG" | |
;; | |
?) | |
die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG" | |
;; | |
esac | |
done | |
shift $(( OPTIND - 1 )) | |
(( EUID == 0 )) || die 'This script must be run with root privileges' | |
(( $# )) || die 'No chroot directory specified' | |
chrootdir=$1 | |
shift | |
[[ -d $chrootdir ]] || die "Can't create chroot on non-directory %s" "$chrootdir" | |
if ! mountpoint -q "$chrootdir"; then | |
warning "$chrootdir is not a mountpoint. This may have undesirable side effects." | |
fi | |
chroot_setup "$chrootdir" || die "failed to setup chroot %s" "$chrootdir" | |
chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf" | |
chroot_args=() | |
[[ $userspec ]] && chroot_args+=(--userspec "$userspec") | |
SHELL=/bin/bash unshare --fork --pid chroot "${chroot_args[@]}" -- "$chrootdir" "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment