Btrfs:认识、从 Ext4 迁移与快照方案

对于 Arch 系等依赖滚动更新的发行版,Btrfs 的快照功能真的是太具有吸引力了。纵使我已经很久没有遇到 “滚炸”、纵使就算 “滚炸” 去 Manjaro 论坛看一眼一般都能解决,但是这些都不如一个 “后悔药” 来得实在 —— 遇到问题,重启、选择老快照、恢复,一切都是那么美好。因此,前阵子(指 12 月中旬)我就把系统分区迁移到 Btrfs 上了。这篇博客就主要记录了迁移与快照的各种实现方案。

Btrfs:现代 Linux 文件系统

概述

本着 Arch 精神,我还是想先简单介绍一下 Btrfs。Btrfs(我一般念 B tree FS)是最早由 Oracle 贡献的 Linux 文件系统,如今已经进入 Linux 内核许久,是最有希望(我认为)成为未来 Linux 主流文件系统的候选者。相较之下,类似的 ZFS 有 License 问题;潜力十足的 ReiserFS 因为作者谋杀入狱后开发进度就不太乐观,且 v4 也没被合入内核;XFS 走的是类似 Ext4 的稳定路线,对新功能的支持较为保守(但是足够稳定,可以用于数据盘)。这个开源软件的命运啊,还真是不可预料。

Btrfs 功能繁多,可以称得上是新文件系统功能的试验田。到目前为止,Btrfs 已经有了包括但不限于如下的功能:

  • 写时复制(CoW)、事务
  • 在线卷管理:碎片整理、大小调整、跨设备卷中物理设备的增删
  • 子卷:可以单独挂载的文件树,类似 LVM 的逻辑卷
  • 软件 RAID
  • 透明压缩:写磁盘时自动压缩
  • 快照
  • 支持数据校验和

组合这些功能,可以实现很多非常不错的功能。比如开启透明压缩、CoW 后跨网络同步磁盘,可以最大限度的减少需要传输的数据量。此外,Btrfs 也提供了一个非常不错的 CLI 接口来进行管理。不过 Btrfs 的缺点也很明显:数据恢复难度显著大于 Ext4 等传统 FS、稳定性一般、读写速度也相对较差(主要是 HDD,SSD 基本没有问题)。这些问题就算到了 Btrfs 上述功能已经完成了将近 15 年的今天,也仍旧需要改善。好在目前 Facebook、群晖等企业都在使用 Btrfs,未来还是明朗的。

不过,这些小问题并不能阻挡我作死尝鲜的步伐。因为把系统分区迁移到 Btrfs 是有若干好处的:

  1. 快照是滚动更新的后悔药
  2. 大量系统文件并不性能敏感,开启透明压缩能节省不少空间
  3. CoW 对 SSD 硬盘十分友好,而 Btrfs 的性能劣势在 SSD 的补足下并不明显(相反 HDD 可能有 40% 的性能损失)
  4. 系统分区也没什么重要数据,炸了也无所谓

子卷和快照

Btrfs 一个非常重要的概念就是子卷。子卷可以理解成为 “盘中盘”,可以单独挂载,行为类似一个文件夹(可以移动,但不能 unlink)。子卷是可以嵌套的,不过此时子卷相当于同时挂载在一颗文件树,因此并不会循环嵌套。子卷可以根据其相对于根子卷(Btrfs 分区自带的子卷,路径是 /,ID 是 5)的路径确定(比如 /subA),不过因为子卷的路径可以被移动,因此具体子卷是通过 ID 定位的。

快照就是基于子卷的,创建快照等同于创建一个和原子卷共享文件的子卷。因此其实快照和原子卷的地位是平等的,创建快照后删除原子卷也没有什么问题。将快照功能与根据路径查找子卷结合,就可以实现对某个路径的快照了:把子卷 /sub 挂载到 /dest,快照时就创建一个快照子卷 /sub_202201,如果要恢复快照就用某个快照子卷替换掉子卷 /sub

从 Ext4 迁移

子卷规划

了解了 Btrfs 之后,就可以考虑如何规划子卷了。区分不同子卷的主要目的是为 Btrfs 提供的大量功能划定作用的粒度。比如对于系统文件(比如配置等等),我们可能不太在意它的存取效率,因此可以划分一个单独的子卷并在挂载的时候开启压缩;对于日志之类的文件,则没有快照的意义,因此另立子卷不进行快照。

参考 Ubuntu 风格的命名方式,我推荐如下的子卷格式:

  • @:根子卷,对应 /。存放需要快照的系统文件
  • @home:家子卷,对应 /home
  • @cache:缓存子卷,对应 /var/cache。包含 Pacman 包缓存等没必要快照的文件
  • @log:日志子卷,对应 /var/log。不快照日志,方便查错

这个规划方式主要是针对 Manjaro 等 Arch 系发行版。如果有其他需要,也可以添加如 @opt@srv 等子卷,反正不要钱也不麻烦(openSUSE 默认有 9 个子卷)。

迁移

规划完子卷后,迁移时按图索骥即可。不过由于我只是想用到 Btrfs 的快照功能,加之比较担心自己的数据,因此并没有迁移家目录。如果有此需求,请自行扩充迁移过程。此外,我也不建议通过迁移工具来直接把 ext4 分区转为 btrfs 分区,因为至少在本文写作时还有见到迁移后使用不稳定的报告。

所以,我最终采用的是 Timeshift 的迁移方式,也可以使用等价的 rsync 指令。首先备份好根分区。之后,建立 btrfs 分区并建立根子卷 @(假设目标分区是 /dev/nvme0n1p1)。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo mkfs.btrfs /dev/nvme0n1p1 # 这一步也可以在分区工具里完成
sudo mount /dev/nvme0n1p1 /mnt/btrfs
sudo btrfs subvolume create /mnt/btrfs/@
sudo mkfs.btrfs/dev/nvme0n1p1 # 这一步也可以在分区工具里完成 sudo mount /dev/nvme0n1p1 /mnt/btrfs sudo btrfs subvolume create /mnt/btrfs/@
sudo mkfs.btrfs /dev/nvme0n1p1  # 这一步也可以在分区工具里完成
sudo mount /dev/nvme0n1p1 /mnt/btrfs
sudo btrfs subvolume create /mnt/btrfs/@

然后就可以进入 Live CD,把原系统分区扬掉了。之后通过 Timeshift 或 rsync 把备份好的根分区恢复到目录 /mnt/btrfs/@,这样就完成了文件的迁移。接下来,逐一的建立子卷。比如要建立 /var/cache 对应的 @cache 子卷:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo btrfs subvolume create /mnt/btrfs/@cache
mv -v /mnt/btrfs/@/var/cache/* /mnt/btrfs/@cache
sudo btrfs subvolume create /mnt/btrfs/@cache mv -v /mnt/btrfs/@/var/cache/* /mnt/btrfs/@cache
sudo btrfs subvolume create /mnt/btrfs/@cache
mv -v /mnt/btrfs/@/var/cache/* /mnt/btrfs/@cache

完成子卷建立后,还需要修改 fstab 以正确挂载(这里编辑的是 /mnt/btrfs/@/etc/fstab)。注意每个子卷都需要增加对应的记录,但是对应的磁盘分区 UUID 都是同一个,指向 Btrfs 分区。分区 UUID 可以通过 blkid 指令查询。此外,非 ssd 磁盘需要去掉 ssd 选项。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
UUID=... / btrfs defaults,noatime,ssd,subvol=@ 0 1
UUID=... /var/log btrfs defaults,noatime,ssd,subvol=@log 0 1
UUID=... /var/cache btrfs defaults,noatime,ssd,subvol=@cache 0 1
UUID=... / btrfs defaults,noatime,ssd,subvol=@ 0 1 UUID=... /var/log btrfs defaults,noatime,ssd,subvol=@log 0 1 UUID=... /var/cache btrfs defaults,noatime,ssd,subvol=@cache 0 1
UUID=...	/	btrfs	defaults,noatime,ssd,subvol=@	0	1
UUID=...	/var/log	btrfs	defaults,noatime,ssd,subvol=@log	0	1
UUID=...	/var/cache	btrfs	defaults,noatime,ssd,subvol=@cache	0	1

最后更新一下引导就可以了。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
manjaro-chroot /mnt/btrfs/@
grub-install --target=x86_64-efi --efi-directory=EFI 文件夹 --bootloader-id=manjaro --recheck
manjaro-chroot /mnt/btrfs/@ grub-install --target=x86_64-efi --efi-directory=EFI 文件夹 --bootloader-id=manjaro --recheck
manjaro-chroot /mnt/btrfs/@
grub-install --target=x86_64-efi --efi-directory=EFI文件夹 --bootloader-id=manjaro --recheck

此时重启系统就应该在根子卷了。

系统快照方案

需求

简单梳理了下需求,并按照重要程度排序:

  • 方便的实时创建快照、实时回滚
  • 可以定时备份、清理
  • 可以启动进快照,以备滚炸之需
  • 在滚动更新前自动创建快照

各种方案们

经过了解与分析,主要的方案有如下几种。

  • Ubuntu 风格:使用 Timeshift,只能识别 @@home 子卷
  • openSUSE 风格:使用 Snapper,可以作用于任何子卷,快照存放在默认的 @/.snapshot
  • Arch Linux 风格:使用 Snapper,可以作用于任何子卷,但创建独立的 @snapshot 子卷

权衡利弊

至于为什么会有那么多方案?没错,因为每一种都多少有点缺陷,所以就有一帮人出来造轮子……

首先是 Timeshift 的解决方案,这个方案就非常有 Ubuntu 的稳定气质。它强制了子卷风格必须是 @@home,也没有提供快照的复杂功能,但是在用户体验上做的很好(尤其是 GUI)。在启动进快照时会自动弹出窗口提示回滚,在 Live CD 里也可以进行恢复操作。因此,对于安稳的使用来说,我最推荐的也就是 Timeshift 方案了(这也是 Manjaro 默认的方案)。

Snapper 和 Timeshift 就很不同了,比起 Timeshift 做的类似 TimeMachine 的系统恢复工作,Snapper 更像是一个快照管理工具。它提供了丰富的快照管理方式:可以设定快照类型,并按类型定制自动清理规则;支持 pre-post 的方式管理快照,即在进行某操作的前后进行快照;甚至可以分析两个快照之间文件系统发生的变化,可以说是非常的 Geek。但更 Geek 的是,它的 GUI 甚至不包含快照恢复功能。对于 openSUSE,快照恢复可以通过 snapper rollback 解决;而对于 Arch Linux 风格,你甚至需要手动输入一堆指令回滚。至于支持 Live CD 回滚?想都别想,老老实实敲指令!

究其因,Snapper 是 openSUSE 的人做的,但是它们的办法却并不是很 Arch。这里的分歧在于快照子卷。Arch Linux 社区认为,恢复快照的方式应该是这样:首先删除老的子卷(@),然后 “复制” 某个快照为新的子卷(实际操作也是一次快照)。但是 openSUSE 社区的思路完全是逆转过来的,它们认为为啥要把系统放在固定的 @子卷呢?不如直接把系统放在一个可写入的快照里面(比如 @/.snapshot/0)!每次回滚,比如说回滚到快照 6,就当场给快照 6 创建一个可写快照 8,然后我的系统就直接变成了快照 8(比如 @/.snapshot/8)。相当于所有快照构成一系列世界线,然后真的系统待在其中一个上。但问题是,这样需要修改 grub 的源码,这就很不 Arch 了。所以 Arch Linux Wiki 里给出的 Snapper 用法就是用 Snapper 管理快照,但是独立快照子卷且手动恢复快照。由于这是违背 Snapper 设计目的的用法,因此部分功能就是有问题的(rollback 命令不可用、无法识别当前所在的快照)。不过我自己设计了一个 Workaround,因此也能在 Manjaro 下实现 openSUSE 的方法,各位可以看完具体方案再做决定。

Ubuntu 风格:Timeshift 方案(推荐)

配置流程

非常滴简单,你只需要:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo pacman -S timeshift timeshift-autosnap-manjaro grub-btrfs
sudo systemctl enable --now grub-btrfs.path
sudo pacman -S timeshift timeshift-autosnap-manjaro grub-btrfs sudo systemctl enable --now grub-btrfs.path
sudo pacman -S timeshift timeshift-autosnap-manjaro grub-btrfs
sudo systemctl enable --now grub-btrfs.path

然后打开 Timeshift,按照配置向导的 Btrfs 设置即可。此时使用 pacman 安装包也会创建快照,如果不需要创建快照,就在指令之前加上 SKIP_AUTOSNAP=。还可以编辑 /etc/timeshift-autosnap.conf 文件来控制快照行为:

  • 设置 updateGrubfalse,因为不更新也会自动触发
  • maxSnapshots 可以设置大一点,免得安装时覆盖了滚系统前的快照。我设置了 5

如果你使用的不是 Manjaro,那安装 timeshift-autosnap 之后需要使用 sudo systemctl edit grub-btrfs.path 并修改为:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[Unit]
Description=Monitors for new snapshots of timeshift
DefaultDependencies=no
Requires=run-timeshift-backup.mount
After=run-timeshift-backup.mount
BindsTo=run-timeshift-backup.mount
[Path]
PathModified=/run/timeshift/backup/timeshift-btrfs/snapshots
[Install]
WantedBy=run-timeshift-backup.mount
[Unit] Description=Monitors for new snapshots of timeshift DefaultDependencies=no Requires=run-timeshift-backup.mount After=run-timeshift-backup.mount BindsTo=run-timeshift-backup.mount [Path] PathModified=/run/timeshift/backup/timeshift-btrfs/snapshots [Install] WantedBy=run-timeshift-backup.mount
[Unit]
Description=Monitors for new snapshots of timeshift
DefaultDependencies=no
Requires=run-timeshift-backup.mount
After=run-timeshift-backup.mount
BindsTo=run-timeshift-backup.mount

[Path]
PathModified=/run/timeshift/backup/timeshift-btrfs/snapshots

[Install]
WantedBy=run-timeshift-backup.mount

Troubleshooting

如果创建快照时卡顿

建议在设定里关闭启用配额组功能,然后运行 sudo btrfs quota disable /

如果 KDE 下无法浏览快照文件

新版本 KDE 应该不会出现这个问题。如果打不开文件夹,可以安装一个其他文件浏览器,比如 Nemo。

rEFInd 支持快照启动

虽然有轮子,但是说实话效果一般且难看。所以我建议 rEFInd 引导 Grub,然后 Grub 负责快照。如果实在看不惯两个界面,建议编辑 /etc/default/grub

  • GRUB_TIMEOUT=2,就留 2 秒时间
  • GRUB_TIMEOUT_STYLE=hidden,黑屏显示,2 秒内按 Shift 可选择快照

Arch Linux/openSUSE 风格:Snapper 方案

配置

两种方案的麻烦程度大差不差。硬要说的话,我个人更喜欢 openSUSE 的方式,因为它能运用到 Snapper 本身的全部功能。虽然看完过程很可能被劝退,但是其实我真的挺喜欢 Snapper 强大的功能,非常值得一试。

1. 安装 Snapper

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo pacman -S snapper snapper-gui
sudo snapper -c root create-config /
sudo pacman -S snapper snapper-gui sudo snapper -c root create-config /
sudo pacman -S snapper snapper-gui
sudo snapper -c root create-config /

2. Arch Linux 风格特有的配置

虽然看着很麻烦,但其实基本照做即可(假设目标分区是 /dev/nvme0n1p1)。大致的操作就是把 Snapper 创建的 @/.snapshots 删掉,替换成独立的子卷 @snapshots

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo umount /.snapshots
sudo rm -r /.snapshots
sudo btrfs subvolume delete /.snapshots
sudo mkdir /.snapshots
sudo mount -o subvol=/ /dev/nvme0n1p1 /mnt
sudo btrfs subvolume create /mnt/@snapshots
sudo umount /.snapshots sudo rm -r /.snapshots sudo btrfs subvolume delete /.snapshots sudo mkdir /.snapshots sudo mount -o subvol=/ /dev/nvme0n1p1 /mnt sudo btrfs subvolume create /mnt/@snapshots
sudo umount /.snapshots
sudo rm -r /.snapshots
sudo btrfs subvolume delete /.snapshots
sudo mkdir /.snapshots
sudo mount -o subvol=/ /dev/nvme0n1p1 /mnt
sudo btrfs subvolume create /mnt/@snapshots

然后修改 /etc/fstab,参考子卷的方式增加一条将子卷 subvol=@snapshots 挂载到 /.snapshots 的即可。运行 sudo mount -a 生效。

3. Snapper 配置

首先是配置定时快照与定时清理。编辑 /etc/snapper/configs/root(也可以通过 GUI 配置:sudo snapper-gui),可以参考这个配置:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
TIMELINE_MIN_AGE="1800"
TIMELINE_LIMIT_HOURLY="0"
TIMELINE_LIMIT_DAILY="2"
TIMELINE_LIMIT_WEEKLY="4"
TIMELINE_LIMIT_MONTHLY="4"
TIMELINE_LIMIT_YEARLY="1"
TIMELINE_MIN_AGE="1800" TIMELINE_LIMIT_HOURLY="0" TIMELINE_LIMIT_DAILY="2" TIMELINE_LIMIT_WEEKLY="4" TIMELINE_LIMIT_MONTHLY="4" TIMELINE_LIMIT_YEARLY="1"
TIMELINE_MIN_AGE="1800"
TIMELINE_LIMIT_HOURLY="0"
TIMELINE_LIMIT_DAILY="2"
TIMELINE_LIMIT_WEEKLY="4"
TIMELINE_LIMIT_MONTHLY="4"
TIMELINE_LIMIT_YEARLY="1"

然后启用定时任务:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer
sudo systemctl enable --now snapper-timeline.timer sudo systemctl enable --now snapper-cleanup.timer
sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer

然后需要配置允许非 root 用户创建、管理快照。也是先编辑 /etc/snapper/configs/root,在 ALLOW_USERS 里增加自己的用户(whoami)。然后修改快照目录的权限:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo chmod 0750 /.snapshots/
sudo chown : 用户 /.snapshots/
sudo chmod 0750 /.snapshots/ sudo chown : 用户 /.snapshots/
sudo chmod 0750 /.snapshots/
sudo chown :用户 /.snapshots/


重启生效。

4.openSUSE 风格特有配置

主要的操作是把系统转移进某一个可写的快照。

  1. 为了使用 manjaro-chroot,安装包 manjaro-tools-base(运行 sudo pacman -S manjaro-tools-base)。其实不装也行,把指令换成 chroot 也一样。
  2. 编辑 /etc/fstab,把挂载根目录一行的 subvol=/ 去掉;参考子卷的方式增加一条将子卷 subvol=/@/.snapshots 挂载到 /.snapshots 的配置。
  3. 创建一个快照作为当前的系统快照:snapper create --read-write --type single -d "Init snapshot"。在指令输出中应该可以看到创建快照的快照号,把它设置为当前快照:sudo btrfs subvolume set-default /.snapshots/快照号/snapshot
  4. 更新一下 Grub,让系统启动到这个快照:manjaro-chroot /.snapshots/快照号/snapshot bash -c "sudo grub-install && sudo update-grub"
  5. 重启。然后看看 findmnt / 里面显示的子卷是不是设置的那个快照号。
  6. 如果是,那就来一个刺激的狠活。先挂载它 sudo mount -o subvol=/ /dev/nvme0n1p1 /mnt,然后进行一个巨大删除 sudo rm -rf /mnt/@/*(也可以手动删除,不过千万别把.snapshot 删了)。

5. 配置 Grub 启动到快照的菜单

安装 grub-btrfs 包(sudo pacman -S grub-btrfs),运行 sudo systemctl edit grub-btrfs.path 并修改为:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[Unit]
Description=Monitors for new snapshots of snapper
DefaultDependencies=no
Requires=
After=
BindsTo=
[Path]
PathModified=/.snapshots
[Install]
WantedBy=multi-user.target
[Unit] Description=Monitors for new snapshots of snapper DefaultDependencies=no Requires= After= BindsTo= [Path] PathModified=/.snapshots [Install] WantedBy=multi-user.target
[Unit]
Description=Monitors for new snapshots of snapper
DefaultDependencies=no
Requires=
After=
BindsTo=

[Path]
PathModified=/.snapshots

[Install]
WantedBy=multi-user.target

然后运行即可:sudo systemctl enable --now grub-btrfs.path

快照回滚

之前提到过,Snapper 的快照回滚并不是一桩易事。不过这些问题都可以通过写脚本来解决。

Arch Linux 风格

先来看看手动的方式吧(假设分区是 /dev/nvme0n1p1):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo mount /dev/nvme0n1p1 /mnt
sudo btrfs subvolume snapshot /mnt/@ /mnt/@bad
sudo btrfs subvolume delete /mnt/@
sudo btrfs subvolume snapshot /mnt/@snapshots / 要恢复的快照号 /snapshot/mnt/@
sudo mount /dev/nvme0n1p1 /mnt sudo btrfs subvolume snapshot /mnt/@ /mnt/@bad sudo btrfs subvolume delete /mnt/@ sudo btrfs subvolume snapshot /mnt/@snapshots / 要恢复的快照号 /snapshot/mnt/@
sudo mount /dev/nvme0n1p1 /mnt
sudo btrfs subvolume snapshot /mnt/@ /mnt/@bad
sudo btrfs subvolume delete /mnt/@
sudo btrfs subvolume snapshot /mnt/@snapshots/要恢复的快照号/snapshot /mnt/@

要恢复的快照号需要手工检查,如果在快照中可以通过 snapper ls 查看。如果在 Live CD 中,可以通过精准的猜测和一点运气和奇迹逐个查看快照文件夹内的 xml 得到。

由于过程确实很繁琐,于是我就写了一个脚本,建议保存在 /usr/local/bin/rollback,这样就可以 rollback 快照号了。脚本会创建一个快照,然后进行恢复操作。如果当前不在快照,还会创建 @old 子卷保存当前系统,需要重启后手动删除。当然,这个脚本可以在 Live CD 里使用。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#!/bin/sh
set -e
if [[ x"$1" == x ]]; then
echo "No snapshot number given." 1>&2
echo "Usage: rollback [snapshot to rollback]" 1>&2
exit 1
fi
root_dev=`findmnt -n -o SOURCE / | sed 's/\[.*\]//g'`
root_subvol=`findmnt -n -o SOURCE / | sed 's/.*\[\(.*\)\].*/\1/'`
echo ">= Rollback to #$1 on device $root_dev"
# create snapshot before
sudo snapper create --read-only --type single -d "Before rollback to #$1" --userdata important=yes
sudo mount -o subvol=/ $root_dev /mnt
# check enviornment
if [[ x"$root_subvol" == x/@ ]]; then
echo "Warning: Not run in a snapshot, a subvolume @old will be created. You should consider remove it after reboot." 1>&2
if [[ -d /mnt/@old ]]; then
echo "Found last @old, remove it."
sudo btrfs subvolume delete /mnt/@old >/dev/null
fi
sudo mv /mnt/@ /mnt/@old
else
sudo btrfs subvolume delete /mnt/@ >/dev/null
fi
sudo btrfs subvolume snapshot /mnt/@snapshots/$1/snapshot /mnt/@ >/dev/null
sudo umount /mnt
#!/bin/sh set -e if [[ x"$1" == x ]]; then echo "No snapshot number given." 1>&2 echo "Usage: rollback [snapshot to rollback]" 1>&2 exit 1 fi root_dev=`findmnt -n -o SOURCE / | sed 's/\[.*\]//g'` root_subvol=`findmnt -n -o SOURCE / | sed 's/.*\[\(.*\)\].*/\1/'` echo ">= Rollback to #$1 on device $root_dev" # create snapshot before sudo snapper create --read-only --type single -d "Before rollback to #$1" --userdata important=yes sudo mount -o subvol=/ $root_dev /mnt # check enviornment if [[ x"$root_subvol" == x/@ ]]; then echo "Warning: Not run in a snapshot, a subvolume @old will be created. You should consider remove it after reboot." 1>&2 if [[ -d /mnt/@old ]]; then echo "Found last @old, remove it." sudo btrfs subvolume delete /mnt/@old >/dev/null fi sudo mv /mnt/@ /mnt/@old else sudo btrfs subvolume delete /mnt/@ >/dev/null fi sudo btrfs subvolume snapshot /mnt/@snapshots/$1/snapshot /mnt/@ >/dev/null sudo umount /mnt
#!/bin/sh
set -e
if [[ x"$1" == x ]]; then
  echo "No snapshot number given." 1>&2
  echo "Usage: rollback [snapshot to rollback]" 1>&2
  exit 1
fi
root_dev=`findmnt -n -o SOURCE / | sed 's/\[.*\]//g'`
root_subvol=`findmnt -n -o SOURCE / | sed 's/.*\[\(.*\)\].*/\1/'`
echo ">= Rollback to #$1 on device $root_dev"
# create snapshot before
sudo snapper create --read-only --type single -d "Before rollback to #$1" --userdata important=yes
sudo mount -o subvol=/ $root_dev /mnt
# check enviornment
if [[ x"$root_subvol" == x/@ ]]; then
  echo "Warning: Not run in a snapshot, a subvolume @old will be created. You should consider remove it after reboot." 1>&2
  if [[ -d /mnt/@old ]]; then
    echo "Found last @old, remove it."
    sudo btrfs subvolume delete /mnt/@old >/dev/null
  fi
  sudo mv /mnt/@ /mnt/@old
else
  sudo btrfs subvolume delete /mnt/@ >/dev/null
fi
sudo btrfs subvolume snapshot /mnt/@snapshots/$1/snapshot /mnt/@ >/dev/null
sudo umount /mnt

openSUSE 风格

如果你在用 openSUSE,那启动进一个快照后运行 snapper rollback 就完事了,但可惜我们并不是 openSUSE。因为正常的 Grub 并不能在不更新的情况下启动到默认子卷,因此只能手动更新它了。同样,用一个脚本来完成这些操作,同样建议保存在 /usr/local/bin/rollback。不过就不需要手动指定快照号了,直接 rollback 即可。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#!/bin/sh
set -e
root_dev=`findmnt -n -o SOURCE / | sed 's/\[.*\]//g'`
sudo snapper rollback $1
echo ">= Update GRUB on $root_dev"
sudo mount $root_dev /mnt
if [[ -d /boot/efi ]]; then
manjaro-chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=manjaro --recheck
else
read -p "Enter MBR device (e.g. /dev/sda): " install_dev
manjaro-chroot /mnt grub-install --force --target=i386-pc --recheck --boot-directory=/boot $install_dev
fi
manjaro-chroot /mnt update-grub
sudo umount /mnt
#!/bin/sh set -e root_dev=`findmnt -n -o SOURCE / | sed 's/\[.*\]//g'` sudo snapper rollback $1 echo ">= Update GRUB on $root_dev" sudo mount $root_dev /mnt if [[ -d /boot/efi ]]; then manjaro-chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=manjaro --recheck else read -p "Enter MBR device (e.g. /dev/sda): " install_dev manjaro-chroot /mnt grub-install --force --target=i386-pc --recheck --boot-directory=/boot $install_dev fi manjaro-chroot /mnt update-grub sudo umount /mnt
#!/bin/sh
set -e
root_dev=`findmnt -n -o SOURCE / | sed 's/\[.*\]//g'`
sudo snapper rollback $1
echo ">= Update GRUB on $root_dev"
sudo mount $root_dev /mnt
if [[ -d /boot/efi ]]; then
  manjaro-chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=manjaro --recheck
else
  read -p "Enter MBR device (e.g. /dev/sda): " install_dev
  manjaro-chroot /mnt grub-install --force --target=i386-pc --recheck --boot-directory=/boot $install_dev
fi
manjaro-chroot /mnt update-grub
sudo umount /mnt

同样,脚本能在 Live CD 里用。此外,虽然如今 PC 分区一般都不会是 MBR 格式,但如果真是则还需要手动输入引导设备。

openSUSE 风格在 Live CD 中的快照回滚

还是基于之前的原因,本来很简单的操作因为 Grub 的缘故还要增加一步更新操作。此外,Live CD 中就需要指定快照号了,确定方法和 Arch 的一样。不过还好,一般的小场面也用不到 Live CD。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo mount /dev/nvme0n1p1 /mnt
sudo btrfs subvolume set-default /mnt/@snapshots / 快照号 /snapshot
manjaro-chroot /mnt grub-install --target=x86_64-efi --efi-directory=EFI 文件夹 --bootloader-id=manjaro --recheck
sudo mount /dev/nvme0n1p1 /mnt sudo btrfs subvolume set-default /mnt/@snapshots / 快照号 /snapshot manjaro-chroot /mnt grub-install --target=x86_64-efi --efi-directory=EFI 文件夹 --bootloader-id=manjaro --recheck
sudo mount /dev/nvme0n1p1 /mnt
sudo btrfs subvolume set-default /mnt/@snapshots/快照号/snapshot
manjaro-chroot /mnt grub-install --target=x86_64-efi --efi-directory=EFI文件夹 --bootloader-id=manjaro --recheck

成功进去系统后建议运行一次 rollback

Troubleshooting

如果创建快照、列出快照时卡顿

真的巨卡无比,建议运行 sudo btrfs quota disable / 就没事了。

rEFInd 支持快照启动

类似 Timeshift,理由、解决方案也一样。

Reference

  1. https://forum.manjaro.org/t/howto-btrfs-and-snapper/25417
  2. https://wiki.archlinux.org/title/snapper
  3. https://bbs.archlinux.org/viewtopic.php?id=194491
  4. https://www.reddit.com/r/openSUSE/comments/l015pb/snapper_rollback_problem/
  5. https://forum.manjaro.org/t/btrfs-snapper-the-suse-way-with-rollback/52279
  6. https://wiki.manjaro.org/index.php/Restore_the_GRUB_Bootloader
  7. https://github.com/teejee2008/timeshift/issues/606
分享到

KAAAsS

喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)

相关日志

  1. 没有图片
  2. 没有图片

评论

还没有评论。

在此评论中不能使用 HTML 标签。