Add support for richer setup and teardown hooks

Multiple setup and teardown hooks may be marked for installation by
adding entries to the respective space-separated lists zfsbootmenu_setup
and zfsbootmenu_teardown. The setup hooks are executed by zfsbootmenu.sh
after setting the `active` mutex to ensure only one set runs at a time;
hooks are allowed to repeat with each invocation of zfsbootmenu.
Teardown hooks are run at the same point as the prior single hook
zfsbootmenu_teardown: right before the final kexec.

Closes: #107.
This commit is contained in:
Andrew J. Hesford 2020-12-08 15:52:31 -05:00
parent 4d93c18ed0
commit 47aa434cbd
8 changed files with 143 additions and 25 deletions

View File

@ -139,14 +139,28 @@ install() {
inst_hook cmdline 95 "${moddir}/zfsbootmenu-parse-commandline.sh" || _ret=$?
inst_hook pre-mount 90 "${moddir}/zfsbootmenu-exec.sh" || _ret=$?
# Install a "teardown" hook if specified and it exists
# Install "setup" hooks
# shellcheck disable=SC2154
if [ -n "${zfsbootmenu_setup}" ]; then
for _exec in ${zfsbootmenu_setup}; do
if [ -x "${_exec}" ]; then
inst_simple "${_exec}" "/libexec/setup.d/$(basename "${_exec}")" || _ret=$?
else
dwarning "setup script (${_exec}) missing or not executable; cannot install"
fi
done
fi
# Install "teardown" hooks
# shellcheck disable=SC2154
if [ -n "${zfsbootmenu_teardown}" ]; then
if [ -x "${zfsbootmenu_teardown}" ]; then
inst_simple "${zfsbootmenu_teardown}" "/libexec/zfsbootmenu-teardown" || _ret=$?
else
dwarning "no executable teardown script (${zfsbootmenu_teardown}); cannot install"
fi
for _exec in ${zfsbootmenu_teardown}; do
if [ -x "${_exec}" ]; then
inst_simple "${_exec}" "/libexec/teardown.d/$(basename "${_exec}")" || _ret=$?
else
dwarning "teardown script (${_exec}) missing or not executable; cannot install"
fi
done
fi
if [ ${_ret} -ne 0 ]; then

View File

@ -6,6 +6,7 @@ trap '' SIGINT
# shellcheck disable=SC1091
test -f /lib/zfsbootmenu-lib.sh && source /lib/zfsbootmenu-lib.sh
# shellcheck disable=SC1091
test -f zfsbootmenu-lib.sh && source zfsbootmenu-lib.sh

View File

@ -207,7 +207,7 @@ draw_pool_status() {
# returns: 1 on error, otherwise does not return
kexec_kernel() {
local selected fs kernel initramfs
local selected fs kernel initramfs tdhook
selected="${1}"
@ -247,8 +247,13 @@ kexec_kernel() {
export_pool "${pool}"
fi
# Run a teardown script, if one exists
[ -x /libexec/zfsbootmenu-teardown ] && /libexec/zfsbootmenu-teardown
# Run teardown hooks, if they exist
if [ -d /libexec/teardown.d ]; then
for tdhook in /libexec/teardown.d/*; do
[ -x "${tdhook}" ] && "${tdhook}"
done
unset tdhook
fi
kexec -e -i
}

View File

@ -30,10 +30,26 @@ while [ -e "${BASE}/active" ]; do
fi
done
# Prevent conflicting use of the boot menu
: > "${BASE}/active"
# shellcheck disable=SC2064
trap "rm -f '${BASE}/active'" EXIT
if [ -r "${BASE}/bootfs" ]; then
read -r BOOTFS < "${BASE}/bootfs"
export BOOTFS
fi
# Run setup hooks, if they exist
if [ -d /libexec/setup.d ]; then
tput clear
for _hook in /libexec/setup.d/*; do
[ -x "${_hook}" ] && "${_hook}"
done
unset _hook
fi
trap '' SIGINT
if command -v fzf >/dev/null 2>&1; then
@ -54,11 +70,6 @@ if [ -z "${FUZZYSEL}" ]; then
exit
fi
if [ -r "${BASE}/bootfs" ]; then
read -r BOOTFS < "${BASE}/bootfs"
export BOOTFS
fi
# Clear screen before a possible password prompt
tput clear

73
contrib/keycache.sh Normal file
View File

@ -0,0 +1,73 @@
#!/bin/bash
## This setup hook attempts to cache keyfiles for ZFS encryptionroots in the
## initramfs, reducing the number of password requests made by ZFSBootMenu.
## ZFSBootMenu will attempt to mount the default boot environment (i.e.,
## whichever boot environment will boot automatically) and, for each
## keylocation that provides a file:// URI, copy the file from the
## corresponding location in the default BE (if it exists) to the expected
## location within the initramfs.
##
## To use, put this script somewhere, make sure it is executable, and add the
## path to the `zfsbootmenu_setup` space-separated list with, e.g.,
##
## zfsbootmenu_setup+=" <path to script> "
##
## in a dracut.conf(5) file inside the directory specified for the option
## `Global.DracutConfDir` in the ZFSBootMenu `config.yaml`.
##
## CAVEATS
## 1. As ZFSBootMenu runs in an initramfs and does not enable swap, I do not
## believe that copying key files to the initramfs will allow the contents
## to be written to unencrypted storage. However, I CANNOT GUARANTEE THIS.
## If you are concerned about key security, make sure you have a thorough
## understanding of the control flow of ZFSBootMenu and this hook before
## enabling it. You have been warned!
##
## 2. Because this only cares about the default BE as a key source, this will
## not cache any key files defined in other BEs.
##
## 3. If you have different BEs that each hold key files with different
## contents from, but conflicting names with, some keys in the default
## environment, any BEs that depend on the non-default keys will be
## inaccessible to ZFSBootMenu when this hook is enabled. ZFSBootMenu will
## find the cached key and attempt (unsuccessfully) to use it on the
## affected BEs rather than forcing a password prompt as it would were the
## keys not cached. (This is probably an inadvisable configuration anyway.
## If you use different keys, just give them unique paths.)
# shellcheck disable=SC1091
[ -r /lib/zfsbootmenu-lib.sh ] && . /lib/zfsbootmenu-lib.sh
# Make sure key environment variables are defined
[ -n "${BOOTFS}" ] || exit 0
# Try to mount the bootfs to search for keys
load_key "${BOOTFS}" || exit 0
mnt="$(mount_zfs "${BOOTFS}")" || exit 0
# Make sure to capture unmount
# shellcheck disable=SC2064
trap "umount '${mnt}'" EXIT
while read -r keyloc; do
# Make sure key location is a file:// URI, strip the scheme
keyfile="${keyloc#file://}"
[ "x${keyloc}" = "x${keyfile}" ] && continue
# If the keyfile already exists, there is nothing left to do
[ -f "${keyfile}" ] && continue
# Make sure the file exists on the bootfs
[ -f "${mnt}/${keyfile}" ] || continue
# Create the key directory if needed
keydir="${keyfile%/*}"
[ "x${keydir}" = "x${keyfile}" ] && keydir=
[ -n "${keydir}" ] && mkdir -p "${keydir}"
# Copy the key in place
cp "${mnt}/${keyfile}" "/${keyfile}"
# This is irrelevant; only root exists in the initramfs
chmod 000 "/${keyfile}"
done <<< "$( zfs list -H -o keylocation )"

View File

@ -12,10 +12,13 @@
##
## This could be adapted to other drivers, including {O,U,E}HCI as necessary.
##
## To use, put this script somewhere, make sure it is executable, and set the
## option `zfsbootmenu_teardown=<path to script>` in a dracut.conf(5) file
## inside the directory specified for `Global.DracutConfDir` in the ZFSBootMenu
## `config.yaml`.
## To use, put this script somewhere, make sure it is executable, and add the
## path to the `zfsbootmenu_teardown` space-separated list with, e.g.,
##
## zfsbootmenu_teardown+=" <path to script> "
##
## in a dracut.conf(5) file inside the directory specified for the option
## `Global.DracutConfDir` in the ZFSBootMenu `config.yaml`.
SYS_XHCI=/sys/bus/pci/drivers/xhci_hcd

View File

@ -217,11 +217,16 @@ Indicate that ZFSBootMenu should be run under \fBtmux\fR in the initramfs. With
.SH "Dracut Options"
.IX Header "Dracut Options"
In addition to standard dracut configuration options, the ZFSBootMenu dracut module supports addtional options to customize boot behavior.
.IP "\fBzfsbootmenu_teardown=<executable>\fR" 4
.IX Item "zfsbootmenu_teardown=<executable>"
An optional variable specifying the path to a teardown script that will be installed in the ZFSBootMenu initramfs. If this key is set but no file exists at the path \fB<executable>\fR or the file is not executable, a warning will be issued but initramfs creation will proceed.
.IP "\fBzfsbootmenu_setup=<executable\-list>\fR" 4
.IX Item "zfsbootmenu_setup=<executable-list>"
An optional variable specifying a space-separated list of paths to setup hooks that will be installed in the ZFSBootMenu initramfs. Any path in the list \fB<executable\-list>\fR that exists and is executable will be installed.
.Sp
Some hardware initialized by the kernel used to boot ZFSBootMenu may not be properly reinitialized when a boot environment is launched. A teardown executable, if provided, will be inovked by ZFSBootMenu immediately before \fBkexec\fR is invoked to jump into the selected kernel. This script can be used, for example, to unbind drivers from hardware or remove kernel modules.
Any installed hooks are run right before the ZFSBootMenu menu will be presented; \s-1ZFS\s0 pools will generally have been imported and the default boot environment will be available in the \fI\s-1BOOTFS\s0\fR environment variable. Hooks will not be run if the countdown timer expires (or was set to zero) and the default boot environment is automatically selected. \fBNote:\fR The hooks may be run multiple times if the menu is invoked multiple times, e.g., by dropping to an emergency shell and then returning to the menu. If a script should only run once, the script is responsible for keeping track of this.
.IP "\fBzfsbootmenu_teardown=<executable\-list>\fR" 4
.IX Item "zfsbootmenu_teardown=<executable-list>"
An optional variable specifying a space-separated list of paths to teardown hooks that will be installed in the ZFSBootMenu initramfs. Any path in the list \fB<executable\-list>\fR that exists and is executable will be installed.
.Sp
Some hardware initialized by the kernel used to boot ZFSBootMenu may not be properly reinitialized when a boot environment is launched. Any teardown hooks installed into the ZFSBootMenu initramfs, will be run immediately before \fBkexec\fR is invoked to jump into the selected kernel. This script can be used, for example, to unbind drivers from hardware or remove kernel modules.
.IP "\fBzfsbootmenu_tmux=true\fR" 4
.IX Item "zfsbootmenu_tmux=true"
An optional variable enabling the installation of \fBtmux\fR and a minimal \fBtmux.conf\fR in the initramfs.

View File

@ -107,11 +107,17 @@ In addition to standard dracut configuration options, the ZFSBootMenu dracut mod
=over 4
=item B<zfsbootmenu_teardown=E<lt>executableE<gt>>
=item B<zfsbootmenu_setup=E<lt>executable-listE<gt>>
An optional variable specifying the path to a teardown script that will be installed in the ZFSBootMenu initramfs. If this key is set but no file exists at the path B<E<lt>executableE<gt>> or the file is not executable, a warning will be issued but initramfs creation will proceed.
An optional variable specifying a space-separated list of paths to setup hooks that will be installed in the ZFSBootMenu initramfs. Any path in the list B<E<lt>executable-listE<gt>> that exists and is executable will be installed.
Some hardware initialized by the kernel used to boot ZFSBootMenu may not be properly reinitialized when a boot environment is launched. A teardown executable, if provided, will be inovked by ZFSBootMenu immediately before B<kexec> is invoked to jump into the selected kernel. This script can be used, for example, to unbind drivers from hardware or remove kernel modules.
Any installed hooks are run right before the ZFSBootMenu menu will be presented; ZFS pools will generally have been imported and the default boot environment will be available in the I<BOOTFS> environment variable. Hooks will not be run if the countdown timer expires (or was set to zero) and the default boot environment is automatically selected. B<Note:> The hooks may be run multiple times if the menu is invoked multiple times, e.g., by dropping to an emergency shell and then returning to the menu. If a script should only run once, the script is responsible for keeping track of this.
=item B<zfsbootmenu_teardown=E<lt>executable-listE<gt>>
An optional variable specifying a space-separated list of paths to teardown hooks that will be installed in the ZFSBootMenu initramfs. Any path in the list B<E<lt>executable-listE<gt>> that exists and is executable will be installed.
Some hardware initialized by the kernel used to boot ZFSBootMenu may not be properly reinitialized when a boot environment is launched. Any teardown hooks installed into the ZFSBootMenu initramfs, will be run immediately before B<kexec> is invoked to jump into the selected kernel. This script can be used, for example, to unbind drivers from hardware or remove kernel modules.
=item B<zfsbootmenu_tmux=true>