Compare commits

..

5 Commits

Author SHA1 Message Date
Wayne Galen
2fcfbe9676 Ignore Podman container images (#380)
Same basic pattern as with Docker, but Podman uses a slightly different
path for this
2025-09-17 07:41:59 +02:00
TNE
9e171282da Get default early initrd list from GRUB_EARLY_INITRD_LINUX_STOCK (#389)
This mimics the behavior of grub more precisely

Fixes #388
2025-09-17 07:41:05 +02:00
cip91sk
b509fcaf61 add support for booting snapshots on LUKS encrypted disk (#333)
* add support for booting snapshots on LUKS encrypted disk

* documentation for booting from LUKS encrypted devices

* better detecting cryptdevice UUID
2025-01-06 08:11:45 +01:00
Pascal J
f682e17b30 Merge pull request #321 from StollD/set-subvolid
Add support for GRUB patches from SUSE
2024-04-08 17:02:40 +02:00
Dorian Stoll
ece8d87151 Add support for GRUB patches from SUSE
Some GRUBs out there (Fedora, openSUSE) have an option that makes all
paths relative to the default subvolume of the filesystem. This can be
used to include /boot in your snapshots and roll them back without
having to regenerate grub.cfg.

However, enabling that option will break grub-btrfs, because loading the
kernel from a different snapshot requires the paths to be absolute.

To make this work, GRUB has to be told explicitly to access the root
subvolume when booting to a snapshot.
2024-03-10 12:56:37 +01:00
5 changed files with 91 additions and 61 deletions

View File

@@ -1,4 +1,4 @@
#! /usr/bin/env sh
#! /usr/bin/env bash
#
# Written by: Antynea
# BTC donation address: 1Lbvz244WA8xbpHek9W2Y12cakM6rDe5Rt
@@ -41,16 +41,14 @@ set -e
sysconfdir="/etc"
grub_btrfs_config="${sysconfdir}/default/grub-btrfs/config"
# shellcheck disable=SC1090
[ -f "$grub_btrfs_config" ] && . "$grub_btrfs_config"
# shellcheck disable=SC1091
[ -f "${sysconfdir}/default/grub" ] && . "${sysconfdir}/default/grub"
## Error Handling
print_error()
{
err_msg="$*"
bug_report="If you think an error has occurred, please file a bug report at \"https://github.com/Antynea/grub-btrfs\""
local err_msg="$*"
local bug_report="If you think an error has occurred, please file a bug report at \"https://github.com/Antynea/grub-btrfs\""
printf "%s\n" "${err_msg}" "${bug_report}" >&2 ;
exit 0
}
@@ -79,10 +77,8 @@ done
## Exit the script, if:
[ "$(echo "$GRUB_BTRFS_DISABLE" | tr '[:upper:]' '[:lower:]')" = 'true' ] && print_error "GRUB_BTRFS_DISABLE is set to true (default=false)"
if ! type btrfs >/dev/null 2>&1; then print_error "btrfs-progs isn't installed"; fi
# shellcheck disable=SC1090,SC2015
[ -f "${GRUB_BTRFS_MKCONFIG_LIB:-/usr/share/grub/grub-mkconfig_lib}" ] && . "${GRUB_BTRFS_MKCONFIG_LIB:-/usr/share/grub/grub-mkconfig_lib}" || print_error "grub-mkconfig_lib couldn't be found"
# shellcheck disable=SC2005
if echo "$(btrfs filesystem df / 2>&1)" | grep "not a btrfs filesystem" >/dev/null 2>&1; then print_error "Root filesystem isn't btrfs"; fi
[[ "$(btrfs filesystem df / 2>&1)" == *"not a btrfs filesystem"* ]] && print_error "Root filesystem isn't btrfs"
printf "Detecting snapshots ...\n" >&2 ;
@@ -114,30 +110,37 @@ fi
## Probe information of Root and Boot devices
# Probe info "Root partition"
# shellcheck disable=SC2154 # grub_probe is provided by grub environment
root_device=$(${grub_probe} --target=device /) # Root device
# shellcheck disable=SC2086 # we actually need word splitting here if we have several root devices (e.g. RAID)
root_uuid=$(${grub_probe} --device ${root_device} --target="fs_uuid" 2>/dev/null) # UUID of the root device
root_uuid_subvolume=$(btrfs subvolume show / 2>/dev/null) || print_error "UUID of the root subvolume is not available"; # If UUID of root subvolume is not available, then exit
root_uuid_subvolume=$(awk -F":" 'match($1, /(^[ \t]+UUID)/) {sub(/^[ \t]+/, "", $2); print $2}' <<EOF
"$root_uuid_subvolume"
EOF
) # UUID of the root subvolume '
root_uuid_subvolume=$(awk -F":" 'match($1, /(^[ \t]+UUID)/) {sub(/^[ \t]+/, "", $2); print $2}' <<< "$root_uuid_subvolume") # UUID of the root subvolume '
# Probe info "Boot partition"
# shellcheck disable=SC2086 # we actually need word splitting here if we have several devices
boot_device=$(${grub_probe} --target=device ${boot_directory}) # Boot device
# shellcheck disable=SC2086 # we actually need word splitting here if we have several devices
boot_uuid=$(${grub_probe} --device ${boot_device} --target="fs_uuid" 2>/dev/null) # UUID of the boot device
boot_uuid_subvolume=$(btrfs subvolume show "$boot_directory" 2>/dev/null) || boot_uuid_subvolume=" UUID: $root_uuid_subvolume"; # If boot folder isn't a subvolume, then UUID=root_uuid_subvolume
boot_uuid_subvolume=$(awk -F":" 'match($1, /(^[ \t]+UUID)/) {sub(/^[ \t]+/, "", $2); print $2}' <<EOF
"$boot_uuid_subvolume"
EOF
) # UUID of the boot subvolume '
# shellcheck disable=SC2086 # we actually need word splitting here if we have several devices
boot_uuid_subvolume=$(awk -F":" 'match($1, /(^[ \t]+UUID)/) {sub(/^[ \t]+/, "", $2); print $2}' <<< "$boot_uuid_subvolume") # UUID of the boot subvolume '
boot_hs=$(${grub_probe} --device ${boot_device} --target="hints_string" 2>/dev/null) # hints string
# shellcheck disable=SC2086 # we actually need word splitting here if we have several devices
boot_fs=$(${grub_probe} --device ${boot_device} --target="fs" 2>/dev/null) # Type filesystem of boot device
# Enable LUKS encrypted devices support
case "$(echo "$GRUB_BTRFS_ENABLE_CRYPTODISK" | tr '[:upper:]' '[:lower:]')" in
true)
list_insmods=()
list_insmods+=("insmod gzio")
list_insmods+=("insmod part_gpt")
list_insmods+=("insmod cryptodisk")
list_insmods+=("insmod luks")
list_insmods+=("insmod gcry_rijndael")
list_insmods+=("insmod gcry_rijndael")
list_insmods+=("insmod gcry_sha256")
list_insmods+=("insmod ${boot_fs}")
list_insmods+=("cryptomount -u $(echo $GRUB_CMDLINE_LINUX_DEFAULT | grep -o -P '(?<=cryptdevice=UUID=).*(?=:cryptdev)')")
;;
*)
list_insmods=("insmod ${boot_fs}")
;;
esac
## Parameters passed to the kernel
kernel_parameters="$GRUB_CMDLINE_LINUX $GRUB_CMDLINE_LINUX_DEFAULT $GRUB_BTRFS_SNAPSHOT_KERNEL_PARAMETERS"
## Mount point location
@@ -159,7 +162,7 @@ fi
## Detect rootflags
detect_rootflags()
{
fstabflags=$(grep -oE '^\s*[^#][[:graph:]]+\s+/\s+btrfs\s+[[:graph:]]+' "${grub_btrfs_mount_point}/${snap_dir_name_trim}/etc/fstab" \
local fstabflags=$(grep -oE '^\s*[^#][[:graph:]]+\s+/\s+btrfs\s+[[:graph:]]+' "${grub_btrfs_mount_point}/${snap_dir_name_trim}/etc/fstab" \
| sed -E 's/^.*[[:space:]]([[:graph:]]+)$/\1/;s/,?subvol(id)?=[^,$]+//g;s/^,//')
rootflags="rootflags=${fstabflags:+$fstabflags,}${GRUB_BTRFS_ROOTFLAGS:+$GRUB_BTRFS_ROOTFLAGS,}"
}
@@ -167,8 +170,8 @@ detect_rootflags()
unmount_grub_btrfs_mount_point()
{
if [ -d "$grub_btrfs_mount_point" ]; then
wait=true
wait_max=0
local wait=true
local wait_max=0
printf "Unmount %s .." "$grub_btrfs_mount_point" >&2;
while $wait; do
if grep -qs "$grub_btrfs_mount_point" /proc/mounts; then
@@ -216,7 +219,6 @@ make_menu_entries()
# prefix_i=${i%%"-"*}
suffix_i=${i#*"-"}
# alt_suffix_i=${i##*"-"}
# shellcheck disable=SC2269 # this is a way to exit the if..elif..
if [ "${kversion}" = "${suffix_i}" ]; then i="${i}";
elif [ "${kversion}.img" = "${suffix_i}" ]; then i="${i}";
elif [ "${kversion}-fallback.img" = "${suffix_i}" ]; then i="${i}";
@@ -235,13 +237,22 @@ make_menu_entries()
if [ x\$feature_all_video_module = xy ]; then
insmod all_video
fi
set gfxpayload=keep
insmod ${boot_fs}
set gfxpayload=keep"
for j in "${insmods[@]}"; do
entry "\
${j}"
done
entry "\
if [ x\$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root ${boot_hs} ${boot_uuid}
else
search --no-floppy --fs-uuid --set=root ${boot_uuid}
fi"
if [ "${SUSE_BTRFS_SNAPSHOT_BOOTING:-"false"}" = "true" ]; then
entry "\
set btrfs_subvolid=5"
fi
entry "\
echo 'Loading Snapshot: "${snap_date_trim}" "${snap_dir_name_trim}"'
echo 'Loading Kernel: "${k}" ...'
linux \"${boot_dir_root_grub}/"${k}"\" root="${LINUX_ROOT_DEVICE}" ${kernel_parameters} ${rootflags}subvol=\""${snap_dir_name_trim}"\""
@@ -276,7 +287,12 @@ make_menu_entries()
search --no-floppy --fs-uuid --set=root ${boot_hs} ${boot_uuid}
else
search --no-floppy --fs-uuid --set=root ${boot_uuid}
fi"
if [ "${SUSE_BTRFS_SNAPSHOT_BOOTING:-"false"}" = "true" ]; then
entry "\
set btrfs_subvolid=5"
fi
entry "\
echo 'Loading Snapshot: "${snap_date_trim}" "${snap_dir_name_trim}"'
echo 'Loading Kernel: "${k}" ...'
linux \"${boot_dir_root_grub}/"${k}"\" root="${LINUX_ROOT_DEVICE}" ${kernel_parameters} ${rootflags}subvol=\""${snap_dir_name_trim}"\""
@@ -296,21 +312,21 @@ make_menu_entries()
## Trim a string from leading and trailing whitespaces
trim() {
var="$*"
local var="$*"
var="${var#"${var%%[![:space:]]*}"}"
var="${var%"${var##*[![:space:]]}"}"
printf '%s' "$var"
echo -n "$var"
}
## List of snapshots on filesystem
snapshot_list()
{
snapper_info="info.xml"
timeshift_info="info.json"
date_snapshots=()
path_snapshots=()
type_snapshots=()
description_snapshots=()
local snapper_info="info.xml"
local timeshift_info="info.json"
local date_snapshots=()
local path_snapshots=()
local type_snapshots=()
local description_snapshots=()
IFS=$'\n'
for snap in $(btrfs subvolume list -sa "${btrfs_subvolume_sort}" /); do # Parse btrfs snapshots
IFS=$oldIFS
@@ -337,8 +353,7 @@ snapshot_list()
local description_snapshot="N/A"
# path to yabsnap snapshot meta data
local yabsnap_info
yabsnap_info="$grub_btrfs_mount_point/${path_snapshot%"/"*}/$(echo "${snap[13]}" | awk -F'/' '{print $3 "-meta.json"}')"
local yabsnap_info="$grub_btrfs_mount_point/${path_snapshot%"/"*}/$(echo "${snap[13]}" | awk -F'/' '{print $3 "-meta.json"}')"
if [[ -s "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$snapper_info" ]] ; then
type_snapshot=$(awk -F"<|>" 'match($2, /^type/) {print $3}' "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$snapper_info") # search matching string beginning "type"
@@ -417,31 +432,30 @@ parse_snapshot_list()
snap_dir_name="$(echo "$item" | cut -d'|' -f2)" # column_2
snap_dir_name_trim="$(trim "$snap_dir_name")"
# shellcheck disable=SC2034 # Used by "title_format" function
snap_snapshot="$snap_dir_name"
# shellcheck disable=SC2034 # Used by "title_format" function
snap_snapshot="$snap_dir_name" # Used by "title_format" function
snap_type="$(echo "$item" | cut -d'|' -f3)" # column_3
# shellcheck disable=SC2034 # Used by "title_format" function
snap_description="$(echo "$item" | cut -d'|' -f4)" # column_4
}
## Detect kernels in "boot_directory"
detect_kernel()
{
list_kernel=""
list_kernel=()
# Original kernel (auto-detect)
for okernel in "${boot_dir}"/vmlinuz-* \
"${boot_dir}"/vmlinux-* \
"${boot_dir}"/kernel-* ; do
[ ! -f "${okernel}" ] && continue;
list_kernel="${list_kernel} $okernel"
list_kernel+=("$okernel")
done
# Custom name kernel in "GRUB_BTRFS_NKERNEL"
if [ -n "${GRUB_BTRFS_NKERNEL}" ] ; then
for ckernel in "${boot_dir}"/${GRUB_BTRFS_NKERNEL} ; do
for ckernel in "${boot_dir}/${GRUB_BTRFS_NKERNEL[@]}" ; do
[ ! -f "${ckernel}" ] && continue;
list_kernel="${list_kernel} $okernel"
list_kernel+=("$ckernel")
done
fi
}
@@ -474,12 +488,8 @@ detect_microcode()
list_ucode=()
# Original intel/amd microcode (auto-detect)
# See "https://www.gnu.org/software/grub/manual/grub/html_node/Simple-configuration.html"
for oiucode in "${boot_dir}"/intel-uc.img \
"${boot_dir}"/intel-ucode.img \
"${boot_dir}"/amd-uc.img \
"${boot_dir}"/amd-ucode.img \
"${boot_dir}"/early_ucode.cpio \
"${boot_dir}"/microcode.cpio; do
for oiucode in ${GRUB_EARLY_INITRD_LINUX_STOCK} ; do
oiucode="${boot_dir}/${oiucode}"
[ ! -f "${oiucode}" ] && continue;
list_ucode+=("$oiucode")
done
@@ -487,7 +497,7 @@ detect_microcode()
# Custom name microcode in "GRUB_BTRFS_CUSTOM_MICROCODE"
if [ -n "${GRUB_BTRFS_CUSTOM_MICROCODE}" ] ; then
for cucode in "${boot_dir}/${GRUB_BTRFS_CUSTOM_MICROCODE[@]}" ; do
[ ! -f "${cucode}" ] && continue
[[ ! -f "${cucode}" ]] && continue
list_ucode+=("$cucode")
done
fi
@@ -500,7 +510,7 @@ title_format()
{
title_menu="|" # "|" is for visuals only
title_submenu="|" # "|" is for visuals only
[ -z "${GRUB_BTRFS_TITLE_FORMAT}" ] && GRUB_BTRFS_TITLE_FORMAT=("date" "snapshot" "type" "description"); # Default parameters
[[ -z "${GRUB_BTRFS_TITLE_FORMAT}" ]] && GRUB_BTRFS_TITLE_FORMAT=("date" "snapshot" "type" "description"); # Default parameters
for key in "${!GRUB_BTRFS_TITLE_FORMAT[@]}"; do
[[ ${GRUB_BTRFS_TITLE_FORMAT[$key],,} != "${title_column[${GRUB_BTRFS_TITLE_FORMAT[$key]}],,}" ]] && continue; # User used wrong parameter
declare -n var="snap_${GRUB_BTRFS_TITLE_FORMAT[$key],,}" # $var is a indirect variable
@@ -515,15 +525,15 @@ title_format()
# Adds a header to the grub-btrfs.cfg file
header_menu()
{
header_entry=""
[ -z "${GRUB_BTRFS_TITLE_FORMAT}" ] && GRUB_BTRFS_TITLE_FORMAT=("date" "snapshot" "type" "description"); # Default parameters
local header_entry=""
[[ -z "${GRUB_BTRFS_TITLE_FORMAT}" ]] && GRUB_BTRFS_TITLE_FORMAT=("date" "snapshot" "type" "description"); # Default parameters
for key in "${!GRUB_BTRFS_TITLE_FORMAT[@]}"; do
[[ ${GRUB_BTRFS_TITLE_FORMAT[$key],,} != "${title_column[${GRUB_BTRFS_TITLE_FORMAT[$key]}],,}" ]] && continue; # User used wrong parameter
declare -n var="snap_${GRUB_BTRFS_TITLE_FORMAT[$key],,}" # $var is a indirect variable
# Center alignment, needed for pretty formatting
local lenght_title_column_left=$((${#var}-${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}))
((lenght_title_column_left%2)) && lenght_title_column_left=$((lenght_title_column_left+1)); # If the difference is an odd number, add an extra space
lenght_title_column_left=$(((lenght_title_column_left/2)+${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}));
lenght_title_column_left=$((((lenght_title_column_left/2)+${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]})));
local lenght_title_column_right=$(((${#var}-lenght_title_column_left)+1)) #+1 is necessary for extra "|" character
header_entry+=$(printf "%${lenght_title_column_left}s%${lenght_title_column_right}s" "${title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}" "|") # Final "|" is for visuals only
done
@@ -542,7 +552,6 @@ boot_bounded()
boot_dir="$grub_btrfs_mount_point/$snap_dir_name_trim$boot_directory"
detect_kernel
if [ -z "${list_kernel}" ]; then continue; fi
# TODO
name_kernel=("${list_kernel[@]##*"/"}")
detect_initramfs
name_initramfs=("${list_initramfs[@]##*"/"}")
@@ -551,6 +560,7 @@ boot_bounded()
detect_rootflags
title_format
boot_dir_root_grub="$(make_system_path_relative_to_its_root "${boot_dir}")" # convert "boot_directory" to root of GRUB (e.g /boot become /)
insmods=("${list_insmods[@]##*"/"}")
make_menu_entries
# show snapshot found during run "grub-mkconfig"
if [ "${GRUB_BTRFS_SHOW_SNAPSHOTS_FOUND:-"true"}" = "true" ]; then
@@ -634,7 +644,6 @@ if [ "${count_limit_snap}" = "0" ] || [ -z "${count_limit_snap}" ]; then
fi
# Move "grub-btrfs.new" to "grub-btrfs.cfg"
header_menu
# shellcheck disable=SC2154 # bindir is provided by grub environment
if "${bindir}/${GRUB_BTRFS_SCRIPT_CHECK:-grub-script-check}" "$grub_btrfs_directory/grub-btrfs.new"; then
cat "$grub_btrfs_directory/grub-btrfs.new" > "$grub_btrfs_directory/grub-btrfs.cfg"
rm -f "$grub_btrfs_directory/grub-btrfs.new" "$grub_btrfs_directory/grub-btrfs.cfg.bkp"

View File

@@ -274,6 +274,10 @@ After that, the daemon should be restarted with:
sudo rc-service grub-btrfsd restart
```
##### 🔒 Snapshots on LUKS encrypted devices
By default, grub-btrfs generates entries that does not load modules for dealing with encrypted devices.
Enable the `GRUB_BTRFS_ENABLE_CRYPTODISK` variable in `/etc/default/grub-btrfs/config` to load said modules and then execute the steps to mount encrypted root after selecting the snapshot.
- - -
### Troubleshooting
If you experience problems with grub-btrfs don't hesitate [to file an issue](https://github.com/Antynea/grub-btrfs/issues/new/choose).

8
config
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
GRUB_BTRFS_VERSION=4.13-fix-bashisms-2024-03-27T20:48:48+00:00
GRUB_BTRFS_VERSION=4.13-yabsnap_info_support-2024-03-06T13:43:57+00:00
# Disable grub-btrfs.
# Default: "false"
@@ -74,7 +74,7 @@ GRUB_BTRFS_IGNORE_SPECIFIC_PATH=("@")
# Any path starting with the specified string will be ignored.
# e.g : if `prefix path` = @, all snapshots beginning with "@/..." will be ignored.
# Default: ("var/lib/docker" "@var/lib/docker" "@/var/lib/docker")
GRUB_BTRFS_IGNORE_PREFIX_PATH=("var/lib/docker" "@var/lib/docker" "@/var/lib/docker")
GRUB_BTRFS_IGNORE_PREFIX_PATH=("var/lib/docker" "@var/lib/docker" "@/var/lib/docker" "var/lib/containers" "@var/lib/containers" "@/var/lib/containers")
# Ignore specific type/tag of snapshot during run "grub-mkconfig".
# For snapper:
@@ -158,3 +158,7 @@ GRUB_BTRFS_IGNORE_PREFIX_PATH=("var/lib/docker" "@var/lib/docker" "@/var/lib/doc
# doesn't work if GRUB_BTRFS_PROTECTION_AUTHORIZED_USERS isn't empty
# Default: "false"
#GRUB_BTRFS_DISABLE_PROTECTION_SUBMENU="true"
# Enable booting from snapshots stored on LUKS encrypted devices
# Default: "false"
#GRUB_BTRFS_ENABLE_CRYPTODISK="true"

View File

@@ -102,6 +102,14 @@ Default: “false”
.IP \(em 4
Example: \fCGRUB_BTRFS_OVERRIDE_BOOT_PARTITION_DETECTION="true"\fP
.SS "\GRUB_BTRFS_ENABLE_CRYPTODISK\fP"
.PP
Enable booting from snapshots stored on LUKS encrypted devices
.IP \(em 4
Default: “false”
.IP \(em 4
Example: \GRUB_BTRFS_ENABLE_CRYPTODISK="true"\fP
.SS "CUSTOM KERNELS"
.SS "\fCGRUB_BTRFS_NKERNEL\fP / \fCGRUB_BTRFS_NINIT\fP / \fCGRUB_BTRFS_CUSTOM_MICROCODE\fP"
.PP

View File

@@ -73,6 +73,11 @@ Change to "true" if your boot partition is not detected as separate.
- Default: "false"
- Example: ~GRUB_BTRFS_OVERRIDE_BOOT_PARTITION_DETECTION="true"~
*** ~GRUB_BTRFS_ENABLE_CRYPTODISK~
Enable booting from snapshots stored on LUKS encrypted devices
- Default: "false"
- Example: ~GRUB_BTRFS_ENABLE_CRYPTODISK="true"~
** CUSTOM KERNELS
*** ~GRUB_BTRFS_NKERNEL~ / ~GRUB_BTRFS_NINIT~ / ~GRUB_BTRFS_CUSTOM_MICROCODE~