The journey continues
First, recap from the previous article, a single grub
boot menu entry can be as simple as:
menuentry 'Debian GNU/Linux' { | |
set root='hd0,gpt2' | |
probe -u $root --set=rootuuid | |
echo 'Loading Linux ...' | |
linux /vmlinuz root=UUID=$rootuuid ro text net.ifnames=0 | |
echo 'Loading initial ramdisk ...' | |
initrd /initrd.img | |
} |
With it, we don't need to hunt for the UUIDs, and we don't need to remember to mount boot partition and update the boot entries any more. It can boot to whatever latest kernel it is, and it will still work even we reformat the partition and install a new Linux, and it will still work even we change the distro from Debian, to Mint, Arch, Fedora, or openSUSE etc.
In this article, we'll first look at those few more boilerplates that enable you to only write the above few lines and everything will work out magically. Then we'll move on to how to make things even simpler, with the help of easygen.
The supporting files
To break free from the rigid format requirement of 40_custom
and allow people only write the above few lines, or to order boot entries in the order we want, instead of being determined by their partition orders, we need to build our grub.cfg
file from ground up ourselves. And, it is not that scary either and could be really straightforward. Here are all the code it needs:
source /boot/grub/grub-main.cfg |
The grub.cfg
file that replace grub
s default .cfg
file. Deliberately made so simple so that it can be easily restored if it was accidentally overwritten.
## grub2 configuration | |
source /boot/grub/grub-header.cfg | |
insmod regexp | |
for config in /boot/grub/*_default.cfg ; do source "$config" ; done | |
for config in /boot/grub/*_options.cfg ; do source "$config" ; done | |
if [ -f /boot/grub/grub-addons.cfg ] ; then | |
source "/boot/grub/grub-addons.cfg" | |
fi | |
source /boot/grub/grub-footer.cfg |
The grub-main.cfg
that the grub.cfg
file actually calls. The idea comes from grml. We can see that it is sourcing a header, a footer, and the main/real .cfg
files, like the entry shown above.
Arrange those .cfg
files so that their order show up in the order you want in the grub menu. For example, this is how I arrange my grub menu:
I.e., in my preference, I want my grub menu to be in the order of:
- My current Linux
- Live Linux from ISO, in case I need to check/repair anything
- My Windows
- My old Linux versions
They are ordered from most probably used to the least probably used. Changing the order is simple, just shift them around in the .cfg
text file.
The grub menu's look-and-feel is defined in the header, and the "Shutdown / Reboot" is defined in footer, showing below respectively.
# == fundementals | |
set menu_color_normal=cyan/blue | |
set menu_color_highlight=white/blue | |
set timeout=3 | |
# == loadfont | |
font=unicode | |
set locale_dir=$prefix/locale | |
set lang=en_US | |
# -- load_video | |
insmod gettext | |
insmod gfxterm | |
insmod all_video | |
# load common modules | |
insmod gzio | |
insmod part_gpt | |
insmod ext2 |
ISO Booting
The bonus and prestige of the whole article, here is how ISO files are automatically discovered and listed:
submenu "Boot from ISO ... >" { | |
set menu_color_normal=black/light-gray | |
set menu_color_highlight=yellow/dark-gray | |
insmod regexp | |
for isofile in /boot/iso/*ubuntu-*-desktop*.iso; do | |
regexp --set=isoname "/boot/iso/(.*)" "$isofile" | |
submenu "$isoname >" "$isofile" { | |
set menu_color_normal=black/light-red | |
set menu_color_highlight=yellow/light-red | |
iso_path="$2" | |
loopback loop "$iso_path" | |
menuentry 'Live (amd64)' { | |
bootoptions="boot=casper iso-scan/filename=$iso_path noeject noprompt" | |
linux (loop)/casper/vmlinuz $bootoptions | |
initrd (loop)/casper/initrd | |
} | |
menuentry 'Live (amd64 failsafe)' { | |
bootoptions="boot=casper iso-scan/filename=$iso_path memtest noapic noapm nodma nomce nolapic nomodeset nosmp nosplash vga=normal" | |
linux (loop)/casper/vmlinuz $bootoptions | |
initrd (loop)/casper/initrd | |
} | |
} | |
done | |
} |
It can automatically discover and list whatever ISO available on disk:
and boot from them:
The red background color is chosen for the grub menu, to symbolize the live system, and also as the notification sign that this will be the last grub menu level, after which the system will be in booting mode.
Can it be even simpler
So far, we can see that using some simple scripts/configs, we can let the grub to show whatever we want it to show, in whatever order we want as well. Now,
Can it be even simpler?
As pointed out in the "Conclusion" section in the last article, the only things that matter in a grub menu entry are the 'Debian GNU/Linux'
and the 'hd0,gpt2'
, the rest are just the boilerplate that don't normally need to be changed.
What if I only want to specify the 'Debian GNU/Linux'
and the 'hd0,gpt2'
part, and let the machine to generate the grub menu entry/entries for me? -- Not a problem, if with the help of easygen.
This is all what you need to specify:
OS: | |
- # Name - Name of the boot entry | |
Name: Debian 11 bullseye | |
# Type - Type of the boot entry | |
# Values can be LinOS, WinOS | |
Type: LinOS | |
# Root - Partition to set grub root with | |
Root: hd0,gpt2 | |
# Disk - Optional, Disk label of the partition | |
Disk: |
Feed that to easygen
as:
easygen os2grub-1
then you'll get:
menuentry 'Debian 11 bullseye' { | |
set root='hd0,gpt2' | |
probe -u $root --set=rootuuid | |
echo 'Booting - Debian 11 bullseye' | |
echo 'Loading Linux Kernel ...' | |
linux /vmlinuz root=UUID=$rootuuid ro text net.ifnames=0 | |
echo 'Loading initial ramdisk ...' | |
initrd /initrd.img | |
} | |
The secret is the easygen's template:
{{range .OS}} | |
{{if eq .Type "LinOS" -}} | |
menuentry '{{.Name}}' { | |
set root='{{.Root}}' | |
probe -u $root --set=rootuuid | |
echo 'Booting - {{.Name}}' | |
echo 'Loading Linux Kernel ...' | |
linux /boot/vmlinuz root=UUID=$rootuuid ro text net.ifnames=0 zswap.enabled=1 zswap.compressor=lz4 zswap.zpool=z3fold zswap.max_pool_percent=52 | |
echo 'Loading initial ramdisk ...' | |
initrd /boot/initrd.img | |
}{{else}}{{if eq .Type "WinOS" -}} | |
menuentry '{{.Name}}' { | |
set root='{{.Root}}' | |
echo 'Booting - {{.Name}}' | |
chainloader /EFI/Microsoft/Boot/bootmgfw.efi | |
}{{end}}{{end}} | |
{{end}} |
Customize it to whatever you like.
But, most people not just has a single boot menu. How do you deal with that? Simple -- with another small tool of grub2yaml.sh
:
sed -En ' | |
1{s/^.*$/OS:/; p} | |
/^submenu .Advanced options for /,/^}/d | |
# WinOS | |
/^menuentry .*Windows/{ | |
s/^menuentry /\n - Name: / | |
s/ --(class|id) .*$/\n Type: WinOS/ | |
p | |
} | |
# LinOS | |
/^menuentry /{ | |
s/^menuentry /\n - Name: / | |
s/ --(class|id) .*$/\n Type: LinOS/ | |
p | |
} | |
# Root | |
/set root=/{ | |
s/^\s*set root=/ Root: / | |
p | |
} | |
' "$@" |
Just run it with
grub2yaml.sh /boot/grub/grub.cfg | tee /tmp/myOSs.cfg
Slightly edit it, and you'll get more entries like this:
OS: | |
- Name: 'Ubuntu 20.04 LTS (20.04) (on /dev/sda3)' | |
Type: LinOS | |
Root: 'hd0,gpt3' | |
- Name: 'Ubuntu 19.04 (19.04) (on /dev/sda7)' | |
Type: LinOS | |
Root: 'hd0,gpt7' | |
- Name: 'Ubuntu 18.04 LTS (18.04) (on /dev/sda6)' | |
Type: LinOS | |
Root: 'hd0,gpt6' | |
- Name: 'Ubuntu 17.04 (17.04) (on /dev/sda8)' | |
Type: LinOS | |
Root: 'hd0,gpt8' | |
- Name: 'Windows Boot Manager (on /dev/sda13)' | |
Type: WinOS | |
Root: 'hd0,gpt13' |
With multiple entries like that, easygen
really starts to shine. You can get the following without a drop of sweat:
menuentry 'Ubuntu 20.04 LTS (20.04) (on /dev/sda3)' { | |
set root='hd0,gpt3' | |
probe -u $root --set=rootuuid | |
echo 'Booting - Ubuntu 20.04 LTS (20.04) (on /dev/sda3)' | |
echo 'Loading Linux Kernel ...' | |
linux /boot/vmlinuz root=UUID=$rootuuid ro text net.ifnames=0 zswap.enabled=1 zswap.compressor=lz4 zswap.zpool=z3fold zswap.max_pool_percent=52 | |
echo 'Loading initial ramdisk ...' | |
initrd /boot/initrd.img | |
} | |
menuentry 'Ubuntu 19.04 (19.04) (on /dev/sda7)' { | |
set root='hd0,gpt7' | |
probe -u $root --set=rootuuid | |
echo 'Booting - Ubuntu 19.04 (19.04) (on /dev/sda7)' | |
echo 'Loading Linux Kernel ...' | |
linux /boot/vmlinuz root=UUID=$rootuuid ro text net.ifnames=0 zswap.enabled=1 zswap.compressor=lz4 zswap.zpool=z3fold zswap.max_pool_percent=52 | |
echo 'Loading initial ramdisk ...' | |
initrd /boot/initrd.img | |
} | |
menuentry 'Ubuntu 18.04 LTS (18.04) (on /dev/sda6)' { | |
set root='hd0,gpt6' | |
probe -u $root --set=rootuuid | |
echo 'Booting - Ubuntu 18.04 LTS (18.04) (on /dev/sda6)' | |
echo 'Loading Linux Kernel ...' | |
linux /boot/vmlinuz root=UUID=$rootuuid ro text net.ifnames=0 zswap.enabled=1 zswap.compressor=lz4 zswap.zpool=z3fold zswap.max_pool_percent=52 | |
echo 'Loading initial ramdisk ...' | |
initrd /boot/initrd.img | |
} | |
menuentry 'Ubuntu 17.04 (17.04) (on /dev/sda8)' { | |
set root='hd0,gpt8' | |
probe -u $root --set=rootuuid | |
echo 'Booting - Ubuntu 17.04 (17.04) (on /dev/sda8)' | |
echo 'Loading Linux Kernel ...' | |
linux /boot/vmlinuz root=UUID=$rootuuid ro text net.ifnames=0 zswap.enabled=1 zswap.compressor=lz4 zswap.zpool=z3fold zswap.max_pool_percent=52 | |
echo 'Loading initial ramdisk ...' | |
initrd /boot/initrd.img | |
} | |
menuentry 'Windows Boot Manager (on /dev/sda13)' { | |
set root='hd0,gpt13' | |
echo 'Booting - Windows Boot Manager (on /dev/sda13)' | |
chainloader /EFI/Microsoft/Boot/bootmgfw.efi | |
} | |
Enjoy
IMHO, the way grub2
generates boot entries automatically, at least as of this article is written, is still a big mess. You may not agree with that, but I do enjoy my grub menu system to be plain and simple, straightforward and out of my way. And, I enjoy my freedom to hand-craft my own boot menus again, just like the good old days, but without an extra spare letter to waste.
Hope you enjoy that as well.
Cheers
Top comments (0)