Installing GRUB on an hybrid BIOS/UEFI stick

The purpose of this article is to explain how to install GRUB on an USB stick, or any other kind of USB mass-storage device, to make it bootable in either legacy BIOS or UEFI mode and allow to load live images installed in a directory.

It is a complement to this article about using GRUB to boot a live image written as a file rather than an image.

After this, the device has a little wasted space and a directory named boot, but is otherwise completely usable for other tasks.

This is written as a script, to have it compute the partitions positions and sizes automatically based on the size of the USB stick. The size can be obtained from /proc/partitions (un units of 1024, so need to multiply by 2) or using /sbin/blockdev --getsz.

We assume we have read and write permissions on the device and use pmount to mount partitions, but otherwise we do not need root privileges. Note that sfdisk is probably not in the default path.

Device path:
Device size: × 512
EFI partition min size: × 1024²
EFI partition alignment: × 1024²
Partitions label:
Filesystem label: (max 11, usually uppercase)

First, let us wipe the device, so that traces of previous partitions and attemps do not interfer. Just wipe the areas outside the main partitions.

dd bs=512 if=/dev/zero seek=0 count=2048 of=/dev/sdc
dd bs=512 if=/dev/zero seek=15767552 count=2048 of=/dev/sdc

Create the partitions. We need a GPT scheme with a big data partition, a small EFI system partition (~100 megaoctets usually recommended, less than on required for GRUB) and a tiny BIOS boot partition (necessary to install MBR GRUB). We also need a protective/hybrid MBR for system that do not understand GPT, with the data and EFI partition and one covering the GPT. The --no-reread is needed if root privileges are not used, due to a tiny bug in sfdisk.

sfdisk --no-reread /dev/sdc <<EOF
label: gpt
unit: sectors
sector-size: 512
1 : start=2048, size=15699968, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name="multiboot"
2 : start=15702016, size=65536, type=uefi, name="multiboot_EFI"
3 : start=15767552, size=2015, type=21686148-6449-6E6F-744E-656564454649, name="multiboot_BIOS"
EOF

sfdisk --no-reread -Y dos /dev/sdc <<EOF
label: dos
sector-size: 512
1 : start=2048, size=15699968, type=0c, bootable
2 : start=15702016, size=65536, type=ef
3 : start=1, size=2047, type=ee
EOF

Create the filesystems.

mkdosfs -n MULTIBOOT /dev/sdc1
mkdosfs /dev/sdc2

Mount the partitions and install GRUB, three times (EFI 64, EFI 32 and legacy).

pmount /dev/sdc1 /media/boot
mkdir /media/boot/boot
pmount /dev/sdc2 /media/efi

grub-install --target=x86_64-efi --removable --no-nvram \
  --root-directory=/media/boot \
  --boot-directory=/media/boot/boot \
  --efi-directory=/media/efi

grub-install --target=i386-efi --removable --no-nvram \
  --root-directory=/media/boot \
  --boot-directory=/media/boot/boot \
  --efi-directory=/media/efi

grub-install --target=i386-pc \
  --root-directory=/media/boot \
  --boot-directory=/media/boot/boot \
  /dev/sdc

pumount /media/efi

Initial configuration for GRUB. The background image can be found in the Debian package desktop-base.

cp /usr/share/desktop-base/active-theme/grub/grub-16x9.png /media/boot/boot/grub/

cat > /media/boot/boot/grub/grub.cfg <<EOF
insmod efi_gop
insmod font
insmod gfxterm
insmod png
loadfont /boot/grub/fonts/unicode.pf2
set gfxmode=auto
set gfxpayload=keep
terminal_output gfxterm
if background_image /boot/grub/grub-16x9.png ; then
  set color_normal=white/black
  set color_highlight=black/white
else
  set menu_color_normal=cyan/blue
  set menu_color_highlight=white/blue
fi
EOF

Let us put something in the device and unmount it.

cp /path/to/some.iso /media/boot/boot/
vim /boot/grub.cfg
pumount /media/boot

ISO images can then be copied to the key and stances added to boot/grub/grub.cfg like explained in this article.

Extra: to test the disk without rebooting a machine, we can use qemu, with the packages qemu-system-x86, qemu-system-gui and ovmf for the UEFI firmware. Assuming the device is plugged as /dev/sdc and we have a raw disk image /tmp/disk we want to use, it can look like that:

cp /usr/share/qemu/OVMF.fd /tmp/
qemu-system-x86_64 \
  -enable-kvm \
  -drive if=pflash,format=raw,file=/tmp/OVMF.fd \
  -drive file=/dev/sdc,format=raw,index=0,media=disk \
  -drive file=/tmp/disk,format=raw,index=1,media=disk \
  -boot order=c \
  -m 512 \
  -display gtk

It is possible that 500 Mo be insufficient for some distros. Even with much more I wasn't able to let Debian Live finish booting.

Extra: configuring Debian Live directly and attempting to enable persistence.