Good old Master Boot Record (MBR) unfortunately cannot address anything beyond 2TB, so partitioning large disks and making them bootable is impossible using MBR. The GUID Partition Table (GPT) solves this problem: It supports disks up to 16EB. However, installing grub does not work without a special BIOS boot partition. If you also want to support booting the same system via UEFI, another partition, the EFI System Partition (ESP), is necessary.
This should post shows you how to partition a disk with GPT and make a bootable Linux system via BIOS/Legacy and UEFI.
Content
Requirements & Assumptions
For this post, I’ll assume that you are running everything on a Debian-based system, and all commands shown must be run as root.
1. Create a minimal Linux (optional)
For this example, we’ll use a small Linux install that we can create with the following commands. This will bootstrap (debootstrap) a minimal Ubuntu 16.04 system to the chroot/ folder which we will later use to boot our system. To create a bootable system, we need a kernel and the bootloader modules and files, so let’s install linux-image-generic and grub-pc:
1 2 3 4 5 6 7 8 9 10 |
# Bootstrap minimal system debootstrap --variant=minbase xenial chroot # Install kernel and grub for d in dev sys proc; do mount --bind /$d chroot/$d done DEBIAN_FRONTEND=noninteractive chroot chroot apt-get install linux-image-generic grub-pc -y --force-yes umount chroot/{dev,proc,sys} |
Alternatively, you can of course use any Linux root file system, provided that it has a kernel and grub. You could, for instance, just copy your own root file system to the chroot/ folder.
2. Creating a GPT with a BIOS boot partition and an EFI System Partition
Now that we have the Linux root file system in the chroot/ folder, we’ll create a sparse file that represents our disk. You may of course do all this with a proper HDD/SSD, i.e. with /dev/sdX, but for testing things using a raw disk file is much easier. The following snippet will create 3 partitions:
- A 1 MB BIOS boot partition (of type ef02) that Grub will use to store its core image.
- A 100 MB EFI System Partition (of type ef00) formatted as FAT32 in which we will store the EFI boot image
- And a partition for our root file system (in our example formatted with ext4)
1 2 3 4 5 6 7 8 9 |
# Create sparse file to represent our disk truncate --size 30G test.img # Create partition layout sgdisk --clear \ --new 1::+1M --typecode=1:ef02 --change-name=1:'BIOS boot partition' \ --new 2::+100M --typecode=2:ef00 --change-name=2:'EFI System' \ --new 3::-0 --typecode=3:8300 --change-name=3:'Linux root filesystem' \ test.img |
Once this is done, you can list the partitions with gdisk -l test.img:
1 2 3 4 5 6 7 8 9 10 |
# Now list the partitions gdisk -l test.img GPT fdisk (gdisk) version 1.0.1 # ... Number Start (sector) End (sector) Size Code Name 1 2048 4095 1024.0 KiB EF02 BIOS boot partition 2 4096 208895 100.0 MiB EF00 EFI System 3 208896 62914526 29.9 GiB 8300 Linux root filesystem |
After the partitions are created, the EFI and root partition need to be formatted:
1 2 3 4 5 6 7 8 9 10 |
# Loop sparse file LOOPDEV=$(losetup --find --show test.img) partprobe ${LOOPDEV} # Create filesystems mkfs.fat -F32 ${LOOPDEV}p2 mkfs.ext4 -F -L "demoroot" ${LOOPDEV}p3 # << Note the label 'demoroot' # Get rid of loop device losetup -d ${LOOPDEV} |
Note that I named the root partition demoroot (the ext4 label). This will be important later in the Grub configuration.
3. Copying the root file system, installing grub into the BIOS partition
Now that the partition to be used for our root file system is formatted, let’s copy our chroot/ directory to it and then install grub to the disk with grub-install. Because the disk is GPT formatted, grub will use the BIOS partition to install its core image:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# Loop sparse file LOOPDEV=$(losetup --find --show test.img) partprobe ${LOOPDEV} # Mount OS partition and copy from chroot MOUNTDIR=$(mktemp -d -t demoXXXXXX) mount ${LOOPDEV}p3 $MOUNTDIR rsync -a chroot/ ${MOUNTDIR}/ # Install grub, create config for d in dev sys proc; do mount --bind /$d ${MOUNTDIR}/$d; done chroot ${MOUNTDIR}/ grub-install --modules="ext2 part_gpt" ${LOOPDEV} chroot ${MOUNTDIR}/ update-grub # Unmount OS partition umount $MOUNTDIR/{dev,proc,sys,} rmdir $MOUNTDIR # Remove loop losetup -d ${LOOPDEV} |
After this, you should be able to boot the system via BIOS from the root GPT partition. I always do that via KVM like this:
1 |
kvm -drive format=raw,file=test.img -serial stdio -m 4G -cpu host -smp 2 |
4. Preparing the EFI partition
After you’ve verified that you can boot via BIOS, let’s make sure that we can also boot on UEFI systems. We formatted the EFI partition earlier. All that’s remaining is to create/copy the bootx64.efi image file and a valid grub.cfg to it.
Grub can create the EFI image via the grub-mkimage command using the Grub modules in /usr/lib/grub/x86_64-efi (part of the grub-efi-amd64-bin package). In order to avoid having to load anything from the file system, we include all the modules in the image. If you are not booted via EFI, you may need to install the grub-efi-amd64-bin package.
The Grub config file in the EFI partition grub.cfg is rather simple: It uses search --label demoroot to look for the root partition and then simply includes the actual grub config file via configfile ....
Let’s mount the EFI partition again, create the image and the grub config:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# Loop sparse file LOOPDEV=$(losetup --find --show test.img) partprobe ${LOOPDEV} # Mount EFI partition MOUNTDIR=$(mktemp -d -t demoXXXXXX) mount ${LOOPDEV}p2 $MOUNTDIR # Create EFI boot image apt-get install grub-efi-amd64-bin -y --force-yes mkdir -p ${MOUNTDIR}/EFI/BOOT grub-mkimage \ -d /usr/lib/grub/x86_64-efi \ -o ${MOUNTDIR}/EFI/BOOT/bootx64.efi \ -p /efi/boot \ -O x86_64-efi \ fat iso9660 part_gpt part_msdos normal boot linux configfile loopback chain efifwsetup efi_gop \ efi_uga ls search search_label search_fs_uuid search_fs_file gfxterm gfxterm_background \ gfxterm_menu test all_video loadenv exfat ext2 ntfs btrfs hfsplus udf # Create grub config cat <<GRUBCFG > ${MOUNTDIR}/EFI/BOOT/grub.cfg search --label demoroot --set prefix # << Note again, this searches by the 'demoroot' label! configfile (\$prefix)/boot/grub/grub.cfg GRUBCFG # Unmount and clean up umount ${MOUNTDIR} losetup -d ${LOOPDEV} |
After that, the EFI partition should contain the two files /EFI/BOOT/bootx64.efi and /EFI/BOOT/grub.cfg. You can verify that like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# Loop sparse file LOOPDEV=$(losetup --find --show test.img) partprobe ${LOOPDEV} # Mount EFI partition MOUNTDIR=$(mktemp -d -t demoXXXXXX) mount ${LOOPDEV}p2 $MOUNTDIR # List things (cd $MOUNTDIR; find .) # Should output this: # ./EFI # ./EFI/BOOT # ./EFI/BOOT/bootx64.efi # ./EFI/BOOT/grub.cfg # Unmount and clean up umount ${MOUNTDIR} losetup -d ${LOOPDEV} rmdir ${MOUNTDIR} |
That’s it. You should now be able to boot this image via UEFI. I always test this with KVM and OVMF:
1 2 |
apt-get install ovmf -y --force-yes kvm --bios /usr/share/qemu/OVMF.fd -net none -drive format=raw,file=test.img -serial stdio -m 4G -cpu host -smp 2 |
5. One script to do it all
If you are an impatient man/woman, then you may just want to run the following script to do all of this in one go. You’ll of course have to adjust it to your needs for production use.
The script can be called in two ways:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Makes a BIOS/UEFI bootable test.img using "demoroot" as OS partition label # and create a minimal chroot via debootstrap in the "chroot/" folder to do so. ./mkbiosefi test.img demoroot # Makes a BIOS/UEFI bootable test.img using "demoroot" as OS partition label, # but re-uses the existing "chroot/" folder (this is faster!) ./mkbiosefi test.img demoroot chroot/ # If you want to use your own image file, you may create it beforehand and loop it truncate -s 40G my.img myloop=$(losetup --find --show my.img) ./mkbiosefi $myloop demo chroot/ # Or use a raw disk, of course ./mkbiosefi /dev/sdX demo chroot/ |
Here’s the script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
#!/bin/bash DISK=$1 BOOTLABEL=$2 ROOTDIR=$3 if [ -z "$DISK" -o -z "$BOOTLABEL" ]; then echo "Syntax: $0 <image|disk> <root-label> [<chroot-dir>]" exit 1 fi if [ "$UID" != "0" ]; then echo "Must be root." exit 1 fi # Exit on errors set -xe # Install dependencies apt-get install -y --force-yes \ debootstrap \ gdisk \ rsync \ grub-efi-amd64-bin \ e2fsprogs # Create chroot (if requested) if [ -z "$ROOTDIR" ]; then ROOTDIR=chroot/ # Bootstrap minimal system debootstrap --variant=minbase xenial chroot # Install kernel and grub for d in dev sys proc; do mount --bind /$d chroot/$d; done DEBIAN_FRONTEND=noninteractive chroot chroot apt-get install linux-image-generic grub-pc -y --force-yes umount chroot/{dev,proc,sys} fi # Create sparse file (if we're not dealing with a block device) if [ ! -b "${DISK}" ]; then truncate --size 30G $DISK fi # Create partition layout sgdisk --clear \ --new 1::+1M --typecode=1:ef02 --change-name=1:'BIOS boot partition' \ --new 2::+100M --typecode=2:ef00 --change-name=2:'EFI System' \ --new 3::-0 --typecode=3:8300 --change-name=3:'Linux root filesystem' \ $DISK # Loop sparse file LOOPDEV=$(losetup --find --show $DISK) partprobe ${LOOPDEV} # Create filesystems mkfs.fat -F32 ${LOOPDEV}p2 mkfs.ext4 -F -L "${BOOTLABEL}" ${LOOPDEV}p3 # Mount OS partition, copy chroot, install grub MOUNTDIR=$(mktemp -d -t demoXXXXXX) mount ${LOOPDEV}p3 ${MOUNTDIR} rsync -a ${ROOTDIR}/ ${MOUNTDIR}/ for d in dev sys proc; do mount --bind /$d ${MOUNTDIR}/$d; done chroot ${MOUNTDIR}/ grub-install --modules="ext2 part_gpt" ${LOOPDEV} chroot ${MOUNTDIR}/ update-grub umount $MOUNTDIR/{dev,proc,sys,} rmdir $MOUNTDIR # Mount EFI partition MOUNTDIR=$(mktemp -d -t demoXXXXXX) mount ${LOOPDEV}p2 $MOUNTDIR mkdir -p ${MOUNTDIR}/EFI/BOOT grub-mkimage \ -d /usr/lib/grub/x86_64-efi \ -o ${MOUNTDIR}/EFI/BOOT/bootx64.efi \ -p /efi/boot \ -O x86_64-efi \ fat iso9660 part_gpt part_msdos normal boot linux configfile loopback chain efifwsetup efi_gop \ efi_uga ls search search_label search_fs_uuid search_fs_file gfxterm gfxterm_background \ gfxterm_menu test all_video loadenv exfat ext2 ntfs btrfs hfsplus udf # Create grub config cat <<GRUBCFG > ${MOUNTDIR}/EFI/BOOT/grub.cfg search --label "${BOOTLABEL}" --set prefix configfile (\$prefix)/boot/grub/grub.cfg GRUBCFG umount $MOUNTDIR rmdir $MOUNTDIR # Remove loop device sync ${LOOPDEV} losetup -d ${LOOPDEV} echo "Done. ${DISK} is ready to be booted via BIOS and UEFI." |
Hey Philipp,
This is a great article, I found it super useful. Thanks :-)
FYI, I had to apt-get the ‘udev’ package because partprobe complained about ‘udevadm’ being missing and I needed to apt-get ‘parted’ so that partprobe was installed. I also added ‘–arch=amd64’ to debootstrap because I wanted a 64bit distro, it seemed to default to ‘i386’ which I assumed mean’t 32bit.
My next step is to investigate the ‘live_boot’ package to see if I can turn the distro into one that can install itself somewhere when booted and I’d like to find a way to squeeze the image size down from 30GB to something much smaller. Any ideas?
Thanks once again for the article,
Best regards
Mark
Is it possible to have a working system on a sparse file with only uefi, without any bios partition? Thanks a lot for your script!
The BIOS partition is used by grub only for BIOS boot. If you want UEFI only all you need is the EFI partition. The EFI grub.cfg references the grub.cfg on the root partition, not the boot partition, so you should be good there. You’d have to adjust the script not to create a BIOS partition, obviously.
mkosi (https://github.com/systemd/mkosi) does the same more or less in a automated manner, but only for UEFI systems.
You probably need to install grml-debootstrap to simplify a lot of this, for Debian Stretch I had to change a lot and it’s a lot easier to have grml-debootstrap generate the correct locales for instance.
Hi
1. Will this technique be good also for buildroot generated rootfs and kernel?
2. I tried the method and the uefi boot didn’t work for me. It reverted to normal boot after few error messages of not being able to uefi boot from cdrom and floppy…???
Thanks
Gil
thanks !
hear https://pastebin.com/wUE25LwT#mkbiosefi is a URL for he script !
I have tried to run this solution on Debian9 Stretch (Linux dt 4.9.0-9-amd64 #1 SMP Debian 4.9.168-1+deb9u3 (2019-06-16) x86_64 GNU/Linux) however during boot I get “attempt to read or write outside of partition” from grub. The boot was through BIOS Boot after first part of the article.
Another thing is that I have got error after this part:
# Unmount OS partition
umount $MOUNTDIR/{dev,proc,sys,}
/tmp/demoQOJKO9/sys: target is busy
(In some cases useful info about processes that
use the device is found by lsof(8) or fuser(1).)
there was system process:
lsof /tmp/demoQOJKO9/sys/
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd-l 397 root 7r REG 0,16 4096 2676 /sys/devices/virtual/tty/tty0/active
which had successfully blocked all of the unmounting attempts. I had dd test.img to usb drive and powered off the system.
Could you point which direction to go to as I belive I have tried all of the posibilities?
Hey… wonderful article. Works splendid. I have one problem btw. I rewrote the scripts to make a Void Linux install. When I boot the Void linux and log in.. take ‘ls /boot’ it only shows the EFI directory not the kernel and initramdisk. This is when I boot using EFI. Do you get the idea whats wrong? Sorry for my bad english.
Hi its helpful for me as newbee but can you explain more details steps as you mention in your sentences
Alternatively, you can of course use any Linux root file system, provided that it has a kernel and grub. You could, for instance, just copy your own root file system to the chroot/ folder.
Cause i like to copy root file system from my ubuntu 20.04.1 live usb to my hdd partition then run your script after step 1
Please…Thank You & Bestregard
I’m new to Linux and found your page when I was searching EFI to BIOS for Linux image. We have multiple systems that has BIOS and BIOS cannot be updated to support EFI. Can an existing GPT EFI Linux disk be converted to BIOS boot? Any information will be highly apricated.
Thanks.