The Miracle of `debootstrap` & Installing Ubuntu to Thin-LVM in a Very *Archlinux-Like Way

Astral Albert Hoffman examines molecule of LSD

Can you spot Jimi Hendrix?

Ubuntu debootstrap for IoT maas router (Internet Gateway/firewall) – a 2-part post.

Part 1: Installing Ubuntu to Thin-lvm in a Very Arch-Like Way

A quick mention: I just copied this out of my plain text notes I do while completing these things. I think it’s fairly error-free, but I’ll come back and flesh it out a bit narratively later.

Any time following along on something like this, don’t forget to change the device names in places like /dev/mapper/yourvg-yourlv etc. to ones that are appropriate for your system. The ones listed herein are appropriate for mine, which conversely also means not necessarily yours.

I love Arch to death and set up a laptop as a router using it, but I also think maas is really awesome, and would love to have it on my internet gateway, so this is the start of me setting up Ubuntu (the only officially-supported platform for maas) in a way that allows me to use some of the benefits of thin-lvm, like snapper snapshots and potentially over-committed storage volumes (or, more realistically, volumes are less static/encumbered than raw partitions), while still retaining the snappiness of a journaled filesystem instead of a CoW FS, the latter of which tend to have irritatingly high latency, particularly with small files and/or little free space, not to mention growing worse over time.

Big thanks to Ansemjo for publishing his how-to for bootstrapping Ubuntu in order to set up LUKS, which was my main source for my adaptation to this guide – his original instructions are here: https://semjonov.de/posts/2021-09/minimal-ubuntu-installation-with-debootstrap/

I have 2x 22GB (useable) Intel 313 SLC SSDs on a RAID-1 mSATA controller I will cat this lv to after it’s created

Bash
lvcreate -V 22G -T vms/pool -n maasrouter

Create VM in virt-manager with LV maasrouter as /dev/vda, boot from Ubuntu 24.04 desktop live iso

(many screenshots of virt-manager taken @ Aug 12 2024)

set display to VGA, turn off spice listen address, boot using ‘Safe Graphics’ GRUB option

Exit live ISO installer, open terminal, update, install the two required dependencies

Bash
ctrl-alt-T, sudo su
apt update && apt install -y debootstrap arch-install-scripts

A quick, garbage-free way to view connected block devices – set it as an alias

Bash
alias lsbk="lsblk | grep -v -E 'loop|sr0'"
lsbk

I had a machine lock up on me while installing the kernel. A quick get-back if the VM dies if fstab is already created [after a hard-reboot];

Bash
apt update && apt install -y arch-install-scripts
mount /dev/mrvg/mrrootlv /mnt
arch-chroot /mnt mount -a

example of lsbk alias (I hate seeing loop devices):

Bash
lsbk

NAME                    MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
vda                     253:0    0    22G  0 disk 
├─vda1                  253:1    0   200M  0 part /efi
├─vda2                  253:2    0     1G  0 part /boot
└─vda3                  253:3    0  20.8G  0 part 
  ├─mrvg-mrpool_tmeta   252:0    0    24M  0 lvm  
   └─mrvg-mrpool-tpool 252:2    0  20.8G  0 lvm  
     ├─mrvg-mrpool     252:3    0  20.8G  1 lvm  
     ├─mrvg-mrswaplv   252:4    0     4G  0 lvm  
     └─mrvg-mrrootlv   252:5    0    16G  0 lvm  /
  └─mrvg-mrpool_tdata   252:1    0  20.8G  0 lvm  
    └─mrvg-mrpool-tpool 252:2    0  20.8G  0 lvm  
      ├─mrvg-mrpool     252:3    0  20.8G  1 lvm  
      ├─mrvg-mrswaplv   252:4    0     4G  0 lvm  
      └─mrvg-mrrootlv   252:5    0    16G  0 lvm  /

Prep drive and partitions:

Bash
sgdisk -g /dev/vda

fdisk /dev/vda
n, 1, enter, +200M
t, 1 (efi)
n, 2, enter, +1G
t, 2, 142 (xbootldr - "linux extended boot")
n, enter, enter, enter (rest of disk)
t, 3, 44 (LVM)
w (save and exit)

Create logical volumes and init filesystems:

Bash
mkfs.msdos -F 32 -n MR_EFI /dev/vda1
mkfs.ext4 -L MR_XBOOTLDR /dev/vda2
pvcreate /dev/vda3
vgcreate mrvg /dev/vda3
lvcreate -l 100%FREE -Zn -c 64 -T mrvg/mrpool
lvcreate -V 4G -T mrvg/mrpool -n mrswaplv
lvcreate -V 16G -T mrvg/mrpool -n mrrootlv
mkswap -L MR_SWAPLV /dev/mrvg/mrswaplv
mkfs.xfs -L MR_ROOTLV /dev/mrvg/mrrootlv

For this next step, you can replace ubuntu-server with ubuntu-desktop or vanilla-gnome-desktop, as of writing apt search ubuntu-desktop reveals several other options on a 24.04 desktop live ISO:

Bash
apt search ubuntu-desktop
Sorting... Done
Full Text Search... Done
edubuntu-desktop/noble 24.04.13 amd64
  educational desktop for Ubuntu

edubuntu-desktop-minimal/noble 24.04.13 amd64
  educational desktop for Ubuntu

kubuntu-desktop/noble 1.451 amd64
  Kubuntu Plasma Desktop/Netbook system

lubuntu-desktop/noble 24.04.10 amd64
  Lubuntu Desktop environment

ubuntu-desktop/noble 1.539 amd64
  Ubuntu desktop system

ubuntu-desktop-minimal/noble 1.539 amd64
  Ubuntu desktop minimal system

xubuntu-desktop/noble 2.262 amd64
  Xubuntu desktop system

xubuntu-desktop-minimal/noble 2.262 amd64
  Xubuntu minimal system

Mount FS structure and bootstrap:

Bash
mount /dev/mrvg/mrrootlv /mnt
mkdir -p /mnt/{boot,efi}
mount /dev/vda1 /mnt/efi
mount /dev/vda2 /mnt/boot
debootstrap --arch=amd64 \
        --components=main,restricted,universe,multiverse \
        --include=systemd-boot,systemd-boot-efi,arch-install-scripts,debootstrap,ubuntu-server,vim-scripts,vim-airline-themes,snapper,git,efibootmgr,bash-completion,tree,postgresql,pydf,thin-provisioning-tools,man-db,apt-file \
        --keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg \
        noble /mnt https://mirror.leaseweb.com/ubuntu/

Copy host apt setup to VM FS:

Bash
cp /etc/apt/sources.list.d/ubuntu.sources /mnt/etc/apt/sources.list.d

Create /mnt/etc/kernel/cmdline with a /dev/mapper appropriate for you:

Bash
echo 'root=/dev/mapper/mrvg-mrrootlv rw rootfs=xfs loglevel=3 audit=0 intel_iommu=1 iommu.passthrough=1' > /mnt/etc/kernel/cmdline

Mount the swap before creating fstab:

Bash
arch-chroot /mnt swapon -L MR_SWAPLV

Create your fstab:

Bash
genfstab -t PARTUUID /mnt > /mnt/etc/fstab

Delete the /mnt from the swap line, change the EFI to UUID:

Bash
gnome-text-editor /mnt/etc/fstab

Here’s a finished example:

Bash
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# /dev/mapper/mrvg-mrrootlv UUID=37374f8c-a4bf-4577-96c0-1cd8a9fbe114 LABEL=MR_ROOTLV
/dev/mapper/mrvg-mrrootlv    /           xfs         rw,relatime,attr2,inode64,logbufs=8,logbsize=64k,sunit=128,swidth=128,noquota   0 1

# /dev/vda1 PARTUUID=a5a6570a-ea64-4480-b6d7-fb3f6a504cbf LABEL=MR_EFI
UUID=C615-2241            /efi        vfat        rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro    0 2

# /dev/vda2 UUID=2a466a62-2a00-4a2e-b8aa-734d71039d67 LABEL=MR_XBOOTLDR
PARTUUID=03b8e7e8-a010-4d06-b09c-5503b609d022    /boot       ext4        rw,relatime 0 2

# /dev/dm-4 UUID=fb30403b-2101-4cf9-a083-a5debd8ad60a LABEL=MR_SWAPLV
/dev/dm-4           none        swap        defaults    0 0

I got this error when trying to chroot:

Bash
arch-chroot /mnt
mount: /mnt/dev: udev already mounted on /dev.
       dmesg(1) may have more information after failed mount system call.
==> ERROR: failed to setup chroot /mnt

Solution was to:

Bash
swapoff -a
umount /mnt/dev

Was irritated bash-completion wasn’t initialized, so set it up for root login (can help diagnoses):

Bash
sed -i 's|#if|if|g' $HOME/.bashrc
sed -i 's|#fi|fi|g' $HOME/.bashrc
sed -i 's|#    . /etc/bash_completion|    . /etc/bash_completion|g' $HOME/.bashrc

# if you also want to uncomment the dircolors aliases:
sed -i 's|#alias|alias|g' $HOME/.bashrc

Make sure snapd and ufw are installed (important for post #2):

Bash
arch-chroot /mnt apt update && apt list --installed | grep -i -E 'snapd|ufw'

I had them both, but looks like this is when I need to upgrade. Might as well install the kernels now, too, but important to update dpkg‘s locale settings before installing any more packages (otherwise, lots of errors):

Bash
arch-chroot /mnt 
locale-gen "en_US.UTF-8"
dpkg-reconfigure locales
(skip to OK, enter)
(select "en_US.UTF-8", OK, enter)

apt upgrade -y 

Install kernel:

Bash
cp /usr/lib/kernel/install.conf /etc/kernel
apt install -y linux-image-generic linux-headers-generic

Whilst still in chroot, check and make sure /mnt/boot has your initrd with tree:

Bash
apt list --installed | grep linux-image

tree /boot
/boot
├── EFI
   └── Linux
├── System.map-6.8.0-40-generic
├── $(cat /etc/machine-id)
   └── 6.8.0-40-generic
       ├── initrd.img-6.8.0-40-generic
       └── linux
├── config-6.8.0-40-generic
├── grub
   ├── gfxblacklist.txt
   └── unicode.pf2
├── initrd.img -> initrd.img-6.8.0-40-generic
├── initrd.img-6.8.0-40-generic
├── initrd.img.old -> initrd.img-6.8.0-40-generic
├── loader
   ├── entries
      └── $(cat /etc/machine-id)-6.8.0-40-generic.conf
   └── entries.srel
├── lost+found
├── vmlinuz -> vmlinuz-6.8.0-40-generic
├── vmlinuz-6.8.0-40-generic
└── vmlinuz.old -> vmlinuz-6.8.0-40-generic

(Still in chroot) install the systemd bootloader – this is best done after linux-image-generic, since it’ll install GRUB’s BOOTx64.EFI over systemd-boot’s copy – bootctl update won’t work for this

Bash
bootctl --esp-path=/efi --boot-path=/boot install

Grab efifs packages (*.efi files) so systemd-boot can load your /boot partition (check the URL https://github.com/pbatard/efifs/releases first to make sure downloading latest ver, and change v1.10 to whatever that might be):

Bash
cd /efi/EFI/systemd/ && mkdir drivers && cd drivers
for EFIFS in affs afs bfs btrfs cbfs cpio_be cpio erofs exfat ext2 f2fs fat hfs hfsplus iso9660 jfs minix2_be minix2 minix3_be minix3 minix_be minix newc nilfs2 ntfs odc procfs reiserfs romfs sfs squash4 tar udf ufs1_be ufs1 ufs2 xfs zfs; do \
wget "https://github.com/pbatard/efifs/releases/download/v1.10/${EFIFS}_x64.efi"; done
cd /

Create a user and a root password, give the user sudo group access:

Bash
# root
passwd

# user
useradd -m avery
passwd avery
usermod -aG sudo avery
chsh avery -s /bin/bash

You can check to make sure sudo is working for your user

Bash
su avery
sudo ls /
[sudo] password for avery: 
(should output ls /)

If you’re using a constrained space like me, you might want to monitor disk usage so far:

Bash
pydf
Filesystem         Size  Used Avail Use%          Mounted on               
/dev/mrvg/mrrootlv  16G 2427M   14G 14.9 [#.....] /                        
/dev/vda2          973M  161M  745M 16.6 [#.....] /boot                    
/dev/vda1          197M 1756k  195M  0.9 [......] /efi                     
efivarfs           256k  143k  108k 55.8 [###...] /sys/firmware/efi/efivars

I’m thinking this system should boot now, so I’m going to power off, change the boot device to /dev/vda, and check it out

Bash
exit  # chroot
poweroff

TL;DR if you experienced boot failure, perhaps you’d not installed thin-provisioning-tools before building your initrd (as I’d neglected). If you did install it, and had no problems, feel free to skip this part unless you’re curious…

I didn’t include thin-provisioning-tools in one of the earlier lists of modules included with debootstrap, so my system didn’t boot. There is a file in thin-provisioning-tools that adds functionality to initramfs-tools package, namely the hook file /usr/share/initramfs-tools/hooks/thin-provisioning-tools that adds the dm-thin-pool module at the bottom of this example of the hook to be compiled along with the rest of the initrd. Here’s the hook:

Bash
# File: /usr/share/initramfs-tools/hooks/thin-provisioning-tools

#!/bin/sh

PREREQ=""

prereqs()
{
    echo "$PREREQ"
}

case $1 in
prereqs)
    prereqs
    exit 0
    ;;
esac

. /usr/share/initramfs-tools/hook-functions

copy_exec /usr/sbin/pdata_tools
ln -s pdata_tools ${DESTDIR}/usr/sbin/cache_check
ln -s pdata_tools ${DESTDIR}/usr/sbin/thin_check

manual_add_modules dm-cache dm-cache-smq dm-thin-pool

If rebuilding your initrd after installing thin-provisioning-tools, run update-initramfs -vuk all and look for lines corresponding to the modules added at the bottom of /usr/share/initramfs-tools/hooks/thin-provisioning-tools in the output – that should be an indicator that required modules are being added now:

Bash
. . .

dracut-install: mkdir '/var/tmp/mkinitramfs_Y5NSco/lib/modules/6.8.0-40-generic/kernel/drivers/md'
dracut-install: cp '/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-cache.ko.zst' '/var/tmp/mkinitramfs_Y5NSco/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-cache.ko.zst'
dracut-install: cp '/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-bufio.ko.zst' '/var/tmp/mkinitramfs_Y5NSco/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-bufio.ko.zst'
dracut-install: cp '/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-bio-prison.ko.zst' '/var/tmp/mkinitramfs_Y5NSco/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-bio-prison.ko.zst'
dracut-install: mkdir '/var/tmp/mkinitramfs_Y5NSco/lib/modules/6.8.0-40-generic/kernel/drivers/md/persistent-data'
dracut-install: cp '/lib/modules/6.8.0-40-generic/kernel/drivers/md/persistent-data/dm-persistent-data.ko.zst' '/var/tmp/mkinitramfs_Y5NSco/lib/modules/6.8.0-40-generic/kernel/drivers/md/persistent-data/dm-persistent-data.ko.zst'
dracut-install: cp '/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-cache-smq.ko.zst' '/var/tmp/mkinitramfs_Y5NSco/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-cache-smq.ko.zst'
dracut-install: cp '/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-thin-pool.ko.zst' '/var/tmp/mkinitramfs_Y5NSco/lib/modules/6.8.0-40-generic/kernel/drivers/md/dm-thin-pool.ko.zst'

. . .

A quick synopsis is that without these tools, initrd will lack required modules, but with it, initrd should boot fine. For a more lengthy explanation, see old workaround:
https://bugs.launchpad.net/ubuntu/+source/lvm2/+bug/1539934/comments/2 [comment 2]
https://askubuntu.com/questions/673815/how-do-i-start-my-laptop-with-root-partition-on-lvm2-thin-pool

Of course, none of this is really necessary if you install the thin-provisioning-tools package initially.

Last, but definitely not least, set up a skeleton config for snapper snapshots reference:

Bash
systemctl disable snapper-timeline.timer
systemctl enable snapper-boot.timer
systemctl enable snapper-cleanup.timer

snapper --no-dbus -c root create-config \
                --fstype="lvm(xfs)" /

snapper --no-dbus set-config \
                NUMBER_LIMIT=6 \
                NUMBER_LIMIT_IMPORTANT=3 \
                TIMELINE_CLEANUP=no \
                TIMELINE_CREATE=no \
                TIMELINE_LIMIT_DAILY=2 \
                TIMELINE_LIMIT_HOURLY=1 \
                TIMELINE_LIMIT_WEEKLY=3 \
                TIMELINE_LIMIT_MONTHLY=5 \
                TIMELINE_LIMIT_QUARTERLY=5 \
                TIMELINE_LIMIT_YEARLY=5

snapper --no-dbus list-configs

Next post will be installing maas to handle dns and dhcp on your internet gateway/firewall


Leave a Reply

Your email address will not be published. Required fields are marked *