zfsbootmenu/testing/setup.sh

392 lines
9.6 KiB
Bash
Executable File

#!/bin/bash
# vim: softtabstop=2 shiftwidth=2 expandtab
YAML=0
GENZBM=0
IMAGE=0
MKINITCPIO=0
SIZE="5G"
POOL_PREFIX="ztest"
# Dracut setup requires a local installation to work
# In "all" mode, dracut is opportunistic
DRACUT="no"
CONFD=0
DISTROS=()
# Dictionary for random pool names, provided by words-en
dictfile="/usr/share/dict/words"
usage() {
local compat_dir
compat_dir="/usr/share/zfs/compatibility.d/"
cat <<EOF
USAGE: $0 [options]
OPTIONS
-h Display this message and exit
-y Create local.yaml
-g Create a generate-zbm symlink
-c Create dracut.conf.d (if dracut is enabled)
-d Create a local dracut tree for local mode
-m Create mkinitcpio.conf
-i Create a test VM image
-a Perform all create options
-D Specify a test directory to use
-s Specify size of VM image
-e Enable native ZFS encryption
-p Specify a pool name
-r Use a randomized pool name
-x Use an existing pool image
-k Populate host SSH host and authorized keys
-M Build the test image on bare metal rather than a VM
-E Add a variable to the image-creation environment
-o Distribution to install (may specify more than one)
[ void, void-musl, alpine, chimera, arch, debian, ubuntu ]
ENVIRONMENT VARIABLES
Certain variables, when set with -E, allow customization of test images:
RELEASE (Debian, Ubuntu)
Specify a particular release to install in the test image
(e.g., "bullseye", "bookworm" or "buster" for Debian; "kinetic" or "jammy" for ubuntu)
APT_REPOS (Debian, Ubuntu)
Specify a space-separated list of specific repositories to configure for apt
(e.g., "main universe multiverse")
KERNEL (Void)
Set KERNEL to the Void kernel series to use (e.g., "linux5.10", "linux6.1")
ZPOOL_COMPAT (All)
Set ZPOOL_COMPAT to one of the ZFS pool compatiblity targets listed below.
$( find "${compat_dir}" -type f | sort | sed "s|${compat_dir}||" | column | sed 's/^/\t/' )
EOF
}
random_dict_value() {
sed -n "$(shuf -i 1-"$( wc -l "${dictfile}" | cut -d ' ' -f 1)" -n 1)"p "${dictfile}" \
| sed s/\'s// \
| tr '[:upper:]' '[:lower:]'
}
random_name() {
echo "$( random_dict_value )$( random_dict_value | sed -e 's/\b./\u\0/' )"
}
if [ $# -eq 0 ]; then
usage
exit
fi
# Environment variables to set for the image creation
ENVIRONS=(
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
)
while getopts "heycgdaiD:s:o:lp:rxkmE:M" opt; do
case "${opt}" in
e)
ENCRYPT=1
;;
y)
YAML=1
;;
c)
CONFD=1
;;
i)
IMAGE=1
;;
d)
DRACUT="yes"
;;
g)
GENZBM=1
;;
m)
MKINITCPIO=1
;;
a)
YAML=1
CONFD=1
IMAGE=1
GENZBM=1
MKINITCPIO=1
# Dracut is opportunistic unless forced earlier
[ "${DRACUT}" = yes ] || DRACUT="maybe"
;;
D)
TESTDIR="${OPTARG}"
;;
s)
SIZE="${OPTARG}"
;;
o)
DISTROS+=( "${OPTARG}" )
;;
p)
POOL_PREFIX="${OPTARG}"
;;
r)
if [ -r "${dictfile}" ]; then
RANDOM_NAME=1
fi
;;
x)
EXISTING_POOL=1
;;
k)
INCLUDE_KEYS=1
;;
E)
ENVIRONS+=( "${OPTARG}" )
;;
M)
BARE_METAL=1
;;
h)
usage
exit 0
;;
*)
usage
exit 1
;;
esac
done
if [ "${#DISTROS[@]}" -lt 1 ]; then
DISTROS=( "void" )
fi
# Assign a default dest directory if one was not provided
if [ -z "${TESTDIR}" ]; then
TESTDIR="./test.${DISTROS[0]}"
fi
TESTDIR="$(realpath "${TESTDIR}")" || exit 1
# Make sure the test directory exists
mkdir -p "${TESTDIR}" || exit 1
# If dracut is opportunistic, determine if it should be available
if [ "${DRACUT}" = "maybe" ]; then
if [ -d /usr/lib/dracut ] && command -v dracut >/dev/null 2>&1; then
DRACUT="yes"
else
DRACUT="no"
fi
fi
if [ "${DRACUT}" = "yes" ]; then
DRACUTBIN="$(command -v dracut)"
if [ ! -x "${DRACUTBIN}" ]; then
echo "ERROR: missing dracut script"
exit 1
fi
if [ ! -d /usr/lib/dracut ]; then
echo "ERROR: missing /usr/lib/dracut"
exit 1
fi
## Populate dracut and dracut.conf.d trees if needed or demanded
if ((CONFD)) || [ ! -d "${TESTDIR}/dracut.conf.d" ]; then
if [ -d "${TESTDIR}/dracut.conf.d" ]; then
echo "Re-creating dracut.conf.d"
rm -r "${TESTDIR}/dracut.conf.d"
else
echo "Creating dracut.conf.d"
fi
if ! cp -Rp ../etc/zfsbootmenu/dracut.conf.d "${TESTDIR}"; then
echo "ERROR: failed to create dracut.conf.d"
exit 1
fi
cat >> "${TESTDIR}/dracut.conf.d/zfsbootmenu.conf" <<-EOF
zfsbootmenu_module_root="$( realpath -e ../zfsbootmenu )"
zfsbootmenu_hook_root="${TESTDIR}/hooks"
EOF
fi
if ((CONFD)) || [ ! -d "${TESTDIR}/dracut" ]; then
if [ -d "${TESTDIR}/dracut" ]; then
echo "Re-creating local dracut tree"
rm -r "${TESTDIR}/dracut"
else
echo "Creating local dracut tree"
fi
cp -a /usr/lib/dracut "${TESTDIR}"
cp "${DRACUTBIN}" "${TESTDIR}/dracut"
# Make sure the zfsbootmenu module is a link to the repo version
_zbm_mod="${TESTDIR}/dracut/modules.d/90zfsbootmenu"
if [ -L "${_zbm_mod}" ]; then
rm "${_zbm_mod}"
elif [ -d "${_zbm_mod}" ]; then
rm -r "${_zbm_mod}"
fi
ln -Tsf "$(realpath -e ../dracut)" "${_zbm_mod}"
fi
fi
if ((MKINITCPIO)) ; then
cat <<-EOF > "${TESTDIR}/mkinitcpio.conf"
for snippet in $( realpath -e "${TESTDIR}" )/mkinitcpio.d/*.conf ; do
source \${snippet}
done
EOF
MKINITCPIOD="${TESTDIR}/mkinitcpio.d"
mkdir -p "${MKINITCPIOD}"
cat <<-EOF > "${MKINITCPIOD}/base.conf"
MODULES=(ahci.ko)
BINARIES=()
FILES=()
HOOKS=(base udev autodetect modconf block filesystems keyboard)
COMPRESSION="cat"
EOF
cat <<-EOF > "${MKINITCPIOD}/modroot.conf"
zfsbootmenu_module_root="$( realpath -e ../zfsbootmenu )"
EOF
cat <<-EOF > "${MKINITCPIOD}/hooks.conf"
zfsbootmenu_hook_root="${TESTDIR}/hooks"
EOF
fi
if ((GENZBM)) ; then
rm -f "${TESTDIR}/generate-zbm"
ln -s "$(realpath -e ../bin/generate-zbm)" "${TESTDIR}/generate-zbm"
fi
# Setup a local config file
if ((YAML)) ; then
echo "Configuring local.yaml"
yamlconf="${TESTDIR}/local.yaml"
STUBS="$(realpath -e stubs)"
cp ../etc/zfsbootmenu/config.yaml "${yamlconf}"
yq-go eval ".Components.ImageDir = \"${TESTDIR}\"" -i "${yamlconf}"
yq-go eval ".Components.Versions = false" -i "${yamlconf}"
yq-go eval ".EFI.ImageDir = \"${TESTDIR}\"" -i "${yamlconf}"
yq-go eval ".EFI.Versions = false" -i "${yamlconf}"
yq-go eval ".EFI.Stub = \"${STUBS}/linuxx64.efi.stub\"" -i "${yamlconf}"
yq-go eval ".Global.ManageImages = true" -i "${yamlconf}"
yq-go eval ".Global.DracutConfDir = \"${TESTDIR}/dracut.conf.d\"" -i "${yamlconf}"
yq-go eval ".Global.PreHooksDir = \"${TESTDIR}/generate-zbm.pre.d\"" -i "${yamlconf}"
yq-go eval ".Global.PostHooksDir = \"${TESTDIR}/generate-zbm.post.d\"" -i "${yamlconf}"
yq-go eval ".Global.InitCPIOConfig = \"${TESTDIR}/mkinitcpio.conf\"" -i "${yamlconf}"
yq-go eval ".Global.InitCPIOHookDirs = [ \"$( realpath -e ../initcpio )\",\"/usr/lib/initcpio\" ]" -i "${yamlconf}"
yq-go eval ".Global.DracutFlags = [ \"--local\" ]" -i "${yamlconf}"
yq-go eval "del(.Global.BootMountPoint)" -i "${yamlconf}"
yq-go eval -P -C "${yamlconf}"
fi
# seed our initial pool name attempt
if ((RANDOM_NAME)); then
POOL_NAME="$( random_name )"
else
POOL_NAME="${POOL_PREFIX}"
idx=0
fi
while [ -z "${EXISTING_POOL}" ]; do
# Check that a file doesn't exist with this name, or that
# a currently-imported pool doesn't have this name
if [ ! -r "${TESTDIR}/${POOL_NAME}-pool.img" ] \
&& ! zpool list -o name -H "${POOL_NAME}" >/dev/null 2>&1
then
break
fi
# Generate a new random name / bump the index
if ((RANDOM_NAME)); then
POOL_NAME="$( random_name )"
else
idx=$(( idx + 1 ))
POOL_NAME="$( printf "${POOL_PREFIX}-%02d" "${idx}" )"
fi
done
echo "Generated pool name: ${POOL_NAME}"
if ((INCLUDE_KEYS)); then
# ssh-keygen expects to dump into ${PREFIX}/etc/ssh
mkdir -p ./keys/etc/ssh
# Generate any missing keys
ssh-keygen -A -f ./keys
# Copy authorized keys for convenience
if [ -r "${HOME}/.ssh/authorized_keys" ] && [ ! -r ./keys/authorized_keys ]; then
cp "${HOME}/.ssh/authorized_keys" ./keys/
fi
fi
# Create image(s) for each specified distro
if ((IMAGE)); then
for DISTRO in "${DISTROS[@]}"; do
builder_args=( )
for environ in "${ENVIRONS[@]}"; do
builder_args+=( "-E" "${environ}" )
done
if ((EXISTING_POOL)); then
builder_args+=( "-x" )
else
if ! qemu-img create "${TESTDIR}/${POOL_NAME}-pool.img" "${SIZE}"; then
echo "ERROR: failed to create pool image"
exit 1
fi
AUTO_POOL_COMPAT=1
for environ in "${ENVIRONS[@]}"; do
[[ "${environ}" =~ "^ZPOOL_COMPAT=" ]] || continue
AUTO_POOL_COMPAT=0
break
done
if ((AUTO_POOL_COMPAT)); then
case "${DISTRO}" in
debian|ubuntu) builder_args+=( -c "openzfs-2.0-linux" ) ;;
esac
fi
fi
if ((ENCRYPT)); then
builder_args+=( -e "${TESTDIR}/${POOL_NAME}.key" )
fi
builder_args+=( "${DISTRO}" "${POOL_NAME}" "${TESTDIR}" )
if ((BARE_METAL)); then
if command -v doas >/dev/null 2>&1; then
SUDO=doas
elif command -v sudo >/dev/null 2>&1; then
SUDO=sudo
else
echo "ERROR: unable to elevate user privileges, install sudo or doas"
exit 1
fi
"${SUDO}" unshare --fork --pid --mount \
./helpers/builder-host.sh "${builder_args[@]}"
else
./helpers/builder-qemu.sh "${builder_args[@]}"
fi
# All subsequent distros use the same pool
EXISTING_POOL=1
done
fi