Mount BEs in a per-environment space

Add routine to set default BE

Handle empty BOOTFS value

increment clone index

Use stored kernel args for an env

Swap header to one line, embed text in method

Clean up BE preview to disambiguate the output
This commit is contained in:
Zach Dykstra 2020-01-06 20:28:56 -06:00
parent d0514dfb25
commit 2e2e5c1e79
5 changed files with 151 additions and 93 deletions

View File

@ -80,6 +80,7 @@ install() {
dracut_install /usr/bin/tail
dracut_install /usr/lib/udev/zvol_id
inst_simple "${moddir}/zfsbootmenu-lib.sh" "/lib/zfsbootmenu-lib.sh"
inst_simple "${moddir}/zfsbootmenu-preview.sh" "/bin/zfsbootmenu-preview.sh"
inst_hook cmdline 95 "${moddir}/zfsbootmenu-parse-commandline.sh"
inst_hook pre-mount 90 "${moddir}/zfsbootmenu.sh"

View File

@ -2,26 +2,6 @@
# ZFS boot menu functions
# arg1: zroot/ROOT/bootenvironment
# prints: zroot_ROOT_bootenvironment
# returns: No return value
underscore() {
local bepath
bepath="${1}"
echo ${bepath} | sed 's,/,_,g'
}
# arg1: zroot_ROOT_bootenvironment
# prints: zroot/ROOT/bootenvironment
# returns: No return value
slash() {
local bepath
bepath="${1}"
echo ${bepath} | sed 's,_,/,g'
}
# arg1: ZFS filesystem name
# arg2: mountpoint
# prints: No output
@ -31,31 +11,18 @@ mount_zfs() {
local fs mnt ret
fs="${1}"
mnt="${2}"
test -d ${mnt} || return 1
mount -o zfsutil -t zfs ${fs} ${mnt}
mnt="${BASE}/${fs}/mnt"
test -d "${mnt}" || mkdir -p "${mnt}"
mount -o zfsutil -t zfs "${fs}" "${mnt}"
ret=$?
echo "${mnt}"
return ${ret}
}
# arg1: mount point
# prints: No output
# returns: 0 on success
umount_zfs() {
local mnt ret
mnt="${1}"
umount ${mnt}
ret=$?
return ${ret}
}
# arg1: Path to file with header/options text
# arg2: Path to file with detected boot environments, 1 per line
# arg1: Path to file with detected boot environments, 1 per line
# prints: key pressed, boot environment
# returns: 130 on error, 0 otherwise
@ -63,13 +30,14 @@ draw_be() {
local env header selected ret
env="${1}"
header="${2}"
test -f ${env} || return 130
test -f ${header} || return 130
selected="$( cat ${header} ${env} | fzf -0 --prompt "BE > " \
--header-lines=2 --expect=alt-k,alt-s,alt-a,alt-r )"
selected="$( cat ${env} | fzf -0 --prompt "BE > " \
--expect=alt-k,alt-s,alt-a,alt-r,alt-d \
--preview-window=up:2 \
--header="[ENTER] boot [ALT+K] kernel [ALT+D] set bootfs [ALT+S] snapshots" \
--preview="zfsbootmenu-preview.sh ${BASE} {} ${BOOTFS}")"
ret=$?
while read -r line; do
if [ -z "$line" ]; then
@ -90,13 +58,8 @@ draw_kernel() {
benv="${1}"
# Set a pretty benv for our prompt
pretty="$( slash ${benv})"
pretty="${pretty#${BASE}/}"
selected="$( cat ${benv} | fzf --prompt "${pretty} > " --tac \
--with-nth=2 --header="[ENTER] boot
[ESC] back")"
selected="$( cat ${BASE}/${benv}/kernels | fzf --prompt "${benv} > " --tac \
--with-nth=2 --header="[ENTER] boot [ESC] back")"
ret=$?
echo "${selected}"
@ -113,8 +76,7 @@ draw_snapshots() {
benv="${1}"
selected="$( zfs list -t snapshot -H -o name ${benv} | fzf --prompt "Snapshot > " --tac \
--header="[ENTER] clone
[ESC] back" )"
--header="[ENTER] clone [ESC] back" )"
ret=$?
echo "${selected}"
return ${ret}
@ -136,19 +98,23 @@ kexec_kernel() {
# initramfs
IFS=' ' read fs kernel initramfs <<<"${selected}"
mount_zfs ${fs} ${BASE_MOUNT}
mnt="$( mount_zfs "${fs}" )"
ret=$?
if [ $ret != 0 ]; then
emergency_shell "unable to mount ${fs}"
fi
test -e ${BASE_MOUNT}/etc/default/grub && . ${BASE_MOUNT}/etc/default/grub
while IFS= read -r line
do
cli_args="${line}"
done < "${BASE}/${fs}/default_args"
kexec -l ${BASE_MOUNT}${kernel} \
--initrd=${BASE_MOUNT}/${initramfs} \
--command-line="root=zfs:${fs} ${GRUB_CMDLINE_LINUX_DEFAULT}"
kexec -l ${mnt}${kernel} \
--initrd=${mnt}/${initramfs} \
--command-line="root=zfs:${fs} ${cli_args}"
umount_zfs ${fs}
umount ${mnt}
# Export if read-write, to ensure a clean pool
pool="${selected%%/*}"
@ -170,7 +136,7 @@ clone_snapshot() {
pool="${selected%%/*}"
# If the pool is read-only, flip the import arg off and, export then import
# If the pool is read-only, flip the arg off then export and import
if [ "$( zpool get -H -o value readonly ${pool} )" = "on" ]; then
export_pool "${pool}"
import_args="${import_args/readonly=on/readonly=off}"
@ -179,6 +145,16 @@ clone_snapshot() {
target="${selected/@/_}"
if $( zfs list -H -o name | grep -q ${target} ); then
last_env="$( zfs list -H -o name | grep ${target} | tail -1 )"
index="${last_env##${target}_}"
index="$(( ${index} + 1 ))"
else
index="0"
fi
target="$( printf "%s_%0.3d" ${target} ${index} )"
zfs clone -o mountpoint=/ \
-o canmount=noauto \
${selected} ${target}
@ -187,7 +163,7 @@ clone_snapshot() {
if [ $ret -eq 0 ]; then
key_wrapper "${target}"
if [ $? -eq 0 ]; then
if output=$( find_be_kernels "${target}" "${BASE_MOUNT}" ); then
if output=$( find_be_kernels "${target}" ); then
echo "${target}" >> ${BASE}/env
return 0
else
@ -204,33 +180,47 @@ clone_snapshot() {
fi
}
set_default_env() {
local selected
selected="${1}"
pool="${selected%%/*}"
# If the pool is read-only, flip the arg off then export and import
if [ "$( zpool get -H -o value readonly ${pool} )" = "on" ]; then
export_pool "${pool}"
import_args="${import_args/readonly=on/readonly=off}"
import_pool "${pool}"
key_wrapper "${pool}"
fi
if output="$( zpool set bootfs=${selected} ${pool} )"; then
BOOTFS="${selected}"
fi
}
# arg1: ZFS filesystem
# arg2: mountpoint
# prints: nothing
# returns: number of kernels found
# returns: 0 if kernels were found
find_be_kernels() {
local fs mnt
fs="${1}"
mnt="${2}"
local sane kernel version pairs
local sane kernel version kernel_records
pairs=()
# Check if /boot even exists in the environment
mount_zfs "${fs}" "${mnt}"
mnt="$( mount_zfs "${fs}" )"
kernel_records="${mnt/mnt/kernels}"
if [ ! -d "${mnt}/boot" ]; then
umount_zfs "${fs}"
return
umount "${mnt}"
return 1
fi
# Create a filename with out /'s
sane="$( underscore ${fs} )"
# Remove this file if it already exists
test -f ${BASE}/${sane} && rm ${BASE}/${sane}
for kernel in $( ls ${mnt}/boot/vmlinux-* \
${mnt}/boot/vmlinuz-* \
${mnt}/boot/kernel-* \
@ -243,16 +233,26 @@ find_be_kernels() {
"initrd-${version}" "initramfs-${version}.img"; do
if test -e "${mnt}/boot/${i}" ; then
echo "${fs} ${kernel} /boot/${i}" >> ${BASE}/${sane}
echo "${fs} ${kernel} /boot/${i}" >> "${kernel_records}"
break
fi
done
done
umount_zfs "${fs}"
defaults="$( select_kernel "${fs}" )"
IFS=' ' read def_fs def_kernel def_initramfs <<<"${defaults}"
def_kernel="$( basename "${def_kernel}" )"
def_version=$( echo $def_kernel | sed -e "s,^[^0-9]*-,,g" )
# Return the number of kernels found
return "${#pairs[@]}"
def_kernel_file="${mnt/mnt/default_kernel}"
echo "${def_version}" > "${def_kernel_file}"
def_args="$( find_kernel_args "${mnt}" )"
def_args_file="${mnt/mnt/default_args}"
echo "${def_args}" > "${def_args_file}"
umount "${mnt}"
return 0
}
# arg1: ZFS filesystem
@ -264,13 +264,12 @@ select_kernel() {
zfsbe="${1}"
local sane specific_kernel kexec_args
sane="$( underscore ${zfsbe} )"
specific_kernel="$( zfs get -H -o value org.zfsbootmenu:kernel ${zfsbe} )"
# No value set, pick the last kernel entry
if [ "${specific_kernel}" = "-" ]; then
kexec_args="$( tail -1 ${BASE}/${sane} )"
kexec_args="$( tail -1 ${BASE}/${zfsbe}/kernels )"
else
while read -r kexec_args; do
local fs kernel initramfs
@ -278,12 +277,30 @@ select_kernel() {
if [[ "${kernel}" =~ "${specific_kernel}" ]]; then
break
fi
done <<<"$( cat ${BASE}/${sane} )"
done <<<"$( cat ${BASE}/${zfsbe}/kernels )"
fi
echo "${kexec_args}"
}
find_kernel_args() {
local zfsbe
zfsbe="${1}"
local arguments
if [ -f "${zfsbe}/etc/default/grub" ]; then
echo "$(
. "${zfsbe}/etc/default/grub" ;
echo "${GRUB_CMDLINE_LINUX_DEFAULT}"
)"
return
fi
# No arguments found, return something generic
echo "quiet loglevel=3"
}
# no arguments
# prints: nothing
# returns: number of pools that can be imported

View File

@ -40,6 +40,10 @@ else
menu_timeout=10
fi
if getargbool 1 die_on_import_failure ; then
info "ZFSBootMenu: Disabling die on import failure"
fi
wait_for_zfs=0
case "${root}" in
""|zfsbootmenu|zfsbootmenu:)

View File

@ -0,0 +1,32 @@
#!/bin/bash
BASE="${1}"
ENV="${2}"
BOOTFS="${3}"
BLUE='\033[0;34m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
while IFS= read -r line
do
selected_kernel="${line}"
done < "${BASE}/${ENV}/default_kernel"
while IFS= read -r line
do
selected_arguments="${line}"
done < "${BASE}/${ENV}/default_args"
if [[ "${BOOTFS}" =~ "${ENV}" ]]; then
selected_env_str="${ENV} (default) - ${selected_kernel}"
else
selected_env_str="${ENV} - ${selected_kernel}"
fi
# Left pad the strings to center them based on the preview width
selected_env_str="$( printf "%*s\n" $(( (${#selected_env_str} + FZF_PREVIEW_COLUMNS) / 2)) "${selected_env_str}" )"
selected_arguments="$( printf "%*s\n" $(( (${#selected_arguments} + FZF_PREVIEW_COLUMNS) / 2)) "${selected_arguments}" )"
echo -e "${GREEN}${selected_env_str}${NC}"
echo "${selected_arguments}"

View File

@ -1,6 +1,8 @@
#!/bin/bash
. /lib/zfsbootmenu-lib.sh
test -f /lib/zfsbootmenu-lib.sh && source /lib/zfsbootmenu-lib.sh
test -f zfsbootmenu-lib.sh && source zfsbootmenu-lib.sh
echo "Loading boot menu ..."
TERM=linux
@ -11,15 +13,7 @@ OLDIFS="$IFS"
export FZF_DEFAULT_OPTS="--layout=reverse-list --cycle \
--inline-info --tac"
BE_SELECTED=0
KERNEL_SELECTED=0
BASE="$( mktemp -d /tmp/zfs.XXXX )"
BASE_MOUNT="${BASE}/be"
mkdir ${BASE_MOUNT}
ENV_HEADER="[ALT+K] select kernel [ENTER] boot\n[ALT+A] all snapshots [ALT+S] BE snapshots"
echo -e ${ENV_HEADER} > ${BASE}/env_header
# I should probably just modprobe zfs right off the bat
# rootok is always 1 here, otherwise we wouldn't be here ...
@ -46,7 +40,10 @@ if [ $ret -gt 0 ]; then
emergency_shell "unable to successfully import a pool"
fi
else
emergency_shell "no pools available to import"
if [ ${die_on_import_failure} -eq 1 ]; then
emergency_shell "no pools available to import"
exit;
fi
fi
# Prefer a specific pool when checking for a bootfs value
@ -129,7 +126,7 @@ if [[ ! -z "${BOOTFS}" ]]; then
fi
# Generate a list of valid kernels for our bootfs
if output=$( find_be_kernels "${BOOTFS}" "${BASE_MOUNT}" ); then
if output=$( find_be_kernels "${BOOTFS}" ); then
# Automatically select a kernel and boot it
kexec_kernel "$( select_kernel "${BOOTFS}" )"
fi
@ -150,7 +147,7 @@ for FS in $( zfs list -H -o name,mountpoint | grep -E "/$" | cut -f1 ); do
fi
# Check for kernels under the mountpoint, add to our BE list
if output=$( find_be_kernels "${FS}" "${BASE_MOUNT}" ); then
if output="$( find_be_kernels "${FS}" )" ; then
echo ${FS} >> ${BASE}/env
fi
done
@ -160,9 +157,12 @@ if [ ! -f ${BASE}/env ]; then
fi
# This is the actual menuing system
BE_SELECTED=0
tput civis
while true; do
if [ ${BE_SELECTED} -eq 0 ]; then
bootenv="$( draw_be "${BASE}/env" "${BASE}/env_header")"
bootenv="$( draw_be "${BASE}/env" )"
ret=$?
# key press
@ -181,7 +181,7 @@ while true; do
exit
;;
"alt-k")
selected_kernel="$( draw_kernel ${BASE}/$( underscore ${selected_be} ) )"
selected_kernel="$( draw_kernel "${selected_be}" )"
ret=$?
if [ $ret -eq 130 ]; then
@ -191,6 +191,10 @@ while true; do
exit
fi
;;
"alt-d")
set_default_env "${selected_be}"
BE_SELECTED=0
;;
"alt-s")
selected_snap="$( draw_snapshots ${selected_be} )"
ret=$?