Support keeping old kernel versions on upgrade

cc @jenneron @craftyguy

We've discussed this idea before, so trying to bring everything together here (I couldn't find an existing issue).

There is the one proposal to essentially bring back support for kernel flavors, so we could have multiple kernel packages installed too. But I think that is entirely orthoganol to this issue.

Currently, when installing a kernel upgrade, if the new kernel has a bug then that's it, you can't downgrade and (if your device is a phone) then you're basically bricked until you get to a computer and manually build a new image.

This is really not great, and we can do better.

I want to keep the scope fairly small here, initially focusing on EFI, and explicitly not using "flavours". The way I'd like for this to work is that on installing a kernel upgrade, the previous two kernels are kept around and offered as alternative boot options by the bootloader.

The "ideal" solution would be to have apk support tracking multiple non-conflicting versions of a package. However this would require a huge amount of work either in apk itself, or in pmaports to maintain several versions of a kernel package. I don't think this kind of generic approach makes sense given the limited usecase.

Instead, I propose introducing a new script in the boot-deploy project: kernel-save and a new boolean deviceinfo variable (yay!) deviceinfo_keep_old_kernels. The kernel package should then have pre and post install scripts which run kernel-save pre and kernel-save post respectively.

kernel-save pre

  • Read /usr/share/boot-deploy/os-customization and check a new keep_old_versions variable, if unset/0 then just exit, if deviceinfo_keep_old_kernels is not true then exit.
  • Determine the currently installed kernel version and check the space use of /lib/modules/linux-$ver, if enough space then make a copy linux-$ver.save, otherwise, print an error that there's no free space and suggest to disable deviceinfo_keep_old_kernels
  • check space in /boot and make a copy of linux.efi, initramfs, initramfs-extra, and the current dtb is applicable (copying all dtbs is a big waste of space for no gain here). These copies should have the kernel version appended to them.
  • Create a new systemd-boot entry (using the boot-deploy-functions implementation) for that kernel version
  • Append the kernel version to /var/cache/boot-deploy.kernel-versions

kernel-save post

  • Read /usr/share/boot-deploy/os-customization and check a new keep_old_versions variable, if unset/0 then just exit, if deviceinfo_keep_old_kernels is not true then exit.
  • Now that apk has removed /lib/modules/linux-$ver, rename the backup copy back
  • Remove the boot entry, /boot files and modules for the oldest installed version (determined by reading the first entry of /var/cache/boot-deploy.kernel-versions) IFF there are more than keep_old_versions entries.
  • Remove the first entry of /var/cache/boot-deploy.kernel-versions

Hopefully this isn't too much into the weeds... I don't think we should aim to support anything other than EFI/systemd-boot initially. Introducing support for additional bootloaders should be fairly straightforward and can be done by whoever wants it...

Edited by Casey