User:Halscode/Atomic Alpine

来自 Alpine Linux
警告:本文档是一个早期草稿,可能不完整,并且可能仍然缺少一些内容。您应该参考 Immutable root with atomic upgrades
具体来说,这是我 (Halscode) 在基于链接指南构建自己的系统时编写的草稿。请参阅 Talk:Immutable root with atomic upgrades 以查看我为使指南工作而做的更改。


待办事项:在各处添加“the/an”,使用 Template:CatTemplate:Cmd 而不是在适当的地方使用 pre 标签


什么?

本文提供了一个基本指南,用于设置基于只读根目录的 Alpine Linux 系统,该系统具有多个启动环境和原子升级,使用现代引导加载程序和 btrfs

为什么?

只读根目录和原子升级以及轻松回滚或启动先前配置的能力是最近越来越流行的概念。例如,提供和推广此类功能的发行版有 Fedora SilverblueOpensuse MicroOSNixOSGNU Guix

虽然 Alpine Linux 有其杀手级功能,但在默认设置下缺少上述功能。这是一个概念验证,证明可以在最小的系统上以最小的方式实现它们。

注意:Alpine Linux 也可以从 RAM 在 无盘模式 下启动(请参阅 Installation),它支持使用 lbu 保留重启之间的更改。

准备工作

您应该拥有可启动的 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 系统中。

注意:如果您想重用数据分区,它必须已经有一个使用子卷的 btrfs 文件系统(因为它将存储 /var 和 /home),或者您必须已经有单独的 /var 和 /home 分区。

如果您不打算重用现有分区,请在目标存储设备上创建一个新的、干净的分区表(我们将使用 /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

分区创建过程包括几个步骤

  1. 起始扇区 - 您可以安全地使用默认值,按
  2. 大小
  3. 类型(作为十六进制代码)- 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 配置中用于标识启动卷。

注意:我们建议为您的根分区至少提供 20 GiB 的空间。
提示:如果您想将交换分区放在末尾,请将其起始扇区设置为例如 -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
提示:您可能想要在此处安装 vimnano,以便更轻松地编辑文件。

现在相应地编辑 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 中。

提示:如果您想稍后持久编辑 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,但如果您需要这些软件包中的任何一个,您还应该添加 testingcommunity。您可以通过重复该命令来执行此操作,但相应地使用 testingcommunity 代替 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,稍后将用于从我们的第一个快照启动。

现在,您应该添加 sysmutsyscln 脚本,您将使用它们来升级系统、在需要时添加新软件以及清理旧快照。

警告:如果您不添加 sysmut,您将来必须手动执行其步骤来更新您的系统。请添加它,或找到或创建一个适合此配置的替代方案。


现在是时候

  • 安装并设置 doas
  • 运行 setup-desktop
  • 如果您使用 setup-desktop 安装了 GNOME(并且如果您需要 WiFi,还需要 networkmanager-wifi),则安装 NetworkManager
  • 确保您安装了 execline,以便您可以运行 sysmut 和 syscln!
  • 安装 wpa_supplicantiwd 以使用 WiFi
  • 安装替代 shell,例如 zshbash 和/或 fish(并根据需要 chsh 它们)
警告:如果您的 PC 只有无线连接,请确保您安装了无线软件,例如 iwdwpa_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
待办事项:挂载方面仍然有些奇怪。运行 umount -a 可能会导致后续命令失败。也许我们需要更具体地说明应该卸载什么(/etc、/home、/var、/dev、/proc、/sys?)


通过设置 ro 标志并卸载根卷来完成编辑快照

# btrfs property set -ts "/mnt/snapshots/20210411212549sdBXyLxg/@" ro true
# umount /mnt

引导加载程序安装

挂载 EFI 分区

# mount -t vfat /dev/sda1 /mnt
# mkdir /mnt/EFI

引导加载程序配置

引导加载程序安装示例有 2 个选项:rEFIndGRUB。有时其中一个会无缘无故地拒绝在系统上工作,在这种情况下,请尝试另一个。

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 替换为您自己的 rootbtrfs 分区)和 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 软件包。


注意:这些当然可以用 POSIX shell 实现,但是,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}