From 9d436adf9590cd69b1c339efc60e1b2777d2ebdd Mon Sep 17 00:00:00 2001 From: Antynea Date: Mon, 25 Oct 2021 15:47:26 +0200 Subject: [PATCH] Reworks many things (#174) #### Script: * Snapper: - Now, the information is retrieved from the info.xml file. * Timeshift: * Now, the information is retrieved from the info.json file. * Probe informations from device: * Add the UUID of the root and boot subvolumes * Show full path snapshot or only name: * Remove, this feature never worked correctly * Grub-menu: * Now displays the following information in separate columns: - Date of snapshot - Path of snapshot - Type/Tags of snapshot if available (snapper/timeshift) - Description/Comments of snapshot if available (snapper/timeshift) * Possibility to display only the desired information(s) (see config file) * Adds a header for the column title * GRUB_BTRFS_PREFIXENTRY is remove * Boot partition detection: * grub-btrfs is now able to detect if the boot folder/partition is a subvolume * GRUB_BTRFS_OVERRIDE_BOOT_PARTITION_DETECTION should no longer be needed for this case * Variables: * Rename some variables * Header: * Update the header to reflect the changes. #### Config: * GRUB_BTRFS_PREFIXENTRY: * Remove, no longer use * GRUB_BTRFS_DISPLAY_PATH_SNAPSHOT: * Remove, no longer use * GRUB_BTRFS_TITLE_FORMAT: * Shows/Hides "date" "snapshot" "type" "description" in the Grub menu, custom order available. Default: ("date" "snapshot" "type" "description") * GRUB_BTRFS_IGNORE_SNAPPER_TYPE: * Rename to GRUB_BTRFS_IGNORE_SNAPSHOT_TYPE - Supports both timeshift and snapper tags/type * GRUB_BTRFS_IGNORE_SNAPPER_DESCRIPTION: * Rename to GRUB_BTRFS_IGNORE_SNAPSHOT_DESCRIPTION - Supports both snapper and timeshift description/comments * GRUB_BTRFS_OVERRIDE_BOOT_PARTITION_DETECTION: * grub-btrfs is now able to detect if the boot folder/partition is a subvolume. Activating this parameter should no longer be necessary for this case. * GRUB_BTRFS_SNAPPER_CONFIG: * Remove, no longer use #### Readme: * Update "What does grub-btrfs v4.xx do" section * Adds support for timeshift --- 41_snapshots-btrfs | 387 ++++++++++++++++++++++----------------------- README.md | 2 +- config | 36 ++--- 3 files changed, 202 insertions(+), 223 deletions(-) mode change 100755 => 100644 41_snapshots-btrfs diff --git a/41_snapshots-btrfs b/41_snapshots-btrfs old mode 100755 new mode 100644 index 90626d3..728cea0 --- a/41_snapshots-btrfs +++ b/41_snapshots-btrfs @@ -16,7 +16,7 @@ # - Automatically Detect if "/boot" is in separate partition. # - Automatically Detect kernel, initramfs and intel/amd microcode in "/boot" directory on snapshots. # - Automatically Create corresponding "menuentry" in grub.cfg. -# - Automatically detect snapper and use snapper's snapshot description if available. +# - Automatically detect the type/tags and descriptions/comments of snapper/timeshift snapshots. # - Automatically generate grub.cfg if you use the provided systemd service. # # Installation: @@ -51,17 +51,24 @@ if ! type btrfs >/dev/null 2>&1; then exit 0; fi # btrfs-progs isn't installed root_fs=$(${grub_probe} --target="fs" / 2>/dev/null) [[ "$root_fs" != "btrfs" ]] && exit 0 +## Error Handling +print_error() +{ + 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 +} + +printf "Detecting snapshots ...\n" >&2 ; + ## Submenu name distro=$(awk -F "=" '/^NAME=/ {gsub(/"/, "", $2); print $2}' /etc/os-release) submenuname=${GRUB_BTRFS_SUBMENUNAME:-"${distro:-Linux} snapshots"} -## Prefix entry -prefixentry=${GRUB_BTRFS_PREFIXENTRY:-"Snapshot:"} ## Limit snapshots to show in the Grub menu (default=50) limit_snap_show="${GRUB_BTRFS_LIMIT:-50}" ## How to sort snapshots list -btrfssubvolsort=(--sort="${GRUB_BTRFS_SUBVOLUME_SORT:-"-rootid"}") -## Snapper's config name -snapper_config=${GRUB_BTRFS_SNAPPER_CONFIG:-"root"} +btrfs_subvolume_sort="--sort=${GRUB_BTRFS_SUBVOLUME_SORT:-"-rootid"}" ## Customize GRUB directory, where "grub.cfg" file is saved grub_directory=${GRUB_BTRFS_GRUB_DIRNAME:-"/boot/grub"} ## Customize BOOT directory, where kernels/initrams/microcode is saved. @@ -81,16 +88,20 @@ fi # Probe info "Root partition" root_device=$(${grub_probe} --target=device /) # Root device 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}' <<< "$root_uuid_subvolume") # UUID of the root subvolume # Probe info "Boot partition" boot_device=$(${grub_probe} --target=device ${boot_directory}) # Boot device 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}' <<< "$boot_uuid_subvolume") # UUID of the boot subvolume boot_hs=$(${grub_probe} --device ${boot_device} --target="hints_string" 2>/dev/null) # hints string boot_fs=$(${grub_probe} --device ${boot_device} --target="fs" 2>/dev/null) # Type filesystem of boot device ## Parameters passed to the kernel kernel_parameters="$GRUB_CMDLINE_LINUX $GRUB_CMDLINE_LINUX_DEFAULT" ## Mount point location -gbgmp=$(mktemp -dt grub-btrfs.XXXXXXXXXX) +grub_btrfs_mount_point=$(mktemp -dt grub-btrfs.XXXXXXXXXX) ## Class for theme CLASS="--class snapshots --class gnu-linux --class gnu --class os" ## save IFS @@ -108,36 +119,27 @@ fi ## Detect rootflags detect_rootflags() { - local fstabflags=$(grep -oE '^\s*[^#][[:graph:]]+\s+/\s+btrfs\s+[[:graph:]]+' "${gbgmp}/${snap_dir_name}/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,}" } ## Path to grub-script-check grub_script_check="${bindir}/grub-script-check" -## Error Handling -print_error() +unmount_grub_btrfs_mount_point() { - 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 -} - -unmount_gbgmp() -{ -if [[ -d "$gbgmp" ]]; then +if [[ -d "$grub_btrfs_mount_point" ]]; then local wait=true local wait_max=0 - printf "Unmount %s .." "$gbgmp" >&2; + printf "Unmount %s .." "$grub_btrfs_mount_point" >&2; while $wait; do - if grep -qs "$gbgmp" /proc/mounts; then + if grep -qs "$grub_btrfs_mount_point" /proc/mounts; then wait_max=$((1+wait_max)) - if umount "$gbgmp" >/dev/null 2>&1; then + if umount "$grub_btrfs_mount_point" >/dev/null 2>&1; then wait=false # umount successful printf " Success\n" >&2; elif [[ $wait_max = 10 ]]; then - printf "\nWarning: Unable to unmount %s in %s\n" "$root_device" "$gbgmp" >&2; + printf "\nWarning: Unable to unmount %s in %s\n" "$root_device" "$grub_btrfs_mount_point" >&2; break; else printf "." >&2 ; # output to show that the script is alive @@ -149,8 +151,8 @@ if [[ -d "$gbgmp" ]]; then fi done if [[ "$wait" != true ]]; then - if ! rm -d "$gbgmp" >/dev/null 2>&1; then - printf "Unable to delete %s: Device or ressource is busy\n" "$gbgmp" >&2; + if ! rm -d "$grub_btrfs_mount_point" >/dev/null 2>&1; then + printf "Unable to delete %s: Device or ressource is busy\n" "$grub_btrfs_mount_point" >&2; fi fi fi @@ -166,8 +168,8 @@ echo "$@" >> "$grub_directory/grub-btrfs.new" make_menu_entries() { ## \" required for snap,kernels,init,microcode with space in their name - entry "submenu '$title_menu' { - submenu '---> $title_menu <---' { echo }" + entry "submenu '${title_menu}' { + submenu '${title_submenu}' { echo }" for k in "${name_kernel[@]}"; do [[ ! -f "${boot_dir}"/"${k}" ]] && continue; kversion=${k#*"-"} @@ -185,10 +187,10 @@ make_menu_entries() for u in "${name_microcode[@]}"; do if [[ "${name_microcode}" != "x" ]] ; then entry " - menuentry '"${k}" & "${i}" & "${u}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {" + menuentry ' "${k}" & "${i}" & "${u}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {" else entry " - menuentry '"${k}" & "${i}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {" + menuentry ' "${k}" & "${i}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {" fi entry "\ if [ x\$feature_all_video_module = xy ]; then @@ -201,9 +203,9 @@ make_menu_entries() else search --no-floppy --fs-uuid --set=root ${boot_uuid} fi - echo 'Loading Snapshot: "${snap_date_time}" "${snap_dir_name}"' + 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}"\"" + linux \"${boot_dir_root_grub}/"${k}"\" root="${LINUX_ROOT_DEVICE}" ${kernel_parameters} ${rootflags}subvol=\""${snap_dir_name_trim}"\"" if [[ "${name_microcode}" != "x" ]] ; then entry "\ echo 'Loading Microcode & Initramfs: "${u}" "${i}" ...' @@ -220,10 +222,10 @@ make_menu_entries() for u in "${name_microcode[@]}"; do if [[ "${name_microcode}" != "x" ]] ; then entry " - menuentry '"${k}" & "${u}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {" + menuentry ' "${k}" & "${u}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {" else entry " - menuentry '"${k}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {" + menuentry ' "${k}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {" fi entry "\ if [ x\$feature_all_video_module = xy ]; then @@ -236,9 +238,9 @@ make_menu_entries() else search --no-floppy --fs-uuid --set=root ${boot_uuid} fi - echo 'Loading Snapshot: "${snap_date_time}" "${snap_dir_name}"' + 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}"\"" + linux \"${boot_dir_root_grub}/"${k}"\" root="${LINUX_ROOT_DEVICE}" ${kernel_parameters} ${rootflags}subvol=\""${snap_dir_name_trim}"\"" if [[ "${name_microcode}" != "x" ]] ; then entry "\ echo 'Loading Microcode: "${u}" ...' @@ -264,109 +266,127 @@ trim() { ## List of snapshots on filesystem snapshot_list() { - # Query info from snapper if it is installed - if type snapper >/dev/null 2>&1; then - if [ -s "/etc/snapper/configs/$snapper_config" ]; then - printf "Info: snapper detected, using config: %s\n" "$snapper_config" >&2 - local snapper_ids=($(snapper --no-dbus -t 0 -c "$snapper_config" list --disable-used-space | tail -n +3 | cut -d'|' -f 1)) - local snapper_types=($(snapper --no-dbus -t 0 -c "$snapper_config" list --disable-used-space | tail -n +3 | cut -d'|' -f 2)) - - IFS=$'\n' - local snapper_descriptions=($(snapper --no-dbus -t 0 -c "$snapper_config" list --disable-used-space | tail -n +3 | rev | cut -d'|' -f 2 | rev)) - else - printf "Warning: snapper detected but config: %s does not exist\n" "$snapper_config" >&2 - fi - fi - + local snapper_info="info.xml" + local timeshift_info="info.json" + local date_snapshots=() + local name_snapshots=() + local info_types=() + local info_descriptions=() IFS=$'\n' - - # Parse btrfs snapshots - local entries=() - local ids=() - local max_entry_length=0 - for snap in $(btrfs subvolume list -sa "${btrfssubvolsort}" /); do + for snap in $(btrfs subvolume list -sa "${btrfs_subvolume_sort}" /); do # Parse btrfs snapshots IFS=$oldIFS snap=($snap) local snap_path_name=${snap[@]:13:${#snap[@]}} - - # Discard deleted snapshots - if [ "$snap_path_name" = "DELETED" ]; then continue; fi - [[ ${snap_path_name%%"/"*} == "" ]] && snap_path_name=${snap_path_name#*"/"} + if [ "$snap_path_name" = "DELETED" ]; then continue; fi # Discard deleted snapshots + [[ ${snap_path_name%%"/"*} == "" ]] && snap_path_name=${snap_path_name#*"/"} # Remove the "" string at the beginning of the path # ignore specific path during run "grub-mkconfig" if [ -n "${GRUB_BTRFS_IGNORE_SPECIFIC_PATH}" ] ; then - for isp in ${GRUB_BTRFS_IGNORE_SPECIFIC_PATH[@]} ; do + for isp in "${GRUB_BTRFS_IGNORE_SPECIFIC_PATH[@]}" ; do [[ "${snap_path_name}" == "${isp}" ]] && continue 2; done fi if [ -n "${GRUB_BTRFS_IGNORE_PREFIX_PATH}" ] ; then - for isp in ${GRUB_BTRFS_IGNORE_PREFIX_PATH[@]} ; do + for isp in "${GRUB_BTRFS_IGNORE_PREFIX_PATH[@]}" ; do [[ "${snap_path_name}" == "${isp}"/* ]] && continue 2; done fi + [[ ! -d "$grub_btrfs_mount_point/$snap_path_name/boot" ]] && continue; # Discard snapshots without /boot folder - # detect if /boot directory exists - [[ ! -d "$gbgmp/$snap_path_name/boot" ]] && continue; - - local id="${snap_path_name//[!0-9]}" # brutal way to get id: remove everything non-numeric - ids+=("$id") - - local entry="${snap[@]:10:2} | ${snap_path_name}" - entries+=("$entry") - - # Find max length of a snapshot entry, needed for pretty formatting - local length="${#entry}" - [[ "$length" -gt "$max_entry_length" ]] && max_entry_length=$length + local date_snapshot="${snap[@]:10:2}" + date_snapshots+=("$date_snapshot") + local name_snapshot="${snap_path_name}" + name_snapshots+=("$name_snapshot") + + # Parse Snapper & timeshift informations + if [[ -s "$grub_btrfs_mount_point/${snap_path_name%"/"*}/$snapper_info" ]] ; then + local snapper_type=$(awk -F"<|>" 'match($2, /^type/) {print $3}' "$grub_btrfs_mount_point/${snap_path_name%"/"*}/$snapper_info") # search matching string beginning "type" + [[ -z "$snapper_type" ]] && info_types+=("no_type") || info_types+=("$snapper_type") + local snapper_description=$(awk -F"<|>" 'match($2, /^description/) {print $3}' "$grub_btrfs_mount_point/${snap_path_name%"/"*}/$snapper_info") # search matching string beginning "description" + [[ -z "$snapper_description" ]] && info_descriptions+=("N/A") || info_descriptions+=("$snapper_description") + elif [[ -s "$grub_btrfs_mount_point/${snap_path_name%"/"*}/$timeshift_info" ]] ; then + local timeshift_type=$(awk -F" : " 'match($1, /^[ \t]+"tags"/) {gsub(/"|,/,"");print $2}' "$grub_btrfs_mount_point/${snap_path_name%"/"*}/$timeshift_info") # search matching string beginning "tags" + [[ -z "$timeshift_type" ]] && info_types+=("no_type") || info_types+=("$timeshift_type") + local timeshift_description=$(awk -F" : " 'match($1, /^[ \t]+"comments"/) {gsub(/"|,/,"");print $2}' "$grub_btrfs_mount_point/${snap_path_name%"/"*}/$timeshift_info") # search matching string beginning "comments" + [[ -z "$timeshift_description" ]] && info_descriptions+=("N/A") || info_descriptions+=("$timeshift_description") + else + info_types+=("N/A") + info_descriptions+=("N/A") + fi + done + + # Find max length of a snapshot date, needed for pretty formatting + local max_date_length=0 + for i in "${date_snapshots[@]}"; do + local date_snapshot=$(trim "${i}") + local length="${#i}" + [[ "$length" -gt "$max_date_length" ]] && max_date_length=$length + done + + # Find max length of a snapshot name, needed for pretty formatting + local max_name_snapshot_length=0 + for i in "${name_snapshots[@]}"; do + local name_snapshot=$(trim "${i}") + local length="${#i}" + [[ "$length" -gt "$max_name_snapshot_length" ]] && max_name_snapshot_length=$length done # Find max length of a snapshot type, needed for pretty formatting local max_type_length=0 - for id in "${ids[@]}"; do - for j in "${!snapper_ids[@]}"; do - local snapper_id="${snapper_ids[$j]//[[:space:]]/}" - if [[ "$snapper_id" == "$id" ]]; then - local snapper_type=$(trim "${snapper_types[$j]}") - local length="${#snapper_type}" - [[ "$length" -gt "$max_type_length" ]] && max_type_length=$length - fi - done + for i in "${info_types[@]}"; do + local info_type=$(trim "${i}") + local length="${#i}" + [[ "$length" -gt "$max_type_length" ]] && max_type_length=$length done - - for i in "${!entries[@]}"; do - local id="${ids[$i]}" - local entry="${entries[$i]}" - for j in "${!snapper_ids[@]}"; do - local snapper_id="${snapper_ids[$j]//[[:space:]]/}" - # remove other non numeric characters - snapper_id="${snapper_id//\*/}" - snapper_id="${snapper_id//\+/}" - snapper_id="${snapper_id//-/}" - if [[ "$snapper_id" == "$id" ]]; then - local snapper_type=$(trim "${snapper_types[$j]}") - local snapper_description=$(trim "${snapper_descriptions[$j]}") - - # ignore snapper_type or snapper_description during run "grub-mkconfig" - if [ -n "${GRUB_BTRFS_IGNORE_SNAPPER_TYPE}" ] ; then - for ist in ${GRUB_BTRFS_IGNORE_SNAPPER_TYPE[@]} ; do - [[ "${snapper_type}" == "${ist}" ]] && continue 3; - done - fi - if [ -n "${GRUB_BTRFS_IGNORE_SNAPPER_DESCRIPTION}" ] ; then - for isd in ${GRUB_BTRFS_IGNORE_SNAPPER_DESCRIPTION[@]} ; do - [[ "${snapper_description}" == "${isd}" ]] && continue 3; - done - fi - printf -v entry "%-${max_entry_length}s | %-${max_type_length}s | %s" "$entry" "$snapper_type" "$snapper_description" - break - fi - done + + # Find max length of a snapshot description, needed for pretty formatting + local max_description_length=0 + for i in "${info_descriptions[@]}"; do + local info_description=$(trim "${i}") + local length="${#i}" + [[ "$length" -gt "$max_description_length" ]] && max_description_length=$length + done + + for i in "${!name_snapshots[@]}"; do + local date_snapshot=$(trim "${date_snapshots[$i]}") + local name_snapshot=$(trim "${name_snapshots[$i]}") + local info_type=$(trim "${info_types[$i]}") + local info_description=$(trim "${info_descriptions[$i]}") + + # ignore snapper/timeshift type or snapper/timeshift description during run "grub-mkconfig" + if [ -n "${GRUB_BTRFS_IGNORE_SNAPSHOT_TYPE}" ] ; then + for ist in "${GRUB_BTRFS_IGNORE_SNAPSHOT_TYPE[@]}" ; do + [[ "${info_type}" == "${ist}" ]] && continue 2; + done + fi + if [ -n "${GRUB_BTRFS_IGNORE_SNAPSHOT_DESCRIPTION}" ] ; then + for isd in "${GRUB_BTRFS_IGNORE_SNAPSHOT_DESCRIPTION[@]}" ; do + [[ "${info_description}" == "${isd}" ]] && continue 2; + done + fi + printf -v entry "%-${max_date_length}s | %-${max_name_snapshot_length}s | %-${max_type_length}s | %-${max_description_length}s |" "$date_snapshot" "$name_snapshot" "$info_type" "$info_description" echo "$entry" done IFS=$oldIFS } -## Detect kernels in "/boot" +## Parse snapshots in snapshot_list +parse_snapshot_list() +{ + snap_date=" $(echo "$item" | cut -d'|' -f1)" # column_1, first space is necessary for pretty formatting + snap_date_trim="$(trim "$snap_date")" + + snap_dir_name="$(echo "$item" | cut -d'|' -f2)" # column_2 + snap_dir_name_trim="$(trim "$snap_dir_name")" + snap_snapshot="$snap_dir_name" # Used by "title_format" function + + snap_type="$(echo "$item" | cut -d'|' -f3)" # column_3 + + snap_description="$(echo "$item" | cut -d'|' -f4)" # column_4 +} + +## Detect kernels in "boot_directory" detect_kernel() { list_kernel=() @@ -378,7 +398,7 @@ detect_kernel() list_kernel+=("$okernel") done - # Custom name kernel in GRUB_BTRFS_NKERNEL + # Custom name kernel in "GRUB_BTRFS_NKERNEL" if [ -n "${GRUB_BTRFS_NKERNEL}" ] ; then for ckernel in "${boot_dir}/${GRUB_BTRFS_NKERNEL[@]}" ; do [[ ! -f "${ckernel}" ]] && continue; @@ -387,7 +407,7 @@ detect_kernel() fi } -## Detect initramfs in "/boot" +## Detect initramfs in "boot_directory" detect_initramfs() { list_initramfs=() @@ -401,7 +421,7 @@ detect_initramfs() list_initramfs+=("$oinitramfs") done - # Custom name initramfs in GRUB_BTRFS_NINIT + # Custom name initramfs in "GRUB_BTRFS_NINIT" if [ -n "${GRUB_BTRFS_NINIT}" ] ; then for cinitramfs in "${boot_dir}/${GRUB_BTRFS_NINIT[@]}" ; do [[ ! -f "${cinitramfs}" ]] && continue; @@ -411,7 +431,7 @@ detect_initramfs() if [ -z "${list_initramfs}" ]; then list_initramfs=(x); fi } -## Detect microcode in "/boot" +## Detect microcode in "boot_directory" detect_microcode() { list_ucode=() @@ -427,7 +447,7 @@ detect_microcode() list_ucode+=("$oiucode") done - # Custom name microcode in GRUB_BTRFS_CUSTOM_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 @@ -437,29 +457,39 @@ detect_microcode() if [ -z "${list_ucode}" ]; then list_ucode=(x); fi } -## Show full path snapshot or only name -path_snapshot() -{ - case "${GRUB_BTRFS_DISPLAY_PATH_SNAPSHOT:-"true"}" in - true) name_snapshot=("${snap_full_name}");; - *) name_snapshot=("${snap_full_name#*"/"}") - esac -} - -## Title format in grub-menu +## Title format in Grub-menu +declare -A title_column=( [date]=Date [snapshot]=Snapshot [type]=Type [description]=Description ) # Column title that appears in the header title_format() { - case "${GRUB_BTRFS_TITLE_FORMAT:-"p/d/n"}" in - p/n/d) title_menu="${prefixentry} ${name_snapshot} ${snap_date_time}";; - p/d) title_menu="${prefixentry} ${snap_date_time}";; - p/n) title_menu="${prefixentry} ${name_snapshot}";; - d/n) title_menu="${snap_date_time} ${name_snapshot}";; - n/d) title_menu="${name_snapshot} ${snap_date_time}";; - p) title_menu="${prefixentry}";; - d) title_menu="${snap_date_time}";; - n) title_menu="${name_snapshot}";; - *) title_menu="${prefixentry} ${snap_date_time} ${name_snapshot}" - esac + 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 + 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 + if [[ "${#var}" -lt "${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}" ]]; then # Add extra spaces if length of $var is smaller than the length of column, needed for pretty formatting + printf -v var "%-$(((${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}-${#var})+${#var}))s" "${var}"; + fi + title_menu+="${var}|" + title_submenu+=" $(trim "${var}") |" + done +} +# Adds a header to the grub-btrfs.cfg file +header_menu() +{ + 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],,}]}))); + 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 + sed -i "1imenuentry '|${header_entry}' { echo }" "$grub_directory/grub-btrfs.new" # First "|" is for visuals only } ## List of kernels, initramfs and microcode in snapshots @@ -470,38 +500,24 @@ boot_bounded() for item in $(snapshot_list); do [[ ${limit_snap_show} -le 0 ]] && break; # fix: limit_snap_show=0 IFS=$oldIFS - snap_full_name="$(echo "$item" | cut -d'|' -f2-)" # do not trim it to keep nice formatting - snap_dir_name="$(echo "$item" | cut -d'|' -f2)" - snap_dir_name="$(trim "$snap_dir_name")" - snap_date_time="$(echo "$item" | cut -d' ' -f1-2)" - snap_date_time="$(trim "$snap_date_time")" - - boot_dir="$gbgmp/$snap_dir_name$boot_directory" - # Kernel (Original + custom kernel) + parse_snapshot_list + boot_dir="$grub_btrfs_mount_point/$snap_dir_name_trim$boot_directory" detect_kernel if [ -z "${list_kernel}" ]; then continue; fi name_kernel=("${list_kernel[@]##*"/"}") - # Detect rootflags - detect_rootflags - # Initramfs (Original + custom initramfs) detect_initramfs name_initramfs=("${list_initramfs[@]##*"/"}") - # microcode (auto-detect + custom microcode) detect_microcode name_microcode=("${list_ucode[@]##*"/"}") + 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 /) + make_menu_entries # show snapshot found during run "grub-mkconfig" if [[ "${GRUB_BTRFS_SHOW_SNAPSHOTS_FOUND:-"true"}" = "true" ]]; then printf "Found snapshot: %s\n" "$item" >&2 ; fi - # Show full path snapshot or only name - path_snapshot - # Title format in grub-menu - title_format - # convert /boot directory to root of GRUB (e.g /boot become /) - boot_dir_root_grub="$(make_system_path_relative_to_its_root "${boot_dir}")" - # Make menuentries - make_menu_entries - ### Limit snapshots found during run "grub-mkconfig" + # Limit snapshots found during run "grub-mkconfig" count_limit_snap=$((1+count_limit_snap)) [[ $count_limit_snap -ge $limit_snap_show ]] && break; done @@ -511,17 +527,12 @@ boot_bounded() boot_separate() { boot_dir="${boot_directory}" - # convert /boot directory to root of GRUB (e.g /boot become /) - boot_dir_root_grub="$(make_system_path_relative_to_its_root "${boot_dir}")" - - # Kernel (Original + custom kernel) + boot_dir_root_grub="$(make_system_path_relative_to_its_root "${boot_dir}")" # convert "boot_directory" to root of GRUB (e.g /boot become /) detect_kernel if [ -z "${list_kernel}" ]; then print_error "Kernels not found."; fi name_kernel=("${list_kernel[@]##*"/"}") - # Initramfs (Original + custom initramfs) detect_initramfs name_initramfs=("${list_initramfs[@]##*"/"}") - # microcode (auto-detect + custom microcode) detect_microcode name_microcode=("${list_ucode[@]##*"/"}") @@ -530,23 +541,14 @@ boot_separate() for item in $(snapshot_list); do [[ ${limit_snap_show} -le 0 ]] && break; # fix: limit_snap_show=0 IFS=$oldIFS - snap_full_name="$(echo "$item" | cut -d'|' -f2-)" # do not trim it to keep nice formatting - snap_dir_name="$(echo "$item" | cut -d'|' -f2)" - snap_dir_name="$(trim "$snap_dir_name")" - snap_date_time="$(echo "$item" | cut -d' ' -f1-2)" - snap_date_time="$(trim "$snap_date_time")" - # Detect rootflags + parse_snapshot_list detect_rootflags + title_format + make_menu_entries # show snapshot found during run "grub-mkconfig" if [[ "${GRUB_BTRFS_SHOW_SNAPSHOTS_FOUND:-"true"}" = "true" ]]; then printf "Found snapshot: %s\n" "$item" >&2 ; fi - # Show full path snapshot or only name - path_snapshot - # Title format in grub-menu - title_format - # Make menuentries - make_menu_entries # Limit snapshots found during run "grub-mkconfig" count_limit_snap=$((1+count_limit_snap)) [[ $count_limit_snap -ge $limit_snap_show ]] && break; @@ -554,32 +556,18 @@ boot_separate() IFS=$oldIFS } -printf "Detecting snapshots ...\n" >&2 ; rm -f "$grub_directory/grub-btrfs.new" -> "$grub_directory/grub-btrfs.new" +> "$grub_directory/grub-btrfs.new" # Create a "grub-btrfs.new" file in "grub_directory" # Create mount point then mounting -[[ ! -d $gbgmp ]] && mkdir -p "$gbgmp" -mount -o ro,subvolid=5 /dev/disk/by-uuid/"$root_uuid" "$gbgmp/" -trap "unmount_gbgmp" EXIT # unmounting mount point on EXIT signal -# Count menuentries -count_warning_menuentries=0 -# Count snapshots -count_limit_snap=0 -# detect uuid requirement +[[ ! -d $grub_btrfs_mount_point ]] && mkdir -p "$grub_btrfs_mount_point" +mount -o ro,subvolid=5 /dev/disk/by-uuid/"$root_uuid" "$grub_btrfs_mount_point/" +trap "unmount_grub_btrfs_mount_point" EXIT # unmounting mount point on EXIT signal +count_warning_menuentries=0 # Count menuentries +count_limit_snap=0 # Count snapshots check_uuid_required # Detects if /boot is a separate partition -if [[ "${GRUB_BTRFS_OVERRIDE_BOOT_PARTITION_DETECTION:-"false"}" == "true" ]]; then - printf "Info: Override boot partition detection : enable \n" >&2 ; - boot_separate -else - if [[ "$root_uuid" != "$boot_uuid" ]]; then - printf "Info: Separate boot partition detected \n" >&2 ; - boot_separate - else - printf "Info: Separate boot partition not detected \n" >&2 ; - boot_bounded - fi -fi +[[ "${GRUB_BTRFS_OVERRIDE_BOOT_PARTITION_DETECTION,,}" == "true" ]] && printf "Override boot partition detection : enable \n" >&2 && boot_separate; +if [[ "$root_uuid" != "$boot_uuid" ]] || [[ "$root_uuid_subvolume" != "$boot_uuid_subvolume" ]]; then boot_separate ; else boot_bounded ; fi # Show warn, menuentries exceeds 250 entries [[ $count_warning_menuentries -ge 250 ]] && printf "Generated %s total GRUB entries. You might experience issues loading snapshots menu in GRUB.\n" "${count_warning_menuentries}" >&2 ; # Show total found snapshots @@ -591,6 +579,7 @@ if [[ "${count_limit_snap}" = "0" || -z "${count_limit_snap}" ]]; then print_error "No snapshots found." fi # Make a submenu in GRUB (grub.cfg) and move "grub-btrfs.new" to "grub-btrfs.cfg" +header_menu if ${grub_script_check} "$grub_directory/grub-btrfs.new"; then cat "$grub_directory/grub-btrfs.new" > "$grub_directory/grub-btrfs.cfg" rm -f "$grub_directory/grub-btrfs.new" diff --git a/README.md b/README.md index 303d5eb..1566a6c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Refer to the [documentation](https://github.com/Antynea/grub-btrfs/blob/master/i * Automatically Detect if "/boot" is in separate partition. * Automatically Detect kernel, initramfs and intel/amd microcode in "/boot" directory on snapshots. * Automatically Create corresponding "menuentry" in `grub.cfg` -* Automatically detect snapper and use snapper's snapshot description if available. +* Automatically detect the type/tags and descriptions/comments of snapper/timeshift snapshots. * Automatically generate `grub.cfg` if you use the provided systemd service. - - - diff --git a/config b/config index cb707f1..8fc424b 100644 --- a/config +++ b/config @@ -8,18 +8,10 @@ # Default: "Use distribution information from /etc/os-release." #GRUB_BTRFS_SUBMENUNAME="Arch Linux snapshots" -# Add a name ahead your snapshots entries in the Grub menu. -# Default: "Snapshot:" -#GRUB_BTRFS_PREFIXENTRY="Snapshot:" - -# Show full path snapshot or only name in the Grub menu, weird reaction with snapper. -# Default: "true" -#GRUB_BTRFS_DISPLAY_PATH_SNAPSHOT="false" - # Custom title. -# shows/hides p"prefix" d"date" n"name" in the Grub menu, separator "/", custom order available. -# Default: "p/d/n" -#GRUB_BTRFS_TITLE_FORMAT="p/d/n" +# Shows/Hides "date" "snapshot" "type" "description" in the Grub menu, custom order available. +# Default: ("date" "snapshot" "type" "description") +#GRUB_BTRFS_TITLE_FORMAT=("date" "snapshot" "type" "description") # Limit the number of snapshots populated in the GRUB menu. # Default: "50" @@ -74,19 +66,22 @@ GRUB_BTRFS_IGNORE_SPECIFIC_PATH=("@") # Default: ("var/lib/docker" "@var/lib/docker" "@/var/lib/docker") GRUB_BTRFS_IGNORE_PREFIX_PATH=("var/lib/docker" "@var/lib/docker" "@/var/lib/docker") -# Ignore specific type of snapper's snapshot during run "grub-mkconfig". +# Ignore specific type/tag of snapshot during run "grub-mkconfig". +# For snapper: # Type = single, pre, post. +# For Timeshift: +# Tag = boot, ondemand, hourly, daily, weekly, monthly. # Default: ("") -GRUB_BTRFS_IGNORE_SNAPPER_TYPE=("") +#GRUB_BTRFS_IGNORE_SNAPSHOT_TYPE=("") -# Ignore specific description of snapper's snapshot during run "grub-mkconfig". +# Ignore specific description of snapshot during run "grub-mkconfig". +# e.g: timeline # Default: ("") -GRUB_BTRFS_IGNORE_SNAPPER_DESCRIPTION=("") +#GRUB_BTRFS_IGNORE_SNAPSHOT_DESCRIPTION=("") # By default "grub-btrfs" automatically detects your boot partition, -# either located at the system root or on a separate partition, -# but cannot detect if it is in a subvolume. -# Change to "true" if you have a boot partition in a different subvolume. +# either located at the system root or on a separate partition or in a subvolume, +# Change to "true" if your boot partition isn't detected as separate. # Default: "false" #GRUB_BTRFS_OVERRIDE_BOOT_PARTITION_DETECTION="true" @@ -111,11 +106,6 @@ GRUB_BTRFS_IGNORE_SNAPPER_DESCRIPTION=("") # Default: grub-mkconfig #GRUB_BTRFS_MKCONFIG=/usr/bin/grub2-mkconfig -# Snapper -# Snapper's config name to use -# Default: "root" -#GRUB_BTRFS_SNAPPER_CONFIG="root" - # Password protection management for submenu,snapshots # Refer to the Grub documentation https://www.gnu.org/software/grub/manual/grub/grub.html#Authentication-and-authorisation # and this comment https://github.com/Antynea/grub-btrfs/issues/95#issuecomment-682295660