Skip to content

Commit b1afdd7

Browse files
committed
bootc: support grub2 serial console customization
Wire the blueprint bootloader customization into the bootc disk image pipeline. When blueprint specifies grub2 serial/terminal settings, emit an org.osbuild.grub2.d stage that writes a drop-in config to `boot/grub2/console.cfg` after bootc install-to-filesystem. This relies on `bootupd`'s `console.cfg` grub config: https://github.com/coreos/bootupd/blob/main/src%2Fgrub2%2Fgrub-static-pre.cfg#L41 This adds a Go wrapper for the new osbuild stage and reads `GetBootloader()` in the bootc image type. Assisted-by: Opencode.ai <Opus 4.6> Requires: osbuild/blueprint#53 Requires: osbuild/osbuild#2473
1 parent 7a7727c commit b1afdd7

4 files changed

Lines changed: 81 additions & 0 deletions

File tree

data/distrodefs/bootc-generic/imagetypes.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787

8888
supported_options_lists:
8989
supported_options_disk: &supported_options_disk
90+
- "customizations.bootloader"
9091
- "customizations.directories"
9192
- "customizations.disk"
9293
- "customizations.files"

pkg/distro/generic/bootc_imagetype.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,20 @@ func (t *bootcImageType) manifestForDisk(bp *blueprint.Blueprint, options distro
256256
img.OSCustomizations.KernelOptionsAppend = append(img.OSCustomizations.KernelOptionsAppend, kopts.Append)
257257
}
258258

259+
if bl := customizations.GetBootloader(); bl != nil && bl.Grub2 != nil {
260+
grub2Cfg := &osbuild.GRUB2Config{}
261+
if len(bl.Grub2.TerminalInput) > 0 {
262+
grub2Cfg.TerminalInput = bl.Grub2.TerminalInput
263+
}
264+
if len(bl.Grub2.TerminalOutput) > 0 {
265+
grub2Cfg.TerminalOutput = bl.Grub2.TerminalOutput
266+
}
267+
if bl.Grub2.Serial != nil {
268+
grub2Cfg.Serial = *bl.Grub2.Serial
269+
}
270+
img.OSCustomizations.Grub2Config = grub2Cfg
271+
}
272+
259273
rootfsMinSize := max(bd.rootfsMinSize, options.Size)
260274

261275
pt, err := t.genPartitionTable(customizations, rootfsMinSize, rng)

pkg/manifest/raw_bootc.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,22 @@ func (p *RawBootcImage) serialize() (osbuild.Pipeline, error) {
309309
postStages = append(postStages, ignitionStage)
310310
}
311311

312+
// Apply grub2 console configuration (terminal_input, terminal_output,
313+
// serial) via the grub2.d stage which writes a drop-in config file
314+
// under boot/grub2/. Like ignition, this writes to /boot so we use
315+
// bootupd mounts.
316+
if grub2dCfg := osbuild.NewGrub2DConfigFromGrub2Config(p.OSCustomizations.Grub2Config); grub2dCfg != nil {
317+
grub2dOpts := &osbuild.Grub2DStageOptions{
318+
Config: grub2dCfg,
319+
}
320+
grub2dStage := osbuild.NewGrub2DStage(grub2dOpts)
321+
grub2dStage.Devices, grub2dStage.Mounts, err = osbuild.GenBootupdDevicesMounts(p.filename, p.PartitionTable, p.platform)
322+
if err != nil {
323+
return osbuild.Pipeline{}, fmt.Errorf("gen devices for grub2.d stage failed %w", err)
324+
}
325+
postStages = append(postStages, grub2dStage)
326+
}
327+
312328
pipeline.AddStages(postStages...)
313329

314330
// In case we created any files in the deploy directory we need to relabel

pkg/osbuild/grub2_d_stage.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package osbuild
2+
3+
// Grub2DStageOptions represents options for the
4+
// org.osbuild.grub2.d stage.
5+
//
6+
// This stage writes a GRUB2 drop-in configuration file at a
7+
// configurable path relative to the filesystem root. The path
8+
// defaults to "boot/grub2/console.cfg".
9+
type Grub2DStageOptions struct {
10+
// Path relative to the filesystem root
11+
// (default: "boot/grub2/console.cfg")
12+
Path string `json:"path,omitempty"`
13+
14+
// GRUB2 configuration to write
15+
Config *Grub2DConfig `json:"config"`
16+
}
17+
18+
// Grub2DConfig contains GRUB2 settings for a drop-in config file.
19+
type Grub2DConfig struct {
20+
TerminalInput []string `json:"terminal_input,omitempty"`
21+
TerminalOutput []string `json:"terminal_output,omitempty"`
22+
Serial string `json:"serial,omitempty"`
23+
}
24+
25+
func (Grub2DStageOptions) isStageOptions() {}
26+
27+
func NewGrub2DStage(options *Grub2DStageOptions) *Stage {
28+
return &Stage{
29+
Type: "org.osbuild.grub2.d",
30+
Options: options,
31+
}
32+
}
33+
34+
// NewGrub2DConfigFromGrub2Config creates a Grub2DConfig from a
35+
// GRUB2Config, extracting only the console-related fields.
36+
// Returns nil if no console settings are present.
37+
func NewGrub2DConfigFromGrub2Config(cfg *GRUB2Config) *Grub2DConfig {
38+
if cfg == nil {
39+
return nil
40+
}
41+
c := &Grub2DConfig{
42+
TerminalInput: cfg.TerminalInput,
43+
TerminalOutput: cfg.TerminalOutput,
44+
Serial: cfg.Serial,
45+
}
46+
if len(c.TerminalInput) == 0 && len(c.TerminalOutput) == 0 && c.Serial == "" {
47+
return nil
48+
}
49+
return c
50+
}

0 commit comments

Comments
 (0)