Skip to content

Instantly share code, notes, and snippets.

@skeeet
Forked from smoser/README.md
Created Apr 27, 2020
Embed
What would you like to do?
Boot a specific installed Ubuntu kernel using grub-reboot and grub-set-default

Ubuntu Grub Boot Kernel

Boot a specific installed Ubuntu kernel using grub-reboot and grub-set-default.

This allows you to pick what kernel you want to boot on next reboot, or set the default, without having to know much about how grub works or editing config files.

Usage

  Usage: boot-kernel [options] [kernel]
     call grub-reboot or grub-set-default to boot the provided kernel.
     options:
        -n | --dry-run      do not make changes, only report what would be done.
             --setup-only   only setup 'saved' in /etc/default/grub.
                            do not supply kernel with --setup-only.
             --default      run 'grub-set-default' rather than 'grub-reboot'
     Examples:
       * boot kernel /boot/vmlinuz-4.13.0-17-generic next time.
         $ boot-kernel /boot/vmlinuz-4.13.0-17-generic
       * edit /etc/default/grub to enable 'saved'
         $ boot-kernel --setup-only
       * set the default kernel.
         $ boot-kernel --default /boot/vmlinuz-4.15.0-22-generic
         $ reboot

Example

Here is an example of using boot-kernel on a 16.04 instance to toggle back and forth between the HWE kernel and the generic -virtual kernel.

  • Install a second kernel.

     % apt-get install -qy linux-image-virtual-hwe-16.04
     % ls /boot/vmlinuz-*
     /boot/vmlinuz-4.13.0-45-generic  /boot/vmlinuz-4.4.0-128-generic
    
  • run with --setup-only to setup. Note that setup would be done anyway if necessary.

     % ./boot-kernel --setup-only
     changing GRUB_DEFAULT from 0 to "saved" in /etc/default/grub
     apply change to /etc/default/grub
        --- /etc/default/grub	2018-06-14 20:28:15.832442556 +0000
        +++ /tmp/boot-kernel.1FIHGf	2018-06-14 20:28:18.520417934 +0000
        @@ -3,7 +3,7 @@
         # For full documentation of the options in this file, see:
         #   info -f grub -n 'Simple configuration'
    
        -GRUB_DEFAULT=0
        +GRUB_DEFAULT=saved
         GRUB_HIDDEN_TIMEOUT=0
         GRUB_HIDDEN_TIMEOUT_QUIET=true
         GRUB_TIMEOUT=0
     execute: update-grub
     Generating grub configuration file ...
     Found linux image: /boot/vmlinuz-4.13.0-45-generic
     Found initrd image: /boot/initrd.img-4.13.0-45-generic
     Found linux image: /boot/vmlinuz-4.4.0-128-generic
     Found initrd image: /boot/initrd.img-4.4.0-128-generic
     done
    
  • boot into the older kernel. With a 4.4 and 4.13 kernel installed we would normally reboot into 4.13.

     % sudo ./boot-kernel /boot/vmlinuz-4.4.0-128-generic
     GRUB_DEFAULT already set to 'saved'. no change necessary.
     selected /boot/vmlinuz-4.4.0-128-generic. entry: Advanced options for Ubuntu>Ubuntu, with Linux 4.4.0-128-generic
     execute: grub-reboot "Advanced options for Ubuntu>Ubuntu, with Linux 4.4.0-128-generic"
    

    That used grub-reboot to update /boot/grub/grubenv to set the next_entry.

     % head -n 2 /boot/grub/grubenv
     # GRUB Environment Block
     next_entry=Advanced options for Ubuntu>Ubuntu, with Linux 4.4.0-128-generic
    

    Now reboot and see that it worked.

     % reboot
     ...
     % uname -r
     4.4.0-128-generic
    

    Because it used grub-reboot and not grub-set-default the next time we will reboot back into the default/newest kernel.

     % head -n 2 /boot/grub/grubenv
     # GRUB Environment Block
     next_entry=
    
  • Make this the default.

     % sudo ./boot-kernel --default /boot/vmlinuz-4.4.0-128-generic
     GRUB_DEFAULT already set to 'saved'. no change necessary.
     selected /boot/vmlinuz-4.4.0-128-generic. entry: Advanced options for Ubuntu>Ubuntu, with Linux 4.4.0-128-generic
     execute: grub-set-default "Advanced options for Ubuntu>Ubuntu, with Linux 4.4.0-128-generic"
     Searching for GRUB installation directory ... found: /boot/grub
    
     % head -n 2 /boot/grub/grubenv
     # GRUB Environment Block
     saved_entry=Advanced options for Ubuntu>Ubuntu, with Linux 4.4.0-128-generic
    

Notes

  • Power Bare Metal: Note, that on power8 bare metal systems (such as ubuntu ppc64el) the boot loader used is not actually grub2, it is petitboot. petitboot does not seem to support this function. I've filed upstream petitboot issue 24.

Links

This was originally put together from googling. The following articles were helpful:

#!/bin/sh
# https://gist.github.com/smoser/4d6371b9e3823b88a65c84ff40d1fd88
#
# This just selects which kernel to boot on an ubuntu
# system and sets it to boot with 'grub-reboot'
#
# based on http://statusq.org/archives/2012/10/24/4584/
fail() { [ $# -eq 0 ] || msg "$@"; exit 1; }
error() { echo "$@" 1>&2; }
msg() { echo "$@" 1>&2; }
Usage() {
cat <<EOF
Usage: ${0##*/} [options] [kernel]
call grub-reboot or grub-set-default to boot the provided kernel.
options:
-n | --dry-run do not make changes, only report what would be done.
--setup-only only setup 'saved' in /etc/default/grub.
do not supply kernel with --setup-only.
--default run 'grub-set-default' rather than 'grub-reboot'
Examples:
* boot kernel /boot/vmlinuz-4.13.0-17-generic next time.
${0##*/} /boot/vmlinuz-4.13.0-17-generic
* edit /etc/default/grub to enable 'saved'
${0##*/} --setup-only
EOF
}
find_kernel() {
local kernel_in="$1" kernel=""
if [ -f "$kernel_in" ]; then
kernel=${kernel_in}
elif [ -f "/boot/$kernel_in" ]; then
kernel="/boot/${kernel_in}"
else
kmatch="$kernel_in"
if [ "${kmatch#*/}" = "$kmatch" ]; then
kmatch="/boot/vmlinu?-${kmatch#vmlinu?-}*"
else
kmatch="/boot/$kmatch"
fi
for f in $kmatch; do
if [ -f "$f" ]; then
if [ -n "$kernel" ]; then
error "multiple kernels match $kmatch"
return 1
fi
kernel="$f"
fi
done
fi
[ -f "$kernel" ] || {
error "did not find kernel matching '$kernel_in'";
return 1;
}
echo "$kernel"
}
read_value() {
local gdef="" fname="$1"
gdef=$(sh -ec '
fail() { ret=$1; shift; echo "$@" 1>&2; exit $ret; }
fname=$1
. "$fname" || fail $? "failed source $fname"
for i in /etc/default/grub.d/*.cfg; do
[ -f "$i" ] || continue
. "$i" || fail $? "failed source $i"
done
echo $GRUB_DEFAULT' -- "$fname") || {
error "Failed to read '$var' setting";
return 1;
}
_RET="$gdef"
}
setup_grub() {
local dry_run="$1" gdef="" var="GRUB_DEFAULT"
local grubfile="/etc/default/grub" modfile=""
read_value "$grubfile" ||
{ error "failed reading value of GRUB_DEFAULT"; return 1; }
gdef="$_RET"
if [ "$gdef" = "saved" ]; then
msg "GRUB_DEFAULT already set to 'saved'. no change necessary."
return 0
fi
msg "changing GRUB_DEFAULT from $gdef to \"saved\" in $grubfile"
modfile=$(mktemp "${TMPDIR:-/tmp/${0##*/}.XXXXXX}") ||
{ error "failed to make temp file"; return 1; }
sed "s,^$var=.*,$var=saved," "$grubfile" > "$modfile" || {
error "failed to edit $grubfile to a tempfile"
rm -f "$modfile"
return 1
}
read_value "$modfile" || {
error "failed to read GRUB_DEFAULT from edited file";
rm -f "$modfile"
return 1
}
[ "$_RET" = "saved" ] || {
error "change of GRUB_DEFAULT in $grubfile did not make a change."
rm -f "$modfile"
return 1
}
msg "apply change to $grubfile";
diff -u "$grubfile" "$modfile" | sed 's,^, ,g' 1>&2
if [ "$dry_run" != "true" ]; then
cp "$modfile" "$grubfile" || {
error "failed to update $grubfile."
rm -f "$modfile"
return 1
}
rm "$modfile"
fi
msg execute: update-grub
if [ "$dry_run" != "true" ]; then
update-grub || {
error "failed update-grub to apply $var=saved";
return 1;
}
fi
}
get_entry() {
local submenu="Advanced options for Ubuntu"
local prefix="Ubuntu, with Linux "
# VER-FLAV like '3.13.0-79-generic'
local kernel="$1" verflav=""
# /boot/vmlinuz-VER-FLAV -> vmlinuz-VER-FLAV
verflav=${kernel##*/}
# vmlinuz-VER-FLAV - VER-FLAV
verflav=${verflav#*-}
entry="${submenu:+${submenu}}>${prefix}$verflav"
if ! grep -q "$prefix$verflav" "/boot/grub/grub.cfg"; then
error "no $prefix$verflav entry in /boot/grub/grub.cfg"
return 1
fi
echo "$entry"
}
main() {
local short_opts="hn"
local long_opts="help,default,dry-run,setup-only"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ Usage 1>&2; return; }
## <<insert default variables here>>
local dry_run=false setup_only=false mode="reboot"
local cur="" next="" kernel="" kernel_in=""
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
--default) mode="set-default";;
-n|--dry-run) dry_run=true;;
--setup-only) setup_only=true;;
--) shift; break;;
esac
shift;
done
if [ "$setup_only" = "true" ]; then
[ $# -eq 0 ] || {
Usage 1>&2; echo "got $# args, expected 0 for setup-only"
return 1;
}
else
[ $# -eq 1 ] || {
Usage 1>&2; echo "got $# args, expected 1. must provide kernel."
return 1;
}
if [ "$setup_only" = "false" ]; then
kernel_in="$1"
kernel=$(find_kernel "${kernel_in}") || return
fi
fi
setup_grub "$dry_run" || return
if [ "$setup_only" = "true" ]; then
return
fi
entry=$(get_entry "$kernel") || fail
msg "selected $kernel. entry: ${entry}"
cmd="grub-$mode"
msg "execute: $cmd \"$entry\""
if [ "$dry_run" != "true" ]; then
"$cmd" "$entry" || {
error "failed: $cmd \"$entry\""
return 1
}
fi
return 0
}
main "$@"
# vi: ts=4 expandtab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment