Multiboot USB
grub.cfg
Finally, the GRUB2 configuration.
What this file contains is one thing and where it lives is another.
Where
The currently running CentOS 7.2 image, a regular install, has its grub.cfg in the ESP partition next to the .efi file:
# find /boot -name grub\* /boot/efi/EFI/centos/grubenv /boot/efi/EFI/centos/grubx64.efi /boot/efi/EFI/centos/grub.cfg /boot/grub /boot/grub2 /boot/grub2/grubenv
Note
CentOS used a bootloader-id of CentOS although the directory on the FAT filesystem was created as centos.
This is slightly confusing as GRUB2 seems to expect the grub.cfg on the USB stick to be in .../boot/grub2.
Trial and error suggests we should be targeting .../boot/grub2/grub.cfg.
What
First Pass
The first time through we might as well get GRUB2 to create a sample file for us. Be warned, though, that the code is simplistic and rummages about in /boot for suitable entries to add. None of which, of course, are on our USB stick.
It does however, get us a decent starter for ten:
grub2-mkconfig -o /tmp/usb-sda/boot/grub2/grub.cfg
You'll see that it finds various linux and initrd images which it adds as GRUB menu options. It'll find roughly as many as you have existing GRUB menu options to boot from!
We can go and look at this and down near the bottom will be the menuentry sections we want to replace. For example:
menuentry 'CentOS Linux (3.10.0-327.10.1.el7.x86_64) 7 (Core)' --class rhel fedora --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-327.10.1.el7.x86_64-advanced-c7de80b1-a4f6-43d8-bc3f-680b9284a0e7' { load_video set gfxpayload=keep insmod gzio insmod part_gpt insmod xfs if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root b48e0b11-72ad-4aa6-8103-2411ce84fa1b else search --no-floppy --fs-uuid --set=root b48e0b11-72ad-4aa6-8103-2411ce84fa1b fi linuxefi /vmlinuz-3.10.0-327.10.1.el7.x86_64 root=/dev/mapper/centos-root ro rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet initrdefi /initramfs-3.10.0-327.10.1.el7.x86_64.img }
We can roughly read that as:
menu entry 'Menu String' <some args> { <sort out the video; load_video is a function defined higher up> <load some modules> <look for a partition with some UUID and set the variable root to its value> linuxefi <vmlinuz file> <kernel args> initrdefi <initrd file> }
Of those, there's a few interesting ones:
the filesystem UUID of the /boot directory
Here, lsblk (or blkid) come to our rescue:
# lsblk -f /dev/sda NAME FSTYPE LABEL UUID MOUNTPOINT sda ├─sda1 vfat 68DA-C96A /tmp/usb-sda/boot/efi └─sda2 xfs dcdff2d3-fb08-420e-a60a-c5df939b497e /tmp/usb-sda
Our USB stick's /boot folder's filesystem UUID is dcdff2d3-fb08-420e-a60a-c5df939b497e (ie. the UUID of the filesystem that has the /boot directory on it).
linuxefi ...
There are a number of non-EFI linux* variants. We won't be covering them here. We will need to find the equivalent vmlinux file, though.
initrdefi ...
Ditto for initrd*.
The Multiboot Trick
It's not a big trick, it's just that GRUB2 can mount ISO files as loop devices and then boot off the resultant loop mount device.
If you were typing everything by hand at the GRUB2 shell prompt, you might type:
set root=(hd0,gpt2) loopback loop /CentOS-7-x86_64-DVD-1511.iso linuxefi (loop)/isolinux/vmlinuz ... initrd (loop)/isolinux/initrd.img
where (hd0,gpt2) is the second GPT partition on hd0 (how do you know it is hd0? Magic!) and the CentOS ISO is in the top-level of that disk (at the same level as the /boot directory). In addition we somehow know what the vmlinuz and initrd filenames are.
We can use some variables (which we'll need for later) and get GRUB to figure out the hard disk for us with:
set ISO="/CentOS-7-x86_64-DVD-1511.iso" set FSUUID=dcdff2d3-fb08-420e-a60a-c5df939b497e search --fs-uuid --set=root $FSUUID loopback loop $ISO linuxefi (loop)/isolinux/vmlinuz ... initrdefi (loop)/isolinux/initrd.img
Filenames
The obvious way to discover the vmlinuz and initrd filenames is to mount the ISO and look for them:
# mkdir /tmp/iso # mount -r CentOS-7-x86_64-DVD-1511.iso /tmp/iso # find /tmp/iso -name vmlinuz\* /tmp/iso/images/pxeboot/vmlinuz /tmp/iso/isolinux/vmlinuz # find /tmp/iso -name initrd\* /tmp/iso/images/pxeboot/initrd.img /tmp/iso/isolinux/initrd.img
(We'll ignore the PXE boot variants!)
Filenames are not consistent across Linux distributions or even across a distribution's variants:
Ubuntu 14.04 Desktop
/casper/vmlinuz.efi and /casper/initrd.lz
Ubuntu 14.04 Server
/install/vmlinuz and /install/initrd.gz
Obviously, copy the ISO files to the xfs partition:
cp *.iso /tmp/usb-sda
If you want to put the ISO files in a subdirectory, no problem. Just remember to insert the subdirectory in the pathname to the ISO files in the menuentry statements!
Kernel Arguments
There's a lot of hidden knowledge here.
Take whatever advice you can from various sources:
- The ArchWiki covers a lot of distributions.
- Some hints about an Ubuntu Live Install and chain loading.
- A gist on various menu entry variants
From these and other sources we might derive some flavours.
A CentOS Install
menuentry '[ISO] CentOS-7-x86_64-DVD-1511' --class rhel fedora --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-327.4.5.el7.x86_64-advanced-9e0fe5e8-33ab-4ea9-8393-fd8697662ae7' { set ISO="/CentOS-7-x86_64-DVD-1511.iso" set FSUUID="dcdff2d3-fb08-420e-a60a-c5df939b497e" search --fs-uuid --set=root $FSUUID loopback loop $ISO load_video set gfxpayload=keep insmod gzio insmod part_gpt insmod xfs linuxefi (loop)/isolinux/vmlinuz inst.stage2=hd:UUID=$FSUUID:$ISO inst.repo=hd:UUID=$FSUUID:$ISO rhgb quiet initrdefi (loop)/isolinux/initrd.img }
A CentOS install requires both the inst.stage2 and inst.repo parameters.
An Ubuntu Server Live Image
menuentry "[ISO] Ubuntu 14.04 LTS (server)" { load_video set gfxpayload=keep set isofile="/ubuntu-14.04-server-amd64.iso" set FSUUID="dcdff2d3-fb08-420e-a60a-c5df939b497e" search --fs-uuid --set=root $FSUUID loopback loop $isofile insmod gzio insmod part_gpt insmod xfs linux (loop)/install/vmlinuz boot=install iso-scan/filename=$isofile liveimg noeject noprompt splash toram -- initrd (loop)/install/initrd.gz }
The key argument here is liveimg.
Ubuntu Desktop
Ubuntu Desktop is essentially the same as the server entry but the vmlinuz and initrd images are in the /casper directory, not the /install directory.
Skip to Next Boot Device
As exit is a valid GRUB2 command which takes us out of the current Boot device and therefore onto the next Boot device as defined in the EFI BootOrder setting.
menuentry "Boot from next device" { load_video exit }
Scripted grub.cfg Annoyances
As sensible people we might have written a script to rummage about in the ISO for sample menu entries to copy for each ISO.
CentOS 7 and now 8 have started using LABEL directives in their menu entries which means they don't have to mess about searching for root filesystems. Something like:
menuentry 'Install CentOS Stream 8.0.1905' --class fedora --class gnu-linux --class gnu --class os { linuxefi /images/pxeboot/vmlinuz inst.stage2=hd:LABEL=CentOS-Stream-BaseOS-x86_64 quiet initrdefi /images/pxeboot/initrd.img }
True enough, the original ISO, if you mount it and run lsblk you'll see it:
# mount CentOS-Stream-x86_64-dvd1.iso /tmp/iso # lsblk -f /dev/loop0 NAME FSTYPE LABEL UUID MOUNTPOINT loop0 iso9660 CentOS-Stream-BaseOS-x86_64 2019-09-20-15-46-31-00 /tmp/iso
That seems fine. However, we have a problem in multi-boot land if we were to naively copy this menu entry: our USB stick isn't labelled with the expected CentOS LABEL for this or any of the other ISO files we're stuffing on the disk.
There's a rather more fatal problem here when we boot. It'll start fine then at some point it'll try to access the kernel using the LABEL, will fail and eventually dracut will start spewing timeout errors and drop you into an emergency shell.
The upshot here is that we can't blindly copy menu entries from the ISO's grub.cfg. We can, however, modify them. In fact, we can throw in a bit of scripting support to help us. We'll know (read: can figure out) the UUID for the xfs partition we create and can twiddle the LABEL parts to refer back to the ISO via the UUID:
set USB_XFS_UUID="9ba5c614-45bc-4b76-a278-21fe709b2e61" menuentry '[ISO] Install CentOS Stream 8.0.1905' --class fedora --class gnu-linux --class gnu --class os { set ISO="/CentOS-Stream-x86_64-dvd1.iso" loopback loop $ISO linuxefi (loop)/images/pxeboot/vmlinuz inst.stage2=hd:UUID=$USB_XFS_UUID:$ISO quiet initrdefi (loop)/images/pxeboot/initrd.img }
Sweet!
Document Actions