blob: 3a57fb6ee4f945dfac0dc10c1758e1d0ef2c9684 [file] [log] [blame]
#!/bin/bash
#
# setup-buildchroot - set up a Debian build chroot
#
# For details, see usage() and Documentation/building-xfstests.md
set -e -u
SCRIPTNAME="$(basename "$0")"
SCRIPTDIR="$(readlink -f "$(dirname "$0")")"
INTERACTIVE=true
DEBIAN_RELEASE=
DEBIAN_RELEASE_DEFAULT=stretch
DEBIAN_ARCH=
DEBIAN_ARCH_DEFAULT=amd64
DEBIAN_MIRROR=
DEBIAN_MIRROR_DEFAULT=http://ftp.debian.org/debian
CHROOT_DIR=
CHROOT_NAME=
CHROOT_USER=
QEMU=
BINFMT_MISC_MNT=/proc/sys/fs/binfmt_misc
SCHROOT_CONFFILE=/etc/schroot/schroot.conf
SCHROOT_CHROOT_D=/etc/schroot/chroot.d
SCHROOT_FSTAB=xfstests-bld/fstab
SCHROOT_FSTAB_FILE=/etc/schroot/$SCHROOT_FSTAB
PROXY=
# Additional packages to install in the build chroot
BUILD_DEPENDENCIES=(
autoconf
autoconf2.64
automake
autopoint
bison
build-essential
ca-certificates
debootstrap
e2fslibs-dev
fakechroot
gettext
git
golang-1.8-go
libdbus-1-3
libgdbm-dev
libkeyutils-dev
libssl-dev
libtool-bin
pkg-config
qemu-utils
symlinks
)
die()
{
local msg
echo 1>&2 "ERROR: $1"
shift
for msg; do
echo 1>&2 " $msg"
done
exit 1
}
log()
{
local msg
echo "[INFO] $1"
shift
for msg; do
echo " $msg"
done
}
run_cmd()
{
log "Running command: $*"
"$@"
}
usage()
{
cat <<EOF
Usage: $SCRIPTNAME [OPTION]...
Set up a Debian chroot for building xfstests tarballs and test appliances. Both
native and foreign chroots are supported; foreign chroots will use QEMU
user-mode emulation. The resulting chroot is added to $SCHROOT_CONFFILE
so that it can be entered using the schroot program. Run with no arguments to
be prompted for the various options, or specify them on the command line.
Options:
--release=RELEASE Debian release to use. Default: $DEBIAN_RELEASE_DEFAULT
--arch=ARCH Debian architecture to use. Default: $DEBIAN_ARCH_DEFAULT
--mirror=MIRROR Debian mirror to use. Default: $DEBIAN_MIRROR_DEFAULT
--chroot-dir=DIR Chroot directory. Default: /chroots/\$RELEASE-\$ARCH
--chroot-name=NAME Name of chroot. Default: basename of \$DIR
--chroot-user=USER User which will be allowed to access the chroot,
***including passwordless root access***. Default:
value of \$SUDO_USER, if any.
--noninteractive Use the defaults rather than prompting
EOF
}
check_prerequisites()
{
if ! type -P debootstrap >/dev/null; then
die "debootstrap is not installed!" \
"On Debian-based systems, run: 'sudo apt-get install debootstrap'"
fi
if ! type -P schroot >/dev/null; then
die "schroot is not installed!" \
"On Debian-based systems, run: 'sudo apt-get install schroot'"
fi
if [ ! -f "$SCHROOT_CONFFILE" ]; then
die "$SCHROOT_CONFFILE does not exist!"
fi
if [ "$(id -u)" != 0 ]; then
die "this script must be run as root!"
fi
}
prompt_for_param()
{
local prompt="$1"
local param_name="$2"
local default_value="$3"
if [ -z "${!param_name}" ] && $INTERACTIVE; then
echo -n "Enter $prompt (${default_value:-none}): "
read -r "$param_name"
fi
if [ -z "${!param_name}" ]; then
declare -g "$param_name=$default_value"
fi
}
select_debian_release()
{
prompt_for_param "Debian release" DEBIAN_RELEASE "$DEBIAN_RELEASE_DEFAULT"
}
select_debian_arch()
{
while true; do
prompt_for_param "Debian architecture" \
DEBIAN_ARCH "$DEBIAN_ARCH_DEFAULT"
local suggestion=
case "$DEBIAN_ARCH" in
x86|i686)
suggestion=i386
;;
x86_64|x86-64|x64)
suggestion=amd64
;;
arm|arm32|aarch32)
suggestion=armhf
;;
aarch64)
suggestion=arm64
;;
esac
if [ -z "$suggestion" ]; then
break
fi
local msg="$DEBIAN_ARCH is not a valid Debian architecture name; did you mean $suggestion?"
if ! $INTERACTIVE; then
die "$msg"
fi
echo "$msg"
DEBIAN_ARCH=
done
}
select_debian_mirror()
{
prompt_for_param "Debian mirror" DEBIAN_MIRROR "$DEBIAN_MIRROR_DEFAULT"
}
select_chroot_dir()
{
prompt_for_param "chroot directory" \
CHROOT_DIR "/chroots/${DEBIAN_RELEASE}-${DEBIAN_ARCH}"
if [ -e "$CHROOT_DIR" ]; then
die "$CHROOT_DIR already exists!"
fi
}
select_chroot_name()
{
prompt_for_param "chroot name" CHROOT_NAME "$(basename "$CHROOT_DIR")"
}
select_chroot_user()
{
prompt_for_param "chroot user" CHROOT_USER "${SUDO_USER:-}"
}
select_proxy()
{
prompt_for_param "proxy" PROXY "${PROXY}"
}
parse_options()
{
local longopts="help"
local options
longopts+=",release:"
longopts+=",arch:"
longopts+=",mirror:"
longopts+=",chroot-dir:"
longopts+=",chroot-name:"
longopts+=",chroot-user:"
longopts+=",noninteractive"
if ! options=$(getopt -o "" -l "$longopts" -- "$@"); then
usage 1>&2
exit 2
fi
eval set -- "$options"
while (( $# >= 0 )); do
case "$1" in
--help)
usage
exit 0
;;
--release)
DEBIAN_RELEASE="$2"
shift
;;
--arch)
DEBIAN_ARCH="$2"
shift
;;
--mirror)
DEBIAN_MIRROR="$2"
shift
;;
--chroot-dir)
CHROOT_DIR="$2"
shift
;;
--chroot-name)
CHROOT_NAME="$2"
shift
;;
--chroot-user)
CHROOT_USER="$2"
shift
;;
--noninteractive)
INTERACTIVE=false
;;
--)
shift
break
;;
*)
echo 1>&2 "Invalid option: \"$1\""
usage 1>&2
exit 2
;;
esac
shift
done
}
is_native_chroot()
{
case "$(uname -m)" in
x86_64) [[ $DEBIAN_ARCH = amd64 || $DEBIAN_ARCH = i386 ]] ;;
aarch64) [[ $DEBIAN_ARCH = arm64 || $DEBIAN_ARCH = armhf ]] ;;
arm) [[ $DEBIAN_ARCH = armhf ]] ;;
ppc64le) [[ $DEBIAN_ARCH = ppc64el ]] ;;
*) [[ $DEBIAN_ARCH = "$(uname -m)" ]] ;;
esac
}
# Validate that binfmt_misc support for the requested architecture is enabled
# and lists a sufficiently new QEMU binary as the interpreter. Then set $QEMU
# to the path to the QEMU binary which should be used.
validate_binfmt_misc()
{
local qemu_arch binfmt binfmt_file full_version version major minor
case "$DEBIAN_ARCH" in
armhf) qemu_arch=arm ;;
arm64) qemu_arch=aarch64 ;;
ppc64el) qemu_arch=ppc64le ;;
*) qemu_arch="$DEBIAN_ARCH" ;;
esac
if [ ! -d "$BINFMT_MISC_MNT" ]; then
die "$BINFMT_MISC_MNT doesn't exist. To set up a chroot for a" \
"foreign architecture, you must enable CONFIG_BINFMT_MISC in your kernel."
fi
if ! mountpoint "$BINFMT_MISC_MNT" &>/dev/null; then
die "binfmt_misc is not mounted on $BINFMT_MISC_MNT!" \
"If your init system isn't mounting binfmt_misc automatically" \
"(systemd should mount it by default), you can add to your fstab:" \
"" \
" binfmt_misc $BINFMT_MISC_MNT binfmt_misc defaults 0 0"
fi
if [ "$(<"$BINFMT_MISC_MNT/status")" = "disabled" ]; then
die "binfmt_misc is currently disabled! To enable it, run:"
" sudo sh -c 'echo 1 > $BINFMT_MISC_MNT/status'"
fi
binfmt="qemu-$qemu_arch"
binfmt_file="$BINFMT_MISC_MNT/$binfmt"
if [ ! -e "$binfmt_file" ]; then
die "No binfmt_misc handler for $binfmt is installed!" \
"Make sure you've installed both the binfmt-support and qemu-user-static" \
"packages, e.g. on Debian-based systems:" \
"" \
" sudo apt-get install binfmt-support qemu-user-static" \
"" \
"on some systems you must also explicitly enable and start the" \
"binfmt-support service:" \
"" \
" sudo systemctl enable --now binfmt-support.service"
fi
QEMU=$(awk '/^interpreter/{print $2}' "$binfmt_file")
full_version=$("$QEMU" -version \
| grep -E -m1 -o 'version +[0-9]+(\.[0-9]+)+.*' \
| sed -r 's/^version +//;s/, Copyright[^.]*$//')
version=$(echo "$full_version" | grep -E -o '^+[0-9]+(\.[0-9]+)+')
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
if (( major < 2 || ( major == 2 && minor < 8 ) )); then
die "$QEMU is too old!" \
"" \
"Version: $full_version" \
"" \
"Due to bugs in old QEMU versions, we require at least QEMU 2.8.0. On" \
"Debian-based systems, try installing the qemu-user-static package from" \
"Debian stretch:" \
"" \
" https://packages.debian.org/stretch/qemu-user-static" \
"" \
"Note that you must update the file $QEMU, or else" \
"update the binfmt entry. (Just putting a newer $(basename "$QEMU")" \
"binary somewhere else on your \$PATH isn't sufficient, since schroot" \
"will take the binary listed as the binfmt interpreter, currently" \
"\"$QEMU\", and bind-mount it into the chroot.)"
fi
log "Detected foreign chroot, using user-mode emulation with $QEMU version $full_version"
}
# Get the schroot configuration from either schroot.conf or entries in
# the chroot.d directory
get_schroot_config()
{
cat $SCHROOT_CONFFILE
if test -d $SCHROOT_CHROOT_D
then
(cd $SCHROOT_CHROOT_D ;
find . -maxdepth 1 -type f |
egrep '^./[a-ZA-Z0-9_-][a-ZA-Z0-9_.-]*$' |
xargs cat)
fi
}
# Extract the schroot.conf entry, if any, for $CHROOT_NAME
extract_schroot_entry()
{
get_schroot_config | awk '
{
if ($0 ~ /^[[:space:]]*\[.*\][[:space:]]*$/) {
sub(/\][[:space:]]*$/, "")
sub(/^[[:space:]]*\[/, "")
section=$0
} else if (section == "'"$CHROOT_NAME"'" && $0 !~ /^[[:space:]]*(#.*)?$/) {
print
}
}
'
}
generate_schroot_entry()
{
# See `man schroot.conf`
#
# Note: 'setup.nssdatabases=' prevents NSS databases, such as passwd
# entries, from being copied into the chroot. They aren't needed anyway,
# and there could be a large number of entries on the host system which
# break things and/or make building things much slower.
cat <<EOF
description=xfstests-bld chroot with Debian $DEBIAN_RELEASE ($DEBIAN_ARCH)
type=directory
directory=$CHROOT_DIR
users=${CHROOT_USER:+$CHROOT_USER,}root
root-users=$CHROOT_USER
setup.fstab=$SCHROOT_FSTAB
setup.nssdatabases=
EOF
}
check_for_conflicting_schroot_entry()
{
if [ -z "$(extract_schroot_entry)" ]; then
return
fi
if cmp -s <(extract_schroot_entry | sort) \
<(generate_schroot_entry | sort)
then
return
fi
log "$SCHROOT_CONFFILE already contains a different entry for [$CHROOT_NAME]"
echo "--> Wanted:"
generate_schroot_entry
echo
echo "--> Found:"
extract_schroot_entry
echo
die "Please either delete the existing entry first, or fix it manually."
}
# Add an entry to schroot.conf for the chroot.
add_schroot_conf_entry()
{
local f
check_for_conflicting_schroot_entry
if [ -n "$(extract_schroot_entry)" ]; then
log "$CHROOT_NAME entry already exists in $SCHROOT_CONFFILE"
return
fi
if test -d $SCHROOT_CHROOT_D
then
f="$SCHROOT_CHROOT_D/$CHROOT_NAME"
else
f="$SCHROOT_CONFFILE"
fi
log "Adding new entry to $f:"
{
echo
echo "# entry added by $SCRIPTNAME"
echo "[$CHROOT_NAME]"
generate_schroot_entry
} | tee -a "$f"
echo
}
# Create the schroot fstab file needed by the chroot. The fstab will contain
# the usual entries to mount sysfs, procfs, etc., as well as an entry that
# bind-mounts the xfstests-bld directory into the chroot.
#
# Note that all chroots created by this script will share the same fstab.
create_schroot_fstab()
{
if [ -e "$SCHROOT_FSTAB_FILE" ]; then
log "$SCHROOT_FSTAB_FILE was already created"
return
fi
mkdir -p "$(dirname "$SCHROOT_FSTAB_FILE")"
cat >"$SCHROOT_FSTAB_FILE" <<EOF
/proc /proc none rw,bind 0 0
/sys /sys none rw,bind 0 0
/dev /dev none rw,bind 0 0
/dev/pts /dev/pts none rw,bind 0 0
/home /home none rw,bind 0 0
/tmp /tmp none rw,bind 0 0
$SCRIPTDIR $SCRIPTDIR none rw,bind 0 0
EOF
log "Created $SCHROOT_FSTAB_FILE" \
"(bind-mount path: $SCRIPTDIR)"
}
run_in_chroot()
{
# Note: we execute the command in a login shell rather than execute it
# directly because this makes the $PATH get set up correctly.
DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
LC_ALL=C LANGUAGE=C LANG=C chroot "$CHROOT_DIR" /bin/sh -l -c "$1"
}
create_chroot()
{
mkdir -p "$CHROOT_DIR"
if ! run_cmd debootstrap --arch "$DEBIAN_ARCH" ${QEMU:+--foreign} \
"$DEBIAN_RELEASE" "$CHROOT_DIR" "$DEBIAN_MIRROR"; then
# Clean up if the first step fails since the problem may be trivial,
# e.g. an invalid release, arch, or mirror. But if we fail later, leave
# the directory around so that the problem can be debugged.
rm -rf "$CHROOT_DIR"
exit 1
fi
if [ -n "$QEMU" ]; then
log "Installing $QEMU as $CHROOT_DIR/usr/bin/$(basename "$QEMU")"
cp "$QEMU" "$CHROOT_DIR/usr/bin/"
log "Entering chroot to complete the second stage of the bootstrapping process"
run_in_chroot "/debootstrap/debootstrap --second-stage"
log "Entering chroot to configure the installed packages"
run_in_chroot "dpkg --configure -a"
log "Creating $CHROOT_DIR/etc/apt/sources.list"
cat >"$CHROOT_DIR/etc/apt/sources.list" <<EOF
deb $DEBIAN_MIRROR $DEBIAN_RELEASE main
deb-src $DEBIAN_MIRROR $DEBIAN_RELEASE main contrib non-free
deb $DEBIAN_MIRROR $DEBIAN_RELEASE-updates main
EOF
# If on the host system /dev/shm is a symlink to /run/shm, configuring
# the initscripts package will fail during 'gen-image'. To work around
# this, explicitly create the /run/shm directory in the build chroot.
mkdir -p "$CHROOT_DIR/run/shm"
chmod 1777 "$CHROOT_DIR/run/shm"
fi
if [ -n "$CHROOT_USER" ]; then
getent passwd "$CHROOT_USER" >> "$CHROOT_DIR/etc/passwd"
getent group "$(id -g "$CHROOT_USER")" >> "$CHROOT_DIR/etc/group"
run_in_chroot "adduser $CHROOT_USER sudo"
fi
}
setup_mtab()
{
log "Linking /etc/mtab to ../proc/self/mounts"
schroot -c "$CHROOT_NAME" -u root -- ln -sf ../proc/self/mounts /etc/mtab
}
install_build_dependencies()
{
log "Installing build dependencies: ${BUILD_DEPENDENCIES[*]}"
schroot -c "$CHROOT_NAME" -u root -- <<EOF
test -z "${PROXY}" || export http_proxy=${PROXY}
apt-get update
apt-get install -y ${BUILD_DEPENDENCIES[@]}
EOF
}
# preparation
parse_options "$@"
check_prerequisites
select_debian_release
select_debian_arch
select_debian_mirror
select_chroot_dir
select_chroot_name
select_chroot_user
select_proxy
check_for_conflicting_schroot_entry
if ! is_native_chroot; then
validate_binfmt_misc
fi
# the real work
create_chroot
add_schroot_conf_entry
create_schroot_fstab
setup_mtab
install_build_dependencies
log "Build chroot was successfully set up. To use it to build a test" \
"appliance, run './do-all --chroot=$CHROOT_NAME'; or to have do-all use" \
"this chroot by default, add the following to your config.custom file:" \
"" \
" BUILD_ENV=\"schroot -c $CHROOT_NAME --\"" \
" SUDO_ENV=\"schroot -c $CHROOT_NAME -u root --\"" \
"" \
"Alternatively, you may build an xfstests tarball on its own:" \
" BUILD_ENV=\"schroot -c $CHROOT_NAME --\"" \
" \$BUILD_ENV make clean" \
" \$BUILD_ENV make" \
" \$BUILD_ENV make tarball"