diff --git a/arch-chroot.in b/arch-chroot.in index fd6140e..055fb85 100644 --- a/arch-chroot.in +++ b/arch-chroot.in @@ -4,11 +4,15 @@ shopt -s extglob m4_include(common) +setup=chroot_setup +unshare="$root_unshare" + usage() { cat <[:group] Specify non-root user and optional group to use If 'command' is unspecified, ${0##*/} will launch /bin/bash. @@ -51,12 +55,16 @@ chroot_add_resolv_conf() { chroot_add_mount /etc/resolv.conf "$resolv_conf" --bind } -while getopts ':hu:' flag; do +while getopts ':hNu:' flag; do case $flag in h) usage exit 0 ;; + N) + setup=unshare_setup + unshare="$user_unshare" + ;; u) userspec=$OPTARG ;; @@ -70,21 +78,27 @@ while getopts ':hu:' flag; do 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" +arch-chroot() { + (( EUID == 0 )) || die 'This script must be run with root privileges' + + [[ -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 + $setup "$chrootdir" || die "failed to setup chroot %s" "$chrootdir" + chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf" -chroot_setup "$chrootdir" || die "failed to setup chroot %s" "$chrootdir" -chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf" + if ! mountpoint -q "$chrootdir"; then + warning "$chrootdir is not a mountpoint. This may have undesirable side effects." + fi + + chroot_args=() + [[ $userspec ]] && chroot_args+=(--userspec "$userspec") -chroot_args=() -[[ $userspec ]] && chroot_args+=(--userspec "$userspec") + SHELL=/bin/bash chroot "${chroot_args[@]}" -- "$chrootdir" "${args[@]}" +} -SHELL=/bin/bash unshare --fork --pid chroot "${chroot_args[@]}" -- "$chrootdir" "$@" +args=("$@") +$unshare bash -c "$(declare_all); arch-chroot" diff --git a/common b/common index 0fe3cb0..7ff0652 100644 --- a/common +++ b/common @@ -100,6 +100,77 @@ chroot_teardown() { unset CHROOT_ACTIVE_MOUNTS } +chroot_add_mount_lazy() { + mount "$@" && CHROOT_ACTIVE_LAZY=("$2" "${CHROOT_ACTIVE_LAZY[@]}") +} + +chroot_bind_device() { + touch "$2" && CHROOT_ACTIVE_FILES=("$2" "${CHROOT_ACTIVE_FILES[@]}") + chroot_add_mount $1 "$2" --bind +} + +chroot_add_link() { + ln -sf "$1" "$2" && CHROOT_ACTIVE_FILES=("$2" "${CHROOT_ACTIVE_FILES[@]}") +} + +unshare_setup() { + CHROOT_ACTIVE_MOUNTS=() + CHROOT_ACTIVE_LAZY=() + CHROOT_ACTIVE_FILES=() + [[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap' + trap 'unshare_teardown' EXIT + + chroot_add_mount_lazy "$1" "$1" --bind && + chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev && + chroot_add_mount_lazy /sys "$1/sys" --rbind && + chroot_add_link "$1/proc/self/fd" "$1/dev/fd" && + chroot_add_link "$1/proc/self/fd/0" "$1/dev/stdin" && + chroot_add_link "$1/proc/self/fd/1" "$1/dev/stdout" && + chroot_add_link "$1/proc/self/fd/2" "$1/dev/stderr" && + chroot_bind_device /dev/full "$1/dev/full" && + chroot_bind_device /dev/null "$1/dev/null" && + chroot_bind_device /dev/random "$1/dev/random" && + chroot_bind_device /dev/tty "$1/dev/tty" && + chroot_bind_device /dev/urandom "$1/dev/urandom" && + chroot_bind_device /dev/zero "$1/dev/zero" && + 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 +} + +unshare_teardown() { + chroot_teardown + + if (( ${#CHROOT_ACTIVE_LAZY[@]} )); then + umount --lazy "${CHROOT_ACTIVE_LAZY[@]}" + fi + unset CHROOT_ACTIVE_LAZY + + if (( ${#CHROOT_ACTIVE_FILES[@]} )); then + rm "${CHROOT_ACTIVE_FILES[@]}" + fi + unset CHROOT_ACTIVE_FILES +} + +root_unshare="unshare --fork --pid" +user_unshare="$root_unshare --mount --map-auto --map-root-user --setuid 0 --setgid 0" + +# This outputs code for declaring all variables to stdout. For example, if +# FOO=BAR, then running +# declare -p FOO +# will result in the output +# declare -- FOO="bar" +# This function may be used to re-declare all currently used variables and +# functions in a new shell. +declare_all() { + # Remove read-only variables to avoid warnings. Unfortunately, declare +r -p + # doesn't work like it looks like it should (declaring only read-write + # variables). However, declare -rp will print out read-only variables, which + # we can then use to remove those definitions. + declare -p | grep -Fvf <(declare -rp) + # Then declare functions + declare -pf +} + try_cast() ( _=$(( $1#$2 )) ) 2>/dev/null diff --git a/completion/arch-chroot.bash b/completion/arch-chroot.bash index 707208a..583bd8f 100644 --- a/completion/arch-chroot.bash +++ b/completion/arch-chroot.bash @@ -2,7 +2,7 @@ _arch_chroot() { compopt +o dirnames local cur prev opts i _init_completion -n : || return - opts="-u -h" + opts="-N -u -h" for i in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do if [[ -d ${i} ]]; then diff --git a/completion/pacstrap.bash b/completion/pacstrap.bash index fb948f0..a77cb04 100644 --- a/completion/pacstrap.bash +++ b/completion/pacstrap.bash @@ -8,7 +8,7 @@ _pacstrap() { COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="-C -c -G -i -M -h" + opts="-C -c -G -i -M -N -h" for i in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do if [[ -d ${i} ]]; then diff --git a/doc/arch-chroot.8.asciidoc b/doc/arch-chroot.8.asciidoc index 5586a46..11fa36d 100644 --- a/doc/arch-chroot.8.asciidoc +++ b/doc/arch-chroot.8.asciidoc @@ -32,6 +32,11 @@ i.e.: Options ------- +*-N*:: + Run in unshare mode. This will use linkman:unshare[1] to create a new + mount and user namespace, allowing regular users to create new system + installations. + *-u [:group]*:: Specify non-root user and optional group to use. diff --git a/doc/pacstrap.8.asciidoc b/doc/pacstrap.8.asciidoc index d3d517c..6eed23e 100644 --- a/doc/pacstrap.8.asciidoc +++ b/doc/pacstrap.8.asciidoc @@ -37,6 +37,11 @@ Options *-M*:: Avoid copying the host's mirrorlist to the target. +*-N*:: + Run in unshare mode. This will use linkman:unshare[1] to create a new + mount and user namespace, allowing regular users to create new system + installations. + *-U*:: Use pacman -U to install packages. Useful for obtaining fine-grained control over the installed packages. diff --git a/pacstrap.in b/pacstrap.in index 703df11..9ffe17c 100644 --- a/pacstrap.in +++ b/pacstrap.in @@ -16,6 +16,8 @@ hostcache=0 copykeyring=1 copymirrorlist=1 pacmode=-Sy +setup=chroot_setup +unshare="$root_unshare" usage() { cat <