2020-11-26
The better way to make an Ubuntu 20.04 ISO that will boot on UEFI systems
Yesterday I wrote about how I made a 20.04 ISO that booted on UEFI systems. It was a messy process with some peculiar things that I didn't understand and places where I had to deviate from Debian's excellent documentation on Repacking a Debian ISO. In response to my entry, Thomas Schmitt (the author of xorriso) got in touch with me and very generously helped me figure out what was really going on. The short version is that I was confused and my problems were due to some underlying issues. So now I have had some learning experiences and I have a better way to do this.
First, I've learned that you don't want to extract ISO images
with 7z
, however tempting and easy it seems. 7z has at least
two issues with ISO images; it will quietly add the El Torito boot images to
the extracted tree, in a new subdirectory called '[BOOT]
', and
it doesn't extract symlinks (and probably not other Rock Ridge attributes).
The Ubuntu 20.04.1 amd64 live server image has some symlinks,
although their presence isn't essential.
The two reliable ways I know of to extract the 20.04.1 ISO image
are with bsdtar
(part of the libarchive-tools package in Ubuntu)
and with xorriso itself. Bsdtar is easier to use but you probably
don't have it installed, while you need xorriso anyway and might
as well use it for this once you know how. So to unpack the ISO
into our scratch tree, you want:
xorriso -osirrox on -indev example.iso -extract / SCRATCH-TREE
(See the Debian wiki
for something you're going to want to do afterward to delete the
tree. Substitute whatever is the correct ISO name here in place of
example.iso
.)
As I discovered due to my conversation with Thomas Schmitt, it can be
important to re-extract the tree any time you think something funny
is going on. My second issue was that my tree's boot/grub/efi.img
had been quietly altered by something in a way that removed its FAT
signature and made UEFI systems refuse to recognize it (I suspect some
of my experimentation with mkisofs
did it, but I don't know for sure).
In a re-extracted tree with a pristine boot/grub/efi.img, the tree's efi.img was valid as an El Torito EFI boot image (and the isolinux.bin is exactly what was used for the original 20.04.1 ISO's El Torito BIOS boot image). So the command to rebuild an ISO that is bootable both as UEFI and BIOS, both as a DVD image and on a USB stick, is:
xorriso -as mkisofs -r \ -V 'Our Ubuntu 20.04 UEFI enabled' \ -o cslab_ubuntu_20.04.iso \ -isohybrid-mbr isohdpfx.bin \ -J -joliet-long \ -b isolinux/isolinux.bin -c isolinux/boot.cat \ -boot-load-size 4 -boot-info-table -no-emul-boot \ -eltorito-alt-boot -e boot/grub/efi.img -no-emul-boot \ -isohybrid-gpt-basdat \ SCRATCH-TREE
(The isohdpfx.bin
file is generated following the instructions in
the Debian wiki page.
This entire command line is pretty much what the Debian wiki says
to do.)
If xorriso
doesn't complain that some symlinks can't be represented
in a Joliet file
name tree, you haven't extracted the 20.04.1 ISO image exactly;
something has dropped the symlinks that should be there.
If you're modifying the ISO image to provide auto-installer data, you need to change both isolinux/txt.cfg
and boot/grub/grub.cfg
. The necessary modifications are covered
in setting up a 20.04 ISO image to auto-install a server (for isolinux) and then yesterday's entry (for GRUB). You may also want to add various
additional files and pieces of data to the ISO, which can be done by
dropping them into the unpacked tree.
(It's also apparently possible to update the version of the installer that's in the ISO image, per here, but the make-edge-iso.sh and inject-subiquity-snap.sh scripts it points to in the subiquity repo are what I would call not trivial and so are beyond what I want to let monkey around in our ISO trees. I've already done enough damage without realizing it in my first attempts. I'll just wait for 20.04.2.)
On the whole this has been a learning experience about not questioning
my assumptions and re-checking my work. I have the entire process
of preparing the extracted ISO's scratch tree more or less automated,
so at any time I could have deleted the existing scratch tree,
re-extracted the ISO (even with 7z
), and managed to build a working
UEFI booting ISO with boot/grub/efi.img. But I just assumed that
the tree was fine and hadn't been changed by anything, and I never
questioned various oddities until later (including the '[BOOT]
'
subdirectory, which wasn't named like anything else on the ISO
image).
Making an Ubuntu 20.04 ISO that will boot on UEFI systems
As part of our overall install process,
for years we've used customized Ubuntu server install images (ie,
ISOs, often burned on to actual DVDs) that were set up with preseed
files for the Debian installer and a few other things we wanted on
our servers from the start. These ISOs have been built in the
traditional way with mkisofs
and so booted with isolinux
. This was
fine for a long time because pretty much all of our servers used
traditional MBR BIOS booting, which is what ISOs use isolinux for.
However, for or reasons outside the scope of this entry, today we
wanted to make our 20.04 ISO image also boot on systems using UEFI
boot. This turned out to be more complicated than I expected.
(For basic background on this, see my earlier entry on setting up a 20.04 ISO image to auto-install a server.)
First, as my co-workers had already discovered long ago, Linux ISOs do UEFI booting using GRUB2, not isolinux, which means that you need to customize the grub.cfg file in order to add the special command line parameters to tell the installer about your 20.04 installer data. We provide the installer data in the ISO image, which means that our kernel command line arguments contain a ';'. In GRUB2, I discovered that this must be quoted:
menuentry "..." { [...] linux /casper/vmlinuz quiet "ds=nocloud;s=/cdrom/cslab/inst/" --- [...] }
(I advise you to modify the title of the menu entries in the ISO's grub.cfg so that you know it's using your modified version. It's a useful reassurance.)
If you don't do this quoting, all the kernel (and the installer)
see is a 'ds=nocloud
' argument. Your installer data will be ignored
(despite being on the ISO image) and you may get confused about
what's wrong.
The way ISOs are made bootable is that they have at least one El
Torito boot
section (see also the OsDev Wiki).
A conventional BIOS bootable ISO has one section; one that can also
be booted through UEFI has a second one that is more intricate. You
can examine various information about El Torito boot sections with
dumpet
, which is in the
standard Ubuntu repositories.
In theory I believe mkisofs
can be used to add a suitable extra
ET boot section. In practice, everyone has switched to building ISO
images with xorriso
,
for good reason. The easiest to follow guide on using xorriso
for
this is the Debian Wiki page on Repacking a Debian ISO, which not only has
plenty of examples but goes the extra distance to explain what the
many xorriso
arguments mean and do (and why they matter).
This is extremely useful since xorriso
has a large and complicated
manpage and other documentation.
Important update: The details of much of the rest of this entry turns out to not be right, because I had a corrupted ISO tree with altered files. For a better procedure and more details, see The better way to make an Ubuntu 20.04 ISO that will boot on UEFI systems. The broad overview of UEFI requiring a GRUB2 EFI image is accurate, though.
However, Ubuntu has a surprise for us (of course). UEFI bootable
Linux ISOs need a GRUB2 EFI image that is embedded into the ISO.
Many examples, including the Debian wiki page, get this image from a
file in the ISO image called boot/grub/efi.img
. The Ubuntu 20.04.1
ISO image has such a file, but it is not actually the correct file
to use. If you build an ISO using this efi.img as the El Torito EFI
boot image, it will fail on at least some UEFI systems. The file
you actually want to use turns out to be '[BOOT]/2-Boot-NoEmul.img
'
in the ISO image.
(Although the 20.04.1 ISO image's isolinux/isolinux.bin
works fine
as the El Torito BIOS boot image, it also appears to not be what
the original 20.04.1 ISO was built with. The authentic thing seems
to be '[BOOT]/1-Boot-NoEmul.img
'. I'm just thankful that Ubuntu
put both in the ISO image, even if it sort of hid them.)
Update: These '[BOOT]
' files aren't in the normal ISO image
itself, but are added by 7z
(likely from the El Torito boot
sections) when it extracts the ISO image into a directory tree for
me. The isolinux.bin
difference is from a boot info table that
contains the block offsets of isolinux.bin
in the ISO. The efi.img
differences are currently more mysterious.
The resulting xorriso
command line I'm using right now
is more or less:
xorriso -as mkisofs -r \ -V 'Our Ubuntu 20.04 UEFI enabled' \ -o cslab_ubuntu_20.04.iso \ -isohybrid-mbr isohdpfx.bin \ -J -joliet-long \ -b isolinux/isolinux.bin -c isolinux/boot.cat \ -boot-load-size 4 -boot-info-table -no-emul-boot \ -eltorito-alt-boot -e '[BOOT]/2-Boot-NoEmul.img' -no-emul-boot \ -isohybrid-gpt-basdat \ SCRATCH-DIRECTORY
(assuming that SCRATCH-DIRECTORY
is your unpacked and modified
version of the 20.04.1 ISO image, and isohdpfx.bin
is generated
following the instructions in the Debian wiki page.)
The ISO created through this definitely boots in VMWare in both UEFI and BIOS mode (and installs afterward). I haven't tried it in UEFI mode on real hardware yet and probably won't for a while.
PS: If you use the Debian wiki's suggested xorriso
command line
to analyze the 20.04.1 ISO image, it will claim that the El Torito
EFI boot image is 'boot/grub/efi.img'. This is definitely not the
case, which you can verify by using dumpet
to extract both of
the actual boot images from the ISO and then cmp
to see what they
match up with.