btrfs

My previous post described how I migrated my girlfriend’s father from Windows 10 to Linux Mint. While doing so, I used the default Ext4 filesystem, as opposed to Btrfs, for the root partition. No big deal, but Btrfs makes it a whole lot easier snapshotting the filesystem and rolling back if needed. Timeshift can automatically take care of all that, and it allows for some more peace of mind while configuring automatic OS updates.

I decided to document how to convert the Ext4 root filesystem to Btrfs, just in case for when I (inevitably) need to do some maintenance on my girlfriend’s father’s computer, such as an OS upgrade. I found a how-to by Klinsmann Öteyo which proved very useful for most steps, apart for some edge-cases such as Btrfs subvolume naming differences to facilitate Timeshift, or when using an encrypted setup (LUKS+LVM).

First, boot with a Linux Mint live image, and identify the partitions on disk:

root # fdisk -l

For me it was:

  • /dev/nvme0n1p1: fat32 ESP (/boot);

  • /dev/nvme0n1p2: ext4 root (/).

Note

Or, if you decided to encrypt the system during installation, like so:

ubiquity encrypt

Then you’d likely have something similar to:

  • /dev/nvme0n1p1: fat32 ESP (/boot/efi);

  • /dev/nvme0n1p2: ext4 boot (/boot);

  • /dev/nvme0n1p3: LUKS encrypted, and when unlocked:

    • /dev/mapper/nvme0n1p3_crypt: LVM physical volume:

      • vgmint: LVM volume group:

        • root: LVM logical volume:

          • /dev/mapper/vgmint-root: ext4 root (/);

        • swap_1: LVM logical volume:

          • /dev/mapper/vgmint-swap_1: swap (swap).

If so, decrypt your LUKS encrypted partition:

root # cryptsetup open /dev/nvme0n1p3 nvme0n1p3_crypt

And replace /dev/nvme0n1p2 with /dev/mapper/vgmint-root for the remainder of this article.

Verify the identified root filesystem is indeed Ext4:

root # file -sL /dev/nvme0n1p2
/dev/nvme0n1p2: Linux rev 1.0 ext4 filesystem data, UUID=881a1715-51b1-4448-ae03-96ff8dfa74d9 (extents) (64bit) (large files) (huge files)

As a precaution, you may wish to check the filesystem for consistency:

root # fsck.ext4 -fyv /dev/nvme0n1p2
e2fsck 1.47.0 (5-Feb-2023)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information

      473863 inodes used (45.73%, out of 1036320)
          83 non-contiguous files (0.0%)
         298 non-contiguous directories (0.1%)
             # of inodes with ind/dind/tind blocks: 0/0/0
             Extent depth histogram: 312233/16
     1902834 blocks used (45.96%, out of 4140032)
           0 bad blocks
           1 large file

      289110 regular files
       21780 directories
           8 character device files
           0 block device files
           0 fifos
           0 links
      162955 symbolic links (161597 fast symbolic links)
           1 socket
------------
      473854 files

If all is well, proceed with the actual conversion:

root # btrfs-convert /dev/nvme0n1p2
btrfs-convert from btrfs-progs v6.6.3

Source filesystem:
  Type:           ext2
  Label:
  Blocksize:      4096
  UUID:           7852c210-3e66-4a19-bf1c-80f28c6de338
Target filesystem:
  Label:
  Blocksize:      4096
  Nodesize:       16384
  UUID:           1cf2c199-9cb1-4e15-9037-585577e363b1
  Checksum:       crc32c
  Features:       extref, skinny-metadata, no-holes, free-space-tree (default)
    Data csum:    yes
    Inline data:  yes
    Copy xattr:   yes
Reported stats:
  Total space:     16957571072
  Free space:       8176861184 (48.22%)
  Inode count:         1036320
  Free inodes:          562457
  Block count:         4140032
Create initial btrfs filesystem
Create ext2 image file
Create btrfs metadata
Copy inodes [O] [    492209/    473863]
Free space cache cleared
Conversion complete

Once converted we need to make some changes to make sure the system properly boots. First, mount your new Btrfs filesystem, and make a new root subvolume called @:

root # mount /dev/nvme0n1p2 /mnt
root # btrfs subvolume create /mnt/@
Create subvolume '/mnt/@'

Then move everything in /mnt to the @ subvolume, except the @ subvolume itself, the ext2_saved subvolume (used for rollback purposes), and the home directory:

root # mv /mnt/!(@|ext2_saved|home) /mnt/@/

Make a new home subvolume called @home, move all home directories in there, and then move the emptied home directory:

root # btrfs subvolume create /mnt/@home
root # mv /mnt/home/* /mnt/@home/
root # rmdir /mnt/home/

Now we have a properly separated subvolume structure for Timeshift. Next we need to make sure the systems boots from the new filesystem. First, identify the new UUID of the Btrfs partition:

root # blkid /dev/nvme0n1p2
/dev/nvme0n1p2: UUID="df9680d5-1381-459e-a6e4-142033afc5f9" UUID_SUB="9e5abb70-37a0-4a57-8871-8ec3a1fbf39e" BLOCK_SIZE="4096" TYPE="btrfs" PARTUUID="a6373cab-b44a-451d-81c7-b6d1b399a5e1"

Edit /mnt/@/etc/fstab by commenting out the old root mount point, and add your new Btrfs subvolume mount points:

# UUID=be1efd93-90d3-473b-adda-eca6560055ce /               ext4    errors=remount-ro 0       1
UUID=df9680d5-1381-459e-a6e4-142033afc5f9 /               btrfs   defaults,subvol=@     0       1
UUID=df9680d5-1381-459e-a6e4-142033afc5f9 /home           btrfs   defaults,subvol=@home 0       2
Note

If you’re using an encrypted (LUKS+LVM) setup as earlier described and you’ve followed along thus far, then this is where to pay extra attention. This is what you’d probably need to change in /mnt/@/etc/fstab:

# /dev/mapper/vgmint-root /               ext4    errors=remount-ro 0       1
/dev/mapper/vgmint-root /               btrfs    defaults,subvol=@     0       1
/dev/mapper/vgmint-root /home           btrfs    defaults,subvol=@home 0       2

No need to update UUIDs.

Now we need to chroot into the new filesystem to fix GRUB. First, unmount /mnt, and then remount it using the @ subvolume. Then, also mount the ESP:

root # umount /mnt
root # mount -o subvol=@ /dev/nvme0n1p2 /mnt
root # mount /dev/nvme0n1p1 /mnt/boot/efi
Note

If you’re using an encrypted (LUKS+LVM) setup as earlier described and you’ve followed along thus far, then this is where to pay extra attention. After unmount /mnt, and remounting the Btrfs @ subvolume, you’d likely need to mount your separate boot partition, and then your ESP:

root # umount /mnt
root # mount -o subvol=@ /dev/mapper/vgmint-root /mnt
root # mount /dev/nvme0n1p2 /mnt/boot
root # mount /dev/nvme0n1p1 /mnt/boot/efi

Then make the necesarry arrangements to chroot into the new system:

root # mount -t proc /proc /mnt/proc
root # mount --rbind /dev /mnt/dev
root # mount --make-rslave /mnt/dev
root # mount --rbind /sys /mnt/sys
root # mount --make-rslave /mnt/sys
root # chroot /mnt /bin/bash

Then fix GRUB, and exit the chroot environment:

root (chroot) # grub-install /dev/nvme0n1
root (chroot) # update-grub
root (chroot) # exit

Now cleanup everything:

root # umount -R /mnt
Note

If you’re using an encrypted (LUKS+LVM) setup as earlier described and you’ve followed along thus far, then also properly close your LUKS encrypted partition:

root # cryptsetup close nvme0n1p3_crypt

No need to change /mnt/etc/crypttab either.

Then reboot, remove the live image, and your system should boot using your newly converted Btrfs filesystem. From there you can open and configure Timeshift.

timeshift

Then, open the Update Manager and open its preferences to configure automatic updates:

automatic updates

And don’t forget to remove the now unnecessary ext2_saved subvolume if you no longer need to rollback to Ext4 again:

root # mount /dev/nvme0n1p2 /mnt
root # btrfs subvolume delete /mnt/ext2_saved
Delete subvolume 256 (no-commit): '/mnt/ext2_saved'
root # umount /mnt