Skip to content

Commit da94578

Browse files
committed
zsnapshots: recovery environment helper tool
In the event that no boot environments with a valid /boot are found, recovery might be as simple as interacting with an older snapshot. The recovery shell tool `zsnapshots` attempts to make that easier by allowing access to the main snapshot interface for a given dataset. fzf defaults suitable for the main interfaces were moved to a lib file that can be sourced. These defaults allow smaller tools to match the same behavior that fzf has when invoked from /bin/zfsbootmenu . select_kernel now returns 1 when the 'kernels' file for a boot environment can't be found, with nothing printed. The caller is expected to handle that condition accordingly. Fixes #418
1 parent b238a35 commit da94578

File tree

11 files changed

+142
-49
lines changed

11 files changed

+142
-49
lines changed

docs/online/recovery-shell.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Common Commands
1616

1717
Directly *kexec* a kernel and initramfs from a boot environment, allowing any kernel and initramfs to be loaded into memory and immediately booted.
1818

19+
**zsnapshots** *dataset*
20+
21+
Access the snapshot browser for a dataset, allowing cloning and rollback operations to be initiated.
22+
1923
**zreport**
2024

2125
List ZFS module, pool and dataset details for bug reports.

releng/rst2help.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#!/bin/bash
2+
# vim: softtabstop=2 shiftwidth=2 expandtab
3+
24
set -e
35

46
td="zfsbootmenu/help-files"
@@ -7,7 +9,8 @@ for size in 52 92 132 ; do
79
for doc in docs/online/*.rst docs/man/zfsbootmenu.7.rst; do
810
[ -d "${td}/${size}" ] || mkdir -p "${td}/${size}"
911
file="$( basename -s .rst "${doc}" ).ansi"
10-
echo "Converting ${doc}"
12+
echo "Converting ${doc} for ${size} columns"
13+
#shellcheck disable=SC2016
1114
COLUMNS="${size}" rst2ansi <(sed -E 's/:(doc|manpage):`([^<`]+)( <[^>]+>)?`/`\2`/g' "${doc}") | \
1215
sed '1s/ - \(.*\)$/\n\n\o033\[1m\1\o033\[0m/;s/\[3m/\[33m/g' > "${td}/${size}/${file}"
1316
done

zfsbootmenu/bin/zfsbootmenu

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ sources=(
99
/lib/zfsbootmenu-lib.sh
1010
/lib/kmsg-log-lib.sh
1111
/etc/profile
12+
/lib/fzf-defaults.sh
1213
)
1314

1415
for src in "${sources[@]}"; do
@@ -76,48 +77,6 @@ if [ -d /libexec/setup.d ]; then
7677
unset _hook
7778
fi
7879

79-
# Override control_term if executing over SSH
80-
# shellcheck disable=SC2034
81-
[ -n "${SSH_TTY}" ] && control_term="${SSH_TTY}"
82-
83-
# Single quote the help binds so that $HELP_SECTION expands when the key combo
84-
# is pressed, and not now.
85-
# Double quote the zlogtail lines so that they expand immediately
86-
87-
# shellcheck disable=SC2016
88-
fuzzy_default_options=(
89-
"--ansi" "--no-clear" "--cycle" "--color=16"
90-
"--layout=reverse-list" "--inline-info" "--tac"
91-
"--bind" '"alt-h:execute[ /libexec/zfsbootmenu-help -L ${HELP_SECTION:-main-screen} ]"'
92-
"--bind" '"ctrl-h:execute[ /libexec/zfsbootmenu-help -L ${HELP_SECTION:-main-screen} ]"'
93-
"--bind" '"ctrl-alt-h:execute[ /libexec/zfsbootmenu-help -L ${HELP_SECTION:-main-screen} ]"'
94-
"--bind" "\"alt-l:execute[ /bin/zlogtail ]${HAS_REFRESH:++refresh-preview}\""
95-
"--bind" "\"ctrl-l:execute[ /bin/zlogtail ]${HAS_REFRESH:++refresh-preview}\""
96-
"--bind" "\"ctrl-alt-l:execute[ /bin/zlogtail ]${HAS_REFRESH:++refresh-preview}\""
97-
)
98-
99-
if [ -n "${HAS_BORDER}" ]; then
100-
# shellcheck disable=SC2016
101-
fuzzy_default_options+=(
102-
"--border-label-pos=top" "--border=top"
103-
"--color=border:white" "--separator=''"
104-
)
105-
fi
106-
107-
# shellcheck disable=SC2016,SC2086
108-
if [ ${loglevel:-4} -eq 7 ] ; then
109-
fuzzy_default_options+=(
110-
"--bind" '"alt-t:execute[ /sbin/ztrace > ${control_term} ]"'
111-
"--bind" '"ctrl-t:execute[ /sbin/ztrace > ${control_term} ]"'
112-
"--bind" '"ctrl-alt-t:execute[ /sbin/ztrace > ${control_term} ]"'
113-
"--bind" '"f12:execute[ /libexec/zfunc emergency_shell \"debugging shell\" > ${control_term} ]"'
114-
)
115-
fi
116-
117-
export FUZZYSEL=fzf
118-
export PREVIEW_HEIGHT=2
119-
export FZF_DEFAULT_OPTS="${fuzzy_default_options[*]}"
120-
12180
# Clear screen before a possible password prompt
12281
tput clear
12382

zfsbootmenu/bin/zsnapshots

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/bash
2+
3+
sources=(
4+
/lib/profiling-lib.sh
5+
/etc/zfsbootmenu.conf
6+
/lib/kmsg-log-lib.sh
7+
/lib/zfsbootmenu-core.sh
8+
/lib/zfsbootmenu-lib.sh
9+
/lib/fzf-defaults.sh
10+
)
11+
12+
for src in "${sources[@]}"; do
13+
# shellcheck disable=SC1090
14+
if ! source "${src}" >/dev/null 2>&1 ; then
15+
echo -e "\033[0;31mWARNING: ${src} was not sourced; unable to proceed\033[0m"
16+
exit
17+
fi
18+
done
19+
20+
# Replace the global_header function with a stub
21+
global_header() {
22+
echo -n -e "\\033[1;33m[ Recover from snapshot ]"
23+
}
24+
25+
if [ $# -ne 1 ] ; then
26+
echo "Usage: $0 filesystem"
27+
exit
28+
fi
29+
30+
fs="${1}"
31+
32+
COLUMNS="$( tput cols )"
33+
34+
while true; do
35+
if ! selection="$( draw_snapshots "${fs}" )" ; then
36+
tput clear
37+
exit
38+
fi
39+
40+
# shellcheck disable=SC2162
41+
IFS=, read subkey selected_snap <<< "${selection}"
42+
selected_snap="${selected_snap%,*}"
43+
44+
case "${subkey}" in
45+
"left"|"right")
46+
continue
47+
;;
48+
esac
49+
50+
if is_snapshot "${selected_snap}" ; then
51+
snapshot_dispatcher "${selected_snap}" "${subkey}"
52+
fi
53+
done

zfsbootmenu/help-files/132/recovery-shell.ansi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
Directly kexec a kernel and initramfs from a boot environment, allowing any kernel and initramfs to be loaded into memory and
1717
immediately booted.
1818

19+
zsnapshots dataset
20+
21+
Access the snapshot browser for a dataset, allowing cloning and rollback operations to be initiated.
22+
1923
zreport
2024

2125
List ZFS module, pool and dataset details for bug reports.

zfsbootmenu/help-files/52/recovery-shell.ansi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
initramfs to be loaded into memory and
2121
immediately booted.
2222

23+
zsnapshots dataset
24+
25+
Access the snapshot browser for a dataset,
26+
allowing cloning and rollback operations to
27+
be initiated.
28+
2329
zreport
2430

2531
List ZFS module, pool and dataset details for

zfsbootmenu/help-files/92/recovery-shell.ansi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
Directly kexec a kernel and initramfs from a boot environment, allowing any kernel and
1717
initramfs to be loaded into memory and immediately booted.
1818

19+
zsnapshots dataset
20+
21+
Access the snapshot browser for a dataset, allowing cloning and rollback operations to
22+
be initiated.
23+
1924
zreport
2025

2126
List ZFS module, pool and dataset details for bug reports.

zfsbootmenu/lib/fzf-defaults.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
# vim: softtabstop=2 shiftwidth=2 expandtab
3+
4+
# Override control_term if executing over SSH
5+
# shellcheck disable=SC2034
6+
[ -n "${SSH_TTY}" ] && control_term="${SSH_TTY}"
7+
8+
# shellcheck disable=SC2016
9+
fuzzy_default_options=(
10+
"--ansi" "--no-clear" "--cycle" "--color=16"
11+
"--layout=reverse-list" "--inline-info" "--tac"
12+
"--bind" '"alt-h:execute[ /libexec/zfsbootmenu-help -L ${HELP_SECTION:-main-screen} ]"'
13+
"--bind" '"ctrl-h:execute[ /libexec/zfsbootmenu-help -L ${HELP_SECTION:-main-screen} ]"'
14+
"--bind" '"ctrl-alt-h:execute[ /libexec/zfsbootmenu-help -L ${HELP_SECTION:-main-screen} ]"'
15+
"--bind" "\"alt-l:execute[ /bin/zlogtail ]${HAS_REFRESH:++refresh-preview}\""
16+
"--bind" "\"ctrl-l:execute[ /bin/zlogtail ]${HAS_REFRESH:++refresh-preview}\""
17+
"--bind" "\"ctrl-alt-l:execute[ /bin/zlogtail ]${HAS_REFRESH:++refresh-preview}\""
18+
)
19+
20+
if [ -n "${HAS_BORDER}" ]; then
21+
# shellcheck disable=SC2016
22+
fuzzy_default_options+=(
23+
"--border-label-pos=top" "--border=top"
24+
"--color=border:white" "--separator=''"
25+
)
26+
fi
27+
28+
# shellcheck disable=SC2016,SC2086
29+
if [ ${loglevel:-4} -eq 7 ] ; then
30+
fuzzy_default_options+=(
31+
"--bind" '"alt-t:execute[ /sbin/ztrace > ${control_term} ]"'
32+
"--bind" '"ctrl-t:execute[ /sbin/ztrace > ${control_term} ]"'
33+
"--bind" '"ctrl-alt-t:execute[ /sbin/ztrace > ${control_term} ]"'
34+
"--bind" '"f12:execute[ /libexec/zfunc emergency_shell \"debugging shell\" > ${control_term} ]"'
35+
)
36+
fi
37+
38+
export FUZZYSEL=fzf
39+
export PREVIEW_HEIGHT=2
40+
export FZF_DEFAULT_OPTS="${fuzzy_default_options[*]}"

zfsbootmenu/lib/zfsbootmenu-completions.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ _mount_zfs() {
7171
}
7272
complete -F _mount_zfs mount_zfs
7373

74+
_zsnapshots() {
75+
local ZFS
76+
COMPREPLY=()
77+
78+
[ "${#COMP_WORDS[@]}" != "2" ] && return
79+
80+
for FS in $( zfs list -H -o name ) ; do
81+
ZFS+=("${FS}")
82+
done
83+
84+
COMPREPLY=( $( compgen -W "${ZFS[*]}" -- "${COMP_WORDS[1]}" ) )
85+
}
86+
complete -F _zsnapshots zsnapshots
87+
7488
_zkexec() {
7589
local ARG index
7690
COMPREPLY=()

zfsbootmenu/lib/zfsbootmenu-core.sh

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -771,10 +771,10 @@ find_be_kernels() {
771771

772772
# arg1: ZFS filesystem
773773
# prints: fs kernel initramfs
774-
# returns: nothing
774+
# returns: 0 if a kernel can be identified, 1 if not
775775

776776
select_kernel() {
777-
local zfsbe bepath specific_kernel kexec_args spec_kexec_args
777+
local zfsbe kernel_list specific_kernel kexec_args spec_kexec_args
778778

779779
zfsbe="${1}"
780780
if [ -z "${zfsbe}" ]; then
@@ -783,10 +783,15 @@ select_kernel() {
783783
fi
784784
zdebug "zfsbe set to ${zfsbe}"
785785

786-
bepath="$( be_location "${zfsbe}" )"
786+
kernel_list="$( be_location "${zfsbe}" )/kernels"
787+
788+
if [ ! -f "${kernel_list}" ]; then
789+
zerror "kernel list '${kernel_list}' missing"
790+
return 1
791+
fi
787792

788793
# By default, select the last kernel entry
789-
kexec_args="$( tail -1 "${bepath}/kernels" )"
794+
kexec_args="$( tail -1 "${kernel_list}" )"
790795

791796
# If a specific kernel is listed, prefer it when possible
792797
specific_kernel="$( zfs get -H -o value org.zfsbootmenu:kernel "${zfsbe}" )"
@@ -800,7 +805,7 @@ select_kernel() {
800805
kexec_args="${spec_kexec_args}"
801806
break
802807
fi
803-
done <<<"$( tac "${bepath}/kernels" )"
808+
done <<<"$( tac "${kernel_list}" )"
804809
fi
805810

806811
zdebug "using kexec args: ${kexec_args}"

zfsbootmenu/libexec/zfsbootmenu-preview

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ ENV="${1}"
1111
BOOTFS="${2}"
1212

1313
# shellcheck disable=SC2034
14-
IFS=' ' read -r _fs selected_kernel _initramfs <<<"$( select_kernel "${ENV}")"
14+
IFS=' ' read -r _fs selected_kernel _initramfs <<<"$( select_kernel "${ENV}" || echo "- missing -" )"
1515
selected_kernel="${selected_kernel##*/}"
1616

1717
if [ "${BOOTFS}" = "${ENV}" ]; then

0 commit comments

Comments
 (0)