Converting an Existing Ubuntu 24.10 Installation on Ext4 to Btrfs with an openSUSE Style Subvolume Layout and Snapper Integration for System Snapshot Management

Published: Oct. 22, 2024, 6 p.m.

Updated: None

The ext4 filesystem -- and its preceding versions, known for its stability and reliability has long been the default filesystem used by most Linux distributions. However, the Btrfs filesystem, due to its scalability and next-generation fault tolerance and self healing features have helped it gain popularity.

openSUSE pioneered its use as the default filesystem almost ten years ago, provisioning it on installations with a complex but very rational subvolume layout, along with Snapper to manage snapshots and rollbacks. Since then maintenance services have increased its reliability and suitability for use as the default filesystem. Recently, an increasing number of distributions are using it as the default filesystem for installations, most notably Fedora, although the Btrfs subvolume layouts these distributions are far simpler than openSUSE's.

An existing ext4 installation can easily be converted to Btrfs with the btrfs-convert command, and with a little more effort, the converted installation can be configured to use the openSUSE subvolume layout. This article describes a process to convert an existing Ubuntu installation on ext4 to Btrfs with the openSUSE style subvolume layout and integrate Snapper for managing snapshots and rollbacks, with the ability to automatically create snapshots periodically, during each boot, and before and after package management transactions.

Introduction

The Btrfsfilesystem is believed by some to be the next default filesystem for Linux, replacing the current de-facto standard filesystem, ext4, used by default in most distributions. It has many advanced features which are not available in the current standard filesystem, such as copy-on-write capability which maintains a version of a file at each modification. This allows individual files to be rolled back to previous versions and even the entire system to be snapshotted and rolled back to a previous state from a snapshot. Also, among other features that make it increasingly in demand by data centers, it is fault tolerant -- not only at the device level but to an individual file level, able te repair corrupted files -- as long as the required number of redundant storage devices are available and configured appropriately.

Ubuntu offers the ZFS filesystem as an option, which has similar fault tolerant features as Btrfs, but does not have the file level self healing features of Btrfs. While ZFS is an option at installation, the default filesystem on Ubuntu installations is ext4. Users who want the advanced features of Btrfs but originally installed with the default ext4 filesystem can easily convert the ext4 filesystem of the original installation to Btrfs. And if they want the simple system snapshot and rollback management by Snapper, which can incorporate automatic periodic snapshot creation and cleanup, automatic snapshot creation at boot, and automatic pre and post snapshots during APT package management transactions, as well as automatic mounting of the correct subvolume that contains the filesystem hierarchy root because the default Btrfs subvolume is set by Snapper, a little more effort will yield such as system.

The process, similar to the one described in A Fedora Installation with an openSUSE Style BtrfsSubvolume Layout and Snapper Intergration for System Snapshots and Rollbacks, involves using btrfs-convert, part of the set of Btrfs utility progras usually packaged as btrfs-progs, to convert the ext4 filesystem while offline from either a live USB or another installation on the same computer. The external Linux used in performing the process for this article was an Arch Linux installation which was installed on Btrfs with an oenSUSE layout and Snapper integration following the process described in An Arch Linux Installation on a BtrfsFilesystem with Snapper for System Snapshots and Rollbacks. Because this external Linux was Btrfs based, the btrfs-progs package was already available. If the external Linux is not Btrfs based, the package must be installed before performing the filesystem conversion.

After the conversion the openSUSE style subvolume layout was created by mounting the converted filesystem from the external system, moving the directories and files in the filesystem hierarchy root, except those parts of the hierarchy to be excluded from system snapshots (/opt, /root, /var/cache, etc.) to the desired subvolume. Other tasks were also necessary to properly configure the system, including setting the default btrfs subvolume, editing the /etc/fstab file to include the subvolumes containing filesystem hierarchy paths to be excluded from system snapshots, editing GRUB scripts in /etc/grub.d for compatibility with automatic mounting of the subvolume containing the filesystem hierarchy root by /etc/fstab without explicitly specifying the subvolume containing the filesystem hierarchy root as a mount option.

The following graphic helps to illustrates the process and the desired configuration. The right part depicts the final subvolume configuration, including aspects of the configuration such as the subvolume used as the filesystem hierarchy root, how this subvolume is mounted, the changes in the subvolume layout as new snapshots are created, and the effect of rollbacks to the subvolume layout. The left part depicts intermediate states of the system at various stages in the process.

Figure 1: The Intermediate State of the Subvolume Layout and the Final State of the Subvolume Layout

The process is summarized below, before the full process found later in this article:

  • Part 1: Prepare the Ext4 Filesystem to Be Converted to Btrfs
    There must be an adequate amount of free space on the filesystem. If there is not, enlarge the partition, if possible, or delete package caches or other unnecessary files to create free space. Check for filesystem fragmentation, as pre-conversion fragmentation will be carried over to the converted filesystem, and defragment if necessary. Also, the filesystem must also be free of errors, so a filesystem check and repair -- if necessary -- is required before the conversion.
  • Part 2: Convert the Ext 4 Partition to Btrfs
    Use the btrfs-convert program to convert the Filesystem containing the root of the Ubuntu installation filesystem hierarchy to Btrfs. Then mount the converted filesystem in the external Linux to view the characteristics of the converted filesystem and to delete the backup subvolume containing a copy of the preconversion image created by the conversion program.
  • Part 3: Create the Subvolumes for the Desired Subvolume Layout
    While the converted Btrfs partition is still mounted from the previous part, create the subvolume to contain all other subvolumes immediately under the automatically created top-level subvolume, at subvolume path /@. Also, create the subvolumes to contain filesystem hierarchy paths to be excluded from system snapshots at subvolume paths /@/@opt, /@/@root, /@/srv, /@/@usrlocal, /@/@varcache, /@/@varlog and, /@/@vartmp. These will eventually contain, respectively, the filesystem hierarchy paths: /opt, /root, /srv, /usr/local, /var/cache, /var/log and, /var/tmp.
  • Part 4: Move Files from the Top Level BtrfsSubvolume (subvolume path /, subvolume ID 5) to the Newly Created Subvolumes
    Move the contents of the top-level subvolume created by the btrfs-convert command, which also places the root of the filesystem hierarchy and all existing files and directories in this subvolume to the appropriate subvolumes created in the previous Part. First we move the filesystem hierarchy paths to be excluded from the system snapshots their respective subvolumes, i.e., /@/@opt, /@/@root, /@/srv, /@/@usrlocal, /@/@varcache, /@/@varlog and, /@/@vartmp. Then move the remaining files, temporarily to the subvolume created to contain all other subvolumes, subvolume path /@, subvolume ID 257.
  • Part 5: Install Snapper and Create Initial Snapshot Subvolume
    Next, chroot into the Ubuntu system and install Snapper. Before the chroot we mount the /@ subvolume to be the root of the choot environment. We also mount the subvolumes under the chroot root mountpoint, as well as the pseudo filesystems. After the Snapper installation, we initialize a Snapper configuration, which also automatically creates a subvolume for snapshots under the subvolume currently mounted as the root of the filesystem (subvolume path /@), and finally create the initial snapshot subvolume which will contain the root of the filesystem hierarchy at the end of the process, and until a rollback is performed. Exit the chroot at the end of this step.
  • Part 6: Move Files from Subvolume Path /@ to Subvolume Path /@/.snapshots/1/snapshot
    After existing the chroot at the end of the previous step, while the mounts created for the chroot are still active, move the remaining -- after the move in Part 4 -- files and directories from subvolume path /@ to the initial snapshot subvolume, subvolume path /@/.snapshots/1/snapshot. After this step, our configuration of the subvolume layout will be complete and as illustrated in the right pane of the figure at the top of the article.
  • Part 7: Modify /etc/fstab to Reflect New BtrfsFilesystem and Subvolume Layout
    Access the Ubuntu installation's /etc/fstab, now in its final home under the initial snapshot subvolume, from the external Linux through the mount point at /mnt/ubuntu-root/.snapshots/1/snapshot/etc/fstab and modify the mount for the root filesystem hierarchy. Also, add mountpoints for the snapshots subvolume (at subvoume path /@/.snapshots) as well as mountpoints for the subvolumes used to contain the filesystem hierarchy paths that are to be excluded from system snapshots.
  • Part 8: Modify GRUB Scripts in /etc/grub.d
    Simiar to the above step with respect to the /etc/fstab, access the GRUB scripts /etc/grub/grub.cfg/10_linux and /etc/grub/grub.cfg/20_linux_xen for compatibility with automatic mounting of the current snapshot subvolume to the mountpoint of the filesystem hierarchy root through the Btrfs default subvolume feature.
  • Part 9: Make Directory to Be Used as a Mountpoint for the .snapshots Subvolume in the Initial Snapshot Subvolume
    Make a directory under the initial snapshot subvolume to be used as a mountpoint for the snapshots subvolume. This directory will correspond to the mountpoint we have already specified for the .snapshots subvolume when we edited the Ubuntu installation's etc/fstab in Part 7.
  • Part 10: Change the Default BtrfsSubvolume from /, Subvolume ID 5 to the Initial Snapshot Subvolume /@/.snapshots/1/snapshot
    Set the Btrfs default subvolume to the initial snapshot subvolume. This will ensure that it is mounted automatically at / without mount options that explicitly specify the subvolume. When a rollback is performed with Snapper, it will change the default subvolume to the one it creates to be used as the new system root, so that it will be mounted automatically.
  • Part 11: Chroot to the Installed System and Reinstall GRUB Firmware, Update GRUB, and Regenerate Initramfs Reinstall the GRUB firmware to avoid being placed in a GRUB shell when rebooting to the Ubuntu system at the end of the process. Also, update GRUB so that its configuration reflects the new kernel image and initial RAM filesystem paths in the initial snapshot subvolume mounted as the root of the filesystem hierarchy. The initial RAM filesystem must also be regenerated.
  • Part 12: Reboot Into Converted System with BtrfsSubvolume Layout and Working Snapper Configuration and Make Final Adjustments to System Configuration
    Finally, reboot into the Ubuntu system and make final adjustments to the system, such as adjusting the frequency of and conditions for automatic snapshot creation and cleanup, as well as other related parameters.

Part 1: Prepare the Ext4 Filesystem to Be converted to Btrfs

Before using the btrfs-convert command, the ext4 filesystem must be in a suitable state for conversion. The filesystem must be error free; it is recommended to perform a a check and, if necessary, a repair of the ext4 filesystem with e2fsck command typically available from a package named e2fsprogs. The filesystem must also have adequate free space to perform the conversion; btrfs-convert(8) states that although the necessary free space can not be predicted before the conversion is performed, several GB of free space may be required per 100GB of filesystem size. It may be helpful to delete unnecessary files, check for fragmentation and defragment if necessary with the e4defrag command, also available from the e2fsprogs package.

The last requirement is that the filesystem block size must be one that is supported by btrfs-convert, which is the same as a valid value for the mkfs.btrfs --sectorsize option. The btrfs-convert man page states that this "is typically the system page size (4KiB on x86_64 machines)", which in my case is the same as the value reported by the tune2fs command.(See Reference 6.)

  1. Determine the kernel block device name of the partition to be converted as this will be used as an argument to the btrfs-convert and any utilities used to prepare the filesystem.
  2. Check the filesystem level block size of the ext4 partition to be converted.
    tune2fs -l /dev/nvme1n1p7
  3. Delete unnecessary files if there isn't an adequate amount of free space on the partition.
  4. Determine the level of fragmentation of the ext4 partition to be converted with e4defrag. If so, defragment the filesystem with the same tool. (See Reference 3)
  5. Unmount the ext4 filesystem to be converted, if it is mounted, and check the filesystem and repair with the e2fsck command. (See Reference )

Part 2: Convert the ext4 Root Partition to Btrfs

The actual Ubuntu root ext4 filesystem conversion is very simple and only requires executing the btrfs-convert command from the external Linux while the Ubuntu root partition is unmounted. The command has numerous options that allow specifying the characteristics of the converted Btrfs filesystem but can be used without any options to use default options. It only takes one argument which specifies the block device identification of the partition to be converted.

  1. Execute the btrfs-convert command in the external Linux.
    󰁹 100%  18:18:15  USER: brook HOST: ARCH-16ITH6
    ~  ❯$ sudo btrfs-convert /dev/nvme1n1p7
    The output from this command is shown in the left pane of the terminal in the first two images of the following set of images, the first taken while the command is still running, and the second after the command has completed.
  2. Mount the converted filesystem.
    󰁹 100%  18:40:30  USER: brook HOST: ARCH-16ITH6
    ~  ❯$ sudo mount /dev/nvme1n1p7 /mnt/ubuntu-root
  3. Verify the mount.
    󰁹 100%  18:40:55  USER: brook HOST: ARCH-16ITH6
    ~  ❯$ mount | grep /mnt
    dev/nvme1n1p7 on /mnt/ubuntu-root type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=5,subvol=/)
  4. View the statistics of the newly converted filesystem with the commands:
    btrfs filesystem show
    as shown in the following listing
    󰁹 100%  18:42:20  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs filesystem show /mnt/ubuntu-root/
    Label: 'UBUNTU_ROOT'  uuid: 72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43
            Total devices 1 FS bytes used 30.72GiB
            devid    1 size 72.50GiB used 52.75GiB path /dev/nvme1n1p7
    and
    btrfs subvolume list
    as shown in the following listing
    󰁹 100%  18:42:31  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume list /mnt/ubuntu-root/
    ID 256 gen 3 top level 5 path ext2_saved
    
    
    The contents of the filesystem can be viewed with the ls command with the mountpoint as an argument. The left pane of the terminal displayed in the third image of the following set shows the output of the above Btrfs commands as well as the directory listing.
  5. Delete the backup of the original filesystem image saved in a subvolume named ext2_saved created by btrfs-convert.
    󰁹 100%  18:51:55  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume delete /mnt/ubuntu-root/ext2_saved
    [sudo] password for brook:
    Delete subvolume 256 (no-commit): '/mnt/ubuntu-root/ext2_saved'
    
    

Figures 2a, 2b, and 2c: Converting the Ubuntu Installation's Ext4 Filesystem Hierarchy Root Partition to Btrfs
The conversion is performed with the btrfs-convert command with an argument identifying the partition (unmounted) to be converted.
Click on any of the thumbnails to view a slideshow of the images.

Part 3: Create the Subvolumes for the Desired Subvolume Layout

In Part 2, after converting the ext4 filesystem to Btrfs, we mounted the partition, specifically the top level subvolume -- the only subvolume at this point, and viewed the filesystem statistics. We continue working on the filesystem from our external Linux installation or live ISO.

  1. Create a the subvolume that will contain all other subvolumes (subvolume path /@).
    󰁹 100%  18:56:42  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume create -p /mnt/ubuntu-root/@
    Create subvolume '/mnt/ubuntu-root/@'
    
    
  2. Create subvolumes under subvolume path /@ for the filesystem hierarchy paths that are to be excluded from system snapshots.
    󰁹 100%  18:59:55  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume create -p /mnt/ubuntu-root/@/@opt
    Create subvolume '/mnt/ubuntu-root/@/@opt'
    
    󰁹 100%  19:01:31  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume create -p /mnt/ubuntu-root/@/@root
    Create subvolume '/mnt/ubuntu-root/@/@root'
    
    󰁹 100%  19:01:40  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume create -p /mnt/ubuntu-root/@/@srv
    Create subvolume '/mnt/ubuntu-root/@/@srv'
    
    󰁹 100%  19:01:47  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume create -p /mnt/ubuntu-root/@/@usrlocal
    Create subvolume '/mnt/ubuntu-root/@/@usrlocal'
    
    󰁹 100%  19:01:58  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume create -p /mnt/ubuntu-root/@/@varcache
    Create subvolume '/mnt/ubuntu-root/@/@varcache'
    
    󰁹 100%  19:02:09  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume create -p /mnt/ubuntu-root/@/@varlog
    Create subvolume '/mnt/ubuntu-root/@/@varlog'
    
    󰁹 100%  19:02:16  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume create -p /mnt/ubuntu-root/@/@vartmp
    Create subvolume '/mnt/ubuntu-root/@/@vartmp'
    
    
  3. List the subvolumes.
    󰁹 100%  19:02:24  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ sudo btrfs subvolume list /mnt/ubuntu-root/
    ID 257 gen 1168 top level 5 path @
    ID 258 gen 1167 top level 257 path @/@opt
    ID 259 gen 1167 top level 257 path @/@root
    ID 260 gen 1167 top level 257 path @/@srv
    ID 261 gen 1167 top level 257 path @/@usrlocal
    ID 262 gen 1168 top level 257 path @/@varcache
    ID 263 gen 1168 top level 257 path @/@varlog
    ID 264 gen 1168 top level 257 path @/@vartmp
    
  4. View the directory listing of the Ubuntu root partition mounted in the external Linux. Because it is a filesystem the mount is of a specific subvolume, in this case the top-level subvolume automatically created at filesystem creation or conversion to Btrfs.
    󰁹 100%  19:06:54  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ ls -la /mnt/ubuntu-root
    total 32
    drwxr-xr-x 1 root root  238 Oct 11 18:59  ./
    drwxr-xr-x 1 root root   22 Oct 10 17:16  ../
    drwxr-xr-x 1 root root   90 Oct 11 19:02 '@'/
    lrwxrwxrwx 1 root root    7 May 27  2022  bin -> usr/bin/
    drwxr-xr-x 1 root root  492 Oct 10 19:20  boot/
    drwxr-xr-x 1 root root    0 May 27  2022  cdrom/
    drwxr-xr-x 1 root root  128 Apr 19  2022  dev/
    drwxr-xr-x 1 root root 5084 Oct 10 19:20  etc/
    drwxr-xr-x 1 root root   20 Oct 10 18:21  home/
    -rw------- 1 root root   64 Oct 10 19:08  i7lp616G-el
    lrwxrwxrwx 1 root root    7 May 27  2022  lib -> usr/lib/
    lrwxrwxrwx 1 root root    9 Oct  3 17:54  lib32 -> usr/lib32/
    lrwxrwxrwx 1 root root    9 May 27  2022  lib64 -> usr/lib64/
    lrwxrwxrwx 1 root root   10 May 27  2022  libx32 -> usr/libx32/
    drwx------ 1 root root    0 May 27  2022  lost+found/
    drwxr-xr-x 1 root root   44 Jul 10  2023  media/
    drwxr-xr-x 1 root root   42 Nov 24  2023  mnt/
    drwxr-xr-x 1 root root  106 Oct  3 18:32  opt/
    drwxr-xr-x 1 root root    0 Apr 18  2022  proc/
    drwx------ 1 root root  256 Oct 11 18:03  root/
    drwxr-xr-x 1 root root  284 May 27  2022  run/
    lrwxrwxrwx 1 root root    8 May 27  2022  sbin -> usr/sbin/
    drwxr-xr-x 1 root root   18 May 26  2023  snap/
    drwxr-xr-x 1 root root    0 Apr 19  2022  srv/
    drwxr-xr-x 1 root root    0 Apr 18  2022  sys/
    drwxrwxrwt 1 root root  384 Oct 10 19:20  tmp/
    drwxr-xr-x 1 root root  116 Dec  2  2023  usr/
    drwxr-xr-x 1 root root  130 Oct  3 18:18  var/
    -rw------- 1 root root   64 Oct  3 18:13  zNUV1bYp-el
    
    Note the @/ directory which allows us to access the newly created /@ subvolume.
  5. We can view the contents of this directory.
    󰁹 100%  19:12:52  USER: brook HOST: ARCH-16ITH6
     ~  ❯$ ls -la /mnt/ubuntu-root/@
    total 0
    drwxr-xr-x 1 root root  90 Oct 11 19:02  ./
    drwxr-xr-x 1 root root 238 Oct 11 18:59  ../
    drwxr-xr-x 1 root root   0 Oct 11 19:01 '@opt'/
    drwxr-xr-x 1 root root   0 Oct 11 19:01 '@root'/
    drwxr-xr-x 1 root root   0 Oct 11 19:01 '@srv'/
    drwxr-xr-x 1 root root   0 Oct 11 19:01 '@usrlocal'/
    drwxr-xr-x 1 root root   0 Oct 11 19:02 '@varcache'/
    drwxr-xr-x 1 root root   0 Oct 11 19:02 '@varlog'/
    drwxr-xr-x 1 root root   0 Oct 11 19:02 '@vartmp'/
    As of now, these directories (and their corresponding subvolumes) are empty. In the next part, we will move the contents of the respective filesystem hierarchy paths to the subvolumes.

Part 4: Move Files from the Top Level BtrfsSubvolume (subvolume path /, subvolume ID 5) to the Newly Created Subvolumes

In this part, we will move the contents of the filesystem hierarchy paths /opt, /root, /srv, /usr/local, /var/cache, /var/log and, /var/tmp to the respective subvolumes created in the last part to contain them, respectively, /@/@opt, /@/@root, /@/srv, /@/@usrlocal, /@/@varcache, /@/@varlog and, /@/@vartmp. The files and directories under these paths will be excluded from system snapshots and remain in these subvolumes in the final system.

We will then move the remaining contents of the top-level subvolume (subvolume path /, subvolume ID 5) to the other subvolume created in the previous step (subvolume path /@, subvolume ID 257). This is an intermediate step before moving these files and directories to their final destination in the initial snapshot subvolume in Part 6, after creating the subvolume for the initial snapshot in Part 5.

  1. It was necessary to switch to root when performing this process at this point with the external Linux used in this process.
    󰁹 100%  19:15:00  USER: brook HOST: ARCH-16ITH6
    PCD: 12s ~  ❯$ su
    Password:
  2. Move the contents of filesystem hierarchy path /opt, currently at subvolume path / in the external Linux path /mnt/ubuntu-root/opt to the @opt subvolume, which in the external Linux is at path /mnt/ubuntu-root/@/@opt/.
    [root@ARCH-16ITH6 brook]# mv -v /mnt/ubuntu-root/opt/* /mnt/ubuntu-root/@/@opt/
    created directory '/mnt/ubuntu-root/@/@opt/firefox-beta'
    copied '/mnt/ubuntu-root/opt/firefox-beta/application.ini' -> '/mnt/ubuntu-root/@/@opt/firefox-beta/application.ini'
    copied '/mnt/ubuntu-root/opt/firefox-beta/libnss3.so' -> '/mnt/ubuntu-root/@/@opt/firefox-beta/libnss3.so'
    copied '/mnt/ubuntu-root/opt/firefox-beta/update-settings.ini' -> '/mnt/ubuntu-root/@/@opt/firefox-beta/update-settings.ini'
    
    ... truncated ...
  3. Move the contents of filesystem hierarchy path /root, currently at subvolume path / in the external Linux path /mnt/ubuntu-root/root to the @root subvolume, which in the external Linux is at path /mnt/ubuntu-root/@/@opt/.
    [root@ARCH-16ITH6 brook]# mv -v /mnt/ubuntu-root/root/* /mnt/ubuntu-root/@/@root/
    
    ... truncated ...
    We can confirm all files were moved from /mnt/ubuntu-root/root to /mnt/ubuntu-root/@/@root (from within subvolume path to subvolume /@/@root in terms of the Ubuntu's new Btrfs partition):
    [root@ARCH-16ITH6 brook]# ls -la /mnt/ubuntu-root/@/@root
    total 36
    drwxr-xr-x 1 root root   256 Oct 11 19:19 .
    drwxr-xr-x 1 root root    90 Oct 11 19:02 ..
    -rw------- 1 root root  1181 Oct 11 18:06 .bash_history
    -rw-r--r-- 1 root root  3106 Oct 15  2021 .bashrc
    drwx------ 1 root root   238 Nov 23  2023 .cache
    drwx------ 1 root root    18 Mar 14  2023 .config
    drwx------ 1 root root    22 Feb 25  2023 .dbus
    drwx------ 1 root root    34 May 24  2022 .launchpadlib
    -rw------- 1 root root    44 Oct 11 18:03 .lesshst
    drwxr-xr-x 1 root root    20 Nov 27  2023 .local
    -rw-r--r-- 1 root root   161 Jul  9  2019 .profile
    drwx------ 1 root root    84 May 27  2022 snap
    drwx------ 1 root root     0 May 26  2023 .ssh
    -rw-r--r-- 1 root root     0 Mar  8  2023 .sudo_as_admin_successful
    drwxr-xr-x 1 root root    20 Dec  2  2023 .vim
    -rw------- 1 root root 13339 Dec  2  2023 .viminfo
    -rw-r--r-- 1 root root   165 Nov 24  2023 .wget-hsts
    Note that in this case all files, including hidden files, have been moved, However, depending on the shell and the external Linux's shell configuration, hidden files may not be moved when using mv with the * glob. (See Reference 7.)
  4. Move the contents of filesystem hierarchy path /usr/local, currently at subvolume path / in the external Linux path /mnt/ubuntu-root/usr/local to the @usrlocal subvolume, which in the external Linux is at path /mnt/ubuntu-root/@/@usrlocal/.
    [root@ARCH-16ITH6 brook]# mv -v /mnt/ubuntu-root/usr/local/* /mnt/ubuntu-root/@/@usrlocal/
    
    ... truncated ...
  5. Move the contents of filesystem hierarchy path /var/cache, currently at subvolume path / in the external Linux path /mnt/ubuntu-root/var/cache to the @varcache subvolume, which in the external Linux is at path /mnt/ubuntu-root/@/@varcache/.
    [root@ARCH-16ITH6 brook]# mv -v /mnt/ubuntu-root/var/cache/* /mnt/ubuntu-root/@/@varcache/
    
    ... truncated ...
  6. Move the contents of filesystem hierarchy path /var/log, currently at subvolume path / in the external Linux path /mnt/ubuntu-root/var/log to the @varlog subvolume, which in the external Linux is at path /mnt/ubuntu-root/@/@varlog/.
    [root@ARCH-16ITH6 brook]# mv -v /mnt/ubuntu-root/var/log/* /mnt/ubuntu-root/@/@varlog/
    
    ... truncated ...
  7. Move the contents of filesystem hierarchy path /var/tmp, currently at subvolume path / in the external Linux path /mnt/ubuntu-root/var/tmp to the @vartmp subvolume, which in the external Linux is at path /mnt/ubuntu-root/@/@vartmp/.
    [root@ARCH-16ITH6 brook]# mv -v /mnt/ubuntu-root/var/tmp/* /mnt/ubuntu-root/@/@vartmp/
    
    ... truncated ...
  8. List the contents of the top-level subvolume, subvolume path / currently mounted in the external Linux at /mnt/ubuntu-root
    [root@ARCH-16ITH6 brook]# ls -la /mnt/ubuntu-root/
    total 32
    drwxr-xr-x 1 root root  238 Oct 11 18:59 .
    drwxr-xr-x 1 root root   22 Oct 10 17:16 ..
    drwxr-xr-x 1 root root   90 Oct 11 19:02 @
    lrwxrwxrwx 1 root root    7 May 27  2022 bin -> usr/bin
    drwxr-xr-x 1 root root  492 Oct 10 19:20 boot
    drwxr-xr-x 1 root root    0 May 27  2022 cdrom
    drwxr-xr-x 1 root root  128 Apr 19  2022 dev
    drwxr-xr-x 1 root root 5084 Oct 10 19:20 etc
    drwxr-xr-x 1 root root   20 Oct 10 18:21 home
    -rw------- 1 root root   64 Oct 10 19:08 i7lp616G-el
    lrwxrwxrwx 1 root root    7 May 27  2022 lib -> usr/lib
    lrwxrwxrwx 1 root root    9 Oct  3 17:54 lib32 -> usr/lib32
    lrwxrwxrwx 1 root root    9 May 27  2022 lib64 -> usr/lib64
    lrwxrwxrwx 1 root root   10 May 27  2022 libx32 -> usr/libx32
    drwx------ 1 root root    0 May 27  2022 lost+found
    drwxr-xr-x 1 root root   44 Jul 10  2023 media
    drwxr-xr-x 1 root root   42 Nov 24  2023 mnt
    drwxr-xr-x 1 root root    0 Oct 11 19:16 opt
    drwxr-xr-x 1 root root    0 Apr 18  2022 proc
    drwx------ 1 root root    0 Oct 11 19:19 root
    drwxr-xr-x 1 root root  284 May 27  2022 run
    lrwxrwxrwx 1 root root    8 May 27  2022 sbin -> usr/sbin
    drwxr-xr-x 1 root root   18 May 26  2023 snap
    drwxr-xr-x 1 root root    0 Apr 19  2022 srv
    drwxr-xr-x 1 root root    0 Apr 18  2022 sys
    drwxrwxrwt 1 root root  384 Oct 10 19:20 tmp
    drwxr-xr-x 1 root root  116 Dec  2  2023 usr
    drwxr-xr-x 1 root root  130 Oct  3 18:18 var
    -rw------- 1 root root   64 Oct  3 18:13 zNUV1bYp-el
  9. Remove the directories at filesystem paths that are to be excluded from the system snapshots. Since we moved them in Steps 2 - 7, above, they are now empty. This is evident in the above listing for /opt and /root.
    [root@ARCH-16ITH6 brook]# rm -r /mnt/ubuntu-root/{opt,root,srv,usr/local,var/cache,var/log,var/tmp}
  10. For comparison with the state after the next step, list the contents of the /@ subvolume in the Ubuntu Btrfsfilesystem, currently accessible in the external Linux at /mnt/ubuntu-root/@
    [root@ARCH-16ITH6 brook]# ls -la /mnt/ubuntu-root/@
    total 0
    drwxr-xr-x 1 root root   90 Oct 11 19:02 .
    drwxr-xr-x 1 root root  218 Oct 11 19:36 ..
    drwxr-xr-x 1 root root  106 Oct 11 19:16 @opt
    drwxr-xr-x 1 root root  256 Oct 11 19:19 @root
    drwxr-xr-x 1 root root    0 Oct 11 19:01 @srv
    drwxr-xr-x 1 root root   86 Oct 11 19:20 @usrlocal
    drwxr-xr-x 1 root root  306 Oct 11 19:23 @varcache
    drwxr-xr-x 1 root root 2808 Oct 11 19:24 @varlog
    drwxr-xr-x 1 root root    0 Oct 11 19:02 @vartmp
    Note that it is empty of actual files and directories. The output shows the subvolumes represented as directories since the top level subvolume, subvolume / is mounted in the external Linux at /mnt/ubuntu-root.
  11. Move the remaining contents of top level subvolume (subvolume path /) to subvolume path /@.
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/* /mnt/ubuntu-root/@/
    mv: cannot move '/mnt/ubuntu-root/@' to a subdirectory of itself, '/mnt/ubuntu-root/@/@'
    The error displayed does not matter; the remaining contents of /mnt/ubuntu-root have been moved to /mnt/ubuntu-root/@ in terms of the external Linux filesystem hierarchy, and in terms of the Btrfssubvolumes on the Ubuntu partition, / to /@. The following listing confirms this.
    [root@ARCH-16ITH6 brook]# ls -la /mnt/ubuntu-root/@
    total 32
    drwxr-xr-x 1 root root  306 Oct 11 19:41 .
    drwxr-xr-x 1 root root    2 Oct 11 19:41 ..
    lrwxrwxrwx 1 root root    7 May 27  2022 bin -> usr/bin
    drwxr-xr-x 1 root root  492 Oct 10 19:20 boot
    drwxr-xr-x 1 root root    0 May 27  2022 cdrom
    drwxr-xr-x 1 root root  128 Apr 19  2022 dev
    drwxr-xr-x 1 root root 5084 Oct 10 19:20 etc
    drwxr-xr-x 1 root root   20 Oct 10 18:21 home
    -rw------- 1 root root   64 Oct 10 19:08 i7lp616G-el
    lrwxrwxrwx 1 root root    7 May 27  2022 lib -> usr/lib
    lrwxrwxrwx 1 root root    9 Oct  3 17:54 lib32 -> usr/lib32
    lrwxrwxrwx 1 root root    9 May 27  2022 lib64 -> usr/lib64
    lrwxrwxrwx 1 root root   10 May 27  2022 libx32 -> usr/libx32
    drwx------ 1 root root    0 May 27  2022 lost+found
    drwxr-xr-x 1 root root   44 Jul 10  2023 media
    drwxr-xr-x 1 root root   42 Nov 24  2023 mnt
    drwxr-xr-x 1 root root  106 Oct 11 19:16 @opt
    drwxr-xr-x 1 root root    0 Apr 18  2022 proc
    drwxr-xr-x 1 root root  256 Oct 11 19:19 @root
    drwxr-xr-x 1 root root  284 May 27  2022 run
    lrwxrwxrwx 1 root root    8 May 27  2022 sbin -> usr/sbin
    drwxr-xr-x 1 root root   18 May 26  2023 snap
    drwxr-xr-x 1 root root    0 Oct 11 19:01 @srv
    drwxr-xr-x 1 root root    0 Apr 18  2022 sys
    drwxrwxrwt 1 root root  384 Oct 10 19:20 tmp
    drwxr-xr-x 1 root root  106 Oct 11 19:36 usr
    drwxr-xr-x 1 root root   86 Oct 11 19:20 @usrlocal
    drwxr-xr-x 1 root root  108 Oct 11 19:36 var
    drwxr-xr-x 1 root root  306 Oct 11 19:23 @varcache
    drwxr-xr-x 1 root root 2808 Oct 11 19:24 @varlog
    drwxr-xr-x 1 root root    0 Oct 11 19:02 @vartmp
    -rw------- 1 root root   64 Oct  3 18:13 zNUV1bYp-el
  12. Unmount the Ubuntu partition.
    [root@ARCH-16ITH6 brook]# umount --recursive /mnt/ubuntu-root

Part 5: Install Snapper and Create Initial Snapshot Subvolume

In this part, we will install Snapper and initialize its configuration, which will automatically create a subvolume for snapshots named .snapshots directly under the subvolume mounted at the filesystem hierarchy root when the initialization command is executed; the subvolume path of this subvolume will be at /@/.snapshots. Then we will create the initial snapshot subvolume at subvolume path /@/.snapshots/1/snapshot, which at the end of the process will contain the Ubuntu installation's filesystem hierarchy root, except the paths that are excluded from system snapshots which will be in their own respective subvolumes, e.g., /opt at /@/@opt, /root at /@/@root, etc.

To do this we will chroot into the installed Ubuntu system, mounting the /@ subvolume -- which currently contains the filesystem hierarchy root of the Ubuntu installation after the actions in Part 4 -- at /mnt/ubuntu-root and the other subvolumes at appropriate mountpoints, e.g., /@/@opt at /mnt/ubuntu-root/opt.

  1. Mount the new /@ subvolume, the other new subvolumes created for filesystem hieraarchy paths to be excluded from snapshots, the EFI System Partition and all of the pseudo filesystems needed for the chroot.
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root -o subvol=/@
    [root@ARCH-16ITH6 brook]# mount UUID="dbe875a9-94a3-4831-8697-0e869ec4a65f" /mnt/ubuntu-root/home
    [root@ARCH-16ITH6 brook]# mount UUID="CEFD-1322" /mnt/ubuntu-root/boot/efi
    [root@ARCH-16ITH6 brook]# mount -t proc /proc /mnt/ubuntu-root/proc/
    [root@ARCH-16ITH6 brook]# mount -t sysfs /sys /mnt/ubuntu-root/sys/
    [root@ARCH-16ITH6 brook]# mount -o bind /dev /mnt/ubuntu-root/dev/
    [root@ARCH-16ITH6 brook]# mount -o bind /dev/pts /mnt/ubuntu-root/dev/pts/
    [root@ARCH-16ITH6 brook]# mount -o bind /run /mnt/ubuntu-root/run/
    [root@ARCH-16ITH6 brook]# mount -o bind /sys/firmware/efi/efivars /mnt/ubuntu-root/sys/firmware/efi/efivars/
    [root@ARCH-16ITH6 brook]# mkdir /mnt/ubuntu-root/{opt,root,srv,usr/local,var/cache,var/log,var/tmp}
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/opt -o subvol=/@/@opt
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/root -o subvol=/@/@root
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/srv -o subvol=/@/@srv
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/var/cache -o subvol=/@/@varcache
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/var/log -o subvol=/@/@varlog
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/var/tmp -o subvol=/@/@vartmp
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/usr/local -o subvol=/@/@usrlocal
  2. Copy /etc/resolv.conf from the external Linux chroot host to the installed Ubuntu, the installation into which we are chrooting.
    [root@ARCH-16ITH6 brook]# cp /etc/resolv.conf /mnt/ubuntu-root/etc/resolv.conf
    If the Ubuntu installation uses systemd-resolved service, /etc/resolv.conf will be a symbolic link to /run/systemd/resolv/stub-resolv.conf. It will be necessary to unlink the file with
    unlink /mnt/ubuntu-root/etc/resolv.conf
  3. Chroot into the installed Ubuntu system.
    [root@ARCH-16ITH6 brook]# chroot /mnt/ubuntu-root /bin/bash
  4. Verify the chroot by viewing the contents of /etc/os-release. The external Linux used for this process was Arch, so the output in the following listing confirms the chroot is active.
    root@ARCH-16ITH6:/# cat /etc/os-release
    PRETTY_NAME="Ubuntu 24.10"
    NAME="Ubuntu"
    VERSION_ID="24.10"
    VERSION="24.10 (Oracular Oriole)"
    VERSION_CODENAME=oracular
    ID=ubuntu
    ID_LIKE=debian
    HOME_URL="https://www.ubuntu.com/"
    SUPPORT_URL="https://help.ubuntu.com/"
    BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
    PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
    UBUNTU_CODENAME=oracular
    LOGO=ubuntu-logo
  5. Install Snapper.
    root@ARCH-16ITH6:/# apt install snapper
    The following package was automatically installed and is no longer required:
      libiw30t64
    Use 'apt autoremove' to remove it.
    
    Installing:
      snapper
    
    Installing dependencies:
      libbtrfs0t64  libsnapper7t64
    
    Summary:
      Upgrading: 0, Installing: 3, Removing: 0, Not Upgrading: 1
      Download size: 761 kB
      Space needed: 2,930 kB / 45.8 GB available
    
    Continue? [Y/n]
    Get:1 http://us.archive.ubuntu.com/ubuntu oracular/main amd64 libbtrfs0t64 amd64 6.6.3-1.2 [13.5 kB]
    Get:2 http://us.archive.ubuntu.com/ubuntu oracular/universe amd64 libsnapper7t64 amd64 0.10.6-1.1build1 [307 kB]
    Get:3 http://us.archive.ubuntu.com/ubuntu oracular/universe amd64 snapper amd64 0.10.6-1.1build1 [441 kB]
    Fetched 761 kB in 1s (917 kB/s)
    Selecting previously unselected package libbtrfs0t64:amd64.
    (Reading database ... 426979 files and directories currently installed.)
    Preparing to unpack .../libbtrfs0t64_6.6.3-1.2_amd64.deb ...
    Unpacking libbtrfs0t64:amd64 (6.6.3-1.2) ...
    Selecting previously unselected package libsnapper7t64:amd64.
    Preparing to unpack .../libsnapper7t64_0.10.6-1.1build1_amd64.deb ...
    Unpacking libsnapper7t64:amd64 (0.10.6-1.1build1) ...
    Selecting previously unselected package snapper.
    Preparing to unpack .../snapper_0.10.6-1.1build1_amd64.deb ...
    Unpacking snapper (0.10.6-1.1build1) ...
    Setting up libbtrfs0t64:amd64 (6.6.3-1.2) ...
    Setting up libsnapper7t64:amd64 (0.10.6-1.1build1) ...
    Setting up snapper (0.10.6-1.1build1) ...
    Created symlink '/etc/systemd/system/timers.target.wants/snapper-boot.timer' →
    '/usr/lib/systemd/system/snapper-boot.timer'.
    Created symlink '/etc/systemd/system/timers.target.wants/snapper-cleanup.timer' →
    '/usr/lib/systemd/system/snapper-cleanup.timer'.
    Created symlink '/etc/systemd/system/timers.target.wants/snapper-timeline.timer' →
    '/usr/lib/systemd/system/snapper-timeline.timer'.
    Created symlink '/etc/systemd/system/sysinit.target.wants/snapperd.service' →
    '/usr/lib/systemd/system/snapperd.service'.
    Running in chroot, ignoring command 'daemon-reload'
    Running in chroot, ignoring command 'is-active'
    snapper-boot.service is a disabled or a static unit, not starting it.
    Running in chroot, ignoring command 'is-active'
    Running in chroot, ignoring command 'is-active'
    snapper-cleanup.service is a disabled or a static unit, not starting it.
    Running in chroot, ignoring command 'is-active'
    Running in chroot, ignoring command 'is-active'
    snapper-timeline.service is a disabled or a static unit, not starting it.
    Running in chroot, ignoring command 'is-active'
    Running in chroot, ignoring command 'is-active'
    Running in chroot, ignoring command 'start'
    Processing triggers for man-db (2.12.1-3) ...
    Processing triggers for dbus (1.14.10-4ubuntu5) ...
    Processing triggers for libc-bin (2.40-1ubuntu3) ...
  6. List the subvolumes for comparison of the current state to the state after the next step.
    root@ARCH-16ITH6:/# btrfs subvolume list /
    ID 257 gen 1192 top level 5 path @
    ID 258 gen 1171 top level 257 path @opt
    ID 259 gen 1190 top level 257 path @root
    ID 260 gen 1177 top level 257 path @srv
    ID 261 gen 1192 top level 257 path @usrlocal
    ID 262 gen 1193 top level 257 path @varcache
    ID 263 gen 1192 top level 257 path @varlog
    ID 264 gen 1179 top level 257 path @vartmp
  7. Create the Snapper configuration for managing snapshots of the subvolume mounted at the filesystem hierarchy root.
    root@ARCH-16ITH6:/# snapper --no-dbus -c root create-config /
  8. List the subvolumes to compare to the previous state. Now, after the Snapper configuration command, there is a new subvolume called .snapshots under the /@ subvolume.
    root@ARCH-16ITH6:/# btrfs subvolume list /
    ID 257 gen 1192 top level 5 path @
    ID 258 gen 1171 top level 257 path @opt
    ID 259 gen 1190 top level 257 path @root
    ID 260 gen 1177 top level 257 path @srv
    ID 261 gen 1192 top level 257 path @usrlocal
    ID 262 gen 1193 top level 257 path @varcache
    ID 263 gen 1192 top level 257 path @varlog
    ID 264 gen 1179 top level 257 path @vartmp
    ID 265 gen 1194 top level 257 path .snapshots
  9. Create initial snapshot subvolume under /@/.snapshots.
    root@ARCH-16ITH6:/# btrfs subvolume create -p /.snapshots/1/snapshot
    Create subvolume '/.snapshots/1/snapshot'
  10. List the subvolumes. There is a new subvolume at subvolume path /@/.snapshots/1/snapshot.
    root@ARCH-16ITH6:/# btrfs subvolume list /
    ID 257 gen 1194 top level 5 path @
    ID 258 gen 1171 top level 257 path @opt
    ID 259 gen 1190 top level 257 path @root
    ID 260 gen 1177 top level 257 path @srv
    ID 261 gen 1192 top level 257 path @usrlocal
    ID 262 gen 1193 top level 257 path @varcache
    ID 263 gen 1192 top level 257 path @varlog
    ID 264 gen 1179 top level 257 path @vartmp
    ID 265 gen 1194 top level 257 path .snapshots
    ID 266 gen 1195 top level 265 path .snapshots/1/snapshot
  11. Exit the chroot.
    root@ARCH-16ITH6:/# exit
    exit

Part 6: Move Files from Subvolume Path /@ to Subvolume Path /@/.snapshots/1/snapshot

At this point in the process, the contents of the paths /opt, /root, /usr/local, /var/cache, /var/log, and /var/tmp that were in the top-level subvolume immediately after the conversion have been relocated to their respective subvolumes under subvolume path /@. The remaining contents of the top-level subvolume were moved temporarily to /@. In this part we move the contents of /@ to the initial snapshot subvolume, /@/.snapshots/1/snapshot, realizing the desired subvolume layout illustrated in the right part of the image at the top of the article.

  1. Move files from subvolume path /@ to subvolume path /@/.snapshots/1/snapshot. To avoid the errors shown in the output, first unmount the EFI System Partition and the pseudo filesystems, mounted for the chroot.
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/* /mnt/ubuntu-root/.snapshots/1/snapshot/
    mv: cannot move '/mnt/ubuntu-root/boot/efi' to '/mnt/ubuntu-root/.snapshots/1/snapshot/boot/efi': Device or resource busy
    mv: cannot move '/mnt/ubuntu-root/dev' to '/mnt/ubuntu-root/.snapshots/1/snapshot/dev': Device or resource busy
    mv: cannot move '/mnt/ubuntu-root/proc' to '/mnt/ubuntu-root/.snapshots/1/snapshot/proc': Device or resource busy
    mv: cannot move '/mnt/ubuntu-root/run' to '/mnt/ubuntu-root/.snapshots/1/snapshot/run': Device or resource busy
    mv: cannot move '/mnt/ubuntu-root/sys' to '/mnt/ubuntu-root/.snapshots/1/snapshot/sys': Device or resource busy
    [root@ARCH-16ITH6 brook]# ls -la /mnt/ubuntu-root /mnt/ubuntu-root/.snapshots/1/snapshot
    /mnt/ubuntu-root:
    total 0
    drwxr-xr-x   1 root root   54 Oct 11 20:32 .
    drwxr-xr-x   1 root root   22 Oct 10 17:16 ..
    drwxr-xr-x   1 root root  492 Oct 10 19:20 boot
    drwxr-xr-x  24 root root 5500 Oct 11 16:50 dev
    dr-xr-xr-x 505 root root    0 Oct 11 19:51 proc
    drwxr-xr-x  34 root root  960 Oct 11 18:50 run
    drwxr-x---   1 root root    2 Oct 11 20:08 .snapshots
    dr-xr-xr-x  13 root root    0 Oct 11 20:14 sys
    
    /mnt/ubuntu-root/.snapshots/1/snapshot:
    total 32
    drwxr-xr-x 1 root root  300 Oct 11 20:32 .
    drwxr-xr-x 1 root root   16 Oct 11 20:08 ..
    lrwxrwxrwx 1 root root    7 May 27  2022 bin -> usr/bin
    drwxr-xr-x 1 root root  486 Oct 10 19:20 boot
    drwxr-xr-x 1 root root    0 May 27  2022 cdrom
    drwxr-xr-x 1 root root 5098 Oct 11 20:06 etc
    drwxr-xr-x 1 root root   20 Oct 10 18:21 home
    -rw------- 1 root root   64 Oct 10 19:08 i7lp616G-el
    lrwxrwxrwx 1 root root    7 May 27  2022 lib -> usr/lib
    lrwxrwxrwx 1 root root    9 Oct  3 17:54 lib32 -> usr/lib32
    lrwxrwxrwx 1 root root    9 May 27  2022 lib64 -> usr/lib64
    lrwxrwxrwx 1 root root   10 May 27  2022 libx32 -> usr/libx32
    drwx------ 1 root root    0 May 27  2022 lost+found
    drwxr-xr-x 1 root root   44 Jul 10  2023 media
    drwxr-xr-x 1 root root   42 Nov 24  2023 mnt
    drwxr-xr-x 1 root root  106 Oct 11 19:16 @opt
    drwxr-xr-x 1 root root    0 Oct 11 19:59 opt
    drwxr-xr-x 1 root root  256 Oct 11 19:19 @root
    drwxr-xr-x 1 root root    0 Oct 11 19:59 root
    lrwxrwxrwx 1 root root    8 May 27  2022 sbin -> usr/sbin
    drwxr-xr-x 1 root root   18 May 26  2023 snap
    drwxr-xr-x 1 root root    0 Oct 11 19:01 @srv
    drwxr-xr-x 1 root root    0 Oct 11 19:59 srv
    drwxrwxrwt 1 root root  384 Oct 10 19:20 tmp
    drwxr-xr-x 1 root root  116 Oct 11 19:59 usr
    drwxr-xr-x 1 root root   86 Oct 11 19:20 @usrlocal
    drwxr-xr-x 1 root root  130 Oct 11 19:59 var
    drwxr-xr-x 1 root root  306 Oct 11 19:23 @varcache
    drwxr-xr-x 1 root root 2808 Oct 11 19:24 @varlog
    drwxr-xr-x 1 root root    0 Oct 11 19:02 @vartmp
    -rw------- 1 root root   64 Oct  3 18:13 zNUV1bYp-el
    [root@ARCH-16ITH6 brook]# ls -la /mnt/ubuntu-root/.* /mnt/ubuntu-root/.snapshots/1/snapshot
    /mnt/ubuntu-root/.snapshots:
    total 0
    drwxr-x--- 1 root root 2 Oct 11 20:08 .
    drwxr-xr-x 1 root root 54 Oct 11 20:32 ..
    drwxr-xr-x 1 root root 16 Oct 11 20:08 1
    
    /mnt/ubuntu-root/.snapshots/1/snapshot:
    total 32
    drwxr-xr-x 1 root root 300 Oct 11 20:32 .
    drwxr-xr-x 1 root root 16 Oct 11 20:08 ..
    lrwxrwxrwx 1 root root 7 May 27 2022 bin -> usr/bin
    drwxr-xr-x 1 root root 486 Oct 10 19:20 boot
    drwxr-xr-x 1 root root 0 May 27 2022 cdrom
    drwxr-xr-x 1 root root 5098 Oct 11 20:06 etc
    drwxr-xr-x 1 root root 20 Oct 10 18:21 home
    -rw------- 1 root root 64 Oct 10 19:08 i7lp616G-el
    lrwxrwxrwx 1 root root 7 May 27 2022 lib -> usr/lib
    lrwxrwxrwx 1 root root 9 Oct 3 17:54 lib32 -> usr/lib32
    lrwxrwxrwx 1 root root 9 May 27 2022 lib64 -> usr/lib64
    lrwxrwxrwx 1 root root 10 May 27 2022 libx32 -> usr/libx32
    drwx------ 1 root root 0 May 27 2022 lost+found
    drwxr-xr-x 1 root root 44 Jul 10 2023 media
    drwxr-xr-x 1 root root 42 Nov 24 2023 mnt
    drwxr-xr-x 1 root root 106 Oct 11 19:16 @opt
    drwxr-xr-x 1 root root 0 Oct 11 19:59 opt
    drwxr-xr-x 1 root root 256 Oct 11 19:19 @root
    drwxr-xr-x 1 root root 0 Oct 11 19:59 root
    lrwxrwxrwx 1 root root 8 May 27 2022 sbin -> usr/sbin
    drwxr-xr-x 1 root root 18 May 26 2023 snap
    drwxr-xr-x 1 root root 0 Oct 11 19:01 @srv
    drwxr-xr-x 1 root root 0 Oct 11 19:59 srv
    drwxrwxrwt 1 root root 384 Oct 10 19:20 tmp
    drwxr-xr-x 1 root root 116 Oct 11 19:59 usr
    drwxr-xr-x 1 root root 86 Oct 11 19:20 @usrlocal
    drwxr-xr-x 1 root root 130 Oct 11 19:59 var
    drwxr-xr-x 1 root root 306 Oct 11 19:23 @varcache
    drwxr-xr-x 1 root root 2808 Oct 11 19:24 @varlog
    drwxr-xr-x 1 root root 0 Oct 11 19:02 @vartmp
    -rw------- 1 root root 64 Oct 3 18:13 zNUV1bYp-el
    [root@ARCH-16ITH6 brook]# btrfs subvolume list /mnt/ubuntu-root
    ID 257 gen 1209 top level 5 path @
    ID 258 gen 1171 top level 266 path .snapshots/1/snapshot/@opt
    ID 259 gen 1196 top level 266 path .snapshots/1/snapshot/@root
    ID 260 gen 1177 top level 266 path .snapshots/1/snapshot/@srv
    ID 261 gen 1192 top level 266 path .snapshots/1/snapshot/@usrlocal
    ID 262 gen 1193 top level 266 path .snapshots/1/snapshot/@varcache
    ID 263 gen 1192 top level 266 path .snapshots/1/snapshot/@varlog
    ID 264 gen 1179 top level 266 path .snapshots/1/snapshot/@vartmp
    ID 265 gen 1210 top level 257 path .snapshots
    ID 266 gen 1211 top level 265 path .snapshots/1/snapshot
  2. Correct the error in the previous step.
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/.snapshots/1/snapshot/@opt /mnt/ubuntu-root/
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/.snapshots/1/snapshot/@root /mnt/ubuntu-root/
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/.snapshots/1/snapshot/@srv /mnt/ubuntu-root/
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/.snapshots/1/snapshot/@usrlocal /mnt/ubuntu-root/
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/.snapshots/1/snapshot/@varcache /mnt/ubuntu-root/
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/.snapshots/1/snapshot/@vartmp /mnt/ubuntu-root/
    [root@ARCH-16ITH6 brook]# mv /mnt/ubuntu-root/.snapshots/1/snapshot/@varlog /mnt/ubuntu-root/
    [root@ARCH-16ITH6 brook]# btrfs subvolume list /mnt/ubuntu-root
    ID 257 gen 1212 top level 5 path @
    ID 258 gen 1171 top level 257 path @opt
    ID 259 gen 1196 top level 257 path @root
    ID 260 gen 1177 top level 257 path @srv
    ID 261 gen 1192 top level 257 path @usrlocal
    ID 262 gen 1193 top level 257 path @varcache
    ID 263 gen 1192 top level 257 path @varlog
    ID 264 gen 1179 top level 257 path @vartmp
    ID 265 gen 1210 top level 257 path .snapshots
    ID 266 gen 1212 top level 265 path .ssnapshots/1/snapshot
    We can also verify that files that should be in the subvolume and not in the initial snapshot subvolume are where they should be. For example the contents of what would normally in /var/cache are now in the @varcache subvolume. Later we will mount this subvolume to /var/cache in /etc/fstab, along with the other subvolumes that should be excluded from the snapshot subvolume.
    [root@ARCH-16ITH6 brook]# ls -la /mnt/ubuntu-root/.snapshots/1/snapshot/var/cache /mnt/ubuntu-root/@varcache
    /mnt/ubuntu-root/.snapshots/1/snapshot/var/cache:
    total 0
    drwxr-xr-x 1 root root   0 Oct 11 19:59 .
    drwxr-xr-x 1 root root 130 Oct 11 19:59 ..
    
    /mnt/ubuntu-root/@varcache:
    total 0
    drwxr-xr-x 1 root root      306 Oct 11 19:23 .
    drwxr-xr-x 1 root root      144 Oct 11 20:45 ..
    drwxr-xr-x 1 root root        0 May 17  2022 adduser
    drwxr-xr-x 1 root root      160 Oct  3 18:18 apparmor
    drwxr-xr-x 1 root root        8 Apr 19  2022 app-info
    drwxr-xr-x 1 root root       70 Oct 11 20:06 apt
    drwxr-xr-x 1 root root      120 Apr 19  2022 cracklib
    drwxrwx--- 1 root      7    112 Oct 10 19:08 cups
    drwxr-xr-x 1  132    122      0 May 26  2023 cups-browsed
    drwxr-xr-x 1 root root      146 Oct 10 19:09 debconf
    drwxr-xr-x 1 root root      310 Apr 19  2022 dictionaries-common
    drwxr-xr-x 1 root root   695866 Oct 10 19:20 fontconfig
    drwxr-xr-x 1 root root        0 Sep  4  2021 fonts
    drwxr-xr-x 1 root root       98 Oct  3 18:11 fwupd
    drwxr-xr-x 1  129 usbmux      0 Jun 29  2022 fwupdmgr
    drwx------ 1 root root       18 Oct 11 20:06 ldconfig
    drwxr-xr-x 1 root root        8 May 27  2022 lightdm
    drwxr-xr-x 1    6 mail      304 Oct 11 20:06 man
    drwxr-xr-x 1 root root       18 Apr 19  2022 PackageKit
    drwx------ 1 root root        0 Feb 25  2023 private
    drwxr-xr-x 1 root root        0 Mar 31  2023 samba
    drwxr-xr-x 1 root root       10 Apr 19  2022 swcatalog
    This is the case for the /root
    [root@ARCH-16ITH6 brook]# ls -la /mnt/ubuntu-root/.snapshots/1/snapshot/root /mnt/ubuntu-root/@root
    /mnt/ubuntu-root/@root:
    total 36
    drwxr-xr-x 1 root root   256 Oct 11 19:19 .
    drwxr-xr-x 1 root root   144 Oct 11 20:45 ..
    -rw------- 1 root root  1390 Oct 11 20:12 .bash_history
    -rw-r--r-- 1 root root  3106 Oct 15  2021 .bashrc
    drwx------ 1 root root   238 Nov 23  2023 .cache
    drwx------ 1 root root    18 Mar 14  2023 .config
    drwx------ 1 root root    22 Feb 25  2023 .dbus
    drwx------ 1 root root    34 May 24  2022 .launchpadlib
    -rw------- 1 root root    44 Oct 11 18:03 .lesshst
    drwxr-xr-x 1 root root    20 Nov 27  2023 .local
    -rw-r--r-- 1 root root   161 Jul  9  2019 .profile
    drwx------ 1 root root    84 May 27  2022 snap
    drwx------ 1 root root     0 May 26  2023 .ssh
    -rw-r--r-- 1 root root     0 Mar  8  2023 .sudo_as_admin_successful
    drwxr-xr-x 1 root root    20 Dec  2  2023 .vim
    -rw------- 1 root root 13339 Dec  2  2023 .viminfo
    -rw-r--r-- 1 root root   165 Nov 24  2023 .wget-hsts
    
    /mnt/ubuntu-root/.snapshots/1/snapshot/root:
    total 0
    drwxr-xr-x 1 root root   0 Oct 11 19:59 .
    drwxr-xr-x 1 root root 210 Oct 11 20:45 ..
    The listings of the other excluded subvolumes are not shown, but they are like /var/cache and /root, in their respective subvolumes.

Part 7: Modify /etc/fstab to Reflect New BtrfsFilesystem and Subvolume Layout

  1. Edit the /etc/fstab file by accessing it in the mount at its new location under the initial snapshot subvolume that will be the new filesystem hierarchy root.
    [root@ARCH-16ITH6 brook]# vim /mnt/ubuntu-root/.snapshots/1/snapshot/etc/fstab
    
    After the edit the file should look like the following listing (obtained after rebooting into the converted Ubuntu system with the subvolume layout configuration completed).
     ╭─ user: brook // host: UBUNTU-16ITH6 in ~ as 🧙 took 5m4s
     ╰─λ cat /etc/fstab
    # /etc/fstab: static file system information.
    #
    # Use 'blkid' to print the universally unique identifier for a
    # device; this may be used with UUID= as a more robust way to name devices
    # that works even if disks are added and removed. See fstab(5).
    #
    #                                                                                                
    # / was on /dev/nvme0n1p7 during installation
    #UUID=be2c6775-9f46-4851-84da-74a979206977      /                               ext4    errors=remount-ro                               0       1
    # After conversion to Btrfs
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /                               btrfs   compress=zstd:3                                 0       0
    # /boot/efi was on /dev/nvme0n1p1 during installation
    UUID=CEFD-1322                                  /boot/efi                       vfat    umask=0077                                      0       1
    # /home was on /dev/nvme1n1p4 during installation
    UUID=dbe875a9-94a3-4831-8697-0e869ec4a65f       /home                           ext4    defaults                                        0       2
    # swap was on /dev/nvme1n1p1 during installation
    UUID=980ddbbf-9b79-4390-be21-850e62b7ebb2       none                            swap    sw                                              0       0
    # After conversion to Btrfs. New .snapshots subvolume
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /.snapshots                     btrfs   subvol=@/.snapshots,compress=zstd:3             0       0
    # After conversion to Btrfs. New @opt subvolume
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /opt                            btrfs   subvol=@/@opt,compress=zstd:3                   0       0
    # After conversion to Btrfs. New @root subvolume
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /root                           btrfs   subvol=@/@root,compress=zstd:3                  0       0
    # After conversion to Btrfs. New @srv subvolume
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /srv                            btrfs   subvol=@/@srv,compress=zstd:3                   0       0
    # After conversion to Btrfs. New @usrlocal subvolume
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /usr/local                      btrfs   subvol=@/@usrlocal,compress=zstd:3              0       0
    # After conversion to Btrfs. New @varcache subvolume
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /var/cache                      btrfs   subvol=@/@varcache,compress=zstd:3              0       0
    # After conversion to Btrfs. New @varlog subvolume
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /var/log                        btrfs   subvol=@/@varlog,compress=zstd:3                0       0
    # After conversion to Btrfs. New @vartmp subvolume
    UUID=72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43       /var/tmp                        btrfs   subvol=@/@vartmp,compress=zstd:3                0       0
    ############################################################################################################
    UUID=4ad30c04-8dc6-4a6b-9052-315a1d22a48f       /home/brook/DataEXT4-16ITH6     ext4    defaults                                        0       2
    UUID=3AC1235B3FBAFA8C                           /home/brook/DataNTFS-16ITH6     ntfs3   uid=1000,gid=100,dmask=022,fmask=133,discard    0       2
    UUID=bb03c8fc-c6f8-452d-a28b-360f405b7c97       /home/brook/VMs                 ext4    defaults                                        0       2
    
     ╭─ user: brook // host: UBUNTU-16ITH6 in ~ as 🧙 took 3ms
     ╰─λ 
    The before edit and after edit state of the file from the external Linux at this point in the process is shown in the following set of images.
    Figures 3a and 3b: Editing /etc/fstab
    The file is in the subvolume -- in terms of subvolume path -- /@/.snapshots/1/snapshot, which we will be automatically mounted at the filesystem hierarchy root when we set it as the Btrfsdefault subvolume.

Part 8: Modify GRUB Scripts in /etc/grub.d

The next step is to modify two of the GRUB scripts in /etc/grub.d that are executed when making the or updating the GRUB configuration file, /etc/grub/grub.cfg by grub-mkconfig, or in Ubuntu by update-grub, which actually executes grub-mkconfig. These two scripts set the subvolume to be used as the root of the filesystem hierarchy in a way that is not compatible with our desired layout and Snapper.

  1. Modify the Ubuntu installation's /etc/grub.d/10_linux file by accessing it in the mount at its new location under the initial snapshot subvolume that will be the new filesystem hierarchy root by removing the string rootflags=subvol=${rootsubvol} .
    [root@ARCH-16ITH6 brook]# vim /mnt/ubuntu-root/.snapshots/1/snapshot/etc/grub.d/10_linux
    Figures 4a, 4b, and 4c: Editing /etc/grub.d/10_linux
    The above images show the original state of the file, the state of the file as it is being modifyied in vim, and the state of the file after the modification.
  2. Modify the Ubuntu installation's /etc/grub.d/20_linux_xen file by accessing it in the mount at its new location under the initial snapshot subvolume that will be the new filesystem hierarchy root by removing the string rootflags=subvol=${rootsubvol} .
    [root@ARCH-16ITH6 brook]# vim /mnt/ubuntu-root/.snapshots/1/snapshot/etc/grub.d/20_linux_xen
    Figures 5a, 5b, and 5c: Editing /etc/grub.d/20_linux_xen

Part 9: Make Directory to Be Used as a Mountpoint for the .snapshots Subvolume in the Initial Snapshot Subvolume

In our final converted Ubuntu system, the snapshots subvolume will be mounted at /.snapshots filesystem hierarchy path. Remember that in Part 7 we have already created an entry in the Ubuntu installation's /etc/fstab for this mount. We now make a directory to mount our snapshots subvolume. Because in the final system the fileystem hierarchy root is actually the subvolume /@/.snapshots/1/snapshot, and because at this point in the process the Ubuntu installation's /@ subvolume is mounted in the external Linux at /mnt/ubuntu-root, we can make the directory at /mnt/ubuntu-root/.snapshots/1/snapshot/.snapshot. When we reboot into the Ubuntu system, the snapshots subvolume will be accessible at /.snapshots as intended and as requied by Snapper.

  1. Make the directory for mounting the snapshots subvolume.
    [root@ARCH-16ITH6 brook]# mkdir /mnt/ubuntu-root/.snapshots/1/snapshot/.snapshots

Part 10: Change the Default BtrfsSubvolume from /, Subvolume ID 5 to the Initial Snapshot Subvolume /@/.snapshots/1/snapshot

  1. View the current default subvolume as set by the btrfs-convert program.
    [root@ARCH-16ITH6 brook]# btrfs subvolume get-default /mnt/ubuntu-root
    ID 5 (FS_TREE)
  2. Get the subvolume ID of the initial snapshots subvolume.
    [root@ARCH-16ITH6 brook]# sudo btrfs subvolume list /mnt/ubuntu-root
    ID 257 gen 1214 top level 5 path @
    ID 258 gen 1216 top level 257 path @opt
    ID 259 gen 1216 top level 257 path @root
    ID 260 gen 1177 top level 257 path @srv
    ID 261 gen 1192 top level 257 path @usrlocal
    ID 262 gen 1215 top level 257 path @varcache
    ID 263 gen 1215 top level 257 path @varlog
    ID 264 gen 1216 top level 257 path @vartmp
    ID 265 gen 1210 top level 257 path .snapshots
    ID 266 gen 1223 top level 265 path .snapshots/1/snapshot
    From the listing we can see the subvolume ID of the initial snapshot subvolume at subvolume path /@/.snapshots/1/snapshot is 266.
  3. Set the Btrfsdefault subvolume to subvolume ID 266.
    [root@ARCH-16ITH6 brook]# btrfs subvolume set-default 266 /mnt/ubuntu-root
  4. Verify that the default subvolume was changed to subvolume ID 266.
    [root@ARCH-16ITH6 brook]# btrfs subvolume get-default /mnt/ubuntu-root
    ID 266 gen 1223 top level 265 path @/.snapshots/1/snapshot
  5. Unmount the Btrfspartition containing the Ubuntu installation.
    [root@ARCH-16ITH6 brook]# umount --recursive /mnt/ubuntu-root
  6. Verify that the partition and any of the subvolumes are no longer mounted.
    [root@ARCH-16ITH6 brook]# mount | grep /mnt
    There should not be any output.

Part 11: Chroot to Installed System and Reinstall GRUB Firmware, Update GRUB, and Regenerate Initramfs

  1. In the external Linux installation or the live ISO, mount the initial snapshot subvolume, i.e., subvolume path /@/snapshots/1/snapshot.
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root
  2. Verify the mount.
    [root@ARCH-16ITH6 brook]# mount | grep /mnt
    /dev/nvme1n1p7 on /mnt/ubuntu-root type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=266,subvol=/@/.snapshots/1/snapshot)
  3. Mount the home partition.
    mount UUID="dbe875a9-94a3-4831-8697-0e869ec4a65f" /mnt/ubuntu-root/home
  4. Make a mount point for the EFI System Partition and mount it.
    [root@ARCH-16ITH6 brook]# mkdir /mnt/ubuntu-root/boot/efi
    [root@ARCH-16ITH6 brook]# mount UUID="CEFD-1322" /mnt/ubuntu-root/boot/efi
  5. Mount the subvolumes for the excluded filesystem hierarchy paths.
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/opt -o subvol=/@/@opt
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/root -o subvol=/@/@root
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/srv -o subvol=/@/@srv
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/var/cache -o subvol=/@/@varcache
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/var/log -o subvol=/@/@varlog
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/var/tmp -o subvol=/@/@vartmp
    [root@ARCH-16ITH6 brook]# mount UUID="72aaa46b-9109-4c3d-9b63-8c8e3cc7fd43" /mnt/ubuntu-root/usr/local -o subvol=/@/@usrlocal
  6. Make mountpoints for the pseudo filesystems and mount them.
    [root@ARCH-16ITH6 brook]# mkdir -p /mnt/ubuntu-root/{dev,dev/pts,proc,run,sys}
    [root@ARCH-16ITH6 brook]# mount -t proc /proc /mnt/ubuntu-root/proc/
    [root@ARCH-16ITH6 brook]# mount -t sysfs /sys /mnt/ubuntu-root/sys/
    [root@ARCH-16ITH6 brook]# mount -o bind /dev /mnt/ubuntu-root/dev/
    [root@ARCH-16ITH6 brook]# mount -o bind /dev/pts /mnt/ubuntu-root/dev/pts/
    [root@ARCH-16ITH6 brook]# mount -o bind /run /mnt/ubuntu-root/run/
    [root@ARCH-16ITH6 brook]# mount -o bind /sys/firmware/efi/efivars /mnt/ubuntu-root/sys/firmware/efi/efivars/
  7. Copy /etc/resolv.conf from the host system to the installed Ubuntu that we will chroot into.
    [root@ARCH-16ITH6 brook]# cp /etc/resolv.conf /mnt/ubuntu-root/etc/resolv.conf
  8. Before chrooting, verify the mounted subvolumes.
    [root@ARCH-16ITH6 brook]# mount | grep /mnt/ubuntu-root
    /dev/nvme1n1p7 on /mnt/ubuntu-root type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=266,subvol=/@/.snapshots/1/snapshot)
    /dev/nvme0n1p4 on /mnt/ubuntu-root/home type ext4 (rw,relatime)
    /dev/nvme1n1p1 on /mnt/ubuntu-root/boot/efi type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
    /dev/nvme1n1p7 on /mnt/ubuntu-root/opt type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/@/@opt)
    /dev/nvme1n1p7 on /mnt/ubuntu-root/root type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=259,subvol=/@/@root)
    /dev/nvme1n1p7 on /mnt/ubuntu-root/srv type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=260,subvol=/@/@srv)
    /dev/nvme1n1p7 on /mnt/ubuntu-root/var/cache type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=262,subvol=/@/@varcache)
    /dev/nvme1n1p7 on /mnt/ubuntu-root/var/log type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=263,subvol=/@/@varlog)
    /dev/nvme1n1p7 on /mnt/ubuntu-root/var/tmp type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=264,subvol=/@/@vartmp)
    /dev/nvme1n1p7 on /mnt/ubuntu-root/usr/local type btrfs (rw,relatime,ssd,discard=async,space_cache=v2,subvolid=261,subvol=/@/@usrlocal)
    /proc on /mnt/ubuntu-root/proc type proc (rw,relatime)
    /sys on /mnt/ubuntu-root/sys type sysfs (rw,relatime)
    dev on /mnt/ubuntu-root/dev type devtmpfs (rw,nosuid,relatime,size=12158680k,nr_inodes=3039670,mode=755,inode64)
    devpts on /mnt/ubuntu-root/dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
    run on /mnt/ubuntu-root/run type tmpfs (rw,nosuid,nodev,relatime,mode=755,inode64)
    efivarfs on /mnt/ubuntu-root/sys/firmware/efi/efivars type efivarfs (rw,nosuid,nodev,noexec,relatime)
  9. Chroot into the Ubuntu system from the external Linux.
    [root@ARCH-16ITH6 brook]# chroot /mnt/ubuntu-root /bin/bash
  10. Verify the chroot.
    root@ARCH-16ITH6:/# cat /etc/os-release
    PRETTY_NAME="Ubuntu 24.10"
    NAME="Ubuntu"
    VERSION_ID="24.10"
    VERSION="24.10 (Oracular Oriole)"
    VERSION_CODENAME=oracular
    ID=ubuntu
    ID_LIKE=debian
    HOME_URL="https://www.ubuntu.com/"
    SUPPORT_URL="https://help.ubuntu.com/"
    BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
    PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
    UBUNTU_CODENAME=oracular
    LOGO=ubuntu-logo
  11. Swich to root.
    root@ARCH-16ITH6:/# su
  12. View GRUB variables.
    root@ARCH-16ITH6:/# grub-editenv list
    Unlike in Fedora (see ), there are no GRUB variables in my Ubuntu installation, continuously upgraded since 22.04.
  13. Re-install GRUB EFI application to ESP.
    root@ARCH-16ITH6:/# grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Ubuntu
    Oct 12 20:21:01 UBUNTU-16ITH6 sudo[7250]:    brook : TTY=pts/1 ; PWD=/home/brook ; USER=root ; COMMAND=/usr/sbin/grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Ubuntu
  14. Update GRUB configuration.
    root@ARCH-16ITH6:/# update-grub
    Oct 12 20:34:27 UBUNTU-16ITH6 sudo[9003]:    brook : TTY=pts/1 ; PWD=/home/brook ; USER=root ; COMMAND=/usr/sbin/update-grub
  15. Update the initramfs.
    root@ARCH-16ITH6:/# update-initramfs
    Oct 14 22:30:38 UBUNTU-16ITH6 sudo[37896]:    brook : TTY=pts/2 ; PWD=/home/brook ; USER=root ; COMMAND=/usr/sbin/update-initramfs -v -c -k all

Figure 6: Reinstalling the GRUB Firmware Application and Updating the GRUB Configuration

Part 12: Reboot Into Converted System with BtrfsSubvolume Layout and Working Snapper Configuration and Make Final Adjustments to System Configuration

We can now reboot into the converted Ubuntu system, which at this point will have the subvolume layout illustrated in the diagram at the top of the article, similar to the one used by openSUSE. The initial snapshot subvolume is automatically mounted at the filesystem hierarchy root / without explicitly specifying the subvolume identifiers in the /etc/fstab mount options. Snapper has been configured and is usable, fully able to rollback the system and set the new post-rollback read-write snapshot it creates as the new subvolume for the filesystem hierarchy root. Snapper is even integrated into APT (see, below) such that pre and post snapshots are automatically created during package management transactions.

The following image of some Konsole panes with outputs of various commands show the characteristics of the system.

Figure 7: The Converted Ubuntu Installation's Btrfs characteristics

APT Integration for Automatic Snapshot Creation Druing Package Management Transactions

Figure 8: Snapper Snapshots on the Converted Ubuntu and Corresponding Subvolumes

Automatic Snapper Snapshot Creation with systemd Timers

Automatic Snapper Snapshot Cleanup

Testing Rollbacks

Figures 9a, 9b, 9c, and 9d: Performing a Rollback with Snapper

References

  1. Bitrot and atomic COWs: Inside “next-gen” filesystems
  2. Ubuntu Manpages: btrfs-convert(8)
  3. How to Defrag an ext4 Filesystem in Linux
  4. Using e2fsck to Find and Repair Disk Errors On ext File Systems
  5. Ubuntu Manpages: mkfs.btrfs(8)
  6. Storage Block Sizes and the blockdev Command
  7. StackExchange, Unix & Linux: How do you move all files (including hidden) from one directory to another?