[pve-devel] [PATCH proxmox-ve 1/1] add efiboot and autoremoval kernel postinst hooks

Stoiko Ivanov s.ivanov at proxmox.com
Thu Jun 27 20:27:57 CEST 2019


In order to add support for booting with ZFS on Root on EFI systems this
patch uses systemd-boot as boot loader.

The rationale for not using grub is that there have been quite a few problems
observed with grub and ZFS, e.g. certain RAID-controllers only having a 32bit
implementation in grub, but ZFS writing the kernelimage or initrd after 2TB
rendering a system unbootable.

Additionally grub only supports a certain subset of zpool features, which
either forces users to not use newer features, or to create an additional small
boot-pool, with the reduced feature set (which seems currently the suggestion
from ZFS-upstream [0,1]).

the kernel-hook scripts are adapted from debian's versions for:
* marking kernels as NeverAutoRemove (/etc/kernel/postinst.d/apt-auto-removal)
* calling update-grub (/etc/kernel/postinst.d/zz-update-grub)
* generating systemd-boot loader entries
  (/usr/lib/kernel/install.d/90-loaderentry.install)

the list of kernels kept installed and configured for booting with systemd-boot
contains:
* the currently running kernel
* the kernel being installed
* the 2 newest kernels (determined by package-name, i.e. ABI-version)
* the latest kernel from each series (e.g. 4.13, 4.15, 5.0) by keeping the
  respective meta-packages installed

the implementation diverges from systemd's boot loader specification [0] in the
following places:
* adding support for multiple ESPs - all bootable disks of a RAID get configured
  with an 512M ESP - the hookscript installs the kernels to all of them
* the ESP(s) are not kept mounted, rather they get mounted and umounted by
  the script:
  * Should the system crash, this should make sure that the ESPs fs does not get
    corrupted
  * Keeping it mounted results in a boot-error, if the ESP, which is usually
    mounted is not available (because the disk died)
* kernels and initrds get installed into 'EFI/proxmox/$kver' instead
  of '$machineid/$kver'

Since copying the necessary kernels needs a logic for cleaning up space in
the ESP, this presents an opportunity to also be more selective about which
kernels are marked as NeverAutoRemove, instead of keeping all versions installed
which results in full disks for our users occasionally.

[0] https://github.com/zfsonlinux/zfs/wiki/Ubuntu-18.04-Root-on-ZFS
[1] https://zfsonlinux.topicbox.com/groups/zfs-discuss/Tfe475cd19433f050
[2] https://systemd.io/BOOT_LOADER_SPECIFICATION
---
 debian/efiboot/functions        |  59 ++++++++++++
 debian/efiboot/pve-auto-removal |  39 ++++++++
 debian/efiboot/zz-pve-efiboot   | 157 ++++++++++++++++++++++++++++++++
 debian/proxmox-ve.install       |   3 +
 4 files changed, 258 insertions(+)
 create mode 100644 debian/efiboot/functions
 create mode 100755 debian/efiboot/pve-auto-removal
 create mode 100755 debian/efiboot/zz-pve-efiboot

diff --git a/debian/efiboot/functions b/debian/efiboot/functions
new file mode 100644
index 0000000..3c56069
--- /dev/null
+++ b/debian/efiboot/functions
@@ -0,0 +1,59 @@
+#! /bin/sh
+set -e
+
+# adapted from /etc/kernel/postinst.d/apt-auto-removal as present in
+# debian's apt package:
+#
+# Mark as not-for-autoremoval those kernel packages that are:
+#  - the currently booted version
+#  - the kernel version we've been called for
+#  - the latest kernel version (as determined by debian version number)
+#  - the second-latest kernel version
+#  - the latest kernel version of each series (e.g. 4.13, 4.15, 5.0) by
+#    marking the meta-packages
+
+kernel_keep_versions() {
+	eval "$(apt-config shell DPKG Dir::bin::dpkg/f)"
+	test -n "$DPKG" || DPKG="/usr/bin/dpkg"
+
+	list="$("${DPKG}" -l | awk '/^[ih][^nc][ ]+pve-kernel-[0-9]+\./ && $2 !~ /-dbg(:.*)?$/ && $2 !~ /-dbgsym(:.*)?$/ { print $2; }' \
+	   | sed -e 's#^pve-kernel-##' -e 's#:[^:]\+ # #')"
+
+	sorted_list="$(echo "$list" | sort --unique --reverse --version-sort)"
+
+	[ -n "$1" ] && install_version="$1"
+
+	running_version="$(uname -r | tr 'A-Z' 'a-z')"
+
+	# ignore the currently running version if attempting a reproducible build
+	if [ -n "${SOURCE_DATE_EPOCH}" ]; then
+		running_version=""
+	fi
+
+	latest_2_versions="$(echo "$sorted_list" | grep -E '^[^ ]+-pve' | head -n2 )"
+
+	series_metapackages="$(echo "$sorted_list" | grep -Ev '^[^ ]+-pve')"
+
+	kernels="$(cat <<-EOF
+		$running_version
+		$install_version
+		$latest_2_versions
+		$series_metapackages
+		EOF
+	)"
+
+	echo "$kernels" | sort -u | sed -e '/^$/ d'
+}
+
+#bootable kernels are the same as the no_autoremove ones without the meta-package
+boot_kernel_list() {
+	list="$(kernel_keep_versions "$@")"
+
+	echo "$list" | grep -E '^[^ ]+-pve'
+
+}
+
+warn() {
+	echo "$@" 1>&2
+}
+
diff --git a/debian/efiboot/pve-auto-removal b/debian/efiboot/pve-auto-removal
new file mode 100755
index 0000000..e06bf0a
--- /dev/null
+++ b/debian/efiboot/pve-auto-removal
@@ -0,0 +1,39 @@
+#! /bin/sh
+set -e
+
+. /usr/share/proxmox-ve/scripts/functions
+
+eval "$(apt-config shell APT_CONF_D Dir::Etc::parts/d)"
+test -n "${APT_CONF_D}" || APT_CONF_D="/etc/apt/apt.conf.d"
+
+config_file="${APT_CONF_D}/76pveconf"
+
+generate_apt_config() {
+
+	kernels="$(kernel_keep_versions "$@")"
+
+	cat <<- EOF
+	// DO NOT EDIT! File autogenerated by $0
+	APT::NeverAutoRemove
+	{
+	EOF
+	for kernel in $kernels; do
+		escaped_kver="$(echo "$kernel" |  sed -e 's#\([\.\+]\)#\\\1#g')"
+		echo "   \"^pve-kernel-${escaped_kver}$\";"
+	done
+	echo '};'
+	if [ "${APT_AUTO_REMOVAL_KERNELS_DEBUG:-false}" = 'true' ]; then
+		cat <<-EOF
+		/* Debug information:
+		# dpkg list:
+		$(dpkg -l | grep 'pve-kernel')
+		# list of installed kernel packages:
+		$kernels
+		*/
+		EOF
+	fi
+}
+
+generate_apt_config "$@" > "${config_file}.dpkg-new"
+mv -f "${config_file}.dpkg-new" "$config_file"
+chmod 444 "$config_file"
diff --git a/debian/efiboot/zz-pve-efiboot b/debian/efiboot/zz-pve-efiboot
new file mode 100755
index 0000000..0af99ab
--- /dev/null
+++ b/debian/efiboot/zz-pve-efiboot
@@ -0,0 +1,157 @@
+#! /bin/sh
+set -e
+
+# adapted from '/etc/kernel/postinst.d/zz-update-grub and
+# /usr/lib/kernel/install.d/90-loaderentry.install, see also
+# https://kernel-team.pages.debian.net/kernel-handbook/ch-update-hooks.html
+
+# relative to the ESP mountpoint
+PMX_ESP_DIR="EFI/proxmox"
+
+MOUNTROOT="${TMPDIR:-/var/tmp}/espmounts"
+
+# TODO:
+# - no mount on /boot/efi - mount all available esps on /var/tmp/esp-UUID
+#   and copy the stuff for all of them (or copy onto first and sync for the
+#   others - or don't copy if unchanged
+# - trap error-conditions and make sure stuff gets unmounted
+# - cleanup - gently delete all kernels not in kernel-keep-list
+
+#[ -f "${LOADERDIR}/loader.conf" ] || exit 0
+#[ -d "${ESPMOUNT}/${PMX_ESP_DIR}" ] || exit 0
+
+if command -V systemd-detect-virt >/dev/null 2>&1 &&
+	systemd-detect-virt --quiet --container; then
+	exit 0
+fi
+
+cleanup() {
+
+	warn "unmounting ESPs"
+	for mount in "${MOUNTROOT}"/* ; do
+		if echo "${mount}" | grep -qE '[0-9a-fA-F]{4}-[0-9a-fA-F]{4}' && \
+			mountpoint -q "${mount}"; then
+			umount "${mount}"
+		fi
+	done
+
+}
+
+trap cleanup EXIT INT TERM QUIT
+
+. /usr/share/proxmox-ve/scripts/functions
+
+BOOT_KVERS="$(boot_kernel_list "$@")"
+
+if [ -f /etc/kernel/cmdline ]; then
+	CMDLINE="$(cat /etc/kernel/cmdline)"
+else
+	warn "No /etc/kernel/cmdline found - falling back to /proc/cmdline"
+	CMDLINE="$(cat /proc/cmdline)"
+fi
+
+
+update_esps() {
+	esps="$(lsblk --list -o PATH,UUID,FSTYPE,PARTTYPE,MOUNTPOINT |
+		awk -v OFS=';' '$3 == "vfat" && $4 == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" && $5 == "" {print $1,$2}')"
+
+	for esp in ${esps}; do
+		path="$(echo "${esp}" | cut -d ';' -f1)"
+		uuid="$(echo "${esp}" | cut -d ';' -f2)"
+		mountpoint="${MOUNTROOT}/${uuid}"
+
+		mkdir -p "${mountpoint}"
+		mount "${path}" "${mountpoint}" || \
+			{ warn "mount of ${esp} failed - skipping"; continue; }
+		if [ ! -f "${mountpoint}/loader/loader.conf" ]; then
+			warn "${path} contains no loader.conf - skipping"
+			continue
+		fi
+		if [ ! -d "${mountpoint}/EFI/proxmox" ]; then
+			warn "${path} contains no EFI/proxmox - skipping"
+			continue
+		fi
+
+		warn "Copying and configuring kernels on ${path}"
+		copy_and_config_kernels "${mountpoint}"
+		remove_old_kernels "${mountpoint}"
+
+		umount "${mountpoint}" || \
+			{ warn "umount of ${esp} failed - failure"; exit 2; }
+
+		rmdir "${mountpoint}"
+	done
+
+}
+
+copy_and_config_kernels() {
+	esp="$1"
+
+
+	for kver in ${BOOT_KVERS}; do
+
+		linux_image="/boot/vmlinuz-${kver}"
+		initrd="/boot/initrd.img-${kver}"
+
+		if [ ! -f "${linux_image}" ]; then
+			warn "No linux-image ${linux_image} found - skipping"
+			continue
+		fi
+		if [ ! -f "${initrd}" ]; then
+			warn "No initrd-image ${initrd} found - skipping"
+			continue
+		fi
+
+		warn "	Copying kernel and creating boot-entry for ${kver}"
+		KERNEL_ESP_DIR="${PMX_ESP_DIR}/${kver}"
+		KERNEL_LIVE_DIR="${esp}/${KERNEL_ESP_DIR}"
+		mkdir -p "${KERNEL_LIVE_DIR}"
+		cp -u --preserve=timestamps "${linux_image}" "${KERNEL_LIVE_DIR}/"
+		cp -u --preserve=timestamps "${initrd}" "${KERNEL_LIVE_DIR}/"
+
+		# create loader entry
+		cat > "${esp}/loader/entries/proxmox-${kver}.conf" <<- EOF
+			title    Proxmox
+			version  ${kver}
+			options   ${CMDLINE}
+			linux    /${KERNEL_ESP_DIR}/vmlinuz-${kver}
+			initrd   /${KERNEL_ESP_DIR}/initrd.img-${kver}
+		EOF
+	done
+
+}
+
+remove_old_kernels() {
+	esp="$1"
+
+	for kerneldir in "${esp}/${PMX_ESP_DIR}"/*; do
+		if [ ! -d "${kerneldir}" ]; then
+			warn "	${kerneldir} is not a directory - skipping"
+			continue
+		fi
+
+		kver="$(echo "${kerneldir}" | sed -r "s#^${esp}/${PMX_ESP_DIR}/(.+)\$#\\1#")"
+
+		echo "${BOOT_KVERS}" | grep -q "${kver}" && continue;
+		warn "	Removing old version ${kver}"
+		rm -rf "${kerneldir}"
+		rm -f "${esp}/loader/entries/proxmox-${kver}.conf"
+	done
+
+}
+
+set -- $DEB_MAINT_PARAMS
+mode="${1#\'}"
+mode="${mode%\'}"
+case $0:$mode in
+	# Only run on postinst configure and postrm remove, to avoid wasting
+	# time by calling update-grub multiple times on upgrade and removal.
+	# Also run if we have no DEB_MAINT_PARAMS, in order to work with old
+	# kernel packages.
+	*/postinst.d/*:|*/postinst.d/*:configure|*/postrm.d/*:|*/postrm.d/*:remove)
+	update_esps
+	;;
+esac
+
+exit 0
+
diff --git a/debian/proxmox-ve.install b/debian/proxmox-ve.install
index e482438..7ac4afc 100644
--- a/debian/proxmox-ve.install
+++ b/debian/proxmox-ve.install
@@ -2,3 +2,6 @@ debian/apthook/10pveapthook etc/apt/apt.conf.d/
 debian/apthook/pve-apt-hook usr/share/proxmox-ve/
 debian/proxmox-ve-release-5.x.gpg etc/apt/trusted.gpg.d/
 debian/proxmox-ve-release-6.x.gpg etc/apt/trusted.gpg.d/
+debian/efiboot/functions usr/share/proxmox-ve/scripts/
+debian/efiboot/pve-auto-removal etc/kernel/postinst.d/
+debian/efiboot/zz-pve-efiboot etc/kernel/postinst.d/
-- 
2.20.1





More information about the pve-devel mailing list