From 61e95cb4b7b354d175d1dfce3d0fa43deefad187 Mon Sep 17 00:00:00 2001 From: Noel Georgi Date: Thu, 23 Oct 2025 21:15:42 +0530 Subject: [PATCH] feat: support bootloader option for ISO Support selecting bootloader option for ISO. Signed-off-by: Noel Georgi --- .../runtime/v1alpha1/bootloader/bootloader.go | 6 +- .../runtime/v1alpha1/v1alpha1_sequencer.go | 2 +- pkg/imager/iso/uefi.go | 23 --- pkg/imager/out.go | 41 ++++- pkg/imager/profile/output.go | 81 ++++++--- pkg/imager/profile/output_test.go | 172 ++++++++++++++++++ pkg/imager/profile/outputkind_enumer.go | 92 +++++----- pkg/imager/profile/profile.go | 16 +- .../profile/testdata/iso-amd64-1.12.0.yaml | 3 + .../profile/testdata/iso-arm64-1.12.0.yaml | 3 + .../testdata/secureboot-iso-amd64-1.12.0.yaml | 1 + .../testdata/secureboot-iso-arm64-1.12.0.yaml | 1 + pkg/machinery/imager/quirks/quirks.go | 12 ++ 13 files changed, 350 insertions(+), 103 deletions(-) create mode 100644 pkg/imager/profile/output_test.go diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/bootloader.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/bootloader.go index 97be78cc5..f084e0911 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/bootloader.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/bootloader.go @@ -83,14 +83,14 @@ func NewAuto() Bootloader { // New returns a new bootloader based on the secureboot flag and architecture. func New(bootloader, talosVersion, arch string) (Bootloader, error) { switch bootloader { - case profile.DiskImageBootloaderGrub.String(): + case profile.BootLoaderKindGrub.String(): g := grub.NewConfig() g.AddResetOption = quirks.New(talosVersion).SupportsResetGRUBOption() return g, nil - case profile.DiskImageBootloaderSDBoot.String(): + case profile.BootLoaderKindSDBoot.String(): return sdboot.New(), nil - case profile.DiskImageBootloaderDualBoot.String(): + case profile.BootLoaderKindDualBoot.String(): return dual.New(), nil default: return nil, fmt.Errorf("unsupported bootloader %q", bootloader) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer.go b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer.go index 273bcb10b..e9447a391 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer.go @@ -104,7 +104,7 @@ func (*Sequencer) Initialize(r runtime.Runtime) []runtime.Phase { return false } - return r.State().Machine().Installed() && val == profile.DiskImageBootloaderDualBoot.String() + return r.State().Machine().Installed() && val == profile.BootLoaderKindDualBoot.String() }, "cleanupBootloader", CleanupBootloader, diff --git a/pkg/imager/iso/uefi.go b/pkg/imager/iso/uefi.go index 82e02de61..e9cf3d71f 100644 --- a/pkg/imager/iso/uefi.go +++ b/pkg/imager/iso/uefi.go @@ -20,29 +20,6 @@ import ( "github.com/siderolabs/talos/pkg/makefs" ) -// UEFIOptions describe the input for the CreateUEFI function. -type UEFIOptions struct { - UKIPath string - SDBootPath string - - // A value in loader.conf secure-boot-enroll: off, manual, if-safe, force. - SDBootSecureBootEnrollKeys string - - // UKISigningCertDer is the DER encoded UKI signing certificate. - UKISigningCertDerPath string - - // optional, for auto-enrolling secureboot keys - PlatformKeyPath string - KeyExchangeKeyPath string - SignatureKeyPath string - - Arch string - Version string - - ScratchDir string - OutPath string -} - const ( // mib is the size of a megabyte. mib = 1024 * 1024 diff --git a/pkg/imager/out.go b/pkg/imager/out.go index 1209020ce..a94776c19 100644 --- a/pkg/imager/out.go +++ b/pkg/imager/out.go @@ -188,6 +188,43 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor if err != nil { return err } + case quirks.New(i.prof.Version).ISOSupportsSettingBootloader(): + options := iso.Options{ + KernelPath: i.prof.Input.Kernel.Path, + InitramfsPath: i.initramfsPath, + Cmdline: i.cmdline, + + UKIPath: i.ukiPath, + SDBootPath: i.sdBootPath, + + SDBootSecureBootEnrollKeys: "off", + + Arch: i.prof.Arch, + Version: i.prof.Version, + + ScratchDir: scratchSpace, + OutPath: path, + } + + switch i.prof.Output.ISOOptions.Bootloader { + case profile.BootLoaderKindDualBoot: + generator, err = options.CreateHybrid(printf) + if err != nil { + return err + } + case profile.BootLoaderKindSDBoot: + generator, err = options.CreateUEFI(printf) + if err != nil { + return err + } + case profile.BootLoaderKindGrub: + generator, err = options.CreateGRUB(printf) + if err != nil { + return err + } + case profile.BootLoaderKindNone: + return fmt.Errorf("cannot create ISO with no bootloader") + } case quirks.New(i.prof.Version).UseSDBootForUEFI(): options := iso.Options{ KernelPath: i.prof.Input.Kernel.Path, @@ -324,10 +361,10 @@ func (i *Imager) buildImage(ctx context.Context, path string, printf func(string if i.prof.Arch == "amd64" && !i.prof.SecureBootEnabled() && quirks.New(i.prof.Version).UseSDBootForUEFI() { // allow overriding the bootloader if provided - if i.prof.Output.ImageOptions.Bootloader == profile.DiskImageBootloaderDualBoot { + if i.prof.Output.ImageOptions.Bootloader == profile.BootLoaderKindDualBoot { metaContents = append(metaContents, meta.Value{ Key: meta.DiskImageBootloader, - Value: profile.DiskImageBootloaderDualBoot.String(), + Value: profile.BootLoaderKindDualBoot.String(), }) } } diff --git a/pkg/imager/profile/output.go b/pkg/imager/profile/output.go index 488b151bb..7d70dadbf 100644 --- a/pkg/imager/profile/output.go +++ b/pkg/imager/profile/output.go @@ -43,7 +43,7 @@ type ImageOptions struct { DiskFormatOptions string `yaml:"diskFormatOptions,omitempty"` // Bootloader is the bootloader to use for the disk image. // If not set, it defaults to dual-boot. - Bootloader DiskImageBootloader `yaml:"bootloader"` + Bootloader BootloaderKind `yaml:"bootloader,omitempty"` } // ISOOptions describes options for the 'iso' output. @@ -52,6 +52,9 @@ type ISOOptions struct { // // If not set, it defaults to if-safe. SDBootEnrollKeys SDBootEnrollKeys `yaml:"sdBootEnrollKeys"` + // Bootloader is the bootloader to use for the iso image. + // If not set, it defaults to dual-boot. + Bootloader BootloaderKind `yaml:"bootloader,omitempty"` } // OutputKind is output specification. @@ -105,53 +108,73 @@ const ( SDBootEnrollKeysOff // off ) -// DiskImageBootloader is a bootloader for the disk image. -type DiskImageBootloader int +// BootloaderKind is a bootloader for the disk image. +type BootloaderKind int const ( - // DiskImageBootloaderDualBoot is the dual-boot bootloader + // BootLoaderKindNone is the zero value. + BootLoaderKindNone BootloaderKind = iota // none + // BootLoaderKindDualBoot is the dual-boot bootloader. // using sd-boot for UEFI and GRUB for BIOS. - DiskImageBootloaderDualBoot DiskImageBootloader = iota // dual-boot - // DiskImageBootloaderSDBoot is the sd-boot bootloader. - DiskImageBootloaderSDBoot // sd-boot - // DiskImageBootloaderGrub is the GRUB bootloader. - DiskImageBootloaderGrub // grub + BootLoaderKindDualBoot // dual-boot + // BootLoaderKindSDBoot is the sd-boot bootloader. + BootLoaderKindSDBoot // sd-boot + // BootLoaderKindGrub is the GRUB bootloader. + BootLoaderKindGrub // grub ) // FillDefaults fills default values for the output. func (o *Output) FillDefaults(arch, version string, secureboot bool) { - if o.Kind == OutKindImage { + switch o.Kind { //nolint:exhaustive + case OutKindImage: if o.ImageOptions == nil { o.ImageOptions = &ImageOptions{} } - useSDBoot := quirks.New(version).UseSDBootForUEFI() - - switch { - case o.ImageOptions.Bootloader != DiskImageBootloaderDualBoot: - // allow user to override bootloader - case secureboot: - // secureboot is always using sd-boot - o.ImageOptions.Bootloader = DiskImageBootloaderSDBoot - case arch == "arm64" && useSDBoot: - // arm64 always uses sd-boot for Talos >= 1.10 - o.ImageOptions.Bootloader = DiskImageBootloaderSDBoot - case !useSDBoot: - // legacy versions of Talos use GRUB for BIOS/UEFI - o.ImageOptions.Bootloader = DiskImageBootloaderGrub - default: - // Default to dual-boot. - o.ImageOptions.Bootloader = DiskImageBootloaderDualBoot - } + o.ImageOptions.Bootloader = o.selectBootloader(o.ImageOptions.Bootloader, arch, version, secureboot) ps := quirks.New(version).PartitionSizes() // bump default image size for expanded boot o.ImageOptions.DiskSize += int64(ps.GrubBootSize()) - 1000*1024*1024 // 1000 MiB - if o.ImageOptions.Bootloader == DiskImageBootloaderDualBoot { + if o.ImageOptions.Bootloader == BootLoaderKindDualBoot { // add extra space for BIOS and BOOT partitions o.ImageOptions.DiskSize += int64(ps.GrubBIOSSize()) + int64(ps.GrubBootSize()) } + + case OutKindISO: + if !quirks.New(version).ISOSupportsSettingBootloader() { + return + } + + if o.ISOOptions == nil { + o.ISOOptions = &ISOOptions{} + } + + o.ISOOptions.Bootloader = o.selectBootloader(o.ISOOptions.Bootloader, arch, version, secureboot) + } +} + +func (o *Output) selectBootloader(current BootloaderKind, arch, version string, secureboot bool) BootloaderKind { + useSDBoot := quirks.New(version).UseSDBootForUEFI() + + switch { + case secureboot: + // secureboot is always using sd-boot + return BootLoaderKindSDBoot + case arch == "arm64" && useSDBoot: + // arm64 always uses sd-boot for Talos >= 1.10 + return BootLoaderKindSDBoot + case !useSDBoot: + // legacy versions of Talos use GRUB for BIOS/UEFI + return BootLoaderKindGrub + default: + // Default to dual-boot if not overridden. + if current == BootLoaderKindNone { + return BootLoaderKindDualBoot + } + + return current } } diff --git a/pkg/imager/profile/output_test.go b/pkg/imager/profile/output_test.go new file mode 100644 index 000000000..27065bb80 --- /dev/null +++ b/pkg/imager/profile/output_test.go @@ -0,0 +1,172 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package profile_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/siderolabs/talos/pkg/imager/profile" + "github.com/siderolabs/talos/pkg/machinery/imager/quirks" +) + +func createOutputWithDefaults(kind profile.OutputKind, arch, version string, secureBoot bool) profile.Output { + out := profile.Output{ + Kind: kind, + } + + out.FillDefaults(arch, version, secureBoot) + + return out +} + +func createOutputWithOverride(kind profile.OutputKind, bootloader profile.BootloaderKind, arch, version string, secureBoot bool) profile.Output { + out := profile.Output{ + Kind: kind, + } + + switch kind { //nolint:exhaustive + case profile.OutKindImage: + out.ImageOptions = &profile.ImageOptions{ + Bootloader: bootloader, + } + case profile.OutKindISO: + if quirks.New(version).ISOSupportsSettingBootloader() { + out.ISOOptions = &profile.ISOOptions{ + Bootloader: bootloader, + } + } + } + + out.FillDefaults(arch, version, secureBoot) + + return out +} + +func TestBootloaderSetting(t *testing.T) { + t.Parallel() + + tests := []struct { + arch string + version string + secureBoot bool + wantImage profile.BootloaderKind + }{ + // Talos < 1.10: GRUB for both amd64/arm64, ISO options not supported + {"amd64", "1.9.0", false, profile.BootLoaderKindGrub}, + {"amd64", "1.9.0", true, profile.BootLoaderKindSDBoot}, + {"arm64", "1.9.0", false, profile.BootLoaderKindGrub}, + {"arm64", "1.9.0", true, profile.BootLoaderKindSDBoot}, + + // Talos 1.10-1.11: amd64=dual-boot, arm64=sd-boot, ISO options not supported + {"amd64", "1.10.0", false, profile.BootLoaderKindDualBoot}, + {"amd64", "1.10.0", true, profile.BootLoaderKindSDBoot}, + {"arm64", "1.10.0", false, profile.BootLoaderKindSDBoot}, + {"arm64", "1.10.0", true, profile.BootLoaderKindSDBoot}, + {"amd64", "1.11.0", false, profile.BootLoaderKindDualBoot}, + {"amd64", "1.11.0", true, profile.BootLoaderKindSDBoot}, + {"arm64", "1.11.0", false, profile.BootLoaderKindSDBoot}, + {"arm64", "1.11.0", true, profile.BootLoaderKindSDBoot}, + + // Talos >= 1.12: amd64=dual-boot, arm64=sd-boot, ISO options supported + {"amd64", "1.12.0", false, profile.BootLoaderKindDualBoot}, + {"amd64", "1.12.0", true, profile.BootLoaderKindSDBoot}, + {"arm64", "1.12.0", false, profile.BootLoaderKindSDBoot}, + {"arm64", "1.12.0", true, profile.BootLoaderKindSDBoot}, + } + + for _, tt := range tests { + name := tt.arch + "-" + tt.version + if tt.secureBoot { + name += "-secureboot" + } + + t.Run(name, func(t *testing.T) { + t.Parallel() + + // Test Image output + img := createOutputWithDefaults(profile.OutKindImage, tt.arch, tt.version, tt.secureBoot) + require.NotNil(t, img.ImageOptions) + require.Equal(t, tt.wantImage, img.ImageOptions.Bootloader) + + // Test ISO output + iso := createOutputWithDefaults(profile.OutKindISO, tt.arch, tt.version, tt.secureBoot) + if quirks.New(tt.version).ISOSupportsSettingBootloader() { + require.NotNil(t, iso.ISOOptions) + require.Equal(t, tt.wantImage, iso.ISOOptions.Bootloader) + } else { + require.Nil(t, iso.ISOOptions) + } + }) + } +} + +func TestBootloaderOverride(t *testing.T) { + t.Parallel() + + tests := []struct { + arch string + version string + secureBoot bool + override profile.BootloaderKind + wantImage profile.BootloaderKind + }{ + // Talos < 1.10: GRUB is forced, overrides are ignored + {"amd64", "1.9.0", false, profile.BootLoaderKindGrub, profile.BootLoaderKindGrub}, + {"amd64", "1.9.0", false, profile.BootLoaderKindSDBoot, profile.BootLoaderKindGrub}, // forced to GRUB + {"amd64", "1.9.0", false, profile.BootLoaderKindDualBoot, profile.BootLoaderKindGrub}, // forced to GRUB + {"amd64", "1.9.0", true, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // secureboot forces sd-boot + {"arm64", "1.9.0", false, profile.BootLoaderKindGrub, profile.BootLoaderKindGrub}, + {"arm64", "1.9.0", false, profile.BootLoaderKindSDBoot, profile.BootLoaderKindGrub}, // forced to GRUB + {"arm64", "1.9.0", true, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // secureboot forces sd-boot + + // Talos 1.10-1.11: amd64 respects override, arm64 forced to sd-boot + {"amd64", "1.10.0", false, profile.BootLoaderKindGrub, profile.BootLoaderKindGrub}, + {"amd64", "1.10.0", false, profile.BootLoaderKindSDBoot, profile.BootLoaderKindSDBoot}, + {"amd64", "1.10.0", false, profile.BootLoaderKindDualBoot, profile.BootLoaderKindDualBoot}, + {"amd64", "1.10.0", true, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // secureboot forces sd-boot + {"arm64", "1.10.0", false, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // arm64 >= 1.10 forces sd-boot + {"arm64", "1.10.0", true, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // secureboot forces sd-boot + {"amd64", "1.11.0", false, profile.BootLoaderKindGrub, profile.BootLoaderKindGrub}, + {"amd64", "1.11.0", false, profile.BootLoaderKindDualBoot, profile.BootLoaderKindDualBoot}, + {"amd64", "1.11.0", true, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // secureboot forces sd-boot + {"arm64", "1.11.0", false, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // arm64 >= 1.10 forces sd-boot + {"arm64", "1.11.0", true, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // secureboot forces sd-boot + + // Talos >= 1.12: amd64 respects override, arm64 forced to sd-boot + {"amd64", "1.12.0", false, profile.BootLoaderKindGrub, profile.BootLoaderKindGrub}, + {"amd64", "1.12.0", false, profile.BootLoaderKindSDBoot, profile.BootLoaderKindSDBoot}, + {"amd64", "1.12.0", false, profile.BootLoaderKindDualBoot, profile.BootLoaderKindDualBoot}, + {"amd64", "1.12.0", true, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // secureboot forces sd-boot + {"arm64", "1.12.0", false, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // arm64 >= 1.10 forces sd-boot + {"arm64", "1.12.0", true, profile.BootLoaderKindGrub, profile.BootLoaderKindSDBoot}, // secureboot forces sd-boot + } + + for _, tt := range tests { + name := tt.arch + "-" + tt.version + "-override-" + tt.override.String() + if tt.secureBoot { + name += "-secureboot" + } + + t.Run(name, func(t *testing.T) { + t.Parallel() + + // Test Image output with override + img := createOutputWithOverride(profile.OutKindImage, tt.override, tt.arch, tt.version, tt.secureBoot) + require.NotNil(t, img.ImageOptions) + require.Equal(t, tt.wantImage, img.ImageOptions.Bootloader) + + // Test ISO output with override + iso := createOutputWithOverride(profile.OutKindISO, tt.override, tt.arch, tt.version, tt.secureBoot) + if quirks.New(tt.version).ISOSupportsSettingBootloader() { + require.NotNil(t, iso.ISOOptions) + require.Equal(t, tt.wantImage, iso.ISOOptions.Bootloader) + } else { + require.Nil(t, iso.ISOOptions) + } + }) + } +} diff --git a/pkg/imager/profile/outputkind_enumer.go b/pkg/imager/profile/outputkind_enumer.go index 9d78bdf04..c7ea01713 100644 --- a/pkg/imager/profile/outputkind_enumer.go +++ b/pkg/imager/profile/outputkind_enumer.go @@ -1,4 +1,4 @@ -// Code generated by "enumer -type OutputKind,OutFormat,DiskFormat,SDBootEnrollKeys,DiskImageBootloader -linecomment -text"; DO NOT EDIT. +// Code generated by "enumer -type OutputKind,OutFormat,DiskFormat,SDBootEnrollKeys,BootloaderKind -linecomment -text"; DO NOT EDIT. package profile @@ -395,73 +395,77 @@ func (i *SDBootEnrollKeys) UnmarshalText(text []byte) error { return err } -const _DiskImageBootloaderName = "dual-bootsd-bootgrub" +const _BootloaderKindName = "nonedual-bootsd-bootgrub" -var _DiskImageBootloaderIndex = [...]uint8{0, 9, 16, 20} +var _BootloaderKindIndex = [...]uint8{0, 4, 13, 20, 24} -const _DiskImageBootloaderLowerName = "dual-bootsd-bootgrub" +const _BootloaderKindLowerName = "nonedual-bootsd-bootgrub" -func (i DiskImageBootloader) String() string { - if i < 0 || i >= DiskImageBootloader(len(_DiskImageBootloaderIndex)-1) { - return fmt.Sprintf("DiskImageBootloader(%d)", i) +func (i BootloaderKind) String() string { + if i < 0 || i >= BootloaderKind(len(_BootloaderKindIndex)-1) { + return fmt.Sprintf("BootloaderKind(%d)", i) } - return _DiskImageBootloaderName[_DiskImageBootloaderIndex[i]:_DiskImageBootloaderIndex[i+1]] + return _BootloaderKindName[_BootloaderKindIndex[i]:_BootloaderKindIndex[i+1]] } // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. -func _DiskImageBootloaderNoOp() { +func _BootloaderKindNoOp() { var x [1]struct{} - _ = x[DiskImageBootloaderDualBoot-(0)] - _ = x[DiskImageBootloaderSDBoot-(1)] - _ = x[DiskImageBootloaderGrub-(2)] + _ = x[BootLoaderKindNone-(0)] + _ = x[BootLoaderKindDualBoot-(1)] + _ = x[BootLoaderKindSDBoot-(2)] + _ = x[BootLoaderKindGrub-(3)] } -var _DiskImageBootloaderValues = []DiskImageBootloader{DiskImageBootloaderDualBoot, DiskImageBootloaderSDBoot, DiskImageBootloaderGrub} +var _BootloaderKindValues = []BootloaderKind{BootLoaderKindNone, BootLoaderKindDualBoot, BootLoaderKindSDBoot, BootLoaderKindGrub} -var _DiskImageBootloaderNameToValueMap = map[string]DiskImageBootloader{ - _DiskImageBootloaderName[0:9]: DiskImageBootloaderDualBoot, - _DiskImageBootloaderLowerName[0:9]: DiskImageBootloaderDualBoot, - _DiskImageBootloaderName[9:16]: DiskImageBootloaderSDBoot, - _DiskImageBootloaderLowerName[9:16]: DiskImageBootloaderSDBoot, - _DiskImageBootloaderName[16:20]: DiskImageBootloaderGrub, - _DiskImageBootloaderLowerName[16:20]: DiskImageBootloaderGrub, +var _BootloaderKindNameToValueMap = map[string]BootloaderKind{ + _BootloaderKindName[0:4]: BootLoaderKindNone, + _BootloaderKindLowerName[0:4]: BootLoaderKindNone, + _BootloaderKindName[4:13]: BootLoaderKindDualBoot, + _BootloaderKindLowerName[4:13]: BootLoaderKindDualBoot, + _BootloaderKindName[13:20]: BootLoaderKindSDBoot, + _BootloaderKindLowerName[13:20]: BootLoaderKindSDBoot, + _BootloaderKindName[20:24]: BootLoaderKindGrub, + _BootloaderKindLowerName[20:24]: BootLoaderKindGrub, } -var _DiskImageBootloaderNames = []string{ - _DiskImageBootloaderName[0:9], - _DiskImageBootloaderName[9:16], - _DiskImageBootloaderName[16:20], +var _BootloaderKindNames = []string{ + _BootloaderKindName[0:4], + _BootloaderKindName[4:13], + _BootloaderKindName[13:20], + _BootloaderKindName[20:24], } -// DiskImageBootloaderString retrieves an enum value from the enum constants string name. +// BootloaderKindString retrieves an enum value from the enum constants string name. // Throws an error if the param is not part of the enum. -func DiskImageBootloaderString(s string) (DiskImageBootloader, error) { - if val, ok := _DiskImageBootloaderNameToValueMap[s]; ok { +func BootloaderKindString(s string) (BootloaderKind, error) { + if val, ok := _BootloaderKindNameToValueMap[s]; ok { return val, nil } - if val, ok := _DiskImageBootloaderNameToValueMap[strings.ToLower(s)]; ok { + if val, ok := _BootloaderKindNameToValueMap[strings.ToLower(s)]; ok { return val, nil } - return 0, fmt.Errorf("%s does not belong to DiskImageBootloader values", s) + return 0, fmt.Errorf("%s does not belong to BootloaderKind values", s) } -// DiskImageBootloaderValues returns all values of the enum -func DiskImageBootloaderValues() []DiskImageBootloader { - return _DiskImageBootloaderValues +// BootloaderKindValues returns all values of the enum +func BootloaderKindValues() []BootloaderKind { + return _BootloaderKindValues } -// DiskImageBootloaderStrings returns a slice of all String values of the enum -func DiskImageBootloaderStrings() []string { - strs := make([]string, len(_DiskImageBootloaderNames)) - copy(strs, _DiskImageBootloaderNames) +// BootloaderKindStrings returns a slice of all String values of the enum +func BootloaderKindStrings() []string { + strs := make([]string, len(_BootloaderKindNames)) + copy(strs, _BootloaderKindNames) return strs } -// IsADiskImageBootloader returns "true" if the value is listed in the enum definition. "false" otherwise -func (i DiskImageBootloader) IsADiskImageBootloader() bool { - for _, v := range _DiskImageBootloaderValues { +// IsABootloaderKind returns "true" if the value is listed in the enum definition. "false" otherwise +func (i BootloaderKind) IsABootloaderKind() bool { + for _, v := range _BootloaderKindValues { if i == v { return true } @@ -469,14 +473,14 @@ func (i DiskImageBootloader) IsADiskImageBootloader() bool { return false } -// MarshalText implements the encoding.TextMarshaler interface for DiskImageBootloader -func (i DiskImageBootloader) MarshalText() ([]byte, error) { +// MarshalText implements the encoding.TextMarshaler interface for BootloaderKind +func (i BootloaderKind) MarshalText() ([]byte, error) { return []byte(i.String()), nil } -// UnmarshalText implements the encoding.TextUnmarshaler interface for DiskImageBootloader -func (i *DiskImageBootloader) UnmarshalText(text []byte) error { +// UnmarshalText implements the encoding.TextUnmarshaler interface for BootloaderKind +func (i *BootloaderKind) UnmarshalText(text []byte) error { var err error - *i, err = DiskImageBootloaderString(string(text)) + *i, err = BootloaderKindString(string(text)) return err } diff --git a/pkg/imager/profile/profile.go b/pkg/imager/profile/profile.go index 2963e5d3a..248ed81b6 100644 --- a/pkg/imager/profile/profile.go +++ b/pkg/imager/profile/profile.go @@ -13,12 +13,13 @@ import ( "github.com/siderolabs/go-pointer" "gopkg.in/yaml.v3" + "github.com/siderolabs/talos/pkg/machinery/imager/quirks" "github.com/siderolabs/talos/pkg/machinery/overlay" ) //go:generate go tool github.com/siderolabs/deep-copy -type Profile -type SecureBootAssets -header-file ../../../hack/boilerplate.txt -o deep_copy.generated.go . -//go:generate go tool github.com/dmarkham/enumer -type OutputKind,OutFormat,DiskFormat,SDBootEnrollKeys,DiskImageBootloader -linecomment -text +//go:generate go tool github.com/dmarkham/enumer -type OutputKind,OutFormat,DiskFormat,SDBootEnrollKeys,BootloaderKind -linecomment -text // Profile describes image generation result. type Profile struct { @@ -82,11 +83,20 @@ func (p *Profile) Validate() error { } } + if p.SecureBootEnabled() && !quirks.New(p.Version).SupportsUKI() { + return fmt.Errorf("secureboot is not supported for Talos version %q", p.Version) + } + switch p.Output.Kind { case OutKindUnknown: return errors.New("unknown output kind") case OutKindISO: // ISO supports all kinds of customization + if quirks.New(p.Version).ISOSupportsSettingBootloader() { + if p.Output.ISOOptions != nil && p.Output.ISOOptions.Bootloader == BootLoaderKindNone { + return errors.New("bootloader cannot be 'none' for ISO output") + } + } case OutKindCmdline: // cmdline supports all kinds of customization case OutKindImage: @@ -98,6 +108,10 @@ func (p *Profile) Validate() error { if p.Output.ImageOptions.DiskSize == 0 { return errors.New("disk size is required for image output") } + + if p.Output.ImageOptions.Bootloader == BootLoaderKindNone { + return errors.New("bootloader cannot be 'none' for disk image output") + } case OutKindInstaller: if len(p.Customization.MetaContents) > 0 { return fmt.Errorf("customization of meta partition is not supported for %s output", p.Output.Kind) diff --git a/pkg/imager/profile/testdata/iso-amd64-1.12.0.yaml b/pkg/imager/profile/testdata/iso-amd64-1.12.0.yaml index 393af8d14..f2224f8ce 100644 --- a/pkg/imager/profile/testdata/iso-amd64-1.12.0.yaml +++ b/pkg/imager/profile/testdata/iso-amd64-1.12.0.yaml @@ -15,4 +15,7 @@ input: imageRef: ghcr.io/siderolabs/installer-base:1.12.0 output: kind: iso + isoOptions: + sdBootEnrollKeys: if-safe + bootloader: dual-boot outFormat: raw diff --git a/pkg/imager/profile/testdata/iso-arm64-1.12.0.yaml b/pkg/imager/profile/testdata/iso-arm64-1.12.0.yaml index 09010cefa..46947a551 100644 --- a/pkg/imager/profile/testdata/iso-arm64-1.12.0.yaml +++ b/pkg/imager/profile/testdata/iso-arm64-1.12.0.yaml @@ -15,4 +15,7 @@ input: imageRef: ghcr.io/siderolabs/installer-base:1.12.0 output: kind: iso + isoOptions: + sdBootEnrollKeys: if-safe + bootloader: sd-boot outFormat: raw diff --git a/pkg/imager/profile/testdata/secureboot-iso-amd64-1.12.0.yaml b/pkg/imager/profile/testdata/secureboot-iso-amd64-1.12.0.yaml index 47b2d55d2..8468bff11 100644 --- a/pkg/imager/profile/testdata/secureboot-iso-amd64-1.12.0.yaml +++ b/pkg/imager/profile/testdata/secureboot-iso-amd64-1.12.0.yaml @@ -23,4 +23,5 @@ output: kind: iso isoOptions: sdBootEnrollKeys: if-safe + bootloader: sd-boot outFormat: raw diff --git a/pkg/imager/profile/testdata/secureboot-iso-arm64-1.12.0.yaml b/pkg/imager/profile/testdata/secureboot-iso-arm64-1.12.0.yaml index 96a1cac83..4bfaa9dfc 100644 --- a/pkg/imager/profile/testdata/secureboot-iso-arm64-1.12.0.yaml +++ b/pkg/imager/profile/testdata/secureboot-iso-arm64-1.12.0.yaml @@ -23,4 +23,5 @@ output: kind: iso isoOptions: sdBootEnrollKeys: if-safe + bootloader: sd-boot outFormat: raw diff --git a/pkg/machinery/imager/quirks/quirks.go b/pkg/machinery/imager/quirks/quirks.go index 2400191e2..e6c3b3f9e 100644 --- a/pkg/machinery/imager/quirks/quirks.go +++ b/pkg/machinery/imager/quirks/quirks.go @@ -298,3 +298,15 @@ func (q Quirks) SupportsDisablingModuleSignatureVerification() bool { return q.v.GTE(minTalosVersionDisableModSigVerify) } + +var minTalosVersionISOSupportsSettingBootloader = semver.MustParse("1.12.0") + +// ISOSupportsSettingBootloader returns true if the Talos version supports setting bootloader for ISO output. +func (q Quirks) ISOSupportsSettingBootloader() bool { + // if the version doesn't parse, we assume it's latest Talos + if q.v == nil { + return true + } + + return q.v.GTE(minTalosVersionISOSupportsSettingBootloader) +}