Distro Hopping Diaries: The Install

This is part two of three. Part one covered why I’m leaving Pop!_OS COSMIC for Ubuntu 26.04 Resolute Raccoon. Part three covers setting this rig up for AI/ML — dual NVIDIA, CUDA, the works. This post is the install itself: what the day actually looked like, including the parts that didn’t go to plan.

If you’re here for a clean step-by-step, this isn’t quite that. It’s a working day, written down. The commands are real and the order is real, but I’m not going to pretend the install was a smooth procession from boot to login. Linux installs rarely are.

The Live USB

USB written, machine rebooted, F2 to pick the boot device, GRUB up, “Try or Install Ubuntu” selected. Standard.

What wasn’t standard: as soon as the desktop came up, both my 4K monitors were live at native resolution with 125% fractional scaling already applied — already a better HiDPI experience than 24.04 ever managed on this hardware. GNOME 50 Wayland, NVIDIA drivers loaded from the live image, KVM-switched displays both behaving.

A Rite of Passage

I opened a terminal — Ptyxis, GNOME 50’s new default, genuinely nice — and started poking at the disks. Within a few minutes the desktop session froze and GNOME kicked me back to GDM. I logged back in. It happened again later. And again.

The pattern was random — different commands, different phases. Eventually I tried single-monitor on the 4060 Ti and the live session settled enough to finish; I suspect nouveau struggling with the 5070 Ti, or with dual 4K displays, or some combination, but I haven’t proven any of it. Not a great look for a flagship LTS three days in, even if the proper NVIDIA driver in the installed system handles all of this fine.

The TPM Detour

Time to install. Interactive installation, default selection, third-party software for graphics enabled (NVIDIA), Erase and Install Ubuntu, target the 1 TB NVMe.

Then the encryption screen, which is where the day got interesting.

In part one I said encrypted /boot wasn’t something I’d try this round. But the encryption screen now offers a “hardware-backed encryption” option with auto-unlock at boot — still LUKS underneath, but the key is sealed in the TPM and released automatically if the system measurements match. You also get a recovery key. Conceptually similar to FileVault — and interestingly, it might solve the unencrypted /boot problem I’ve had on Ubuntu for years.

I got excited. Picked it. Clicked next.

Hardware-backed encryption could not be enabled.

The error said the TPM was already in use, and offered three ways to clear it plus a manual path. Took the first option — clears the TPM and reboots. Back through the installer to the encryption screen. Same option, same error — but this time only the manual path was offered.

I had a guess. Pop!_OS doesn’t run with Secure Boot enabled (System76 kernel module signing makes this complicated), so my motherboard had been in “Other OS” mode — ASUS-speak for Secure Boot off. Into firmware: switched to “Windows” mode (Secure Boot on), cleared the keys to start fresh, made sure the discrete TPM was selected over the CPU’s fTPM. Saved, rebooted.

Back to the installer. Still failed. Tried again with default keys instead of cleared. Still failed.

At this point I’d burned forty-five minutes on something I’d told myself in part one I wasn’t even going to try. Made the call: passphrase encryption, document the attempt, come back another evening. Might be that this specific TPM/motherboard combination doesn’t play well with 26.04’s installer flow — that’s a rabbit hole for a focused session.

So: /boot stays unencrypted on this install. The same Ubuntu limitation as always, the gap Pop!_OS closes cleanly. I’d rather have a working machine tonight than spend the evening fighting firmware.

The Install Itself, and Then The Real Work

The standard install ran without drama. User created, timezone set, click the green button, watch the slideshow. Twenty minutes later: “Installation complete.”

Don’t click restart. Click “Continue testing.” We’re not done.

What Ubuntu’s installer just gave me is a perfectly reasonable system: ESP on partition 1, an unencrypted ext4 /boot on partition 2, a LUKS2 container on partition 3 holding an LVM physical volume, with a single logical volume formatted ext4 holding everything else. Mounted at /target in the live session. Functional. Boring. Not what I want.

What I want is btrfs with @ and @home subvolumes — the layout Timeshift’s btrfs mode expects. The good news: I don’t have to reinstall to get there. btrfs-convert will do an in-place conversion from ext4 to btrfs, and from there the rest is mount, move, edit, chroot, regenerate.

Let me check what we have. I narrow to just the target disk to keep the output readable (a broader lsblk -a -f -e 7 was useful earlier to see everything attached, with -e 7 filtering the live ISO’s loop devices):

lsblk -a -f /dev/nvme1n1
NAME                          FSTYPE       FSVER  LABEL  UUID  FSAVAIL  FSUSE%  MOUNTPOINTS
nvme1n1
├─nvme1n1p1                   vfat         FAT32         XXXX   1G       1%      /target/boot/efi
├─nvme1n1p2                   ext4         1.0           XXXX   1.7G     4%      /target/boot
└─nvme1n1p3                   crypto_LUKS  2             XXXX
  └─dm_crypt-0                LVM2_member  LVM2 001      XXXX
    └─ubuntu--vg-ubuntu--lv   ext4         1.0           XXXX   851.2G   2%      /target

Layout confirmed. Three partitions on the 1 TB NVMe, LUKS on the third, LVM inside that, single ext4 logical volume mounted at /target. That’s what we’re going to convert.

Renaming the LVM volumes (cosmetic but I like it)

Renaming the volume group and logical volume to match the Pop!_OS convention — data/root instead of ubuntu-vg/ubuntu-lv — because I like it.

vgrename ubuntu-vg data
  Volume group "ubuntu-vg" successfully renamed to "data"
lvrename /dev/data/ubuntu-lv /dev/data/root
  Renamed "ubuntu-lv" to "root" in volume group "data"

Disable swap.img

Ubuntu’s installer makes a /swap.img swapfile. I’m using zram swap instead (later in this post), so:

sed -i 's|/swap.img|#/swap.img|g' /target/etc/fstab
rm /target/swap.img

Convert ext4 to btrfs

Unmount the target filesystem, fsck for sanity, then convert in place:

umount -l /target
e2fsck /dev/data/root
e2fsck 1.47.2 (1-Jan-2025)
/dev/data/root: clean, 173245/60850176 files, 5988034/243385344 blocks

btrfs-convert /dev/data/root
btrfs-convert from btrfs-progs v6.17.1

Source filesystem:
  Type:                ext2
  Blocksize:           4096
  UUID:                XXXX
Target filesystem:
  Blocksize:           4096
  Nodesize:            16384
  UUID:                XXXX
  Checksum:            crc32c
  Features:            extref, skinny-metadata, no-holes, free-space-tree (default)
Reported stats:
  Total space:         996906369024
  Free space:          971289317376 (97.43%)
  Inode count:         60850176
  Block count:         243385344
Create initial btrfs filesystem
Create ext2 image file
Create btrfs metadata
Copy inodes [O] [    175189/  173245]
Free space cache cleared
Conversion complete

That’s the part that always feels a bit miraculous. An entire ext4 filesystem with a freshly installed Ubuntu in it, converted to btrfs in place, in maybe ninety seconds, without losing data. btrfs-convert keeps a snapshot of the original ext2 image (which it can be rolled back from if something goes wrong) inside the new btrfs as ext2_saved. Once you trust the conversion, delete it.

Mount the freshly-converted btrfs at the filesystem root level so we can manipulate subvolumes:

mount -o subvolid=5,defaults /dev/data/root /target
rm -rf /target/ext2_saved

subvolid=5 mounts the actual btrfs root, not a subvolume — important when you’re about to create subvolumes.

Create the subvolumes

Timeshift’s btrfs mode expects @ for root and @home for home. That’s the layout we’re going to create.

Create @ and verify:

btrfs subvolume create /target/@
Create subvolume '/target/@'
ls -a /target
.  ..  @  bin  boot  cdrom  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var

@ is now a subvolume sitting alongside the existing tree. Move everything except @ itself into @, using find so we don’t have to fuss with shell glob options:

find /target -mindepth 1 -maxdepth 1 ! -name '@' -exec mv -t /target/@/ {} +
ls -a /target
.  ..  @
ls -a /target/@
.  ..  bin  boot  cdrom  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var

Then create @home and move the user’s home directory into it:

btrfs subvolume create /target/@home
Create subvolume '/target/@home'
mv /target/@/home/* /target/@home/
ls /target/@/home/
ls /target/@home/
myuser

This is the step where a mistake renders the system unbootable. Verify what you moved. Verify the destination. Don’t shortcut this.

Fix fstab and grub

Open /target/@/etc/fstab in nano. Two edits.

The line that mounts / currently looks like this:

/dev/disk/by-id/dm-uuid-LVM-XXX  /  ext4  defaults  0  1

Change ext4 to btrfs and append ,subvol=@ to defaults:

/dev/disk/by-id/dm-uuid-LVM-XXX  /      btrfs  defaults,subvol=@      0  1
/dev/disk/by-id/dm-uuid-LVM-XXX  /home  btrfs  defaults,subvol=@home  0  1

The second line is a duplicate of the first with / → /home and subvol=@ → subvol=@home. Save.

GRUB needs to know to boot from @. In /target/@/etc/default/grub, append rootflags=subvol=@ to GRUB_CMDLINE_LINUX_DEFAULT:

# before
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
# after
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash rootflags=subvol=@"

Chroot and regenerate

Unmount, then remount as @, bind-mount the virtual filesystems, chroot in:

umount -l /target
mount -o subvol=@,defaults /dev/data/root /target
for i in /dev /dev/pts /proc /sys /run; do mount -B $i /target$i; done
chroot /target/

Inside the chroot, mount -av picks up the new fstab and brings up /home, /boot, /boot/efi:

mount -av
/                : ignored
mount: (hint) your fstab has been modified, but systemd still uses
       the old version; use 'systemctl daemon-reload' to reload.
/home            : successfully mounted
/boot            : successfully mounted
/boot/efi        : successfully mounted

The systemd hint is harmless inside a chroot — there’s no live systemd to reload here. The real system will pick up the new fstab on next boot.

Install btrfs-progs (this triggers a dracut initramfs regeneration with btrfs support included), regenerate grub, regenerate initramfs once more for paranoia:

apt install btrfs-progs
...
Processing triggers for dracut (110-11) ...
update-initramfs: Generating /boot/initrd.img-7.0.0-14-generic
Processing triggers for man-db (2.13.1-1build1) ...

update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/kdump-tools.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-7.0.0-14-generic
Found initrd image: /boot/initrd.img-7.0.0-14-generic
Found memtest86+ 64bit EFI image: /boot/mt86+x64
Adding boot menu entry for UEFI Firmware Settings ...
done

update-initramfs -c -k all
update-initramfs: Generating /boot/initrd.img-7.0.0-14-generic

Worth noting: the dracut line in the apt output. Part one talked about Ubuntu 26.04 finally moving to dracut as the default initramfs generator. There it is, doing its job.

Install Timeshift while we’re here:

apt install timeshift

And zram swap, since we disabled the disk swapfile earlier. 128 GB of RAM means I’m not really going to swap anyway, but zram is still useful as compressed in-memory swap for the rare case I do, and the OOM killer behaves better when there’s some swap available.

apt-get install zram-tools
nano /etc/default/zramswap

The defaults need two changes. ALGO=lz4 → zstd (better compression ratio, roughly comparable speed on modern CPUs). PERCENT=50 → 15 (15% of 128 GB is ~19 GB compressed swap, which is plenty for an OOM safety net). Save.

apt’s post-install hook tries to start zramswap.service, but inside a chroot systemd isn’t really running — the log shows Running in chroot, ignoring command 'start'. That’s fine. The service starts on next boot.

Exit the chroot, exit the root shell, reboot, eject the USB when prompted.

(For honesty’s sake: the live session kicked me back to GDM one final time during this last bit. By then everything was written, every command had run, the chroot was clean. Random as ever, harmless this time. I logged back in, finished the reboot, moved on.)

First Boot

LUKS prompt. Passphrase entered. GRUB picks up the kernel and initramfs from the (still unencrypted) /boot. Initramfs unlocks the LUKS volume, finds LVM, finds the btrfs filesystem, mounts @ as /, hands off to systemd, GDM comes up.

Login. Welcome screen. Both monitors at 4K with fractional scaling. Working.

Verifying Timeshift and zram

Launch Timeshift. In the wizard: snapshot type = btrfs, target = /, frequency to taste, and whether to include @home in snapshots is your call (I do, the cost is negligible because btrfs snapshots are copy-on-write). Click “Create” to take the first manual snapshot.

To verify it actually exists as a btrfs subvolume, mount the filesystem root and look:

sudo mount -o subvolid=5,defaults /dev/data/root /mnt/
ls /mnt/
@  @home  timeshift-btrfs
sudo btrfs subvolume list /mnt
ID 257 gen 176 top level 5 path @
ID 258 gen 176 top level 5 path @home
ID 259 gen 167 top level 5 path timeshift-btrfs/snapshots/2026-04-27_11-14-24/@
ID 260 gen 169 top level 5 path timeshift-btrfs/snapshots/2026-04-27_11-14-24/@home

There it is. @ and @home are the live subvolumes, and Timeshift’s first snapshot is a parallel @/@home pair under timeshift-btrfs/snapshots/<timestamp>/. Restorable from a live USB if anything goes sideways.

And zram:

zramctl
NAME       ALGORITHM  DISKSIZE  DATA  COMPR  TOTAL  STREAMS  MOUNTPOINT
/dev/zram0 zstd          18.2G    4K    59B    20K        1  [SWAP]
swapon --show
NAME       TYPE       SIZE  USED  PRIO
/dev/zram0 partition  18.2G    0B   100

This is the moment the install stops being an install and starts being a system. From here, a botched apt upgrade or a misconfigured system file is recoverable in seconds.

Where I Landed

The system as of tonight:

  • Ubuntu 26.04 LTS Resolute Raccoon, kernel 7.0
  • GNOME 50 Wayland, both 4K displays at fractional scaling, NVIDIA drivers loaded
  • LUKS2-encrypted root, btrfs with @ and @home subvolumes
  • ext4 /boot, unencrypted — same Ubuntu limitation as always, hardware TPM unlock didn’t pan out today, separate session another day
  • zram swap, 19 GB compressed, no disk swap
  • Timeshift in btrfs mode with the first snapshot taken

Both GPUs visible to nvidia-smi:

nvidia-smi
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 595.58.03            Driver Version: 595.58.03    CUDA Version: 13.2         |
+-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4060 Ti     Off | 00000000:05:00.0  On   |                  N/A |
| 30%   41C    P8              4W / 165W  |    585MiB / 16380MiB   |      1%      Default |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA GeForce RTX 5070 Ti     Off | 00000000:0C:00.0 Off   |                  N/A |
|  0%   45C    P8             23W / 300W  |     15MiB / 16303MiB   |      0%      Default |
+-----------------------------------------+------------------------+----------------------+

The 4060 Ti is doing real work — driving the display, with gnome-shell, Xwayland, nautilus, and ptyxis all holding small chunks of its VRAM. The 5070 Ti is idle at P8 and 23 watts. Waiting.

Which is the cliffhanger for part three.

Honest Reckoning

Part one was a love letter to what Pop!_OS does well and a bet on Ubuntu 26.04. The bet mostly paid out. GNOME 50 is genuinely better than COSMIC for me. Fractional scaling on dual 4K is the cleanest it’s been on this hardware. Dracut is in. The install path that gets me to a Timeshift-friendly btrfs layout works, even if it requires explicit work post-install.

What didn’t pay out: the live session was less stable than I’d want for an LTS three days in. Hardware-backed TPM encryption was a forty-five minute detour that ended where I started. /boot is still unencrypted, still my unsolved problem.

I’ll come back to all of those. None of them are blockers. This is a working machine, doing exactly what I asked it to do, with the layout I want and the snapshots I trust.

Tomorrow — well, later this week — part three. Setting this rig up for AI/ML. The 5070 Ti has been sitting at idle through this entire post and it’s getting impatient.

Standing on shoulders

None of this is mine — it’s distilled from years of reading other people’s notes and arguing with strangers in forums. A few that I leaned on:

  • Ubuntu Help — the canonical docs, always the right first stop.
  • System76 Support — encyclopedic, even when you’re leaving Pop!_OS.
  • r/Ubuntu and r/pop_os — where someone has hit your weird edge case three years before you do.
  • Willi Mutschler’s Linux notes — clean, opinionated, well-written walkthroughs.
  • And many other Stack Overflow answers, blog posts, and forum threads lost to time and memory.