From 9b7f9806b48bf8ccf114e56e657430dc10c74298 Mon Sep 17 00:00:00 2001 From: Vince Perri <5596945+vinceaperri@users.noreply.github.com> Date: Fri, 8 May 2026 21:19:45 +0000 Subject: [PATCH 1/2] Support distro-aware grub stub assets and multiple EFI stub dirs --- .../imagegen/installutils/installutils.go | 106 ++++++++++-------- .../efi/grub/{grub.cfg => grub-azl3.cfg} | 0 .../resources/assets/efi/grub/grub-azl4.cfg | 3 + .../assets/grub2/{grub => grub-azl3} | 0 .../internal/resources/assets/grub2/grub-azl4 | 8 ++ toolkit/tools/internal/resources/resources.go | 7 +- .../distrohandler_azurelinux.go | 20 +++- .../distrohandler_fedora.go | 4 +- .../pkg/imagecustomizerlib/imageutils.go | 4 +- 9 files changed, 99 insertions(+), 53 deletions(-) rename toolkit/tools/internal/resources/assets/efi/grub/{grub.cfg => grub-azl3.cfg} (100%) create mode 100644 toolkit/tools/internal/resources/assets/efi/grub/grub-azl4.cfg rename toolkit/tools/internal/resources/assets/grub2/{grub => grub-azl3} (100%) create mode 100644 toolkit/tools/internal/resources/assets/grub2/grub-azl4 diff --git a/toolkit/tools/imagegen/installutils/installutils.go b/toolkit/tools/imagegen/installutils/installutils.go index a5db73cfaa..b3e949ab63 100644 --- a/toolkit/tools/imagegen/installutils/installutils.go +++ b/toolkit/tools/imagegen/installutils/installutils.go @@ -108,6 +108,17 @@ const ( bootUsrConfigFileMode = 0644 ) +var ( + GrubStubDirsAzl3 = []string{ + "boot/grub2", + } + + GrubStubDirsAzl4 = []string{ + "EFI/fedora", + "EFI/BOOT", + } +) + // CreateMountPointPartitionMap creates a map between the mountpoint supplied in the config file and the device path // of the partition // - partDevPathMap is a map of partition IDs to partition device paths @@ -352,7 +363,8 @@ func addEntryToFstab(fullFstabPath, mountPoint, devicePath, fsType, mountArgs st func ConfigureDiskBootloaderWithRootMountIdType(bootType string, encryptionEnable bool, rootMountIdentifier configuration.MountIdentifier, kernelCommandLine configuration.KernelCommandLine, installChroot *safechroot.Chroot, diskDevPath string, mountPointMap map[string]string, - encryptedRoot diskutils.EncryptedRootDevice, enableGrubMkconfig bool, + encryptedRoot diskutils.EncryptedRootDevice, enableGrubMkconfig bool, assetGrubDefFile, + grubEnvRelPath, assetGrubStubFile string, grubStubDirs []string, ) (err error) { // Add bootloader. Prefer a separate boot partition if one exists. bootDevice, isBootPartitionSeparate := mountPointMap[bootMountPoint] @@ -375,7 +387,8 @@ func ConfigureDiskBootloaderWithRootMountIdType(bootType string, encryptionEnabl return } - err = InstallBootloader(installChroot, encryptionEnable, bootType, bootUUID, bootPrefix, diskDevPath) + err = InstallBootloader(installChroot, encryptionEnable, bootType, bootUUID, bootPrefix, diskDevPath, + assetGrubStubFile, grubStubDirs) if err != nil { err = fmt.Errorf("failed to install bootloader: %s", err) return @@ -399,13 +412,13 @@ func ConfigureDiskBootloaderWithRootMountIdType(bootType string, encryptionEnabl // Grub will always use filesystem UUID, never PARTUUID or PARTLABEL err = InstallGrubDefaults(installChroot.RootDir(), rootDevice, bootUUID, bootPrefix, encryptedRoot, - kernelCommandLine, isBootPartitionSeparate, !enableGrubMkconfig /*includeLegacyCfg*/) + kernelCommandLine, isBootPartitionSeparate, !enableGrubMkconfig /*includeLegacyCfg*/, assetGrubDefFile) if err != nil { err = fmt.Errorf("failed to install main grub config file: %s", err) return } - err = InstallGrubEnv(installChroot.RootDir()) + err = InstallGrubEnv(installChroot.RootDir(), grubEnvRelPath) if err != nil { err = fmt.Errorf("failed to install grubenv file: %s", err) return @@ -423,13 +436,11 @@ func ConfigureDiskBootloaderWithRootMountIdType(bootType string, encryptionEnabl return } -// InstallGrubEnv installs an empty grubenv f -func InstallGrubEnv(installRoot string) (err error) { - const ( - assetGrubEnvFile = "assets/grub2/grubenv" - grubEnvFile = "boot/grub2/grubenv" - ) - installGrubEnvFile := filepath.Join(installRoot, grubEnvFile) +// InstallGrubEnv installs an empty grubenv file from the given embedded resource path to the given +// install-root-relative destination (e.g. FedoraGrubEnvRelPath or DebianGrubEnvRelPath). +func InstallGrubEnv(installRoot string, grubEnvRelPath string) (err error) { + assetGrubEnvFile := "assets/grub2/grubenv" + installGrubEnvFile := filepath.Join(installRoot, grubEnvRelPath) err = file.CopyResourceFile(resources.ResourcesFS, assetGrubEnvFile, installGrubEnvFile, bootDirectoryDirMode, bootDirectoryFileMode) if err != nil { @@ -450,14 +461,15 @@ func InstallGrubEnv(installRoot string) (err error) { // - readOnlyRoot holds the dm-verity read-only root partition information if dm-verity is enabled. // - isBootPartitionSeparate is a boolean value which is true if the /boot partition is separate from the root partition // - includeLegacyCfg specifies if the legacy grub.cfg from Azure Linux should also be added. +// - assetGrubDefFile is the embedded resource path to use as the /etc/default/grub template. // Note: this boot partition could be different than the boot partition specified in the bootloader. // This boot partition specifically indicates where to find the kernel, config files, and initrd func InstallGrubDefaults(installRoot, rootDevice, bootUUID, bootPrefix string, encryptedRoot diskutils.EncryptedRootDevice, kernelCommandLine configuration.KernelCommandLine, - isBootPartitionSeparate bool, includeLegacyCfg bool, + isBootPartitionSeparate bool, includeLegacyCfg bool, assetGrubDefFile string, ) (err error) { // Copy the bootloader's /etc/default/grub and set the file permission - err = installGrubTemplateFile(resources.AssetsGrubDefFile, GrubDefFile, installRoot, rootDevice, bootUUID, + err = installGrubTemplateFile(assetGrubDefFile, GrubDefFile, installRoot, rootDevice, bootUUID, bootPrefix, encryptedRoot, kernelCommandLine, isBootPartitionSeparate) if err != nil { logger.Log.Warnf("Failed to install (%s): %v", GrubDefFile, err) @@ -846,7 +858,7 @@ func sed(find, replace, delimiter, file string) (err error) { // Note: this boot partition could be different than the boot partition specified in the main grub config. // This boot partition specifically indicates where to find the main grub cfg func InstallBootloader(installChroot *safechroot.Chroot, encryptEnabled bool, bootType, bootUUID, bootPrefix, - bootDevPath string, + bootDevPath, grubAssetFileName string, grubFinalDirs []string, ) (err error) { const ( efiMountPoint = "/boot/efi" @@ -865,7 +877,7 @@ func InstallBootloader(installChroot *safechroot.Chroot, encryptEnabled bool, bo } case efiBootType: efiPath := filepath.Join(installChroot.RootDir(), efiMountPoint) - err = installEfiBootloader(encryptEnabled, efiPath, bootUUID, bootPrefix) + err = installEfiBootloader(encryptEnabled, efiPath, bootUUID, bootPrefix, grubAssetFileName, grubFinalDirs) if err != nil { return } @@ -1022,43 +1034,41 @@ func enableCryptoDisk() (err error) { // installRoot/boot/efi folder // It is expected that shim (bootx64.efi) and grub2 (grub2.efi) are installed // into the EFI directory via the package list installation mechanism. -func installEfiBootloader(encryptEnabled bool, installRoot, bootUUID, bootPrefix string) (err error) { - const ( - defaultCfgFilename = "grub.cfg" - grubAssetDir = "assets/efi/grub" - grubFinalDir = "boot/grub2" - ) +func installEfiBootloader(encryptEnabled bool, installRoot, bootUUID, bootPrefix, assetGrubStubFile string, + grubStubDirs []string, +) (err error) { + prefixPath := filepath.Join("/", bootPrefix, "grub2") - // Copy the bootloader's grub.cfg - grubAssetPath := filepath.Join(grubAssetDir, defaultCfgFilename) - grubFinalPath := filepath.Join(installRoot, grubFinalDir, defaultCfgFilename) - err = file.CopyResourceFile(resources.ResourcesFS, grubAssetPath, grubFinalPath, bootDirectoryDirMode, - bootDirectoryFileMode) - if err != nil { - logger.Log.Warnf("Failed to copy grub.cfg: %v", err) - return - } + for _, grubFinalDir := range grubStubDirs { + // Copy the bootloader's grub.cfg + grubFinalPath := filepath.Join(installRoot, grubFinalDir, "grub.cfg") + err = file.CopyResourceFile(resources.ResourcesFS, assetGrubStubFile, grubFinalPath, bootDirectoryDirMode, + bootDirectoryFileMode) + if err != nil { + logger.Log.Warnf("Failed to copy grub.cfg: %v", err) + return + } - // Add in bootUUID - err = setGrubCfgBootUUID(bootUUID, grubFinalPath) - if err != nil { - logger.Log.Warnf("Failed to set bootUUID in grub.cfg: %v", err) - return - } + // Add in bootUUID + err = setGrubCfgBootUUID(bootUUID, grubFinalPath) + if err != nil { + logger.Log.Warnf("Failed to set bootUUID in grub.cfg: %v", err) + return + } - // Set the boot prefix path - prefixPath := filepath.Join("/", bootPrefix, "grub2") - err = setGrubCfgPrefixPath(prefixPath, grubFinalPath) - if err != nil { - logger.Log.Warnf("Failed to set prefixPath in grub.cfg: %v", err) - return - } + // Set the boot prefix path + err = setGrubCfgPrefixPath(prefixPath, grubFinalPath) + if err != nil { + logger.Log.Warnf("Failed to set prefixPath in grub.cfg: %v", err) + return + } - // Add in encrypted volume mount command if needed - err = setGrubCfgEncryptedVolume(grubFinalPath, encryptEnabled) - if err != nil { - logger.Log.Warnf("Failed to set encrypted volume in grub.cfg: %v", err) - return + // Add in encrypted volume mount command if needed + err = setGrubCfgEncryptedVolume(grubFinalPath, encryptEnabled) + if err != nil { + logger.Log.Warnf("Failed to set encrypted volume in grub.cfg: %v", err) + return + } } return diff --git a/toolkit/tools/internal/resources/assets/efi/grub/grub.cfg b/toolkit/tools/internal/resources/assets/efi/grub/grub-azl3.cfg similarity index 100% rename from toolkit/tools/internal/resources/assets/efi/grub/grub.cfg rename to toolkit/tools/internal/resources/assets/efi/grub/grub-azl3.cfg diff --git a/toolkit/tools/internal/resources/assets/efi/grub/grub-azl4.cfg b/toolkit/tools/internal/resources/assets/efi/grub/grub-azl4.cfg new file mode 100644 index 0000000000..695680cba5 --- /dev/null +++ b/toolkit/tools/internal/resources/assets/efi/grub/grub-azl4.cfg @@ -0,0 +1,3 @@ +search --fs-uuid --set=root {{.BootUUID}} +set prefix=($root){{.PrefixPath}} +configfile ($root){{.PrefixPath}}/grub.cfg diff --git a/toolkit/tools/internal/resources/assets/grub2/grub b/toolkit/tools/internal/resources/assets/grub2/grub-azl3 similarity index 100% rename from toolkit/tools/internal/resources/assets/grub2/grub rename to toolkit/tools/internal/resources/assets/grub2/grub-azl3 diff --git a/toolkit/tools/internal/resources/assets/grub2/grub-azl4 b/toolkit/tools/internal/resources/assets/grub2/grub-azl4 new file mode 100644 index 0000000000..82a6f033a9 --- /dev/null +++ b/toolkit/tools/internal/resources/assets/grub2/grub-azl4 @@ -0,0 +1,8 @@ +GRUB_CMDLINE_LINUX="{{.LuksUUID}} {{.LVM}} {{.IMAPolicy}} {{.SELinux}} {{.FIPS}} {{.CGroup}}" +GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0 rd.shell=0 {{.ExtraCommandLine}}" +GRUB_ENABLE_BLSCFG=true +GRUB_GFXMODE=auto +GRUB_TERMINAL_INPUT="console" +GRUB_TERMINAL_OUTPUT="gfxterm" +GRUB_TIMEOUT=0 +GRUB_DEFAULT=saved diff --git a/toolkit/tools/internal/resources/resources.go b/toolkit/tools/internal/resources/resources.go index 963fe5fd58..2e428a02c3 100644 --- a/toolkit/tools/internal/resources/resources.go +++ b/toolkit/tools/internal/resources/resources.go @@ -9,8 +9,11 @@ import ( const ( // Assets - AssetsGrubCfgFile = "assets/grub2/grub.cfg" - AssetsGrubDefFile = "assets/grub2/grub" + AssetsGrubCfgFile = "assets/grub2/grub.cfg" + AssetsGrubDefFileAzl3 = "assets/grub2/grub-azl3" + AssetsGrubDefFileAzl4 = "assets/grub2/grub-azl4" + AssetsGrubStubFileAzl3 = "assets/efi/grub/grub-azl3.cfg" + AssetsGrubStubFileAzl4 = "assets/efi/grub/grub-azl4.cfg" // Verity Signature Module Files VerityMountBootPartitionSetupFile = "verity-signature/90mountbootpartition/module-setup.sh" diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux.go index 2f6dd25883..59f51980e4 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux.go @@ -12,6 +12,7 @@ import ( "github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagegen/installutils" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/imageconnection" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/logger" + "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/resources" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/safechroot" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/shell" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/targetos" @@ -179,6 +180,23 @@ func (d *azureLinuxDistroHandler) ConfigureDiskBootLoader(imageConnection *image // And for new images, always use grub-mkconfig. forceGrubMkconfig := newImage || d.version != "2.0" + var assetGrubDefFile string + var assetGrubStubFile string + var grubStubDirs []string + switch d.version { + case "2.0", "3.0": + assetGrubDefFile = resources.AssetsGrubDefFileAzl3 + assetGrubStubFile = resources.AssetsGrubStubFileAzl3 + grubStubDirs = installutils.GrubStubDirsAzl3 + case "4.0": + assetGrubDefFile = resources.AssetsGrubDefFileAzl4 + assetGrubStubFile = resources.AssetsGrubStubFileAzl4 + grubStubDirs = installutils.GrubStubDirsAzl4 + default: + return fmt.Errorf("unsupported Azure Linux version: %s", d.version) + } + return configureDiskBootLoader(imageConnection, rootMountIdType, bootType, selinuxConfig, kernelCommandLine, - currentSELinuxMode, forceGrubMkconfig, d) + currentSELinuxMode, forceGrubMkconfig, d, assetGrubDefFile, installutils.FedoraGrubEnvRelPath, + assetGrubStubFile, grubStubDirs) } diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go index c698fe070e..150a2a831d 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go @@ -13,6 +13,7 @@ import ( "github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagegen/installutils" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/imageconnection" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/logger" + "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/resources" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/safechroot" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/shell" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/targetos" @@ -140,5 +141,6 @@ func (d *fedoraDistroHandler) ConfigureDiskBootLoader(imageConnection *imageconn currentSELinuxMode imagecustomizerapi.SELinuxMode, newImage bool, ) error { return configureDiskBootLoader(imageConnection, rootMountIdType, bootType, selinuxConfig, kernelCommandLine, - currentSELinuxMode, true /* forceGrubMkconfig */, d) + currentSELinuxMode, true /* forceGrubMkconfig */, d, resources.AssetsGrubDefFileAzl3, + installutils.FedoraGrubEnvRelPath, resources.AssetsGrubStubFileAzl4, installutils.GrubStubDirsAzl4) } diff --git a/toolkit/tools/pkg/imagecustomizerlib/imageutils.go b/toolkit/tools/pkg/imagecustomizerlib/imageutils.go index f9e99dd2ed..c0cfd97853 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/imageutils.go +++ b/toolkit/tools/pkg/imagecustomizerlib/imageutils.go @@ -222,6 +222,7 @@ func configureDiskBootLoader(imageConnection *imageconnection.ImageConnection, rootMountIdType imagecustomizerapi.MountIdentifierType, bootType imagecustomizerapi.BootType, selinuxConfig imagecustomizerapi.SELinux, kernelCommandLine imagecustomizerapi.KernelCommandLine, currentSELinuxMode imagecustomizerapi.SELinuxMode, forceGrubMkconfig bool, distroHandler DistroHandler, + assetGrubDefFile string, grubEnvRelPath string, assetGrubStubFile string, grubStubDirs []string, ) error { imagerBootType, err := bootTypeToImager(bootType) if err != nil { @@ -261,7 +262,8 @@ func configureDiskBootLoader(imageConnection *imageconnection.ImageConnection, // Configure the boot loader. err = installutils.ConfigureDiskBootloaderWithRootMountIdType(imagerBootType, false, imagerRootMountIdType, imagerKernelCommandLine, imageConnection.Chroot(), imageConnection.Loopback().DevicePath(), mountPointMap, - diskutils.EncryptedRootDevice{}, useGrubMkconfig) + diskutils.EncryptedRootDevice{}, useGrubMkconfig, assetGrubDefFile, grubEnvRelPath, assetGrubStubFile, + grubStubDirs) if err != nil { return fmt.Errorf("failed to install bootloader:\n%w", err) } From bb1b031dc0b0f3bd92779efea6d513ad111aa285 Mon Sep 17 00:00:00 2001 From: Vince Perri <5596945+vinceaperri@users.noreply.github.com> Date: Wed, 13 May 2026 18:00:11 +0000 Subject: [PATCH 2/2] reorganize assets --- .../azurelinux-3.0/efi/grub/grub.cfg} | 0 .../azurelinux-3.0/grub2/grub} | 0 .../azurelinux-4.0/efi/grub/grub.cfg} | 0 .../azurelinux-4.0/grub2/grub} | 0 toolkit/tools/internal/resources/resources.go | 12 +++++++----- 5 files changed, 7 insertions(+), 5 deletions(-) rename toolkit/tools/internal/resources/assets/{efi/grub/grub-azl3.cfg => azurelinux/azurelinux-3.0/efi/grub/grub.cfg} (100%) rename toolkit/tools/internal/resources/assets/{grub2/grub-azl3 => azurelinux/azurelinux-3.0/grub2/grub} (100%) rename toolkit/tools/internal/resources/assets/{efi/grub/grub-azl4.cfg => azurelinux/azurelinux-4.0/efi/grub/grub.cfg} (100%) rename toolkit/tools/internal/resources/assets/{grub2/grub-azl4 => azurelinux/azurelinux-4.0/grub2/grub} (100%) diff --git a/toolkit/tools/internal/resources/assets/efi/grub/grub-azl3.cfg b/toolkit/tools/internal/resources/assets/azurelinux/azurelinux-3.0/efi/grub/grub.cfg similarity index 100% rename from toolkit/tools/internal/resources/assets/efi/grub/grub-azl3.cfg rename to toolkit/tools/internal/resources/assets/azurelinux/azurelinux-3.0/efi/grub/grub.cfg diff --git a/toolkit/tools/internal/resources/assets/grub2/grub-azl3 b/toolkit/tools/internal/resources/assets/azurelinux/azurelinux-3.0/grub2/grub similarity index 100% rename from toolkit/tools/internal/resources/assets/grub2/grub-azl3 rename to toolkit/tools/internal/resources/assets/azurelinux/azurelinux-3.0/grub2/grub diff --git a/toolkit/tools/internal/resources/assets/efi/grub/grub-azl4.cfg b/toolkit/tools/internal/resources/assets/azurelinux/azurelinux-4.0/efi/grub/grub.cfg similarity index 100% rename from toolkit/tools/internal/resources/assets/efi/grub/grub-azl4.cfg rename to toolkit/tools/internal/resources/assets/azurelinux/azurelinux-4.0/efi/grub/grub.cfg diff --git a/toolkit/tools/internal/resources/assets/grub2/grub-azl4 b/toolkit/tools/internal/resources/assets/azurelinux/azurelinux-4.0/grub2/grub similarity index 100% rename from toolkit/tools/internal/resources/assets/grub2/grub-azl4 rename to toolkit/tools/internal/resources/assets/azurelinux/azurelinux-4.0/grub2/grub diff --git a/toolkit/tools/internal/resources/resources.go b/toolkit/tools/internal/resources/resources.go index 2e428a02c3..ad6e1fc6c2 100644 --- a/toolkit/tools/internal/resources/resources.go +++ b/toolkit/tools/internal/resources/resources.go @@ -9,11 +9,13 @@ import ( const ( // Assets - AssetsGrubCfgFile = "assets/grub2/grub.cfg" - AssetsGrubDefFileAzl3 = "assets/grub2/grub-azl3" - AssetsGrubDefFileAzl4 = "assets/grub2/grub-azl4" - AssetsGrubStubFileAzl3 = "assets/efi/grub/grub-azl3.cfg" - AssetsGrubStubFileAzl4 = "assets/efi/grub/grub-azl4.cfg" + AssetsGrubCfgFile = "assets/grub2/grub.cfg" + + AssetsGrubDefFileAzl3 = "assets/azurelinux/azurelinux-3.0/grub2/grub" + AssetsGrubStubFileAzl3 = "assets/azurelinux/azurelinux-3.0/efi/grub/grub.cfg" + + AssetsGrubDefFileAzl4 = "assets/azurelinux/azurelinux-4.0/grub2/grub" + AssetsGrubStubFileAzl4 = "assets/azurelinux/azurelinux-4.0/efi/grub/grub.cfg" // Verity Signature Module Files VerityMountBootPartitionSetupFile = "verity-signature/90mountbootpartition/module-setup.sh"