Grub and snappy updates
This week, the snappy powered Ubuntu Core landed some interesting changes with
regards to how it handles grub
based systems.
History
The original implementation was based on traditional Ubuntu systems, where a
bunch of files that any debian package could setup influenced the resulting
grub.cfg
that resulted after running update-grub
. This produced a
grub.cfg
that was really hard to manage or debug, and what is most important,
out of our control. This also differed greatly from our u-boot
story where
any gadget could override the boot setup so it bootstraps as needed.
We don’t want to own the logic for this, but only provide the guidelines for the necessary primitives for proper rollback at that level to work.
These steps also make our bootloader story look and feel more similar between each other where soon we may be able to just not care about it as the logic will be driven as if it were a black box.
Rolling it out
Even though this worked targeted the development release, also known as the
rolling one, we trie to make sure all systems would transition to this model
transparently. Given the model though, it isn’t a one step solution as we need
to be able to update to systems which do not run update-grub
and rollback
to systems that do. We also need to update from a sytem that has this new
snappy logic to one that doesn’t. This was solved with a very grub.cfg
slick grub.cfg
delivered through the gadget
packages (still named oem
in
current implementations), in contrast, similar to the u-boot
and uEnv.txt
mechanics.
On a running system, these would be the steps taken:
- Device is running a version that runs
update-grub
. oem
package update is delivered throughautopilot
containing the newgrub.cfg
.- The
os
is updated bringing in some newsnappy
logic. - The next
os
update would be driven by the newsnappy
logic which wouldsync
grub.cfg
from theoem
package into the bootloader area. This new snappy would not runupdate-grub
. The system would boot from the legacy kernel paths as if it were a delta update no new kernel would be delivered. - Updates would rinse and repeat, when there’s a new kernel provided in an
update, the bootloader
a
andb
locations would be used to store that kernel,grub.cfg
already has logic to boot from those locations so the change is transparent. - On the next update, kernel asset file syncing would take place and populate
the other label (
a
orb
).
This is the development release so we shouldn’t worry to much about breaking, but why do so if it can be avoided ;-)
Outcome
The resulting code base is much simpler, there are less headaches and we don’t
need to maintain or understand huge grub script snippets. Just for kicks, this
is the grub.cfg
we use:
set default=0
set timeout=3
insmod part_gpt
insmod ext2
if [ -s $prefix/grubenv ]; then
load_env
fi
if [ -z "$snappy_mode" ]; then
set snappy_mode=regular
save_env snappy_mode
fi
if [ -z "$snappy_ab" ]; then
set snappy_ab=a
save_env snappy_ab
fi
if [ "$snappy_mode" = "try" ]; then
if [ "$snappy_trial_boot" = "1" ]; then
# Previous boot failed to unset snappy_trial_boot, so toggle
# rootfs.
if [ "$snappy_ab" = "a" ]; then
set snappy_ab=b
else
set snappy_ab=a
fi
save_env snappy_ab
else
# Trial mode so set the snappy_trial_boot (which snappy is
# expected to unset).
#
# Note: don't use the standard recordfail variable since that forces
# the menu to be displayed and sets an infinite timeout if set.
set snappy_trial_boot=1
save_env snappy_trial_boot
fi
fi
set label="system-$snappy_ab"
set cmdline="root=LABEL=$label ro init=/lib/systemd/systemd console=ttyS0 console=tty1 panic=-1"
menuentry "$label" {
if [ -e "$prefix/$snappy_ab/vmlinuz" ]; then
linux $prefix/$snappy_ab/vmlinuz $cmdline
initrd $prefix/$snappy_ab/initrd.img
else
# old-style kernel-in-os-partition
search --no-floppy --set --label "$label"
linux /vmlinuz $cmdline
initrd /initrd.img
fi
}
and this was the grub.cfg
that was auto generated:
#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by grub-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub
#
### BEGIN /etc/grub.d/00_header ###
if [ -s $prefix/grubenv ]; then
set have_grubenv=true
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 [ x"${feature_menuentry_id}" = xy ]; then
menuentry_id_option="--id"
else
menuentry_id_option=""
fi
export menuentry_id_option
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 recordfail {
set recordfail=1
if [ -n "${have_grubenv}" ]; then if [ -z "${boot_once}" ]; then save_env recordfail; fi; fi
}
function load_video {
if [ x$feature_all_video_module = xy ]; 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
}
terminal_input console
terminal_output console
if [ "${recordfail}" = 1 ] ; then
set timeout=0
else
if [ x$feature_timeout_style = xy ] ; then
set timeout_style=hidden
set timeout=0
# Fallback hidden-timeout code in case the timeout_style feature is
# unavailable.
elif sleep --interruptible 0 ; then
set timeout=0
fi
fi
### END /etc/grub.d/00_header ###
### BEGIN /etc/grub.d/05_debian_theme ###
set menu_color_normal=white/black
set menu_color_highlight=black/light-gray
### END /etc/grub.d/05_debian_theme ###
### BEGIN /etc/grub.d/09_snappy ###
menuentry 'Ubuntu Core Snappy system-b rootfs' $menuentry_id_option 'gnulinux-simple-LABEL=system-b' {
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
set root='hd0,gpt4'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt4 --hint-efi=hd0,gpt4 --hint-baremetal=ahci0,gpt4 4ff468ee-953f-45df-a751-e6232a1c8ef7
else
search --no-floppy --fs-uuid --set=root 4ff468ee-953f-45df-a751-e6232a1c8ef7
fi
linux /boot/vmlinuz-3.19.0-22-generic root=LABEL=system-b ro init=/lib/systemd/systemd console=tty1 console=ttyS0 panic=-1
initrd /boot/initrd.img-3.19.0-22-generic
}
menuentry 'Ubuntu Core Snappy system-a rootfs' $menuentry_id_option 'gnulinux-simple-LABEL=system-a' {
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
set root='hd0,gpt3'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt3 --hint-efi=hd0,gpt3 --hint-baremetal=ahci0,gpt3 47ea9cad-ec4f-4290-85e8-eee7dac19f1c
else
search --no-floppy --fs-uuid --set=root 47ea9cad-ec4f-4290-85e8-eee7dac19f1c
fi
linux /boot/vmlinuz-3.19.0-22-generic root=LABEL=system-a ro init=/lib/systemd/systemd console=tty1 console=ttyS0 panic=-1
initrd /boot/initrd.img-3.19.0-22-generic
}
# set defaults
if [ -z "$snappy_mode" ]; then
set snappy_mode=regular
save_env snappy_mode
fi
if [ -z "$snappy_ab" ]; then
set snappy_ab=a
save_env snappy_ab
fi
if [ "$snappy_mode" = "try" ]; then
if [ "$snappy_trial_boot" = "1" ]; then
# Previous boot failed to unset snappy_trial_boot, so toggle
# rootfs.
if [ "$snappy_ab" = "a" ]; then
set default="Ubuntu Core Snappy system-b rootfs"
set snappy_ab=b
else
set snappy_ab=a
set default="Ubuntu Core Snappy system-a rootfs"
fi
save_env snappy_ab
else
# Trial mode so set the snappy_trial_boot (which snappy is
# expected to unset).
#
# Note: don't use the standard recordfail variable since that forces
# the menu to be displayed and sets an infinite timeout if set.
set snappy_trial_boot=1
save_env snappy_trial_boot
if [ "$snappy_ab" = "a" ]; then
set default="Ubuntu Core Snappy system-a rootfs"
else
set default="Ubuntu Core Snappy system-b rootfs"
fi
fi
else
if [ "$snappy_ab" = "a" ]; then
set default="Ubuntu Core Snappy system-a rootfs"
else
set default="Ubuntu Core Snappy system-b rootfs"
fi
fi
### END /etc/grub.d/09_snappy ###
### BEGIN /etc/grub.d/10_linux ###
function gfxmode {
set gfxpayload="${1}"
if [ "${1}" = "keep" ]; then
set vt_handoff=vt.handoff=7
else
set vt_handoff=
fi
}
if [ "${recordfail}" != 1 ]; then
if [ -e ${prefix}/gfxblacklist.txt ]; then
if hwmatch ${prefix}/gfxblacklist.txt 3; then
if [ ${match} = 0 ]; then
set linux_gfx_mode=keep
else
set linux_gfx_mode=text
fi
else
set linux_gfx_mode=text
fi
else
set linux_gfx_mode=keep
fi
else
set linux_gfx_mode=text
fi
export linux_gfx_mode
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-4ff468ee-953f-45df-a751-e6232a1c8ef7' {
recordfail
load_video
gfxmode $linux_gfx_mode
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
set root='hd0,gpt4'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt4 --hint-efi=hd0,gpt4 --hint-baremetal=ahci0,gpt4 4ff468ee-953f-45df-a751-e6232a1c8ef7
else
search --no-floppy --fs-uuid --set=root 4ff468ee-953f-45df-a751-e6232a1c8ef7
fi
linux /boot/vmlinuz-3.19.0-22-generic root=/dev/sda4 ro init=/lib/systemd/systemd console=tty1 console=ttyS0 panic=-1
initrd /boot/initrd.img-3.19.0-22-generic
}
submenu 'Advanced options for Ubuntu' $menuentry_id_option 'gnulinux-advanced-4ff468ee-953f-45df-a751-e6232a1c8ef7' {
menuentry 'Ubuntu, with Linux 3.19.0-22-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-3.19.0-22-generic-advanced-4ff468ee-953f-45df-a751-e6232a1c8ef7' {
recordfail
load_video
gfxmode $linux_gfx_mode
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
set root='hd0,gpt4'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt4 --hint-efi=hd0,gpt4 --hint-baremetal=ahci0,gpt4 4ff468ee-953f-45df-a751-e6232a1c8ef7
else
search --no-floppy --fs-uuid --set=root 4ff468ee-953f-45df-a751-e6232a1c8ef7
fi
echo 'Loading Linux 3.19.0-22-generic ...'
linux /boot/vmlinuz-3.19.0-22-generic root=/dev/sda4 ro init=/lib/systemd/systemd console=tty1 console=ttyS0 panic=-1
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img-3.19.0-22-generic
}
menuentry 'Ubuntu, with Linux 3.19.0-22-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-3.19.0-22-generic-recovery-4ff468ee-953f-45df-a751-e6232a1c8ef7' {
recordfail
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
set root='hd0,gpt4'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt4 --hint-efi=hd0,gpt4 --hint-baremetal=ahci0,gpt4 4ff468ee-953f-45df-a751-e6232a1c8ef7
else
search --no-floppy --fs-uuid --set=root 4ff468ee-953f-45df-a751-e6232a1c8ef7
fi
echo 'Loading Linux 3.19.0-22-generic ...'
linux /boot/vmlinuz-3.19.0-22-generic root=/dev/sda4 ro single nomodeset init=/lib/systemd/systemd
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img-3.19.0-22-generic
}
}
### END /etc/grub.d/10_linux ###
### BEGIN /etc/grub.d/20_linux_xen ###
### END /etc/grub.d/20_linux_xen ###
### BEGIN /etc/grub.d/30_os-prober ###
### END /etc/grub.d/30_os-prober ###
### BEGIN /etc/grub.d/30_uefi-firmware ###
### END /etc/grub.d/30_uefi-firmware ###
### BEGIN /etc/grub.d/40_custom ###
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
### END /etc/grub.d/40_custom ###
### BEGIN /etc/grub.d/41_custom ###
if [ -f ${config_directory}/custom.cfg ]; then
source ${config_directory}/custom.cfg
elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then
source $prefix/custom.cfg;
fi
### END /etc/grub.d/41_custom ###