User:Halscode/Atomic Alpine

具体来说,这是我 (Halscode) 在基于链接指南构建自己的系统时编写的草稿。请参阅 Talk:Immutable root with atomic upgrades 以查看我为使指南工作而做的更改。

什么?
本文提供了一个基本指南,用于设置基于只读根目录的 Alpine Linux 系统,该系统具有多个启动环境和原子升级,使用现代引导加载程序和 btrfs。
为什么?
只读根目录和原子升级以及轻松回滚或启动先前配置的能力是最近越来越流行的概念。例如,提供和推广此类功能的发行版有 Fedora Silverblue、Opensuse MicroOS、NixOS 和 GNU Guix。
虽然 Alpine Linux 有其杀手级功能,但在默认设置下缺少上述功能。这是一个概念验证,证明可以在最小的系统上以最小的方式实现它们。
准备工作
您应该拥有可启动的 Alpine 介质。获取它的过程在安装页面上描述。
如果您无法通过以太网连接到互联网,您将需要安装并设置 wpa_supplicant 以连接到互联网。按照链接的说明进行操作,直到标题“在系统启动时自动配置”;由于安装程序系统不是永久性的,因此该步骤不是必需的。标准镜像不包含 iwd,因此您必须使用 wpa_supplicant 进行设置过程。
安装程序将需要基本镜像上不可用的几个软件包。设置您的网络和软件仓库
# setup-interfaces # if you've already set up wifi, you can skip this # setup-apkrepos
磁盘分区
在本指南中,假定您有一个全新的 UEFI 系统,没有操作系统,并且刚刚使用 USB 闪存驱动器或 CD 启动到 Alpine Live 系统中。
如果您不打算重用现有分区,请在目标存储设备上创建一个新的、干净的分区表(我们将使用 /dev/sda
)
# apk add gptfdisk # gdisk /dev/sda > o ↵ # creates a new GPT partition table > y ↵ # confirm > w ↵ # commits this new table to disk > y ↵ # confirm
现在我们可以定义分区了
# cgdisk /dev/sda
分区创建过程包括几个步骤
- 起始扇区 - 您可以安全地使用默认值,按 ↵
- 大小
- 类型(作为十六进制代码)- EFI 是 ef00,Linux 文件系统是 8300,Swap 是 8200。
结果表
Part. # Size Partition Type Partition Name ---------------------------------------------------------------- 1 200.0 MiB EFI System EFI 2 200.0 GiB Linux filesystem ROOT 3 32.0 GiB Linux swap SWAP
ROOT
分区名称稍后将在 rEFInd 配置中用于标识启动卷。
-16G
。这为其提供了 16 GiB 的空间,因此大小应保持默认值(这将是 16GiB)。交换分区大小应介于您拥有的 RAM 容量和该容量的两倍之间。如果您不使用交换分区,您可能会遇到性能问题,并且将无法挂起到磁盘。
您可能需要手动重新读取分区表以获取下一步所需的块设备。为此
# partprobe /dev/sda # mdev -s
下一步是创建文件系统
# apk add btrfs-progs # includes mkfs.btrfs # mkfs.vfat -F32 /dev/sda1 # EFI # mkfs.btrfs /dev/sda2 # ROOT # mkswap /dev/sda3 # SWAP
现在我们可以挂载我们的根卷
# mount -t btrfs /dev/sda2 /mnt
文件系统结构
现在我们应该创建文件结构,这将提供可靠的原子系统升级。
从以下目录开始
# mkdir /mnt/next
存储下一个 current
链接,由于 busybox mv
如何进行原子链接替换,这是必要的。
# mkdir /mnt/commons
存储常见的非快照子卷。
我们可以立即填充它
# btrfs subvolume create /mnt/commons/@var # btrfs subvolume create /mnt/commons/@home
让我们继续为 /var
添加几个需要的文件夹,以防它们成为问题
# mkdir /mnt/commons/@var/empty # mkdir /mnt/commons/@var/tmp # this will be mounted over
接下来,最重要的目录
# mkdir /mnt/snapshots
存储包含属于一代的快照的目录。
# mkdir /mnt/links
存储包含指向快照世代的链接的目录世代。
让我们创建第一代并用一个 OS 根快照 @
填充它
# NEWSNAPSHOTS="$(date -u +"%Y%m%d%H%M%S")$(cat /dev/urandom | tr -dc 'a-zA-Z' | fold -w 8 | head -n 1)" # mkdir "/mnt/snapshots/$NEWSNAPSHOTS" # btrfs subvolume create /mnt/snapshots/$NEWSNAPSHOTS/@ # btrfs subvolume create /mnt/snapshots/$NEWSNAPSHOTS/@etc
填充 links
# NEWLINKS="$(date -u +"%Y%m%d%H%M%S")$(cat /dev/urandom | tr -dc 'a-zA-Z' | fold -w 8 | head -n 1)" # mkdir "/mnt/links/$NEWLINKS" # ln -s "../../snapshots/$NEWSNAPSHOTS" "/mnt/links/$NEWLINKS/0" # ln -s "../../snapshots/$NEWSNAPSHOTS" "/mnt/links/$NEWLINKS/1" # ln -s "../../snapshots/$NEWSNAPSHOTS" "/mnt/links/$NEWLINKS/2" # ln -s "../../snapshots/$NEWSNAPSHOTS" "/mnt/links/$NEWLINKS/3"
您可以根据需要拥有任意数量的链接,只需相应地将更改应用于下面描述的 rEFInd 配置和升级脚本。
将指向最新链接世代的链接
# ln -s "./links/$NEWLINKS" /mnt/current
此设置允许我们只拥有指向 /current/0/@
、/current/1/@
等的静态 rEFInd 配置,而实际的底层启动环境将随着每次升级而更改。
但是,文件系统挂载服务如何知道当前加载的是哪个快照世代?
答案是 btrfs 根目录中的通用 fstab
。
首先获取分区的 UUID(这会将它们放入文件中以便于访问)
# blkid > /mnt/fstab
现在相应地编辑 fstab
# vi /mnt/fstab
替换下面的 UUID,但它应该看起来像
UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 / btrfs subvol=CURRENT_SNAPSHOTS_PATH/@,ro,noatime 0 0 UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 /etc btrfs subvol=CURRENT_SNAPSHOTS_PATH/@etc,rw,noatime 0 0 UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 /var btrfs subvol=/commons/@var,rw,noatime 0 0 UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 /home btrfs subvol=/commons/@home,rw,noatime 0 0 # UUID=2FE6-837A /boot/efi vfat rw,noatime,discard 0 2 tmpfs /tmp tmpfs mode=1777,noatime,nosuid,nodev,size=2G 0 0 tmpfs /var/tmp tmpfs mode=1777,noatime,nosuid,nodev,size=2G 0 0 UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 swap swap rw,noatime,discard 0 0
CURRENT_SNAPSHOTS_PATH
将被 sysmut 替换,例如 /snapshots/20210411212549sdBXyLxg
,结果将在应用时通过管道传输到创建的快照的 /etc/fstab
中。
mount -t btrfs /dev/sda2 /mnt
)并编辑 /mnt/fstab
。您不必在 sysmut 中执行此操作,但建议这样做,因为您的更改将在您退出时应用于下次启动。这是现在挂载在 /mnt
上的根 btrfs 卷结构应该看起来像的样子
|--mnt | |--commons | | |--@var | | |--@home | |--current | |--fstab | |--links | | |--20210411213742qwrXAJBz | | | |--0 | | | |--1 | | | |--2 | | | |--3 | |--next | |--snapshots | | |--20210411212549sdBXyLxg | | | |--@ | | | |--@etc
基础系统安装
准备好目录结构后,我们可以开始安装基本的 Alpine Linux 系统。
考虑到安装是从 Alpine 系统完成的,我们只需要 该过程的以下部分
# apk -X https://dl-cdn.alpinelinux.org/alpine/latest-stable/main -U --allow-untrusted -p /mnt/snapshots/20210411212549sdBXyLxg/@ --initdb add alpine-base
将 /etc
的内容复制到 etc 子卷
# cp -r /mnt/snapshots/20210411212549sdBXyLxg/@/etc/* /mnt/snapshots/20210411212549sdBXyLxg/@etc
现在我们可以设置基本 chroot 以完成安装过程
# export SNP="/mnt/snapshots/20210411212549sdBXyLxg/@" # # tip: use tab to fill in the correct snapshot; this one is just an example # mount -o bind /dev $SNP/dev # mount -t proc none $SNP/proc # mount -t sysfs sys $SNP/sys # sed "s#CURRENT_SNAPSHOTS_PATH#/snapshots/20210411212549sdBXyLxg#g" /mnt/fstab > "$SNP/etc/fstab" # cp -L /etc/resolv.conf "$SNP/etc/" # so DNS still works # chroot "$SNP" /bin/sh # mount -a
一旦进入 chroot,定义软件仓库
# echo "https://dl-cdn.alpinelinux.org/alpine/latest-stable/main" >> /etc/apk/repositories
此示例仅显示 main
,但如果您需要这些软件包中的任何一个,您还应该添加 testing
和 community
。您可以通过重复该命令来执行此操作,但相应地使用 testing
或 community
代替 main
。
现在是固件、内核和 btrfs 软件包的时候了
# setup-apkrepos # apk add -U linux-firmware linux-lts btrfs-progs
linux-firmware
更改为适合您系统的自定义固件包集,例如,linux-firmware-amd linux-firmware-amd-ucode linux-firmware-amdgpu linux-firmware-ath10k linux-firmware-qca
用于典型的 AMD 笔记本电脑。将 btrfs
功能添加到 mkinitfs.conf
并手动运行 mkinitfs
也很重要
# vi /etc/mkinitfs/mkinitfs.conf # mkinitfs "$(ls /lib/modules)"
这些步骤准备内核并生成 initramfs
,稍后将用于从我们的第一个快照启动。
现在,您应该添加 sysmut 和 syscln 脚本,您将使用它们来升级系统、在需要时添加新软件以及清理旧快照。

现在是时候
- 安装并设置 doas
- 运行 setup-desktop
- 如果您使用 setup-desktop 安装了 GNOME(并且如果您需要 WiFi,还需要 networkmanager-wifi),则安装 NetworkManager
- 确保您安装了 execline,以便您可以运行 sysmut 和 syscln!
- 安装 wpa_supplicant 或 iwd 以使用 WiFi
- 安装替代 shell,例如 zsh、bash 和/或 fish(并根据需要
chsh
它们)

iwd
或 wpa_supplicant
,这样您就不会在首次启动时与网络断开连接。安装程序中的网络连接不会延续到已安装的系统! 因此,如果您需要 WiFi 并且您不这样做,您可能无法重新获得它!
配置系统
首先为 root 设置密码
# passwd root
不要忘记将必要的服务添加到它们各自的运行级别
rc-update add devfs sysinit rc-update add dmesg sysinit rc-update add mdev sysinit rc-update add hwdrivers sysinit rc-update add hwclock boot rc-update add modules boot rc-update add sysctl boot rc-update add hostname boot rc-update add bootmisc boot rc-update add syslog boot rc-update add mount-ro shutdown rc-update add killprocs shutdown rc-update add savecache shutdown
准备和配置快照后,我们可以退出 chroot 并卸载所有内容
# umount -a # exit

通过设置 ro
标志并卸载根卷来完成编辑快照
# btrfs property set -ts "/mnt/snapshots/20210411212549sdBXyLxg/@" ro true # umount /mnt
引导加载程序安装
挂载 EFI 分区
# mount -t vfat /dev/sda1 /mnt # mkdir /mnt/EFI
引导加载程序配置
引导加载程序安装示例有 2 个选项:rEFInd
和 GRUB
。有时其中一个会无缘无故地拒绝在系统上工作,在这种情况下,请尝试另一个。
rEFInd
检查 refind
软件包的最新版本号
# echo "https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories # apk update # apk info refind
手动下载最新版本(替换下面示例中的 0.13.2-r3)的 refind
软件包
# wget https://dl-cdn.alpinelinux.org/alpine/edge/testing/x86_64/refind-0.13.2-r3.apk
解压准备好的 rEFInd 存档并将相关文件复制到 /mnt/EFI/
# tar -xzf refind-0.13.2-r3.apk # cp -r usr/share/refind /mnt/EFI/ # cd /mnt/EFI/refind
重命名配置文件并编辑它
# mv refind.conf-sample refind.conf # vi refind.conf
并将以下内容附加到文件末尾,请记住将示例 UUID 替换为您自己的 root
(btrfs 分区)和 resume
(交换分区)的 UUID。请记住,如果您在“磁盘分区”阶段将 btrfs 卷命名为 ROOT
以外的名称,则必须相应地更改下面的 volume
字段。
# ONLY include the below line on x86_64 systems: this works around a bug in rEFInd scan_driver_dirs EFI/refind/drivers_x86_64 menuentry "Alpine Linux" { icon /EFI/refind/icons/os_linux.png volume "ROOT" loader /current/0/@/boot/vmlinuz-lts initrd /current/0/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/0/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash" submenuentry "Boot fallback 1" { loader /current/1/@/boot/vmlinuz-lts initrd /current/1/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/1/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash" } submenuentry "Boot fallback 2" { loader /current/2/@/boot/vmlinuz-lts initrd /current/2/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/2/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash" } submenuentry "Boot fallback 3" { loader /current/3/@/boot/vmlinuz-lts initrd /current/3/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/3/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash" } }
"ROOT"
是 btrfs 分区的 PARTLABEL
。您也可以改用 PARTUUID
。要同时获取 blkid
,可以使用 blkid 软件包中的 blkid
。busybox 中包含的 blkid
不提供此信息。您不能在
options
中使用标签。您需要将其替换为分区的 UUID。您现在要做的就是重启,移除您的介质,您应该就完成了
# umount /mnt
# reboot
使用 overlaytmpfs
选项
您可以将 overlayfs 与 Alpine init 脚本中内置的 tmpfs 结合使用,以允许 rootfs 中的更改,这些更改将在重启时自动还原。要使用此功能,请将 overlaytmpfs
添加到 refind.conf
中的内核启动选项,例如
... initrd /current/0/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/0/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 overlaytmpfs quiet splash" submenuentry "Boot fallback 1" { ...
您可能还希望对每个 submenuentry 执行此操作,或者改为为每个快照创建单独的 submenuentry。因此,该文件可能看起来像
# ONLY include the below line on x86_64 systems: this works around a bug in rEFInd scan_driver_dirs EFI/refind/drivers_x86_64 menuentry "Alpine Linux" { icon /EFI/refind/icons/os_linux.png volume "ROOT" loader /current/0/@/boot/vmlinuz-lts initrd /current/0/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/0/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash" submenuentry "Boot fallback 1" { loader /current/1/@/boot/vmlinuz-lts initrd /current/1/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/1/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash" } submenuentry "Boot fallback 2" { loader /current/2/@/boot/vmlinuz-lts initrd /current/2/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/2/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash" } submenuentry "Boot fallback 3" { loader /current/3/@/boot/vmlinuz-lts initrd /current/3/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/3/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash" } submenuentry "Boot current with overlaytmpfs" { loader /current/0/@/boot/vmlinuz-lts initrd /current/0/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/0/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 overlaytmpfs quiet splash" } submenuentry "Boot fallback 1 with overlaytmpfs" { loader /current/1/@/boot/vmlinuz-lts initrd /current/1/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/1/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 overlaytmpfs quiet splash" } submenuentry "Boot fallback 2 with overlaytmpfs" { loader /current/2/@/boot/vmlinuz-lts initrd /current/2/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/2/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 overlaytmpfs quiet splash" } submenuentry "Boot fallback 3 with overlaytmpfs" { loader /current/3/@/boot/vmlinuz-lts initrd /current/3/@/boot/initramfs-lts options "root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/3/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 overlaytmpfs quiet splash" } }
GRUB
# apk add grub-efi
这次 GRUB 需要两个配置文件,因为我们将使用 grub-mkstandalone
。第一个配置文件是内部的,应该只指向第二个文件,我们在其中存储菜单
# cd /tmp # vi grub_internal.cfg
将内容设置为以下内容,但请确保将 2FE6-837A
替换为您自己的 EFI 分区 UUID
insmod part_gpt insmod fat search --set efi --fs-uuid 2FE6-837A configfile (${efi})/EFI/grub/grub.cfg
第二个配置文件是主配置文件,我们在其中描述整个启动菜单。
# vi grub.cfg
设置为包含,但替换 UUID
set timeout=3 menuentry "Alpine Linux Current" { search --set root --fs-uuid b9ff5e7b-e128-4e64-861a-2fdd794a9828 linux /current/0/@/boot/vmlinuz-edge root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/0/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash initrd /current/0/@/boot/initramfs-edge } menuentry "Alpine Linux Snapshot 1" { search --set root --fs-uuid b9ff5e7b-e128-4e64-861a-2fdd794a9828 linux /current/1/@/boot/vmlinuz-edge root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/1/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash initrd /current/1/@/boot/initramfs-edge } menuentry "Alpine Linux Snapshot 2" { search --set root --fs-uuid b9ff5e7b-e128-4e64-861a-2fdd794a9828 linux /current/2/@/boot/vmlinuz-edge root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/2/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash initrd /current/2/@/boot/initramfs-edge } menuentry "Alpine Linux Snapshot 3" { search --set root --fs-uuid b9ff5e7b-e128-4e64-861a-2fdd794a9828 linux /current/3/@/boot/vmlinuz-edge root=UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 rootfstype=btrfs rootflags=subvol=/current/3/@,ro,noatime resume=UUID=f0239163-9d46-47c1-67a4-3ee1d63d0676 quiet splash initrd /current/3/@/boot/initramfs-edge }
生成 grubx64.efi
二进制文件
# grub-mkstandalone -O x86_64-efi -o grubx64.efi "boot/grub/grub.cfg=/tmp/grub_internal.cfg" # mkdir /mnt/EFI/grub # mv grubx64.efi /mnt/EFI/grub/ # mv grub.cfg /mnt/EFI/grub/
添加 EFI 启动项
要将选定的引导加载程序添加到 UEFI,efibootmgr
是一个合适的工具。以下示例适用于 rEFInd,但可以轻松地为 GRUB 进行调整
# apk add efibootmgr # efibootmgr --create --disk /dev/sda --part 1 --loader /EFI/refind/refind_x64.efi --label "rEFInd" --verbose
/dev/sda
是我们的磁盘设备,1
是包含引导加载程序数据的 FAT32 分区的编号。
更新或更改系统


execline
实现的,并且系统需要 execline
软件包。
execline
提供了许多运行时优势,并且生成的脚本更具可读性。# touch /usr/sbin/sysmut # chmod +x /usr/sbin/sysmut # vi /usr/sbin/sysmut
更改系统的示例脚本
#!/bin/execlineb -W unshare --mount importas -D 0 source 1 define mnt /media/root if { mkdir -p ${mnt} } if { mount -t btrfs -o rw,noatime UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 ${mnt} } foreground { backtick -E dt { date -u +%Y%m%d%H%M%S } backtick -E rnd { pipeline { cat /dev/urandom } pipeline { tr -dc a-zA-Z } pipeline { fold -w 8 } head -n 1 } define newsnap ${dt}${rnd} if { mkdir -p ${mnt}/snapshots/${newsnap} } if { btrfs subvolume snapshot ${mnt}/current/${source}/@ ${mnt}/snapshots/${newsnap}/@ } if { btrfs subvolume snapshot ${mnt}/current/${source}/@etc ${mnt}/snapshots/${newsnap}/@etc } if { redirfd -w 1 ${mnt}/snapshots/${newsnap}/@etc/fstab sed s#CURRENT_SNAPSHOTS_PATH#/snapshots/${newsnap}#g ${mnt}/fstab } if { mount -t proc none ${mnt}/snapshots/${newsnap}/@/proc } if { mount -t sysfs sys ${mnt}/snapshots/${newsnap}/@/sys } if { mount -o bind,ro /dev ${mnt}/snapshots/${newsnap}/@/dev } foreground { foreground { mount -o bind,ro /etc/resolv.conf ${mnt}/snapshots/${newsnap}/@etc/resolv.conf } foreground { chroot ${mnt}/snapshots/${newsnap}/@ foreground { mount -a } foreground { echo "=== Entering sysmut (make your changes now)" } foreground { sh } importas apply ? foreground { umount -a } exit ${apply} } importas apply ? foreground { redirfd -w 2 /dev/null umount ${mnt}/snapshots/${newsnap}/@etc/resolv.conf } ifelse { exit ${apply} } { if { btrfs property set -ts ${mnt}/snapshots/${newsnap}/@ ro true } define newlink ${dt}${rnd} if { mkdir -p ${mnt}/links/${newlink} } if { ln -s ../../snapshots/${newsnap} ${mnt}/links/${newlink}/0 } if { cp -P ${mnt}/current/0 ${mnt}/links/${newlink}/1 } if { cp -P ${mnt}/current/1 ${mnt}/links/${newlink}/2 } if { cp -P ${mnt}/current/2 ${mnt}/links/${newlink}/3 } if { mkdir -p ${mnt}/next } if { ln -sfn ./links/${newlink} ${mnt}/next/current } if { mv ${mnt}/next/current ${mnt}/ } echo "=== Changes applied" } # Remove failed snapshot btrfs subvolume delete ${mnt}/snapshots/${newsnap}/@ btrfs subvolume delete ${mnt}/snapshots/${newsnap}/@etc rm -r ${mnt}/snapshots/${newsnap} echo "=== Changes discarded" } foreground { redirfd -w 2 /dev/null umount ${mnt}/snapshots/${newsnap}/@/proc } foreground { redirfd -w 2 /dev/null umount ${mnt}/snapshots/${newsnap}/@/sys } redirfd -w 2 /dev/null umount ${mnt}/snapshots/${newsnap}/@/dev } umount ${mnt}
它将使您进入 chroot 到新快照的 root shell,您可以在其中应用您喜欢的任何更改。新快照的来源由第一个也是唯一一个参数定义,形式为数字。如果未提供参数,则将 0
(当前最新)作为来源。
如果 chroot shell 以错误退出,则不会切换到新快照。这意味着您可以在 chroot 中手动放弃更改,方法是
# exit 1
删除未使用的快照
未使用的快照可以通过以下方式进行垃圾回收
# touch /usr/sbin/syscln # chmod +x /usr/sbin/syscln # vi /usr/sbin/syscln
#!/bin/execlineb -W unshare --mount define mnt /media/root if { mkdir -p ${mnt} } if { mount -t btrfs -o rw,noatime,compress=zstd:3 UUID=b9ff5e7b-e128-4e64-861a-2fdd794a9828 ${mnt} } foreground { foreground { pipeline { foreground { pipeline { find -H ${mnt}/snapshots/ -maxdepth 1 -mindepth 1 -print0 } xargs -0 -r realpath } pipeline { find -H ${mnt}/current/ -maxdepth 1 -mindepth 1 -print0 } xargs -0 -r realpath } pipeline { tr \\n \\0 } pipeline { sort -z } pipeline { uniq -u -z } pipeline { xargs -0 -r -n 1 -I [] find -H [] -maxdepth 1 -mindepth 1 -print0 } xargs -0 -r btrfs subvolume delete } foreground { find -H ${mnt}/snapshots/ -maxdepth 1 -mindepth 1 -empty -type d -delete } foreground { pipeline { foreground { pipeline { find -H ${mnt}/links/ -maxdepth 1 -mindepth 1 -print0 } xargs -0 -r realpath } realpath ${mnt}/current } pipeline { tr \\n \\0 } pipeline { sort -z } pipeline { uniq -u -z } pipeline { xargs -0 -r -n 1 -I [] find -H [] -maxdepth 1 -mindepth 1 -print0 } xargs -0 -r -n 1 unlink } find -H ${mnt}/links/ -maxdepth 1 -mindepth 1 -empty -type d -delete } umount ${mnt}