Categories: technology Tags: VM Linux

Automating creation of Debian VM using Deboostrap



This whole little adventure started as a result of wanting to set up a small VM as a PiHole for ad blocking. There’s no reason that the process had to be automated, but I thought that since I was likely to have to do a bit of messing around with this, it would be good to approach it that way, refresh my memory on kvm/qemu and maybe try out stuff like ansible that I haven’t taken the time to try out before now.


Creating the volume, partition table and filesystems

Going to go for the more “modern” approach, gpt partition table, and using nbd for the mounting step (as opposed to losetup).

The partitioning scheme is 10GB altogether, 270MB EFI partition, followed by 1GB swap (TODO: Does this really make sense??), and remainder as root partition.

qemu-img create -f qcow2 debian-bullseye.qcow2 10G
sudo modprobe nbd
sudo qemu-nbd -c /dev/nbd0 debian-bullseye.qcow2
sudo parted -s -a optimal -- /dev/nbd0 mklabel gpt  \
    mkpart primary fat32        1MiB 270MiB \
    mkpart primary linux-swap 300MiB   1GiB \
    mkpart primary ext4         1GiB -0 \
    name 1 uefi \
    name 2 swap \
    name 3 root \
    set 1 esp on
sudo mkfs -t fat -F 32 -n EFI /dev/nbd0p1
sudo mkswap -L swap /dev/nbd0p2
sudo mkfs -t ext4 -L root /dev/nbd0p3 
sudo blkid # Need to do sudo so that we refresh the records
swap_uuid="$(/usr/sbin/blkid | grep '^/dev/nbd0' | \
    grep ' LABEL="swap" ' | grep -o ' UUID="[^"]\+"' | sed -e 's/^ //' )"
root_uuid="$(/usr/sbin/blkid | grep '^/dev/nbd0' | \
    grep ' LABEL="root" ' | grep -o ' UUID="[^"]\+"' | sed -e 's/^ //' )"
efi_uuid="$(/usr/sbin/blkid | grep '^/dev/nbd0' | \
    grep ' LABEL="EFI" '  | grep -o ' UUID="[^"]\+"' | sed -e 's/^ //' )"
sudo mount $root_uuid /mnt/tmp/

Initial mount and running deboostrap

I’m going to mount and build the system under /mnt/tmp, that may or may not suit for your circumstances. Additionally, the packages to include will likely need a bit of tweaking depending on the purposes you have in mind for the VM after build.

For what it’s worth, the deboostrap takes about 9 minutes on my machine/network connection. Note the inclusion of the kernel packages and grub which are of course required to make the system bootable. I’ve included grub-pc-bin as well as the grub-efi-amd64 package that is expected to be used primarily.

sudo mount $root_uuid /mnt/tmp/
sudo /sbin/debootstrap --arch amd64 \
    bullseye /mnt/tmp http://ftp.uk.debian.org/debian

Need an /etc/fstab file to be created too. Blkid being used as it’s more robust/unambiguous than most other approaches…

cat - >> ./tmp.fstab << EOF
# /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).
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
$swap_uuid none swap sw  0       0
$root_uuid / ext4 errors=remount-ro 0 1
$efi_uuid /boot/efi vfat defaults 0 1

sudo mv ./tmp.fstab /mnt/tmp/etc/fstab

Chroot into the new system

sudo mount -o bind,ro /dev /mnt/tmp/dev
sudo mount -t proc none /mnt/tmp/proc
sudo mount -t sysfs none /mnt/tmp/sys
sudo LANG=C.UTF-8 chroot /mnt/tmp /bin/bash

And then commands within the chroot environment (will wrap these up into a script too):

debconf-set-selections <<EOF
tzdata tzdata/Areas select Europe
tzdata tzdata/Zones/Europe select London
# This is necessary as tzdata will assume these are manually set and override the debconf values with their settings
rm -f /etc/localtime /etc/timezone
DEBCONF_NONINTERACTIVE_SEEN=true dpkg-reconfigure -f noninteractive tzdata

cat - >> /etc/network/interfaces << EOF
auto lo
iface lo inet loopback

echo "debian-bullseye" > /etc/hostname

echo "       debian-bullseye.cryptid.icu    debian-bullseye" >> /etc/hosts

cat - >> /etc/apt/sources.list << EOF
# First should already be there
# deb http://ftp.uk.debian.org/debian/ bullseye main
deb http://deb.debian.org/debian/ bullseye main contrib non-free
deb http://deb.debian.org/debian/ bullseye-updates main contrib non-free
deb-src http://deb.debian.org/debian/ bullseye main contrib non-free

apt update

debconf-set-selections <<EOF
locales locales/locales_to_be_generated multiselect en_GB.UTF-8 UTF-8
locales locales/default_environment_locale select en_GB.UTF-8
keyboard-configuration keyboard-configuration/layoutcode string gb
keyboard-configuration keyboard-configuration/variant select English (UK)
keyboard-configuration keyboard-configuration/model select Generic 105-key PC (intl.)
# Stop anything overriding debconf's settings
rm -f /etc/default/locale /etc/locale.gen /etc/default/keyboard
apt-get install locales console-setup

Grub Installation

This was probably what I found trickiest first time through, or made the most mistakes with. Remember, this is still happening within the chroot environment! I’m lifting wholesale here from Laurence Hurst.

apt-get install grub-efi-amd64
# Add console=ttyS0 so we get early boot messages on the serial console.
sed -i -e 's/^\\(GRUB_CMDLINE_LINUX="[^"]*\\)"$/\\1 console=ttyS0"/' /etc/default/grub
# Tell GRUB to use the serial console
cat - >>/etc/default/grub <<EOF
GRUB_SERIAL_COMMAND="serial --unit=0 --speed=9600 --stop=1"
grub-install --target=x86_64-efi

# Now exit the chroot
umount /boot/efi/

Tidying up and try first boot…

sudo umount /mnt/tmp/dev /mnt/tmp/proc /mnt/tmp/sys /mnt/tmp/boot/efi 
sudo umount /mnt/tmp 
# and detach the volume
sudo qemu-nbd -d /dev/nbd0

Collecting some of the current pages I have open, and which I read on route to solution.

>> Home