Cannot Manage BTRFS Snapshot Rollback

Hello, Garuda lovers,

Indeed, I have difficulty when I rollback via BTRFS Assistant and Boot Menu. I don't know what is causing the problem. Every time I follow the paths presented in the Garuda Linux guide;

You are currently booted into snapshot @/.snapshots/486/snapshot

Would you like to restore it?

image

It doesn't matter if I say No or Yes because I get this question on every reboot. I wish snapshot rollback was as simple as it looks and is on paper, but something always goes wrong. When I start snapshot from Boot Menu and then do recovery, the grub menu switches to rescue mode due to "grub debug malloc not found".

cat /etc/fstab
<file system>             <mount point>  <type>  <options>  <dump>  <pass>
UUID=766D-CFA8                            /boot/efi      vfat    umask=0077 0 2
UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd /              btrfs   subvol=/@,defaults,noatime,compress=zstd,discard=async,ssd 0 0
UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd /home          btrfs   subvol=/@home,defaults,noatime,compress=zstd,discard=async,ssd 0 0
UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd /root          btrfs   subvol=/@root,defaults,noatime,compress=zstd,discard=async,ssd 0 0
UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd /srv           btrfs   subvol=/@srv,defaults,noatime,compress=zstd,discard=async,ssd 0 0
UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd /var/cache     btrfs   subvol=/@cache,defaults,noatime,compress=zstd,discard=async,ssd 0 0
UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd /var/log       btrfs   subvol=/@log,defaults,noatime,compress=zstd,discard=async,ssd 0 0
UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd /var/tmp       btrfs   subvol=/@tmp,defaults,noatime,compress=zstd,discard=async,ssd 0 0
tmpfs                                     /tmp           tmpfs   defaults,noatime,mode=1777 0 0

Normally when I use the .snaphots subvolume, a new subvolume is created in BTRFS Assistant every time.

For example, I just wanted to go back to snapshot 486 and it ended up creating a second subvolume and instead of the previous .snaphots it now creates a path like .snapshots/486/....

How can I make a healthy rollback from snapshot 486 in the @ path and how can I get rid of this restore question that comes up every boot.

Although new snapshots are now taken this way, the same question comes up on the reboot screen every time.

I think the best way for snapper and rollback is to get rollback using command line for example;

sudo snapper --ambit classic rollback 102

*102 example snaphot.

Please someone teach me this simple but complex operation with complicated results. I don't know if Timeshift is simpler or I am doing something wrong.

In a nutshell, how can I go back to taking snapshots in the @.snaphots path and most importantly rollback snapshot 486 and ensure a smooth startup.

Thank you.

It looks like you made snapshot 486 writable and then booted into it. This is a really bad idea. There is a reason Garuda uses read-only snapshots.

You likely ran grub-mkconfig either directly or as part of some other operation. When you boot off a snapshot and do that, the grub.cfg file will then be updated to have your snapshot written into it.

From then on, no matter what you do, you will always be booted into that snapshot.

Since you are booted into the snapshot, you are not getting snapshots inside your snapshot which is why the series has started over.

2 Likes

the solution is?

I just used snapshot menu when boot, so should choose another "read only" snaphot?

btw please add more details into the docs, some tiny video tutorials would be great.

That depends how long ago you made this mistake and if you need the data you have created since then.

Basically you have been using snapshot 486 as your running system since that point. If you switch to another snapshot, you will lose everything done since then.

2 Likes

so I'm done?

fresh installing without touching my user files looking only solution, right?

No, I am not sure how our conversation up to this point led you to that conclusion.

You first need to answer the questions

  • How long ago did you make this mistake?
  • Do you have a good snapshot from before that point?
  • Do you need the data that was changed since then?
1 Like

1- 06/12/2022 that writable snapshot was created
2- Unfortunately before this one no any other snaphot(s)
3- I could not get what you mean, system files or user files?

If it will affect to my users files yeah I need them but only system files/setting then no matter.

Can you post the contents of /etc/grub.d/41_snapshots-btrfs

I will be away for a bit but I will review it when I get back.

#! /usr/bin/env bash
#
# Written by: Antynea
# BTC donation address: 1Lbvz244WA8xbpHek9W2Y12cakM6rDe5Rt
# Github: https://github.com/Antynea/grub-btrfs
#
# Purpose:
#   Improves Grub by adding "btrfs snapshots" to the Grub menu.
#   You can boot your system on a "snapshot" from the Grub menu.
#   Supports manual snapshots, snapper, timeshift ...
#   Warning : booting on read-only snapshots can be tricky.
#   (Read about it, https://github.com/Antynea/grub-btrfs#warning-booting-on-read-only-snapshots-can-be-tricky)
#
# What this script does:
# - Automatically List snapshots existing on root partition (btrfs).
# - 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 the type/tags and descriptions/comments of snapper/timeshift snapshots.
# - Automatically generate grub.cfg if you use the provided systemd service.
#
# Installation:
# - Refer to https://github.com/Antynea/grub-btrfs#installation-
#
# Customization:
#  You have the possibility to modify many parameters in /etc/default/grub-btrfs/config.
#
# Automatically update Grub
#  If you would like grub-btrfs menu to automatically update when a snapshot is created or deleted:
#  - Refer to https://github.com/Antynea/grub-btrfs#automatically-update-grub.
#
# Special thanks for assistance and contributions:
# - My friends
# - All contributors on Github
#

set -e

sysconfdir="/etc"
grub_btrfs_config="${sysconfdir}/default/grub-btrfs/config"

[[ -f "$grub_btrfs_config" ]] && . "$grub_btrfs_config"
[[ -f "${sysconfdir}/default/grub" ]] && . "${sysconfdir}/default/grub"

## Exit the script, if:
[[ "${GRUB_BTRFS_DISABLE,,}" == "true" ]] && exit 0 # Disable Grub-btrfs is set to true (default=false)
if ! type btrfs >/dev/null 2>&1; then exit 0; fi # btrfs-progs isn't installed
[[ -f "${GRUB_BTRFS_MKCONFIG_LIB:-/usr/share/grub/grub-mkconfig_lib}" ]] && . "${GRUB_BTRFS_MKCONFIG_LIB:-/usr/share/grub/grub-mkconfig_lib}" || exit 0 # grub-mkconfig_lib couldn't be found
# Root filesystem isn't btrfs
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"}
## Limit snapshots to show in the Grub menu (default=50)
limit_snap_show="${GRUB_BTRFS_LIMIT:-50}"
## How to sort snapshots list
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.
boot_directory=${GRUB_BTRFS_BOOT_DIRNAME:-"/boot"}
## Password protection management for submenu
# Protection support for submenu (--unrestricted)
case "${GRUB_BTRFS_DISABLE_PROTECTION_SUBMENU,,}" in
    true)   unrestricted_access_submenu="--unrestricted ";;
    *)      unrestricted_access_submenu=""
esac
# Authorized users (--users foo,bar)
if [ -n "${GRUB_BTRFS_PROTECTION_AUTHORIZED_USERS}" ] ; then
    protection_authorized_users="--users ${GRUB_BTRFS_PROTECTION_AUTHORIZED_USERS} "
fi

## Probe informations of Root and Boot devices
# 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
grub_btrfs_mount_point=$(mktemp -dt grub-btrfs.XXXXXXXXXX)
## Class for theme
CLASS="--class snapshots --class gnu-linux --class gnu --class os"
## save IFS
oldIFS=$IFS
## Detect uuid requirement (lvm,btrfs...)
check_uuid_required() {
if [ "x${root_uuid}" = "x" ] || [ "x${GRUB_DISABLE_LINUX_UUID}" = "xtrue" ] \
    || ! test -e "/dev/disk/by-uuid/${root_uuid}" \
    || ( test -e "${root_device}" && uses_abstraction "${root_device}" lvm ); then
    LINUX_ROOT_DEVICE=${root_device}
else
    LINUX_ROOT_DEVICE=UUID=${root_uuid}
fi
}
## Detect rootflags
detect_rootflags()
{
    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,}"
}

unmount_grub_btrfs_mount_point()
{
if [[ -d "$grub_btrfs_mount_point" ]]; then
    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
            wait_max=$((1+wait_max))
            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" "$grub_btrfs_mount_point" >&2;
                break;
            else
                printf "." >&2 ; # output to show that the script is alive
                sleep 2 # wait 2 seconds before retry
            fi
        else
            wait=false # not mounted
            printf " Success\n" >&2;
        fi
    done
    if [[ "$wait" != true ]]; then
        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
}

## Create entry
entry()
{
echo "$@" >> "$grub_directory/grub-btrfs.new"
}

## menu entries
make_menu_entries()
{
## \" required for snap,kernels,init,microcode with space in their name
    entry "submenu '${title_menu}' {
    submenu '${title_submenu}' { echo }"
    for k in "${name_kernel[@]}"; do
        [[ ! -f "${boot_dir}"/"${k}" ]] && continue;
        kversion=${k#*"-"}
        for i in "${name_initramfs[@]}"; do
            if [[ "${name_initramfs}" != "x" ]] ; then
                # prefix_i=${i%%"-"*}
                suffix_i=${i#*"-"}
                # alt_suffix_i=${i##*"-"}
                if   [ "${kversion}" = "${suffix_i}" ];                 then i="${i}";
                elif [ "${kversion}.img" = "${suffix_i}" ];             then i="${i}";
                elif [ "${kversion}-fallback.img" = "${suffix_i}" ];    then i="${i}";
                elif [ "${kversion}.gz" = "${suffix_i}" ];              then i="${i}";
                else continue;
                fi
                for u in "${name_microcode[@]}"; do
                    if [[ "${name_microcode}" != "x" ]] ; then
                    entry "
    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' {"
                    fi
                    entry "\
        if [ x\$feature_all_video_module = xy ]; then
        insmod all_video
        fi
        set gfxpayload=keep
        insmod ${boot_fs}
        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
        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}"\""
                    if [[ "${name_microcode}" != "x" ]] ; then
                        entry "\
        echo 'Loading Microcode & Initramfs: "${u}" "${i}" ...'
        initrd \"${boot_dir_root_grub}/"${u}"\" \"${boot_dir_root_grub}/"${i}"\""
                    else
                        entry "\
        echo 'Loading Initramfs: "${i}" ...'
        initrd \"${boot_dir_root_grub}/"${i}"\""
                    fi
                    entry "    }"
                    count_warning_menuentries=$((1+count_warning_menuentries))
                done
            else
                for u in "${name_microcode[@]}"; do
                    if [[ "${name_microcode}" != "x" ]] ; then
                    entry "
    menuentry '  "${k}" & "${u}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {"
                    else
                    entry "
    menuentry '  "${k}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {"
                    fi
                    entry "\
        if [ x\$feature_all_video_module = xy ]; then
        insmod all_video
        fi
        set gfxpayload=keep
        insmod ${boot_fs}
        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
        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}"\""
                    if [[ "${name_microcode}" != "x" ]] ; then
                        entry "\
        echo 'Loading Microcode: "${u}" ...'
        initrd \"${boot_dir_root_grub}/"${u}"\""
                    fi
                    entry "    }"
                    count_warning_menuentries=$((1+count_warning_menuentries))
                done
            fi
        done
    done
    entry  "}"
}

## Trim a string from leading and trailing whitespaces
trim() {
    local var="$*"
    var="${var#"${var%%[![:space:]]*}"}"
    var="${var%"${var##*[![:space:]]}"}"
    echo -n "$var"
}

## List of snapshots on filesystem
snapshot_list()
{
    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
        snap=($snap)
        local path_snapshot=${snap[@]:13:${#snap[@]}}
        if [ "$path_snapshot" = "DELETED" ]; then continue; fi # Discard deleted snapshots
        [[ ${path_snapshot%%"/"*} == "<FS_TREE>" ]] && path_snapshot=${path_snapshot#*"/"} # Remove the "<FS_TREE>" 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
                [[ "${path_snapshot}" == "${isp}" ]] && continue 2;
            done
        fi
        if [ -n "${GRUB_BTRFS_IGNORE_PREFIX_PATH}" ] ; then
            for isp in "${GRUB_BTRFS_IGNORE_PREFIX_PATH[@]}" ; do
                [[ "${path_snapshot}" == "${isp}"/* ]] && continue 2;
            done
        fi
        [[ ! -d "$grub_btrfs_mount_point/$path_snapshot/boot" ]] && continue; # Discard snapshots without /boot folder

        # Parse Snapper & timeshift informations
        local type_snapshot="N/A"
        local description_snapshot="N/A"
        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"
            description_snapshot=$(awk -F"<|>" 'match($2, /^description/) {print $3}' "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$snapper_info") # search matching string beginning "description"
        elif [[ -s "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$timeshift_info" ]] ; then
            type_snapshot=$(awk -F" : " 'match($1, /^[ \t]+"tags"/) {gsub(/"|,/,"");print $2}' "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$timeshift_info") # search matching string beginning "tags"
            description_snapshot=$(awk -F" : " 'match($1, /^[ \t]+"comments"/) {gsub(/"|,/,"");print $2}' "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$timeshift_info") # search matching string beginning "comments"
        fi
        [[ -z "$type_snapshot" ]] && type_snapshot=("N/A")
        [[ -z "$description_snapshot" ]] && description_snapshot=("N/A")

        # ignore specific {type,tag,description} of snapshot during run "grub-mkconfig"
        if [ -n "${GRUB_BTRFS_IGNORE_SNAPSHOT_TYPE}" ] ; then
            for ist in "${GRUB_BTRFS_IGNORE_SNAPSHOT_TYPE[@]}" ; do
                [[ "${type_snapshot}" == "${ist}" ]] && continue 2;
            done
        fi
        if [ -n "${GRUB_BTRFS_IGNORE_SNAPSHOT_DESCRIPTION}" ] ; then
            for isd in "${GRUB_BTRFS_IGNORE_SNAPSHOT_DESCRIPTION[@]}" ; do
                [[ "${description_snapshot}" == "${isd}" ]] && continue 2;
            done
        fi

        local date_snapshot="${snap[@]:10:2}"
        date_snapshots+=("$date_snapshot")
        path_snapshots+=("$path_snapshot")
        type_snapshots+=("$type_snapshot")
        description_snapshots+=("$description_snapshot")
    done

    # Find max length of a snapshot date, needed for pretty formatting
    local max_date_length=0
    for i in "${date_snapshots[@]}"; do
        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_path_length=0
    for i in "${path_snapshots[@]}"; do
        local length="${#i}"
        [[ "$length" -gt "$max_path_length" ]] && max_path_length=$length
    done

    # Find max length of a snapshot type, needed for pretty formatting
    local max_type_length=0
    for i in "${type_snapshots[@]}"; do
        local length="${#i}"
        [[ "$length" -gt "$max_type_length" ]] && max_type_length=$length
    done

    # Find max length of a snapshot description, needed for pretty formatting
    local max_description_length=0
    for i in "${description_snapshots[@]}"; do
        local length="${#i}"
        [[ "$length" -gt "$max_description_length" ]] && max_description_length=$length
    done

    for i in "${!path_snapshots[@]}"; do
        printf -v entry "%-${max_date_length}s | %-${max_path_length}s | %-${max_type_length}s | %-${max_description_length}s |" "${date_snapshots[$i]}" "${path_snapshots[$i]}" "${type_snapshots[$i]}" "${description_snapshots[$i]}"
        echo "$entry"
    done

    IFS=$oldIFS
}

## 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=()
    # Original kernel (auto-detect)
    for okernel in  "${boot_dir}"/vmlinuz-* \
                    "${boot_dir}"/vmlinux-* \
                    "${boot_dir}"/kernel-* ; do
        [[ ! -f "${okernel}" ]] && continue;
        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
            [[ ! -f "${ckernel}" ]] && continue;
            list_kernel+=("$ckernel")
        done
    fi
}

## Detect initramfs in "boot_directory"
detect_initramfs()
{
    list_initramfs=()
    # Original initramfs (auto-detect)
    for oinitramfs in   "${boot_dir}"/initrd.img-* \
                        "${boot_dir}"/initramfs-* \
                        "${boot_dir}"/initrd-* ; do
        [[ ! -f "${oinitramfs}" ]] && continue;
        list_initramfs+=("$oinitramfs")
    done

    # 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;
            list_initramfs+=("$cinitramfs")
        done
    fi
    if [ -z "${list_initramfs}" ]; then list_initramfs=(x); fi
}

## Detect microcode in "boot_directory"
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
        [[ ! -f "${oiucode}" ]] && continue;
        list_ucode+=("$oiucode")
    done

    # 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
            list_ucode+=("$cucode")
        done
    fi
    if [ -z "${list_ucode}" ]; then list_ucode=(x); fi
}

## 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()
{
    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
boot_bounded()
{
    # Initialize menu entries
    IFS=$'\n'
    for item in $(snapshot_list); do
        [[ ${limit_snap_show} -le 0 ]] && break; # fix: limit_snap_show=0
        IFS=$oldIFS
        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_initramfs
        name_initramfs=("${list_initramfs[@]##*"/"}")
        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
        # Limit snapshots found during run "grub-mkconfig"
        count_limit_snap=$((1+count_limit_snap))
        [[ $count_limit_snap -ge $limit_snap_show ]] && break;
    done
    IFS=$oldIFS
}

boot_separate()
{
    boot_dir="${boot_directory}"
    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[@]##*"/"}")
    detect_initramfs
    name_initramfs=("${list_initramfs[@]##*"/"}")
    detect_microcode
    name_microcode=("${list_ucode[@]##*"/"}")

    # Initialize menu entries
    IFS=$'\n'
    for item in $(snapshot_list); do
        [[ ${limit_snap_show} -le 0 ]] && break; # fix: limit_snap_show=0
        IFS=$oldIFS
        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
        # Limit snapshots found during run "grub-mkconfig"
        count_limit_snap=$((1+count_limit_snap))
        [[ $count_limit_snap -ge $limit_snap_show ]] && break;
    done
    IFS=$oldIFS
}

rm -f "$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 $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
[[ "${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
if [[ "${GRUB_BTRFS_SHOW_TOTAL_SNAPSHOTS_FOUND:-"true"}" = "true" && -n "${count_limit_snap}" && "${count_limit_snap}" != "0" ]]; then
    printf "Found %s snapshot(s)\n" "${count_limit_snap}" >&2 ;
fi
# if no snapshot found, exit
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 "${bindir}/${GRUB_BTRFS_SCRIPT_CHECK:-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"
    cat << EOF
submenu '${submenuname}' ${protection_authorized_users}${unrestricted_access_submenu}{
    configfile "\${prefix}/grub-btrfs.cfg"
}
EOF
else
    print_error "Syntax errors are detected in generated grub-btrfs.cfg file."
fi

Oops. I asked for the wrong file. We need to see the file with the actual generated submenu entries in it. I don't have a machine running grub-btrfs available right now but if you look inside grub.cfg you should find the name of that file.

#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by grub-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub
#

### BEGIN /etc/grub.d/00_header ###
insmod part_gpt
insmod part_msdos
if [ -s $prefix/grubenv ]; then
  load_env
fi
if [ "${next_entry}" ] ; then
   set default="${next_entry}"
   set next_entry=
   save_env next_entry
   set boot_once=true
else
   set default="1>0"
fi

if [ x"${feature_menuentry_id}" = xy ]; then
  menuentry_id_option="--id"
else
  menuentry_id_option=""
fi

export menuentry_id_option

if [ "${prev_saved_entry}" ]; then
  set saved_entry="${prev_saved_entry}"
  save_env saved_entry
  set prev_saved_entry=
  save_env prev_saved_entry
  set boot_once=true
fi

function savedefault {
  if [ -z "${boot_once}" ]; then
    saved_entry="${chosen}"
    save_env saved_entry
  fi
}

function load_video {
  if [ x$feature_all_video_module = xy ]; then
    insmod all_video
  else
    insmod efi_gop
    insmod efi_uga
    insmod ieee1275_fb
    insmod vbe
    insmod vga
    insmod video_bochs
    insmod video_cirrus
  fi
}

if [ x$feature_default_font_path = xy ] ; then
   font=unicode
else
insmod part_gpt
insmod btrfs
search --no-floppy --fs-uuid --set=root b9a63755-06cc-46d3-a3a0-84752c0237dd
    font="/@/.snapshots/486/snapshot/usr/share/grub/unicode.pf2"
fi

if loadfont $font ; then
  set gfxmode=auto
  load_video
  insmod gfxterm
  set locale_dir=$prefix/locale
  set lang=en_US
  insmod gettext
fi
terminal_input console
terminal_output gfxterm
insmod part_gpt
insmod btrfs
search --no-floppy --fs-uuid --set=root b9a63755-06cc-46d3-a3a0-84752c0237dd
insmod gfxmenu
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/dejavu_sans_12.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/dejavu_sans_14.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/dejavu_sans_16.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/dejavu_sans_24.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/dejavu_sans_48.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/dejavu_sans_bold_16.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/dejavu_sans_mono_12.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/terminus-12.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/terminus-14.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/terminus-16.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/terminus-18.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/terminus-b12.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/terminus-b14.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/terminus-b16.pf2
loadfont ($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/terminus-b18.pf2
insmod png
set theme=($root)/@/.snapshots/486/snapshot/usr/share/grub/themes/garuda-dr460nized/theme.txt
export theme
if [ x$feature_timeout_style = xy ] ; then
  set timeout_style=menu
  set timeout=5
# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
  set timeout=5
fi
### END /etc/grub.d/00_header ###

### BEGIN /etc/grub.d/10_linux ###
menuentry 'Garuda Linux' --class garuda --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-b9a63755-06cc-46d3-a3a0-84752c0237dd' {
	load_video
	set gfxpayload=keep
	insmod gzio
	insmod part_gpt
	insmod btrfs
	search --no-floppy --fs-uuid --set=root b9a63755-06cc-46d3-a3a0-84752c0237dd
	echo	'Loading Linux linux-lts ...'
	linux	/@/.snapshots/486/snapshot/boot/vmlinuz-linux-lts root=UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd rw rootflags=subvol=@/.snapshots/486/snapshot  quiet quiet splash rd.udev.log_priority=3 vt.global_cursor_default=0 loglevel=3
	echo	'Loading initial ramdisk ...'
	initrd	/@/.snapshots/486/snapshot/boot/amd-ucode.img /@/.snapshots/486/snapshot/boot/initramfs-linux-lts.img
}
submenu 'Advanced options for Garuda Linux' $menuentry_id_option 'gnulinux-advanced-b9a63755-06cc-46d3-a3a0-84752c0237dd' {
	menuentry 'Garuda Linux, with Linux linux-lts' --class garuda --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-linux-lts-advanced-b9a63755-06cc-46d3-a3a0-84752c0237dd' {
		load_video
		set gfxpayload=keep
		insmod gzio
		insmod part_gpt
		insmod btrfs
		search --no-floppy --fs-uuid --set=root b9a63755-06cc-46d3-a3a0-84752c0237dd
		echo	'Loading Linux linux-lts ...'
		linux	/@/.snapshots/486/snapshot/boot/vmlinuz-linux-lts root=UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd rw rootflags=subvol=@/.snapshots/486/snapshot  quiet quiet splash rd.udev.log_priority=3 vt.global_cursor_default=0 loglevel=3
		echo	'Loading initial ramdisk ...'
		initrd	/@/.snapshots/486/snapshot/boot/amd-ucode.img /@/.snapshots/486/snapshot/boot/initramfs-linux-lts.img
	}
	menuentry 'Garuda Linux, with Linux linux-lts (fallback initramfs)' --class garuda --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-linux-lts-fallback-b9a63755-06cc-46d3-a3a0-84752c0237dd' {
		load_video
		set gfxpayload=keep
		insmod gzio
		insmod part_gpt
		insmod btrfs
		search --no-floppy --fs-uuid --set=root b9a63755-06cc-46d3-a3a0-84752c0237dd
		echo	'Loading Linux linux-lts ...'
		linux	/@/.snapshots/486/snapshot/boot/vmlinuz-linux-lts root=UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd rw rootflags=subvol=@/.snapshots/486/snapshot  quiet quiet splash rd.udev.log_priority=3 vt.global_cursor_default=0 loglevel=3
		echo	'Loading initial ramdisk ...'
		initrd	/@/.snapshots/486/snapshot/boot/amd-ucode.img /@/.snapshots/486/snapshot/boot/initramfs-linux-lts-fallback.img
	}
	menuentry 'Garuda Linux, with Linux linux-lts (recovery mode)' --class garuda --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-linux-lts-recovery-b9a63755-06cc-46d3-a3a0-84752c0237dd' {
		load_video
		set gfxpayload=keep
		insmod gzio
		insmod part_gpt
		insmod btrfs
		search --no-floppy --fs-uuid --set=root b9a63755-06cc-46d3-a3a0-84752c0237dd
		echo	'Loading Linux linux-lts ...'
		linux	/@/.snapshots/486/snapshot/boot/vmlinuz-linux-lts root=UUID=b9a63755-06cc-46d3-a3a0-84752c0237dd rw single rootflags=subvol=@/.snapshots/486/snapshot 
		echo	'Loading initial ramdisk ...'
		initrd	/@/.snapshots/486/snapshot/boot/amd-ucode.img /@/.snapshots/486/snapshot/boot/initramfs-linux-lts-fallback.img
	}
}

### END /etc/grub.d/10_linux ###

### BEGIN /etc/grub.d/20_linux_xen ###
### END /etc/grub.d/20_linux_xen ###

### BEGIN /etc/grub.d/30_os-prober ###
menuentry 'Windows Boot Manager (on /dev/nvme0n1p1)' --class windows --class os $menuentry_id_option 'osprober-efi-766D-CFA8' {
	insmod part_gpt
	insmod fat
	search --no-floppy --fs-uuid --set=root 766D-CFA8
	chainloader /EFI/Microsoft/Boot/bootmgfw.efi
}
### END /etc/grub.d/30_os-prober ###

### BEGIN /etc/grub.d/30_uefi-firmware ###
menuentry 'UEFI Firmware Settings' $menuentry_id_option 'uefi-firmware' {
	fwsetup
}
### END /etc/grub.d/30_uefi-firmware ###

### BEGIN /etc/grub.d/35_fwupd ###
### END /etc/grub.d/35_fwupd ###

### BEGIN /etc/grub.d/40_custom ###
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
### END /etc/grub.d/40_custom ###

### BEGIN /etc/grub.d/41_custom ###
if [ -f  ${config_directory}/custom.cfg ]; then
  source ${config_directory}/custom.cfg
elif [ -z "${config_directory}" -a -f  $prefix/custom.cfg ]; then
  source $prefix/custom.cfg
fi
### END /etc/grub.d/41_custom ###

### BEGIN /etc/grub.d/41_snapshots-btrfs ###
submenu 'Garuda Linux snapshots' {
    configfile "${prefix}/grub-btrfs.cfg"
}
### END /etc/grub.d/41_snapshots-btrfs ###

### BEGIN /etc/grub.d/60_memtest86+ ###
if [ "${grub_platform}" == "pc" ]; then
    menuentry "Memory Tester (memtest86+)" --class memtest86 --class gnu --class tool {
        search --fs-uuid --no-floppy --set=root  b9a63755-06cc-46d3-a3a0-84752c0237dd
        linux16 /@/.snapshots/486/snapshot/boot/memtest86+/memtest.bin 
    }
fi
### END /etc/grub.d/60_memtest86+ ###

### BEGIN /etc/grub.d/61_custom_leave_options ###
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.

menuentry "Shutdown" --class shutdown {
	echo "System shutting down..."
	halt
}

menuentry "Restart" --class restart {
	echo "System rebooting..."
	reboot
}

#if [ ${grub_platform} == "efi" ]; then
#	menuentry "Firmware Setup (UEFI)" --class recovery {
#		fwsetup
#	}
#fi
### END /etc/grub.d/61_custom_leave_options ###

is it grub-btrfs.cfg?

Yes grub-btrfs.cfg

cannot paste here due to character limits

An error occurred: Body is limited to 60000 characters; you entered 257470.

can I use privatebin link here, any security issues?

Yes. Use some type of pastebin service.

there it is

That file looks fine. Try booting into a snapper snapshot with a high number and then restoring that snapshot. Note which snapshot you pick in case it doesn't work and we need to investigate it.

Don't do anything except boot into the snapshot, restore and the reboot. Don't make it writable or change anything else.

1 Like

it does not help why because every boot with snapshot brings 486 numbered snapshot


and new subvolumes are being created after every restore.

still could not get the idea of that BTRFS Assistant on rollback.

Which snapshot did you restore?

Tried all and same result.

Unfortunately left Garuda Linux maybe won't use bleeding distros a while. I have switched to OpenSuse Leap 15.4 it works perfect too.

But missing Garuda :face_holding_back_tears:

And I advise you to add my case into docs, regarding to BTRFS snapshots thread that why must not do boot into writable snapshots, with red BIG LETTERS

And simple mode I've learned via OpenSuse is what;
1- Start latest working snapshot from Grub
2- Check session whether everthing works
3- If it's okay then
sudo snapper rollback
4- Reboot

I think that my mistake occured at those levels due to BTRFS Assistant's mislead.
Thanks for your time and help we may close this topic.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.