Skip to main content

从 Btrfs 快照生成 rEFInd 手动引导节

项目描述

重新查找 btrfs

目录

描述

此工具用于自动执行从rEFInd启动到Btrfs快照所需的一些繁琐任务。重新定义grub-btrfsGRUB的意义。

它的作用如下:

  • 收集有关系统中存在的块设备的信息
  • 标识ESP(通过 GPT GUID 或 MBR ID)
  • 收集有关已安装文件系统的信息(来自mtab),这些信息存在于所有找到的块设备上
  • 识别根安装点并收集有关安装在所述安装点的子卷的信息
  • 在已配置目录(或多个目录)中搜索已识别子卷的快照
  • 在 ESP 上搜索 rEFInd 的主配置文件并对其进行解析以从中提取手动引导节(如果存在,也会分析包含的配置)
  • 选择配置的最新快照数量,如果它们是可写的,则使用它们,如果不是,它也可以(取决于配置):
    • 将它们的只读标志设置为 false,从而使它们可写
    • 在配置的位置从它们创建新的可写快照
  • 将每个选定快照的fstab文件中的根挂载点与快照本身对齐
  • 删除过时的先前创建的可写快照(如果存在)
  • 从每个相关字段与每个选定快照对齐的已识别节生成新的手动引导节
  • 最后,它将生成的手动引导节保存在单独的配置文件中(将它们输出到子目录)并将每个文件包含在主配置文件中,以免不必要地混乱

如果检测到单独的 /boot 分区,则仅修改与 / 相关的字段(“subvol”和/或“subvolid”),而“loader”和“initrd”字段(前者也可以嵌套在“options”中字段)不受影响。
不用说,这种设置的后果是无法通过简单地启动到快照来缓解有问题的内核升级。

此工具还将检测 / 作为快照挂载的情况(这意味着您已经启动到其中),发出警告并简单地退出,而例如,Snapper将很高兴地继续创建其快照,无论如何。默认情况下,此行为是可配置和启用的。

先决条件

必须满足以下条件(此时有些条件可能是多余的)才能使此工具正常运行:

  • 挂载的 ESP(不支持自动发现和/或挂载)
  • Btrfs 格式化文件系统,子卷挂载为 /
  • 至少一个根子卷的快照
  • ESP 上存在 rEFInd 安装
  • 至少一个手动引导节(在 rEFInd 的主配置文件或其中包含的任何其他配置文件中找到)定义为(参见ArchWiki示例)其自己的“选项”字段或至少属于的任何此类字段其子菜单之一包含以下引导加载程序选项的定义:
    • “root”选项必须与根分区(按 PARTUUID 或 PARTLABEL)、其文件系统(按 UUID 或 LABEL)或与本身代表根分区的块设备(按名称)匹配
    • “rootflags”选项必须定义与根子卷的逻辑路径匹配的“subvol”子选项和/或与根子卷的 ID 匹配的“subvolid”子选项

安装

该工具目前仅在AUR中可用,这意味着Arch Linux用户(以及我想衍生发行版的用户)可以轻松安装它。

它带有一个脚本(refind-btrfs),可用于按需执行所述步骤(需要root权限才能运行它)。还有一个名为refind-btrfs.service的systemd服务,它在后台操作模式下运行该工具,一旦在相同的监视快照目录中发生更改(快照创建或删除),就会自动执行所描述的步骤就像它在其中搜索快照一样。如果您使用 Snapper 以及它在启动时拍摄常规快照的功能,则此服务也应考虑这些,因为它设置为在 Snapper 的相关服务启动之前启动(名为 snapper-boot.service 的服务)。
在第一次运行脚本或启用和启动服务之前,请确保至少检查并修改配置文件 (/etc/refind-btrfs.conf) 以满足您自己的需要。

如果您希望检查正在运行的服务的当前状态和日志输出,可以执行以下操作:

systemctl status refind-btrfs
journalctl -u refind-btrfs -b

或者,存在一个PyPI包,但请记住,由于libbtrfsutil在 PyPI 上不可用,它需要已经存在于系统站点包中(准确地说是它的 Python 绑定),因为它不能作为依赖项自动拉入. 很可能它可供您选择的发行版使用(搜索名为“btrfs-progs”的包),但您很可能已经安装了它,因为我想您毕竟正在使用 Btrfs。此外,此
目录 中包含的每个文件都应复制到以下位置:

  • refind-btrfs 脚本到 /usr/bin (或者你保存系统范围可执行文件的任何地方)
  • refind-btrfs.conf-sample 作为 refind-btrfs.conf(没有“-sample”后缀)到 /etc
  • refind-btrfs.service 到 /usr/lib/systemd/system (如果您正在使用 systemd 并希望利用快照目录监视功能)

如果需要自定义生成的引导节的图标功能(将在下一节中解释),最初可以通过使用以下命令安装此软件包来启用它:

pip install refind-btrfs[custom_icon]

您还应该在 /var/lib 中创建一个名为“refind-btrfs”的空目录,因为该工具期望它存在。此外,如果您希望能够使用自定义图标生成的 Btrfs 徽标嵌入模式,您还应该将“ icons ”目录复制到之前创建的目录中。

配置

每个选项都在示例配置文件中详细解释。
如果您选择使用提供的 systemd 服务并希望在配置文件运行时更改搜索目录(在这种情况下,这些实际上是监视目录),您必须在这样做后手动重新启动它,因为目录观察者仅启动一次,不执行自动重启。

默认配置旨在启用与 Snapper 的无缝集成,因为我正在使用它,但工具本身不依赖于它,应该可以在不同的设置下运行。此外,默认情况下,该工具配置为创建用于引导的新可写快照,而不是对找到的快照的只读标志进行就地修改,因为我相信这是更安全(甚至可能更明智)的选择。
Timeshift用户可以尝试将默认快照搜索目录设置为“/run/timeshift/backup/timeshift-btrfs/snapshots”,并将对应的最大搜索深度设置为三个。

如果您在自动定位 ESP 时遇到问题,“esp_uuid”选项可能会很有用。如果提供了实际的 UUID(不是默认的,空的),则该值将用于比较分区 UUID(由 lsblk 返回),而不是将其类型与硬编码的 GPT UUID 或 MBR ID 值进行比较。

还实现了自定义生成的引导节图标支持,默认情况下,源引导节的图标被重用。可以提供自己的自定义图标路径或将 Btrfs 徽标(有两种变体和每个变体三种尺寸)嵌入到源引导节的图标中。然后将该组合图标用作生成的引导节的图标。
为了让这两种额外的操作模式(不是默认模式)工作,必须安装一个可选的依赖项——即可以从 Arch Linux 官方存储库PyPI安装的Pillow库。

在验证生成的手动引导节之前,不要盲目地尝试引导到给定的快照(仅仅因为没有报告错误),要么通过检查保存它的文件内容,要么通过查看引导加载程序在验证所选快照的 fstab 文件之前使用 rEFInd选项。

例子

给定这样的设置:

  • 设备 /dev/nvme0n1 其中:
    • ESP 在 /dev/nvme0n1p3 上,安装在 /efi
    • / 在 /dev/nvme0n1p8
    • /boot 包含在 /dev/nvme0n1p8 中(不是单独的分区)
  • 挂载为 / 的子卷名为 @
  • fstab 文件的根挂载点:
UUID=95250e8a-5870-45df-a7b3-3b3ee8873c16 / btrfs rw,noatime,compress-force=zstd:2,ssd,space_cache=v2,commit=15,subvolid=256,subvol=/@ 0 0
  • 在 refind.conf 文件(在本例中为 rEFInd 的主配置文件)中定义的手动引导节:
menuentry "Arch Linux - Stable" {
    icon /EFI/refind/icons/os_arch.png
    volume ARCH
    loader /@/boot/vmlinuz-linux
    initrd /@/boot/initramfs-linux.img
    options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@ initrd=@\boot\intel-ucode.img"
    submenuentry "Boot - fallback" {
        initrd /@/boot/initramfs-linux-fallback.img
    }
    submenuentry "Boot - terminal" {
        add_options "systemd.unit=multi-user.target"
    }
}
  • 位于 /.snapshots 目录中的五个只读快照,该目录本身被挂载为名为 @snapper-root 的子卷(最后一点并不特别相关):

    绝对路径 创作时间 子卷 ID
    /.snapshots/1/snapshot 10-12-2020 01:00:00 498
    /.snapshots/2/snapshot 11-12-2020 02:00:00 499
    /.snapshots/3/snapshot 12-12-2020 03:00:00 500
    /.snapshots/4/snapshot 13-12-2020 04:00:00 501
    /.snapshots/5/snapshot 14-12-2020 05:00:00 502
  • refind-btrfs.conf 文件已更改,使得“selection_count”选项设置为 3 而不是默认的 5

运行时,此工具应选择最新的三个快照(列表中的 3、4 和 5)并在“destination_dir”选项配置的目录中从这些快照中创建新的可写快照,其中每个快照通过格式化创建时间来命名("YYYY-mm-dd_HH-MM-SS") 创建它的快照,为其添加“rwsnap”前缀,并将原始快照的子卷 ID 添加为后缀。在极少数情况下,当不同的快照具有相同的时间戳时,它们的单调数字 ID 可以确保唯一性。

之后,生成的快照的生成名称应如下所示:

  • rwsnap_2020-12-12_03-00-00_ID500,
  • rwsnap_2020-12-13_04-00-00_ID501 和
  • rwsnap_2020-12-14_05-00-00_ID502

这种命名方案对我来说很有意义,因为在选择要从您启动的快照时,您很可能想知道原始快照是何时创建的,而不是从它创建的快照,因为时间延迟取决于此工具的运行时间,以及是否足够大,完全可以误导你。如果您选择使用 systemd 服务,则此延迟应该不会很大(理想情况下,最坏的情况下仅测量几秒钟)。

最新快照的 fstab 文件应该(在修改后)包含一个根挂载点,如下所示:

UUID=95250e8a-5870-45df-a7b3-3b3ee8873c16 / btrfs rw,noatime,compress-force=zstd:2,ssd,space_cache=v2,commit=15,subvolid=503,subvol=/@/root/.refind-btrfs/rwsnap_2020-12-14_05-00-00_ID502 0 0

我在这里假设下一个可用的子卷 ID 是 503(增量为 1),这意味着可写快照是在拍摄原始快照后立即创建的,但不一定是这种情况,其具体值不会'最终没那么重要,只要它直接对应于它绝对应该的新创建的快照(否则,将它安装为 / 将由于不匹配而失败)。

通过此设置,新创建的快照最终嵌套在根子卷下,但您当然可以根据需要进行自己的调整。此工具只会在目标目录不存在的情况下创建它。除此之外它不会做任何事情。
我亲自在默认文件系统子卷 (ID 5) 下直接创建了另一个名为 @rw-snapshots 的子卷,并将其安装在 /root/.refind-btrfs。在我的例子中,rwsnap_2020-12-14_05-00-00_ID502 的逻辑路径是 /@rw-snapshots/rwsnap_2020-12-14_05-00-00_ID502。

生成的手动引导节的文件名格式为“{volume}_{loader}.conf”,并转换为全小写字母,在本示例中,这将生成一个名为“arch_vmlinuz-linux.conf”的文件。然后将该文件保存在名为“btrfs-snapshot-stanzas”的子目录(相对于 rEFInd 的根目录)中,最后通过附加一个“include”指令将其包含在主配置文件中,对于本示例,该指令再次如下所示: “包括 btrfs-snapshot-stanzas/arch_vmlinuz-linux.conf”。最后一步仅在初始运行期间执行一次。之后,它被检测为已包含在主配置文件中。

你可以随意重新排列附加的包含指令,这个工具并不关心它们在主配置文件中的确切位置。如果您定义了多个引导节(例如,每个都指向不同的内核映像)并希望更改引导菜单条目的顺序,这将特别有用。

生成文件的内容(代表生成的节)应如下所示:

menuentry "Arch Linux - Stable (rwsnap_2020-12-14_05-00-00_ID502)" {
    icon /EFI/refind/icons/os_arch.png
    volume ARCH
    loader /@/root/.refind-btrfs/rwsnap_2020-12-14_05-00-00_ID502/boot/vmlinuz-linux
    initrd /@/root/.refind-btrfs/rwsnap_2020-12-14_05-00-00_ID502/boot/initramfs-linux.img
    options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-14_05-00-00_ID502 initrd=@\root\.refind-btrfs\rwsnap_2020-12-14_05-00-00_ID502\boot\intel-ucode.img"
    submenuentry "Arch Linux - Stable (rwsnap_2020-12-13_04-00-00_ID501)" {
        loader /@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501/boot/vmlinuz-linux
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501/boot/initramfs-linux.img
        options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501 initrd=@\root\.refind-btrfs\rwsnap_2020-12-13_04-00-00_ID501\boot\intel-ucode.img"
    }
    submenuentry "Arch Linux - Stable (rwsnap_2020-12-12_03-00-00_ID500)" {
        loader /@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500/boot/vmlinuz-linux
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500/boot/initramfs-linux.img
        options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500 initrd=@\root\.refind-btrfs\rwsnap_2020-12-12_03-00-00_ID500\boot\intel-ucode.img"
    }
}

您可能已经注意到,该工具利用了 rEFInd 的覆盖特性,也就是说,“子菜单”部分用于通过覆盖主要的“loader”、“initrd”和“options”字段将连续快照合并到节本身本身代表最新快照的引导节。

如果您已将此工具配置为还考虑原始引导节的子菜单,则生成的引导节应如下所示:

menuentry "Arch Linux - Stable (rwsnap_2020-12-14_05-00-00_ID502)" {
    icon /EFI/refind/icons/os_arch.png
    volume ARCH
    loader /@/root/.refind-btrfs/rwsnap_2020-12-14_05-00-00_ID502/boot/vmlinuz-linux
    initrd /@/root/.refind-btrfs/rwsnap_2020-12-14_05-00-00_ID502/boot/initramfs-linux.img
    options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-14_05-00-00_ID502 initrd=@\root\.refind-btrfs\rwsnap_2020-12-14_05-00-00_ID502\boot\intel-ucode.img"
    submenuentry "Boot - fallback (rwsnap_2020-12-14_05-00-00_ID502)" {
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-14_05-00-00_ID502/boot/initramfs-linux-fallback.img
    }
    submenuentry "Boot - terminal (rwsnap_2020-12-14_05-00-00_ID502)" {
        add_options "systemd.unit=multi-user.target"
    }
    submenuentry "Arch Linux - Stable (rwsnap_2020-12-13_04-00-00_ID501)" {
        loader /@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501/boot/vmlinuz-linux
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501/boot/initramfs-linux.img
        options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501 initrd=@\root\.refind-btrfs\rwsnap_2020-12-13_04-00-00_ID501\boot\intel-ucode.img"
    }
    submenuentry "Boot - fallback (rwsnap_2020-12-13_04-00-00_ID501)" {
        loader /@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501/boot/vmlinuz-linux
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501/boot/initramfs-linux-fallback.img
        options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501 initrd=@\root\.refind-btrfs\rwsnap_2020-12-13_04-00-00_ID01\boot\intel-ucode.img"
    }
    submenuentry "Boot - terminal (rwsnap_2020-12-13_04-00-00_ID501)" {
        loader /@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501/boot/vmlinuz-linux
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501/boot/initramfs-linux.img
        options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-13_04-00-00_ID501 initrd=@\root\.refind-btrfs\rwsnap_2020-12-13_04-00-00_ID501\boot\intel-ucode.img systemd.unit=multi-user.target"
    }
    submenuentry "Arch Linux - Stable (rwsnap_2020-12-12_03-00-00_ID500)" {
        loader /@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500/boot/vmlinuz-linux
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500/boot/initramfs-linux.img
        options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500 initrd=@\root\.refind-btrfs\rwsnap_2020-12-12_03-00-00_ID500\boot\intel-ucode.img"
    }
    submenuentry "Boot - fallback (rwsnap_2020-12-12_03-00-00_ID500)" {
        loader /@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500/boot/vmlinuz-linux
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500/boot/initramfs-linux-fallback.img
        options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500 initrd=@\root\.refind-btrfs\rwsnap_2020-12-12_03-00-00_ID500\boot\intel-ucode.img"
    }
    submenuentry "Boot - terminal (rwsnap_2020-12-12_03-00-00_ID500)" {
        loader /@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500/boot/vmlinuz-linux
        initrd /@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500/boot/initramfs-linux.img
        options "root=PARTUUID=048d6fcd-c88c-504d-bd51-dfc0a5bf762d rw add_efi_memmap rootflags=subvol=@/root/.refind-btrfs/rwsnap_2020-12-12_03-00-00_ID500 initrd=@\root\.refind-btrfs\rwsnap_2020-12-12_03-00-00_ID500\boot\intel-ucode.img systemd.unit=multi-user.target"
    }
}

一些值得注意的细节是,属于连续快照的任何给定子菜单的“add_options”字段(如果存在)与相应快照的子菜单的“选项”字段合并,还有一个事实是最新快照的子菜单隐含地继承了它们自己在原始引导节中没有覆盖的那些主要节的字段。因此,这些子菜单的定义有意与原始引导节中的对应项相似。

这就是该工具成功完成其工作后,具有三个不同内核(XanMod、Stable 和 LTS)的 Arch Linux 安装应如何出现在 rEFInd(显示默认主题)中:

rEFInd 截图默认

在这里,每个手动引导节都使用基于默认 Arch Linux OS 图标的自定义图标。然后 Btrfs 徽标也嵌入到这些图标中(通过将此选项设置为“embed_btrfs_logo”),并且生成的图标被定义为其相应生成的引导节的一部分。

通过使用较暗的主题(例如Nord主题 - 如下图所示)和使用“倒置”Btrfs 徽标的变体(与上一个屏幕截图中显示的“原始”不同),相同的 Arch Linux 安装应该出现在 rEFInd 中,如下所示:

rEFInd 截图 Nord

执行

最相关的依赖项:

  • 使用lsblk收集块设备和 ESP 信息(支持 JSON 输出)
  • mtab 信息是使用findmnt收集的(同样的注释适用于输出)
  • 所有提到的子卷和快照操作都是使用libbtrfsutil执行的
  • ANLTR4用于生成 rEFInd 配置文件分析所需的词法分析器和解析器
  • 看门狗用于快照目录监视功能并以非递归方式使用(监视所有配置的搜索目录以及嵌套在这些目录下的目录,直到配置的最大深度减一)
  • python-systemd用于通知 systemd 服务准备就绪(因为它的类型设置为“通知”),也用于记录到日志

搁置用于跟踪当前处理的快照,并避免每次分析 rEFInd 配置文件,因为这是一项相当昂贵的任务。如果修改的当前时间和实际时间不同(st_mtime用于此目的),则会执行新的分析,这意味着简单地触摸文件也应该触发新的分析(不计算文件哈希,也不会因此进行比较)。这一事实也解释了在 /var/lib 中需要一个目录,因为数据库文件驻留在其中。

目录监视机制有点不幸,因为它对于手头的任务来说太过分了。尽管 Watchdog 是一个很棒的、久经考验的库并且很多人都在使用它,但我觉得这个解决方案并不是特别适合这个工具,但它现在就足够了,因为我没有更好的主意( grub-btrfs 也依赖于类似的机制),至少在 Btrfs 作者开发出这个有用的特性或类似的东西之前是这样。

进一步的努力

目前,如果创建可写快照成功但从它们生成手动引导节失败(无论出于何种原因),此工具不会自行清理。正确的做法是完全删除这些快照(从而撤消上一步所做的更改或通常称为回滚),这意味着当且仅当所有步骤都被认为是整个运行成功它执行成功。
这种行为将与原子性相媲美大多数数据库系统遵循的原则。通过在下一次尝试运行该工具时发出相关警告(因为此时可写快照已经存在并且它们不应该存在),但也继续执行后续步骤,以不同的方式覆盖了前面提到的场景. 当然,这不是一个通用的解决方案,但更多的是针对这种可能情况的解决方法。
话虽如此,能够以某种方式预览此工具提出的更改也将是有益的,尤其是在更改其配置之后。

与 Snapper 所做的类似,更精细的快照选择机制将受到赞赏,即选择可配置数量的每日、每周等快照以包含在生成的手动引导节中。

生成的引导节的名称使用不理想的硬编码格式字符串进行初始化。为用户提供一种使用预定义变量(源快照的创建时间、其数字 ID 等)以及一些完全任意的部分的组合来定义自己的格式字符串的方法会更方便。

但是,在尝试实现任何这些闪亮的功能之前,应该正确记录该项目的源代码并为其编写测试,因为目前还没有。由于不同的测试用例数量众多,后者也是一项相当大的工作。幸运的是,所有的外部依赖项(操作系统命令、第三方库调用和类似的)都被抽象出来了,这意味着事先不需要关于要测试的代码库的重要准备步骤。

项目详情


下载文件

下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

源分布

refind-btrfs-0.5.1.tar.gz (197.4 kB 查看哈希

已上传 source

内置分布

refind_btrfs-0.5.1-py3-none-any.whl (247.6 kB 查看哈希

已上传 py3