User:Goverp/ReusableGRUBMenu

From Gentoo Wiki
Jump to:navigation Jump to:search

This article contains a manual grub configuration file for an unthemed grub menu, suitable for simple Gentoo (and similar) linux installations. Once installed, it's no longer necessary to run grub-mkconfig after installing a new kernel. The menu code is a lot simpler than that generated by grub-mkconfig. It should work with both BIOS and GPT partition setups. It probably only works for x86/IA64 architectures

The article assumes familiarity with GRUB and Gentoo Linux Kernel configuration.

It may be worthwhile to use "grub-mkconfig" on your system first, and compare the resulting configuration with the one here, especially for machines with different operating systems installed, or configured to use advanced features such as root file systems on RAID arrays or encrypted file systems.

See some #Example menus below.

Warning
The configuration must be customized to identify your root device! It needs customization of the lines selecting a background image and colour scheme and the kernel command line parameters and recovery option to pass to the select kernel. If you don't use the names /boot/vmlinuz[...] and optionally /boot/initramfs[...] the script building the menus must be customized.
  • Remember that a broken grub.cfg means you may have to use the GRUB command line to boot your system.
  • Always run
    user $grub-syntax-check
    before installing a manually-edited grub configuration.
  • Install or reinstall sys-boot/grub including the "emu" platform to provide a grub test environment:
FILE /etc/portage/make.confExample GRUB platform variable
GRUB_PLATFORMS="efi-64 emu"
  • Create your new grub configuration in a test directory, say /boot/test/grub.cfg and then run
    user $grub-emu -d /boot/test
    to test it. Use grub's "e" key to edit (or in reality see) the resulting menu entries. (Terminate the emulator by entering command mode and typing "exit".) Only move the configuration into /boot/grub when you are completely happy it's correct.
  • If all else fails, it should be possible to enter the grub command line, and enter something like
    set root=/dev/sda1
    linux /boot/vmlinuz
    initrd /boot/initramfs.img
    boot

The configuration provides:

  • entries to boot kernels identified by links such as vmlinuz, vmlinuz.old and for consistency, vmlinuz.new;
  • a submenu of all vmlinux-v.r.m-xxxx entries so you can boot a chosen kernel by version/release/modification level; and
  • a submenu of kernels booted with a recovery option.
FILE /boot/grub/grub.cfgGRUB configuration
#
# Manual grub configuration file for an unthemed grub menu.
# Once installed, you should not need to run grub-mkconfig
# each time you install a new kernel.
#
# You MUST CUSTOMIZE this file before use.
# Keep a copy of your previous menu to hand.
# See https://wiki.gentoo.org/User:Goverp/ReusableGRUBMenu
#
# Paul Gover, September 2020.

# The code immediately below is copied from that produced by grub-mkconfig
# It appears to do something with persisting the chosen boot entry.
### BEGIN /etc/grub.d/00_header ###
if [ -s $prefix/grubenv ]
then load_env
fi

if [ "${next_entry}" ]
then
   set default="${next_entry}"
   set next_entry=
   save_env next_entry
   set boot_once=true
else
   set default="0"
fi

if [ "${prev_saved_entry}" ]
then
  set saved_entry="${prev_saved_entry}"
  save_env saved_entry
  set prev_saved_entry=
  save_env prev_saved_entry
  set boot_once=true
fi

function savedefault {
  if [ -z "${boot_once}" ]
   then
    saved_entry="${chosen}"
    save_env saved_entry
  fi
}

function load_video {
  if [ "$feature_all_video_module" = "y" ]
  then insmod all_video
  else
    insmod efi_gop
    insmod efi_uga
    insmod ieee1275_fb
    insmod vbe
    insmod vga
    insmod video_bochs
    insmod video_cirrus
  fi
}

if [ "$feature_default_font_path" = "y" ]
then font=unicode
else
   insmod part_gpt
   insmod f2fs

   search --no-floppy --fs-uuid --set=root 558709cf-d87b-4627-8b0b-9fa21f48e5e7
   font="/usr/share/grub/unicode.pf2"
fi

if loadfont $font
then
  set gfxmode=auto
  load_video
  insmod gfxterm
  set locale_dir=$prefix/locale
  set lang=en_GB
  insmod gettext
fi

terminal_output gfxterm
if [ "$feature_timeout_style" = "y" ]
then
  set timeout_style=menu
  set timeout=15
else set timeout=15
fi

### END old /etc/grub.d/00_header as updated by me ###

### Insert grub modules for GPT partition tables, filtering, ext/2/3/4, f2fs and regex
# You can add other grub modules for mdadm, btrfs etc. if needed
# or remove f2fs.
insmod part_gpt
insmod part_msdos
insmod diskfilter
insmod ext2
insmod f2fs
insmod regexp   # Enables filename globbing in for loops

### Function to find the newest file version with filename $1-*
# Result will be in $latest
# Not used in the code below, but it's there if you need it.
# Parameter 1 is the path and prefix to kernels, such as /boot/vmlinuz
function findlatest {
  latest=
  for file in $1-*
  do
     if [ "$latest" == "" -o $file -nt $latest ]
     then latest=$file
     fi 
  done 
}

### CUSTOMIZE the following for your choice of background.
insmod jpeg
background_image /boot/grub/DarkestHour.jpg
set color_normal=white/black
set gfxpayload=text

# CUSTOMIZE the line below to find your root device.
# See "info grub" for more information on the alternatives.
# You could use --fs-label or a simple device address
# but uuid is probably more reliable and secure.
search --no-floppy --fs-uuid --set=root  558709cf-d87b-4627-8b0b-9fa21f48e5e7

# CUSTOMIZE your kernel command line parameters.
KernelOptions="rootfstype=f2fs root=LABEL=gentoo fsck.f2fs=-a settimeout mdadm net.ifnames=0 acpi_enforce_resources=lax"
RecoveryOption="softlevel=S"

### Create a menuentry to boot a kernel, optionally with an initramfs
# Parameters:
#	1: the entry title
#	2: the kernel name suffix, either a hyphen followed by a version number, or one of ".old", ".new" or "" (current)
#	3: the command line parameters to pass to the kernel
# If no kernel with the given suffix exists, then skip the menu entry.
#
# Note that both menuentry and submenu run as subprocesses with a new environment
# This apparently means that BootDir and similar need to be passed as parameters in the menu command
function kernelentry {
	if [ -f /boot/vmlinuz$2 ]
	then
		menuentry --id="vmlinuz$2" "$1" "/boot/vmlinuz$2" "/boot/initramfs$2.img" "$3" {
			echo	"Loading Linux $2 ..."
			linux	$2 $4
			if [ -f $3 ]
			then
				echo	"Loading initramfs $3 ..."
				initrd	$3
			fi		
		}
	fi
}

### Create a title or blank line in the grub menu
function spacer {
	menuentry "$1" {
		# grub-script-check demands some content in this empty separator
		return
	}
}

### Compare version numbers; returns true if $1 precedes $2
function precedes {
	# regex calls prefix strings with X to avoid leading hyphens being interpreted as options
	# Note, if grub script supported printf, it would be much faster to create a canonical version from the  VMR-rc fields
	# (e.g. 5.9.2-rc3 -> 005090203) 

	# Parse out V.R.M from the parameters
	regexp --set=1:a --set=2:arc "([0-9.]+)(.*\$)" "X$1"
	regexp --set=1:b --set=2:brc "([0-9.]+)(.*\$)" "X$2"

	while true
	do
		a1=""
		b1=""
		# Parse out the next numeric field in the V.R.M
		regexp --set=1:a1 --set=2:a "([0-9]+)(.*\$)" "X$a"
		regexp --set=1:b1 --set=2:b "([0-9]+)(.*\$)" "X$b"

		# In the following comparisons, append 0s to prevent invalid null string comparisons
		if   [ "${a1}0" -lt "${b1}0" ]
		then return 0
		elif [ "${a1}0" -gt "${b1}0" ]
		then return 1
		elif [ "$a1$b1" = "" ]
		then break
		fi 

	done
	# V.R.M equal, assume the rest contain release candidate (rc) numbers
	regexp --set=a1 "([0-9]+)" "X$arc"
	regexp --set=b1 "([0-9]+)" "X$brc"

	# Note that e.g. 5.4.6-rc1 PRECEDES 5.4.6, since the latter is a real release, not a candidate
	if   [ -z "$a1" ]
	then return 1
	elif [ -z "$b1" ]
	then return 0
	fi

	# We have 2 release candidate numbers; compare them
	test "${a1}0" -lt "${b1}0"
}

### Finally, build the grub menu
# First, up to three generic kernel entries, depending which symlinks exist
kernelentry "Linux testing kernel"	".new"	"$KernelOptions"

kernelentry "Linux kernel"		""	"$KernelOptions"

kernelentry "Linux previous kernel"	".old"	"$KernelOptions"

spacer

# A couple of useful entries that don't boot linux
menuentry " * Reboot" {
	reboot
}

menuentry " * Halt" {
	halt
}

spacer

# A submenu for each versioned kernel name in /boot
# Submenus don't seem to inherit script variables, so we must pass them as parameters
submenu --id="kernel_list.new" "Kernels by version --->" "$KernelOptions" {
	spacer "Kernels by version"
	spacer

	# Generate the list of kernels sorted by decending order of version number
	# This is a very inefficient algorithm, but noone else is using the cpu...
	last="-99999999"
	max="-00000000"
	while true
	do
		next="$max"
		for kernel in /boot/vmlinuz-*
		do
			regexp --set=suffix "^/boot/vmlinuz(.*)" "$kernel"
			if precedes "$next" "$suffix"
			then	if precedes "$suffix" "$last"
				then next="$suffix"
				fi
			fi
		done

		if [ "$next" = "$max" ]
		then break
		fi

		kernelentry "Gentoo GNU/Linux vmlinuz$next" "$next" "$2"
		last="$next"
	done
}

spacer

# A submenu for each kernel name in /boot, passing the recovery option
submenu --id="recovery" " * Recovery options --->" "$KernelOptions $RecoveryOption" {
	spacer "Recovery options"
	spacer

	# Note the slightly different list to include the symlinks as well as the versioned kernels
        for kernel in /boot/vmlinuz*
        do
                regexp --set=suffix "^/boot/vmlinuz(.*)" "$kernel"
                kernelentry "Gentoo GNU/Linux vmlinuz$suffix - Recovery mode" "$suffix" "$2"
        done
}

Example menus

The following menu and submenu are screen images captured from "grub-emu" in a terminal window, and do not show the graphics background.

The main menu:


The Kernels by version submenu: