DEV Community

Cover image for Journey towards a maintenance free system multi booting
suntong
suntong

Posted on • Edited on

2 1

Journey towards a maintenance free system multi booting

Problem statement and goal

This article tries to simply the situation for people who dual boot/multi boot their computer systems, be them Debian, Mint, Arch, Fedora, openSUSE, etc, or Windows.

For multi booting all the operation systems, I have a standalone boot partition as well as an UEFI ESP partition. From there, any operation systems on my disk can be booted. A Debian boot entry looks like this:

menuentry 'Debian GNU/Linux' --class debian --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-22fbb9d0-eac8-4c62-b27c-1f67ecdb2cde' {
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_msdos
insmod ext2
set root='hd0,msdos5'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos5 --hint-efi=hd0,msdos5 --hint-baremetal=ahci0,msdos5 22fbb9d0-eac8-4c62-b27c-1f67ecdb2cde
else
search --no-floppy --fs-uuid --set=root 22fbb9d0-eac8-4c62-b27c-1f67ecdb2cde
fi
echo 'Loading Linux 5.10.0-6-amd64 ...'
linux /boot/vmlinuz-5.10.0-6-amd64 root=UUID=22fbb9d0-eac8-4c62-b27c-1f67ecdb2cde ro net.ifnames=0 quiet
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img-5.10.0-6-amd64
}
view raw grub1.cfg hosted with ❤ by GitHub

Let me stress again that this is just one single entry. There are also several "Advanced options" that goes with each Linux system, so my multi-boot grub.cfg file looks scary, because it is humongous. But that's not the only problem.

Problem #1: Because it is in a standalone boot partition, I normally don't need to mount it, but whenever I got my kernel manually or automatically upgraded, I have to remember to mount this partition and update the boot entries there. Otherwise,

  • I will not be able to boot up my system again, if the old kernels are somehow/automatically purged.
  • I will not be able to take advantage of the new kernel, even if they have been installed.

Problem #2: I like to bring my 40_custom up to the top of my menu selection, instead of down at the bottom.

Problem #3: I like to order my boot entries in the order I want, instead of being determined by their partition orders.

Although grub can generate boot entries automatically, all above problem are blind spots of grub, and you have to come up with your own ways of dealing with them.

The good news is that the grub.cfg file does not have to be so humongous, scary, and messy. At the end of this article, you'll find that things can be pretty simple. Scroll to the bottom to see if you don't want to go through the discovery journey that leads towards it. I.e. you'll find that

Things can be so simple that people can start writing/maintaining the grub boot menu by hands now. And in the next blog, we can see that things can be even more simpler, with the help of easygen.

Let the journey begin -- we'll going to go through a series of steps to simplify the grub.cfg file so that in the end, they can be edited manually. Now, step number one,

Remove the "Advanced options"

Let's take a look how big my grub.cfg file is:

$ wc /boot/grub/grub.cfg
  355  1338 15006 /boot/grub/grub.cfg
Enter fullscreen mode Exit fullscreen mode

Over 350 lines! Let's first simplify the grub.cfg file by remove the "Advanced options" menu entries, as I've been using Linux for over 20 years, and not a single chance had I ever need to dip into those options. Moreover, the grub menu can be easily edited if I want, say to boot into single mode. They can go for sure:

$ sed '/^submenu .Advanced options for /,/^}/d' /boot/grub/grub.cfg | wc
    183     581    5602
Enter fullscreen mode Exit fullscreen mode

The file size almost dropped in half and now is under 200 lines.

Remove the unnecessary if conditions

Look at the following lines in the above posted grub boot menu:

if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos5 --hint-efi=hd0,msdos5 --hint-baremetal=ahci0,msdos5 22fbb9d0-eac8-4c62-b27c-1f67ecdb2cde
else
search --no-floppy --fs-uuid --set=root 22fbb9d0-eac8-4c62-b27c-1f67ecdb2cde
fi
view raw grub2.cfg hosted with ❤ by GitHub

Has anyone ever wondering what it is doing?

Well, it turns out that

the conditional code checking for this feature is pointless in all modern Linux distributions

So why still keep it around?

These if conditions are second that need to go. After that, a single boot entry will look like:

mmenuentry 'Debian GNU/Linux' {
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
set root='hd0,gpt2'
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 20a4d9df-8d2d-4401-af19-3261fac51526
echo 'Loading Linux 5.10.0-9-amd64 ...'
linux /boot/vmlinuz-5.10.0-9-amd64 root=UUID=20a4d9df-8d2d-4401-af19-3261fac51526 ro text net.ifnames=0 quiet
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img-5.10.0-9-amd64
}
view raw grub3.cfg hosted with ❤ by GitHub

Remove the unnecessary module loading

Now, why are there so many insmod statements in each boot entry? My computer contains either Linux or Windows, and they reside all in GPT, as ext4 partitions. I.e.,

All my Linux will need the same modules, so it doesn't make sense to repeat every one of them in each individual boot entry.

Can I load all the modules I need only once at the beginning, and save each individual boot entries from repeating themselves?

It turns out that I can. So now my Linux boot entry looks like this:

menuentry 'Debian GNU/Linux Specific' {
set root='hd0,gpt2'
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 20a4d9df-8d2d-4401-af19-3261fac51526
echo 'Loading Linux ...'
linux /boot/vmlinuz-5.10.0-9-amd64 root=UUID=20a4d9df-8d2d-4401-af19-3261fac51526 ro text net.ifnames=0 quiet
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img-5.10.0-9-amd64
}
view raw grub4.cfg hosted with ❤ by GitHub

A little help please

It'll be tedious to go through each boot entry and do the above clean up, so a little help from a script will definitely be the way to go, and here it is:

/^submenu .Advanced options for /,/^}/d
/^menuentry /{ s/^/\n/; s/--\(class\|id\) .*$/{/ }
/load_video\|insmod/d
# remove entire if block
/feature_platform_search_hint/{ N; N; N; N; d; }
s/^\( \|\t\)/ /
view raw grub2clean.sed hosted with ❤ by GitHub

Toward maintenance free booting

Now it is time to tackle Problem #1 -- whenever my kernel is upgraded, I don't want to

  • mount my boot partition, and
  • update the boot entries there

I want my boot partition's boot entries to be able to survive such change and always boot to the latest kernel, whatever they are. Is that possible? Well,

It sure can, using the trick like this:

menuentry 'Debian GNU/Linux Simple' {
set root='hd0,gpt2'
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 20a4d9df-8d2d-4401-af19-3261fac51526
echo 'Loading Linux ...'
linux /vmlinuz root=UUID=20a4d9df-8d2d-4401-af19-3261fac51526 ro text net.ifnames=0
echo 'Loading initial ramdisk ...'
initrd /initrd.img
}
view raw grub5.cfg hosted with ❤ by GitHub

Maintenance free, at last!

Look at the linux /vmlinuz line, there is still a root=UUID=xxx parameter there. Now what if I reformat the partition and install a new Linux? The UUID will surely change and the above boot entry will surely not be able to survive such change.

This hurdle is the most difficult of all. Luckily someone has already looked into such problem, but sadly, nobody else even care, judging from the up-votes from the SO question and answer. Anyway, here, the prestige of the whole article, here is the solution:

menuentry 'Debian GNU/Linux Default' {
set root='hd0,gpt2'
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 20a4d9df-8d2d-4401-af19-3261fac51526
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
}
view raw grub6.cfg hosted with ❤ by GitHub

The probe -u $root --set=rootuuid line will set the $rootuuid variable, then it get passed to the next linux /vmlinuz line.

Hooray~~, problem solved!!

One step further

Now, take a closer look at the search --no-floppy --fs-uuid ... line.

What does it do?

It actually set the $root grub variable according to a fixed UUID value on that line, and therefore change the boot partition, which will undermine the whole effort of our above attempt.

That will surely need to go away.

Summary of the journey

The above discovery journey was a step by step one and it has been logged here:

menuentry 'Debian GNU/Linux Default' {
set root='hd0,gpt2'
#search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 20a4d9df-8d2d-4401-af19-3261fac51526
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
}
menuentry 'Debian GNU/Linux Simple' {
set root='hd0,gpt2'
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 20a4d9df-8d2d-4401-af19-3261fac51526
echo 'Loading Linux ...'
linux /vmlinuz root=UUID=20a4d9df-8d2d-4401-af19-3261fac51526 ro text net.ifnames=0
echo 'Loading initial ramdisk ...'
initrd /initrd.img
}
menuentry 'Debian GNU/Linux Specific' {
set root='hd0,gpt2'
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 20a4d9df-8d2d-4401-af19-3261fac51526
echo 'Loading Linux ...'
linux /boot/vmlinuz-5.10.0-9-amd64 root=UUID=20a4d9df-8d2d-4401-af19-3261fac51526 ro text net.ifnames=0 quiet
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img-5.10.0-9-amd64
}
mmenuentry 'Debian GNU/Linux Org' {
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
set root='hd0,gpt2'
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 20a4d9df-8d2d-4401-af19-3261fac51526
echo 'Loading Linux 5.10.0-9-amd64 ...'
linux /boot/vmlinuz-5.10.0-9-amd64 root=UUID=20a4d9df-8d2d-4401-af19-3261fac51526 ro text net.ifnames=0 quiet
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img-5.10.0-9-amd64
}
view raw os0_default.cfg hosted with ❤ by GitHub

The boot entries were build from the ground up and move step by step to the top. All entries are verified to be working fine.

Conclusion

Once upon a time, people write their own boot menu. But grub2 came alone and people just gave up such practice and rely solely on the machine.

The journey in this article show that things do not have to be that way as the only way -- we can still write our own boot menu, just do not let the humongous file size scare you off, as a single boot menu entry now 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
}
view raw grub7.cfg hosted with ❤ by GitHub

Most of which are boilerplate that don't need to be changed. I.e., the only things that matter are the 'Debian GNU/Linux' and the 'hd0,gpt2'. 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.

There are a few more boilerplates that need to be in place so that you can only write the above few lines and everything will work out magically, which will be covered in next article, with the help of easygen.

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more