diff --git a/data/distrodefs/bootc-generic/imagetypes.yaml b/data/distrodefs/bootc-generic/imagetypes.yaml index bf8b750f2c..07464ea693 100644 --- a/data/distrodefs/bootc-generic/imagetypes.yaml +++ b/data/distrodefs/bootc-generic/imagetypes.yaml @@ -87,6 +87,7 @@ supported_options_lists: supported_options_disk: &supported_options_disk + - "customizations.bootloader" - "customizations.directories" - "customizations.disk" - "customizations.files" diff --git a/pkg/distro/generic/bootc_imagetype.go b/pkg/distro/generic/bootc_imagetype.go index d73c2222d9..60e87dec53 100644 --- a/pkg/distro/generic/bootc_imagetype.go +++ b/pkg/distro/generic/bootc_imagetype.go @@ -256,6 +256,20 @@ func (t *bootcImageType) manifestForDisk(bp *blueprint.Blueprint, options distro img.OSCustomizations.KernelOptionsAppend = append(img.OSCustomizations.KernelOptionsAppend, kopts.Append) } + if bl := customizations.GetBootloader(); bl != nil && bl.Grub2 != nil { + grub2Cfg := &osbuild.GRUB2Config{} + if len(bl.Grub2.TerminalInput) > 0 { + grub2Cfg.TerminalInput = bl.Grub2.TerminalInput + } + if len(bl.Grub2.TerminalOutput) > 0 { + grub2Cfg.TerminalOutput = bl.Grub2.TerminalOutput + } + if bl.Grub2.Serial != nil { + grub2Cfg.Serial = *bl.Grub2.Serial + } + img.OSCustomizations.Grub2Config = grub2Cfg + } + rootfsMinSize := max(bd.rootfsMinSize, options.Size) pt, err := t.genPartitionTable(customizations, rootfsMinSize, rng) diff --git a/pkg/manifest/raw_bootc.go b/pkg/manifest/raw_bootc.go index d28aac62f8..5bd5395f76 100644 --- a/pkg/manifest/raw_bootc.go +++ b/pkg/manifest/raw_bootc.go @@ -309,6 +309,22 @@ func (p *RawBootcImage) serialize() (osbuild.Pipeline, error) { postStages = append(postStages, ignitionStage) } + // Apply grub2 console configuration (terminal_input, terminal_output, + // serial) via the grub2.d stage which writes a drop-in config file + // under boot/grub2/. Like ignition, this writes to /boot so we use + // bootupd mounts. + if grub2dCfg := osbuild.NewGrub2DConfigFromGrub2Config(p.OSCustomizations.Grub2Config); grub2dCfg != nil { + grub2dOpts := &osbuild.Grub2DStageOptions{ + Config: grub2dCfg, + } + grub2dStage := osbuild.NewGrub2DStage(grub2dOpts) + grub2dStage.Devices, grub2dStage.Mounts, err = osbuild.GenBootupdDevicesMounts(p.filename, p.PartitionTable, p.platform) + if err != nil { + return osbuild.Pipeline{}, fmt.Errorf("gen devices for grub2.d stage failed %w", err) + } + postStages = append(postStages, grub2dStage) + } + pipeline.AddStages(postStages...) // In case we created any files in the deploy directory we need to relabel diff --git a/pkg/osbuild/grub2_d_stage.go b/pkg/osbuild/grub2_d_stage.go new file mode 100644 index 0000000000..b94872a28d --- /dev/null +++ b/pkg/osbuild/grub2_d_stage.go @@ -0,0 +1,50 @@ +package osbuild + +// Grub2DStageOptions represents options for the +// org.osbuild.grub2.d stage. +// +// This stage writes a GRUB2 drop-in configuration file at a +// configurable path relative to the filesystem root. The path +// defaults to "boot/grub2/console.cfg". +type Grub2DStageOptions struct { + // Path relative to the filesystem root + // (default: "boot/grub2/console.cfg") + Path string `json:"path,omitempty"` + + // GRUB2 configuration to write + Config *Grub2DConfig `json:"config"` +} + +// Grub2DConfig contains GRUB2 settings for a drop-in config file. +type Grub2DConfig struct { + TerminalInput []string `json:"terminal_input,omitempty"` + TerminalOutput []string `json:"terminal_output,omitempty"` + Serial string `json:"serial,omitempty"` +} + +func (Grub2DStageOptions) isStageOptions() {} + +func NewGrub2DStage(options *Grub2DStageOptions) *Stage { + return &Stage{ + Type: "org.osbuild.grub2.d", + Options: options, + } +} + +// NewGrub2DConfigFromGrub2Config creates a Grub2DConfig from a +// GRUB2Config, extracting only the console-related fields. +// Returns nil if no console settings are present. +func NewGrub2DConfigFromGrub2Config(cfg *GRUB2Config) *Grub2DConfig { + if cfg == nil { + return nil + } + c := &Grub2DConfig{ + TerminalInput: cfg.TerminalInput, + TerminalOutput: cfg.TerminalOutput, + Serial: cfg.Serial, + } + if len(c.TerminalInput) == 0 && len(c.TerminalOutput) == 0 && c.Serial == "" { + return nil + } + return c +}