liveng structure

We have previously deployed a stock Debian live with the use of dd, preserving all the structures contained within the downloaded ISO image, resulting in a live operating system without proper UEFI or Secure Boot support and without the wows of a kernel update (on a ISO9660 filesystem) feature.

In order to build a liveng operating system, we must now restart from scratch, however, and keep only a bunch of the old files - only the the kernel, the initrd and the filesystem.squashfs are really needed.

For future reference, the device file corresponding to the whole USB key (please plug the USB key in and use fdisk -l as stated previously) and the path of the downloaded Debian live image are kept in two variables, together with the size of the image file itself in a third one:

device="/dev/sdx"
imageFile="/path/to/debian-live-x.y.z-arch-desktop.iso"
imageFileSize=$(du -m ${imageFile} | awk '{print $1}')

As done before, I’m trying avoiding some hard drive wiping (and some heart attacks) by changing the device file name to /dev/sdx for the whole liveng documentation, please remember to change it for your case.

We are also tracking the name of the files we’ll need, which are contained inside the ISO image:

mount ${imageFile} /mnt

configFile=$(ls /mnt/live/ | awk '{print $1}' | grep config)
initrdFile=$(ls /mnt/live/ | awk '{print $1}' | grep initrd)
vmlinuzFile=$(ls /mnt/live/ | awk '{print $1}' | grep vmlinuz)
systemMapFile=$(ls /mnt/live/ | awk '{print $1}' | grep System)

umount /mnt

We will unomunt now every possible mountpoint that you or your desktop environment may have enstablished with the key’s partitions, and wipe all the contained filesystems’ key structures:

cd /tmp

# Unmount.
for i in $(mount | grep ${device} | awk '{print $1}'); do umount $i; done

# Wipe.
wipefs -af ${device}

GUID partition table

Now we are creating a new empty GUID partition table (GPT for its friends); this operation deletes all the exisiting partitions and creates a new protective MBR (for backward compatibility with BIOS hardware):

printf "o\nY\nw\nY\n" | gdisk ${device} && sync

You can launch gdisk ${device} and type all the options by hand in order to see what the previous command actually does.

Resulting partitioning scheme (with fdisk -l):

Disk /dev/sdb: 29,4 GiB, 31608274944 bytes, 61734912 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 1D0FE347-D269-4E57-843C-AA5DAEA2A7A1

No partitions available so far. So good.

System partition

Now it’s the turn of manually creating the first partition onto the USB key; we will always use gdisk, for the older fdisk is not GPT compliant. The first partition will host most of the files contained within the downloaded Debian live image, so we are creating it of the same size as the image, which is consistent with our scope:

printf "n\n\n\n+${imageFileSize}M\n8300\nw\nY\n" | gdisk ${device} && sync

Previous line means:

Command (? for help): n
Partition number (1-128, default 1):
First sector (34-61734878, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-61734878, default = 61734878) or {+-}size{KMGTP}: +2300M
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to 'Linux filesystem'

Command (? for help): w
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!
Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to /dev/sdb.
The operation has completed successfully.

Resulting partitioning scheme (with fdisk -l):

Disk /dev/sdb: 29,4 GiB, 31608274944 bytes, 61734912 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 1D0FE347-D269-4E57-843C-AA5DAEA2A7A1

Device     Start     End Sectors  Size Type
/dev/sdb1   2048 4712447 4710400  2,3G Linux filesystem

One unformatted partition has been created.

With the next step, xorriso will help us writing the files to the new newely created USB’s partition, maintaining the ISO9660 filesystem (rtfm of xorriso for more):

xorriso -indev ${imageFile} -boot_image any discard -overwrite on -volid LIVENG_SYSTEM -move live/${configFile} live/config -move live/${initrdFile} live/initrd.img -move live/${vmlinuzFile} live/vmlinuz -move live/${systemMapFile} live/System.map -rm_r .disk boot d-i dists isolinux pool -- -outdev stdio:${device}1 -blank as_needed

As the result:

ISO image produced: 1081069 sectors
Written to medium : 1081248 sectors at LBA 32
Writing to 'stdio:/dev/sdb1' completed successfully.

Having a look that everything is as expected:

mount ${device}1 /mnt/
ls /mnt/live/

Will display:

config  filesystem.squashfs  initrd.img  System.map  vmlinuz

As you may have noted, all the files have been renamed, in order for us to ease the configuration of GRUB. Now we can unmount the partition:

umount /mnt

Second system partition

With the same technique used before, we are now adding a second system partition to the key, which will contain the kernel and the initrd files only. The bootloader will be then instructed to boot from this partition, because the second system partition will always contain the most updated kernel and initrd files.

At every kernel update, this small partition will be overwritten, with the use of xorriso, by the postinst script of the liveng kernel package. (First) System partition files are kept at their default state and can be useful in case of recovery or when a complete persistence reset is performed.

Creating the second partition:

printf "n\n\n\n+256M\n8300\nw\nY\n" | gdisk ${device}

fdisk -l:

Device       Start     End Sectors  Size Type
/dev/sdc1     2048 4624383 4622336  2,2G Linux filesystem
/dev/sdc2  4624384 5148671  524288  256M Linux filesystem

With the next step, xorriso will write the kernel and initrd files into the second system partition (ISO9660):

xorriso -indev ${imageFile} -boot_image any discard -overwrite on -volid 'LIVENG_SYSTEM2' -move live/${configFile} live/config -move live/${initrdFile} live/initrd.img -move live/${vmlinuzFile} live/vmlinuz -move live/${systemMapFile} live/System.map -rm_r .disk boot d-i dists isolinux pool live/filesystem.squashfs -- -outdev stdio:${device}2 -blank as_needed

Having a look that everything is as expected:

mount ${device}2 /mnt/
ls /mnt/live/
umount /mnt

Will display:

config    initrd.img    System.map    vmlinuz

GRUB bootloader (UEFI)

The GPT partitioning scheme has been set up and the system partitions have been created. We are now adding the UEFI partition, which must be FAT (guess who introduced the UEFI Secure Boot…).

Every standard UEFI firmware must look into the /efi/boot/ folder of the FAT partition of the booting device for a file named boot{arch}.efi, so we are just creating the folder on the USB drive and copy a GRUB UEFI-compliant bootloader binary to this location.

But first we need to generate our own GRUB bootloader image with some modules included. The following command will generate the GRUB image. grub-efi-amd64* amd64 Debian packages are needed - novices are advised to download the bootx64.efi hosted at liveng Github repository (docs/grub-bin/) instead of creating a new one:

grub-mkimage -o bootx64.efi -p /efi/boot -O x86_64-efi \
    fat iso9660 part_gpt part_msdos \
    normal boot linux configfile loopback chain keylayouts \
    efifwsetup efi_gop efi_uga jpeg png \
    ls search search_label search_fs_uuid search_fs_file \
    gfxterm gfxterm_background gfxterm_menu test all_video loadenv \
    exfat ext2 ntfs udf password password_pbkdf2 pbkdf2 linuxefi

We are ready to create the third (UEFI, FAT) partition, sized 32MB, onto the key:

printf "n\n\n\n+32M\nef00\nw\nY\n" | gdisk ${device} && sync
mkfs.vfat -n "UEFI Boot" ${device}3

Resulting partitioning scheme (with fdisk -l):

Device       Start     End Sectors  Size Type
/dev/sdc1     2048 4624383 4622336  2,2G Linux filesystem
/dev/sdc2  4624384 5148671  524288  256M Linux filesystem
/dev/sdc3  5148672 5214207   65536   32M EFI System

Now we will copy the GRUB binary to the UEFI partition, as stated before:

mount ${device}3 /mnt/
mkdir -p /mnt/efi/boot
cp /path/to/bootx64.efi /mnt/efi/boot

and setup the GRUB config file so that GRUB will be able to locate the kernel, initrd and filesystem.squashfs files upon booting. Kernel and initrd files will be loaded from the second system partition and the filesystem.squashfs will be loaded from the first system partition (thanks to the fromiso live-boot’s directive):

    cat > /mnt/efi/boot/grub.cfg <<EOF
menuentry 'liveng standard boot' --unrestricted {
    insmod iso9660
    search --no-floppy --set=root --hint-efi=hd0,gpt2 --fs-uuid $(blkid -s UUID ${device}2 | awk -F\" '{print $2}')
    linux /live/vmlinuz initrd=/live/initrd.img fromiso=$(blkid -s UUID ${device}1 | awk -F\" '{print $2}') boot=live live-noconfig persistence
    initrd /live/initrd.img
}
EOF

We also add a fallback boot, which loads all the files from the original (first) system partition, just in case something goes wrong:

    cat >> /mnt/efi/boot/grub.cfg <<EOF
menuentry 'liveng fallback boot' --unrestricted {
    insmod iso9660
    search --no-floppy --set=root --hint-efi=hd0,gpt1 --fs-uuid $(blkid -s UUID ${device}1 | awk -F\" '{print $2}')
    linux /live/vmlinuz initrd=/live/initrd.img fromiso=$(blkid -s UUID ${device}1 | awk -F\" '{print $2}') boot=live live-noconfig persistence liveng-fallback
    initrd /live/initrd.img
}
EOF

umount /mnt

The $(blkid -s UUID ${device}x | awk -F\" '{print $2}') command gets the UUID of the ${device}x partition, given during the partition creation.

We will use the persistence directive later on; if no persistence partition is found, the (live-boot modified) initrd will union-mount the filesystem.squashfs with the RAM disk. The liveng-fallback will be of use later on, too.

GRUB bootloader (BIOS)

The GPT partitioning scheme we previously set up also contains a protective MBR, which assures BIOS compatibility and the possibility to create more than 4 primary partitions.

We will now set up GRUB for the BIOS compatibility - as said, unlike UEFI, BIOS needs a first-stage bootloader (the boot sector code) contained within the MBR of the key:

mount ${device}3 /mnt/
cp -R /path/to/grub-bios /mnt/boot

Where grub-bios is a folder containing “standard” GRUB binaries for BIOS. You can download a compressed archive of it from the liveng Github repository (docs/grub-bin/).

grub.cfg:

    cat > /mnt/boot/grub/grub.cfg <<EOF
menuentry 'liveng standard boot' --unrestricted {
    insmod iso9660
    search --no-floppy --set=root --hint-efi=hd0,gpt2 --fs-uuid $(blkid -s UUID ${device}2 | awk -F\" '{print $2}')
    linux /live/vmlinuz initrd=/live/initrd.img fromiso=$(blkid -s UUID ${device}1 | awk -F\" '{print $2}') boot=live live-noconfig persistence
    initrd /live/initrd.img
}
EOF

We also add a fallback boot, which loads all the files from the original (first) system partition, just in case something goes wrong:

    cat >> /mnt/boot/grub/grub.cfg <<EOF
menuentry 'liveng fallback boot' --unrestricted {
    insmod iso9660
    search --no-floppy --set=root --hint-efi=hd0,gpt1 --fs-uuid $(blkid -s UUID ${device}1 | awk -F\" '{print $2}')
    linux /live/vmlinuz initrd=/live/initrd.img fromiso=$(blkid -s UUID ${device}1 | awk -F\" '{print $2}') boot=live live-noconfig persistence liveng-fallback
    initrd /live/initrd.img
}
EOF

Finally, install first-stage GRUB:

grub-install --root-directory=/mnt ${device} --force

umount /mnt

So far, so good

So far, we have set up a USB key with a GPT and protective MBR partitioning scheme, with three partitions, two for the system and one for the UEFI compliance. We also used and installed GRUB as the bootloader instead of syslinux/isolinux - this will give us much more flexibility.

We will go back to kernel update later on.

Live system is still non-persistent, please do a couple of UEFI (non-Secure Boot) and BIOS test bootings to verify all is set up correctly…