diff --git a/internal/distro/image_config.go b/internal/distro/image_config.go new file mode 100644 index 000000000..146c6f516 --- /dev/null +++ b/internal/distro/image_config.go @@ -0,0 +1,48 @@ +package distro + +import "github.com/osbuild/osbuild-composer/internal/osbuild2" + +// ImageConfig represents a (default) configuration applied to the image +type ImageConfig struct { + Timezone string + TimeSynchronization *osbuild2.ChronyStageOptions + Locale string + Keyboard *osbuild2.KeymapStageOptions + EnabledServices []string + DisabledServices []string + DefaultTarget string + Sysconfig []*osbuild2.SysconfigStageOptions +} + +// InheritFrom inherits unset values from the provided parent configuration and +// returns a new structure instance, which is a result of the inheritance. +func (c *ImageConfig) InheritFrom(parentConfig *ImageConfig) *ImageConfig { + finalConfig := ImageConfig(*c) + if parentConfig != nil { + if finalConfig.Timezone == "" { + finalConfig.Timezone = parentConfig.Timezone + } + if finalConfig.TimeSynchronization == nil { + finalConfig.TimeSynchronization = parentConfig.TimeSynchronization + } + if finalConfig.Locale == "" { + finalConfig.Locale = parentConfig.Locale + } + if finalConfig.Keyboard == nil { + finalConfig.Keyboard = parentConfig.Keyboard + } + if finalConfig.EnabledServices == nil { + finalConfig.EnabledServices = parentConfig.EnabledServices + } + if finalConfig.DisabledServices == nil { + finalConfig.DisabledServices = parentConfig.DisabledServices + } + if finalConfig.DefaultTarget == "" { + finalConfig.DefaultTarget = parentConfig.DefaultTarget + } + if finalConfig.Sysconfig == nil { + finalConfig.Sysconfig = parentConfig.Sysconfig + } + } + return &finalConfig +} diff --git a/internal/distro/image_config_test.go b/internal/distro/image_config_test.go new file mode 100644 index 000000000..dc5c57199 --- /dev/null +++ b/internal/distro/image_config_test.go @@ -0,0 +1,167 @@ +package distro + +import ( + "testing" + + "github.com/osbuild/osbuild-composer/internal/common" + "github.com/osbuild/osbuild-composer/internal/osbuild2" + "github.com/stretchr/testify/assert" +) + +func TestImageConfigInheritFrom(t *testing.T) { + tests := []struct { + name string + distroConfig *ImageConfig + imageConfig *ImageConfig + expectedConfig *ImageConfig + }{ + { + name: "inheritance with overridden values", + distroConfig: &ImageConfig{ + Timezone: "America/New_York", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Timeservers: []string{"127.0.0.1"}, + }, + Locale: "en_US.UTF-8", + Keyboard: &osbuild2.KeymapStageOptions{ + Keymap: "us", + }, + EnabledServices: []string{"sshd"}, + DisabledServices: []string{"named"}, + DefaultTarget: "multi-user.target", + }, + imageConfig: &ImageConfig{ + Timezone: "UTC", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Servers: []osbuild2.ChronyConfigServer{ + { + Hostname: "169.254.169.123", + Prefer: common.BoolToPtr(true), + Iburst: common.BoolToPtr(true), + Minpoll: common.IntToPtr(4), + Maxpoll: common.IntToPtr(4), + }, + }, + LeapsecTz: common.StringToPtr(""), + }, + }, + expectedConfig: &ImageConfig{ + Timezone: "UTC", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Servers: []osbuild2.ChronyConfigServer{ + { + Hostname: "169.254.169.123", + Prefer: common.BoolToPtr(true), + Iburst: common.BoolToPtr(true), + Minpoll: common.IntToPtr(4), + Maxpoll: common.IntToPtr(4), + }, + }, + LeapsecTz: common.StringToPtr(""), + }, + Locale: "en_US.UTF-8", + Keyboard: &osbuild2.KeymapStageOptions{ + Keymap: "us", + }, + EnabledServices: []string{"sshd"}, + DisabledServices: []string{"named"}, + DefaultTarget: "multi-user.target", + }, + }, + { + name: "empty image type configuration", + distroConfig: &ImageConfig{ + Timezone: "America/New_York", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Timeservers: []string{"127.0.0.1"}, + }, + Locale: "en_US.UTF-8", + Keyboard: &osbuild2.KeymapStageOptions{ + Keymap: "us", + }, + EnabledServices: []string{"sshd"}, + DisabledServices: []string{"named"}, + DefaultTarget: "multi-user.target", + }, + imageConfig: &ImageConfig{}, + expectedConfig: &ImageConfig{ + Timezone: "America/New_York", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Timeservers: []string{"127.0.0.1"}, + }, + Locale: "en_US.UTF-8", + Keyboard: &osbuild2.KeymapStageOptions{ + Keymap: "us", + }, + EnabledServices: []string{"sshd"}, + DisabledServices: []string{"named"}, + DefaultTarget: "multi-user.target", + }, + }, + { + name: "empty distro configuration", + distroConfig: &ImageConfig{}, + imageConfig: &ImageConfig{ + Timezone: "America/New_York", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Timeservers: []string{"127.0.0.1"}, + }, + Locale: "en_US.UTF-8", + Keyboard: &osbuild2.KeymapStageOptions{ + Keymap: "us", + }, + EnabledServices: []string{"sshd"}, + DisabledServices: []string{"named"}, + DefaultTarget: "multi-user.target", + }, + expectedConfig: &ImageConfig{ + Timezone: "America/New_York", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Timeservers: []string{"127.0.0.1"}, + }, + Locale: "en_US.UTF-8", + Keyboard: &osbuild2.KeymapStageOptions{ + Keymap: "us", + }, + EnabledServices: []string{"sshd"}, + DisabledServices: []string{"named"}, + DefaultTarget: "multi-user.target", + }, + }, + { + name: "empty distro configuration", + distroConfig: nil, + imageConfig: &ImageConfig{ + Timezone: "America/New_York", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Timeservers: []string{"127.0.0.1"}, + }, + Locale: "en_US.UTF-8", + Keyboard: &osbuild2.KeymapStageOptions{ + Keymap: "us", + }, + EnabledServices: []string{"sshd"}, + DisabledServices: []string{"named"}, + DefaultTarget: "multi-user.target", + }, + expectedConfig: &ImageConfig{ + Timezone: "America/New_York", + TimeSynchronization: &osbuild2.ChronyStageOptions{ + Timeservers: []string{"127.0.0.1"}, + }, + Locale: "en_US.UTF-8", + Keyboard: &osbuild2.KeymapStageOptions{ + Keymap: "us", + }, + EnabledServices: []string{"sshd"}, + DisabledServices: []string{"named"}, + DefaultTarget: "multi-user.target", + }, + }, + } + for idx, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.expectedConfig, tt.imageConfig.InheritFrom(tt.distroConfig), "test case %q failed (idx %d)", tt.name, idx) + }) + } +} diff --git a/internal/distro/rhel90/distro.go b/internal/distro/rhel90/distro.go index 9588846c2..2379cb913 100644 --- a/internal/distro/rhel90/distro.go +++ b/internal/distro/rhel90/distro.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/osbuild/osbuild-composer/internal/blueprint" + "github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/osbuild-composer/internal/disk" "github.com/osbuild/osbuild-composer/internal/distro" osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" @@ -40,41 +41,62 @@ var mountpointAllowList = []string{ } type distribution struct { - name string - product string - osVersion string - releaseVersion string - modulePlatformID string - vendor string - ostreeRefTmpl string - isolabelTmpl string - runner string - arches map[string]distro.Arch + name string + product string + osVersion string + releaseVersion string + modulePlatformID string + vendor string + ostreeRefTmpl string + isolabelTmpl string + runner string + arches map[string]distro.Arch + defaultImageConfig *distro.ImageConfig +} + +// RHEL-based OS image configuration defaults +var defaultDistroImageConfig = &distro.ImageConfig{ + Timezone: "America/New_York", + Locale: "en_US.UTF-8", + Sysconfig: []*osbuild.SysconfigStageOptions{ + { + Kernel: &osbuild.SysconfigKernelOptions{ + UpdateDefault: true, + DefaultKernel: "kernel", + }, + Network: &osbuild.SysconfigNetworkOptions{ + Networking: true, + NoZeroConf: true, + }, + }, + }, } // distribution objects without the arches > image types var distroMap = map[string]distribution{ "rhel-90": { - name: "rhel-90", - product: "Red Hat Enterprise Linux", - osVersion: "9.0", - releaseVersion: "9", - modulePlatformID: "platform:el9", - vendor: "redhat", - ostreeRefTmpl: "rhel/9/%s/edge", - isolabelTmpl: "RHEL-9-0-0-BaseOS-%s", - runner: "org.osbuild.rhel90", + name: "rhel-90", + product: "Red Hat Enterprise Linux", + osVersion: "9.0", + releaseVersion: "9", + modulePlatformID: "platform:el9", + vendor: "redhat", + ostreeRefTmpl: "rhel/9/%s/edge", + isolabelTmpl: "RHEL-9-0-0-BaseOS-%s", + runner: "org.osbuild.rhel90", + defaultImageConfig: defaultDistroImageConfig, }, "centos-9": { - name: "centos-9", - product: "CentOS Stream", - osVersion: "9-stream", - releaseVersion: "9", - modulePlatformID: "platform:el9", - vendor: "centos", - ostreeRefTmpl: "centos/9/%s/edge", - isolabelTmpl: "CentOS-Stream-9-%s-dvd", - runner: "org.osbuild.centos9", + name: "centos-9", + product: "CentOS Stream", + osVersion: "9-stream", + releaseVersion: "9", + modulePlatformID: "platform:el9", + vendor: "centos", + ostreeRefTmpl: "centos/9/%s/edge", + isolabelTmpl: "CentOS-Stream-9-%s-dvd", + runner: "org.osbuild.centos9", + defaultImageConfig: defaultDistroImageConfig, }, } @@ -128,6 +150,10 @@ func (d *distribution) isRHEL() bool { return strings.HasPrefix(d.name, "rhel") } +func (d *distribution) getDefaultImageConfig() *distro.ImageConfig { + return d.defaultImageConfig +} + type architecture struct { distro *distribution name string @@ -194,21 +220,19 @@ type pipelinesFunc func(t *imageType, customizations *blueprint.Customizations, type packageSetFunc func(t *imageType) rpmmd.PackageSet type imageType struct { - arch *architecture - name string - nameAliases []string - filename string - mimeType string - packageSets map[string]packageSetFunc - enabledServices []string - disabledServices []string - defaultTarget string - kernelOptions string - defaultSize uint64 - buildPipelines []string - payloadPipelines []string - exports []string - pipelines pipelinesFunc + arch *architecture + name string + nameAliases []string + filename string + mimeType string + packageSets map[string]packageSetFunc + defaultImageConfig *distro.ImageConfig + kernelOptions string + defaultSize uint64 + buildPipelines []string + payloadPipelines []string + exports []string + pipelines pipelinesFunc // bootISO: installable ISO bootISO bool @@ -359,6 +383,15 @@ func (t *imageType) getPartitionTable( return disk.CreatePartitionTable(mountpoints, options.Size, basePartitionTable, rng), nil } +func (t *imageType) getDefaultImageConfig() *distro.ImageConfig { + // ensure that image always returns non-nil default config + imageConfig := t.defaultImageConfig + if imageConfig == nil { + imageConfig = &distro.ImageConfig{} + } + return imageConfig.InheritFrom(t.arch.distro.getDefaultImageConfig()) +} + // local type for ostree commit metadata used to define commit sources type ostreeCommit struct { Checksum string @@ -569,7 +602,9 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: edgeBuildPackageSet, osPkgsKey: edgeCommitPackageSet, }, - enabledServices: edgeServices, + defaultImageConfig: &distro.ImageConfig{ + EnabledServices: edgeServices, + }, rpmOstree: true, pipelines: edgeCommitPipelines, buildPipelines: []string{"build"}, @@ -591,7 +626,9 @@ func newDistro(distroName string) distro.Distro { } }, }, - enabledServices: edgeServices, + defaultImageConfig: &distro.ImageConfig{ + EnabledServices: edgeServices, + }, rpmOstree: true, bootISO: false, pipelines: edgeContainerPipelines, @@ -636,7 +673,9 @@ func newDistro(distroName string) distro.Distro { osPkgsKey: edgeCommitPackageSet, installerPkgsKey: edgeInstallerPackageSet, }, - enabledServices: edgeServices, + defaultImageConfig: &distro.ImageConfig{ + EnabledServices: edgeServices, + }, rpmOstree: true, bootISO: true, pipelines: edgeInstallerPipelines, @@ -661,7 +700,9 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: edgeInstallerBuildPackageSet, installerPkgsKey: edgeSimplifiedInstallerPackageSet, }, - enabledServices: edgeServices, + defaultImageConfig: &distro.ImageConfig{ + EnabledServices: edgeServices, + }, defaultSize: 10 * GigaByte, rpmOstree: true, bootable: true, @@ -677,12 +718,14 @@ func newDistro(distroName string) distro.Distro { name: "qcow2", filename: "disk.qcow2", mimeType: "application/x-qemu-disk", - defaultTarget: "multi-user.target", kernelOptions: "console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0 crashkernel=auto", packageSets: map[string]packageSetFunc{ buildPkgsKey: distroBuildPackageSet, osPkgsKey: qcow2CommonPackageSet, }, + defaultImageConfig: &distro.ImageConfig{ + DefaultTarget: "multi-user.target", + }, bootable: true, defaultSize: 10 * GigaByte, pipelines: qcow2Pipelines, @@ -700,11 +743,13 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: distroBuildPackageSet, osPkgsKey: vhdCommonPackageSet, }, - enabledServices: []string{ - "sshd", - "waagent", + defaultImageConfig: &distro.ImageConfig{ + EnabledServices: []string{ + "sshd", + "waagent", + }, + DefaultTarget: "multi-user.target", }, - defaultTarget: "multi-user.target", kernelOptions: "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0", bootable: true, defaultSize: 4 * GigaByte, @@ -751,18 +796,66 @@ func newDistro(distroName string) distro.Distro { basePartitionTables: defaultBasePartitionTables, } - // EC2 services - ec2EnabledServices := []string{ - "sshd", - "NetworkManager", - "nm-cloud-setup.service", - "nm-cloud-setup.timer", - "cloud-init", - "cloud-init-local", - "cloud-config", - "cloud-final", - "reboot.target", - "tuned", + // default EC2 images config + defaultEc2ImageConfig := &distro.ImageConfig{ + Timezone: "UTC", + TimeSynchronization: &osbuild.ChronyStageOptions{ + Servers: []osbuild.ChronyConfigServer{ + { + Hostname: "169.254.169.123", + Prefer: common.BoolToPtr(true), + Iburst: common.BoolToPtr(true), + Minpoll: common.IntToPtr(4), + Maxpoll: common.IntToPtr(4), + }, + }, + // empty string will remove any occurrences of the option from the configuration + LeapsecTz: common.StringToPtr(""), + }, + Keyboard: &osbuild.KeymapStageOptions{ + Keymap: "us", + X11Keymap: &osbuild.X11KeymapOptions{ + Layouts: []string{"us"}, + }, + }, + EnabledServices: []string{ + "sshd", + "NetworkManager", + "nm-cloud-setup.service", + "nm-cloud-setup.timer", + "cloud-init", + "cloud-init-local", + "cloud-config", + "cloud-final", + "reboot.target", + "tuned", + }, + DefaultTarget: "multi-user.target", + Sysconfig: []*osbuild.SysconfigStageOptions{ + { + Kernel: &osbuild.SysconfigKernelOptions{ + UpdateDefault: true, + DefaultKernel: "kernel", + }, + Network: &osbuild.SysconfigNetworkOptions{ + Networking: true, + NoZeroConf: true, + }, + NetworkScripts: &osbuild.NetworkScriptsOptions{ + IfcfgFiles: map[string]osbuild.IfcfgFile{ + "eth0": { + Device: "eth0", + Bootproto: osbuild.IfcfgBootprotoDHCP, + OnBoot: common.BoolToPtr(true), + Type: osbuild.IfcfgTypeEthernet, + UserCtl: common.BoolToPtr(true), + PeerDNS: common.BoolToPtr(true), + IPv6Init: common.BoolToPtr(false), + }, + }, + }, + }, + }, } amiImgTypeX86_64 := imageType{ @@ -773,8 +866,7 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: ec2BuildPackageSet, osPkgsKey: ec2CommonPackageSet, }, - defaultTarget: "multi-user.target", - enabledServices: ec2EnabledServices, + defaultImageConfig: defaultEc2ImageConfig, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", bootable: true, bootType: distro.LegacyBootType, @@ -794,8 +886,7 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: ec2BuildPackageSet, osPkgsKey: ec2CommonPackageSet, }, - defaultTarget: "multi-user.target", - enabledServices: ec2EnabledServices, + defaultImageConfig: defaultEc2ImageConfig, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0 crashkernel=auto", bootable: true, defaultSize: 10 * GigaByte, @@ -814,8 +905,7 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: ec2BuildPackageSet, osPkgsKey: rhelEc2PackageSet, }, - defaultTarget: "multi-user.target", - enabledServices: ec2EnabledServices, + defaultImageConfig: defaultEc2ImageConfig, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", bootable: true, bootType: distro.LegacyBootType, @@ -835,8 +925,7 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: ec2BuildPackageSet, osPkgsKey: rhelEc2PackageSet, }, - defaultTarget: "multi-user.target", - enabledServices: ec2EnabledServices, + defaultImageConfig: defaultEc2ImageConfig, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0 crashkernel=auto", bootable: true, defaultSize: 10 * GigaByte, @@ -855,8 +944,7 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: ec2BuildPackageSet, osPkgsKey: rhelEc2HaPackageSet, }, - defaultTarget: "multi-user.target", - enabledServices: ec2EnabledServices, + defaultImageConfig: defaultEc2ImageConfig, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", bootable: true, bootType: distro.LegacyBootType, @@ -876,8 +964,7 @@ func newDistro(distroName string) distro.Distro { buildPkgsKey: ec2BuildPackageSet, osPkgsKey: rhelEc2SapPackageSet, }, - defaultTarget: "multi-user.target", - enabledServices: ec2EnabledServices, + defaultImageConfig: defaultEc2ImageConfig, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto processor.max_cstate=1 intel_idle.max_cstate=1", bootable: true, bootType: distro.LegacyBootType, diff --git a/internal/distro/rhel90/pipelines.go b/internal/distro/rhel90/pipelines.go index a5b31f4cb..23bf8924f 100644 --- a/internal/distro/rhel90/pipelines.go +++ b/internal/distro/rhel90/pipelines.go @@ -24,7 +24,7 @@ func qcow2Pipelines(t *imageType, customizations *blueprint.Customizations, opti return nil, err } - treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, &partitionTable) + treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, &partitionTable) if err != nil { return nil, err } @@ -77,7 +77,7 @@ func vhdPipelines(t *imageType, customizations *blueprint.Customizations, option return nil, err } - treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, &partitionTable) + treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, &partitionTable) if err != nil { return nil, err } @@ -107,7 +107,7 @@ func vmdkPipelines(t *imageType, customizations *blueprint.Customizations, optio return nil, err } - treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, &partitionTable) + treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, &partitionTable) if err != nil { return nil, err } @@ -137,7 +137,7 @@ func openstackPipelines(t *imageType, customizations *blueprint.Customizations, return nil, err } - treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, &partitionTable) + treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, &partitionTable) if err != nil { return nil, err } @@ -169,16 +169,17 @@ func openstackPipelines(t *imageType, customizations *blueprint.Customizations, // as the last one to the returned pipeline. The stage is not appended on purpose, to allow caller to append // any additional stages to the pipeline, but before the SELinuxStage, which must be always the last one. func ec2BaseTreePipeline( + t *imageType, repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, - enabledServices, disabledServices []string, - defaultTarget string, - withRHUI, isRHEL bool, + withRHUI bool, pt *disk.PartitionTable) (*osbuild.Pipeline, error) { + // COMMON WITH osPipeline - START + imageConfig := t.getDefaultImageConfig() p := new(osbuild.Pipeline) p.Name = "os" p.Build = "name:build" @@ -196,17 +197,12 @@ func ec2BaseTreePipeline( if language != nil { p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) } else { - p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: imageConfig.Locale})) } if keyboard != nil { p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard})) - } else { - p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{ - Keymap: "us", - X11Keymap: &osbuild.X11KeymapOptions{ - Layouts: []string{"us"}, - }, - })) + } else if imageConfig.Keyboard != nil { + p.AddStage(osbuild.NewKeymapStage(imageConfig.Keyboard)) } if hostname := c.GetHostname(); hostname != nil { @@ -217,25 +213,13 @@ func ec2BaseTreePipeline( if timezone != nil { p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone})) } else { - p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: "UTC"})) + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: imageConfig.Timezone})) } if len(ntpServers) > 0 { p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) - } else { - p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{ - Servers: []osbuild.ChronyConfigServer{ - { - Hostname: "169.254.169.123", - Prefer: common.BoolToPtr(true), - Iburst: common.BoolToPtr(true), - Minpoll: common.IntToPtr(4), - Maxpoll: common.IntToPtr(4), - }, - }, - // empty string will remove any occurrences of the option from the configuration - LeapsecTz: common.StringToPtr(""), - })) + } else if imageConfig.TimeSynchronization != nil { + p.AddStage(osbuild.NewChronyStage(imageConfig.TimeSynchronization)) } if groups := c.GetGroups(); len(groups) > 0 { @@ -250,14 +234,25 @@ func ec2BaseTreePipeline( p.AddStage(osbuild.NewUsersStage(userOptions)) } - if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { - p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) + if services := c.GetServices(); services != nil || imageConfig.EnabledServices != nil || + imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != "" { + p.AddStage(osbuild.NewSystemdStage(systemdStageOptions( + imageConfig.EnabledServices, + imageConfig.DisabledServices, + services, + imageConfig.DefaultTarget, + ))) } if firewall := c.GetFirewall(); firewall != nil { p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) } + for _, sysconfigConfig := range imageConfig.Sysconfig { + p.AddStage(osbuild.NewSysconfigStage(sysconfigConfig)) + } + // COMMON WITH osPipeline - END + p.AddStage(osbuild.NewSystemdLogindStage(&osbuild.SystemdLogindStageOptions{ Filename: "00-getty-fixes.conf", Config: osbuild.SystemdLogindConfigDropin{ @@ -268,30 +263,6 @@ func ec2BaseTreePipeline( }, })) - p.AddStage(osbuild.NewSysconfigStage(&osbuild.SysconfigStageOptions{ - Kernel: &osbuild.SysconfigKernelOptions{ - UpdateDefault: true, - DefaultKernel: "kernel", - }, - Network: &osbuild.SysconfigNetworkOptions{ - Networking: true, - NoZeroConf: true, - }, - NetworkScripts: &osbuild.NetworkScriptsOptions{ - IfcfgFiles: map[string]osbuild.IfcfgFile{ - "eth0": { - Device: "eth0", - Bootproto: osbuild.IfcfgBootprotoDHCP, - OnBoot: common.BoolToPtr(true), - Type: osbuild.IfcfgTypeEthernet, - UserCtl: common.BoolToPtr(true), - PeerDNS: common.BoolToPtr(true), - IPv6Init: common.BoolToPtr(false), - }, - }, - }, - })) - p.AddStage(osbuild.NewCloudInitStage(&osbuild.CloudInitStageOptions{ Filename: "00-rhel-default-user.cfg", Config: osbuild.CloudInitConfigFile{ @@ -332,7 +303,7 @@ func ec2BaseTreePipeline( Profile: "sssd", })) - if isRHEL { + if t.arch.distro.isRHEL() { if options.Subscription != nil { commands := []string{ fmt.Sprintf("/usr/sbin/subscription-manager register --org=%s --activationkey=%s --serverurl %s --baseurl %s", options.Subscription.Organization, options.Subscription.ActivationKey, options.Subscription.ServerUrl, options.Subscription.BaseUrl), @@ -375,11 +346,10 @@ func ec2BaseTreePipeline( return p, nil } -func ec2X86_64BaseTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, - c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, - defaultTarget string, withRHUI, isRHEL bool, pt *disk.PartitionTable) (*osbuild.Pipeline, error) { +func ec2X86_64BaseTreePipeline(t *imageType, repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, + c *blueprint.Customizations, options distro.ImageOptions, withRHUI bool, pt *disk.PartitionTable) (*osbuild.Pipeline, error) { - treePipeline, err := ec2BaseTreePipeline(repos, packages, bpPackages, c, options, enabledServices, disabledServices, defaultTarget, withRHUI, isRHEL, pt) + treePipeline, err := ec2BaseTreePipeline(t, repos, packages, bpPackages, c, options, withRHUI, pt) if err != nil { return nil, err } @@ -415,10 +385,10 @@ func ec2CommonPipelines(t *imageType, customizations *blueprint.Customizations, switch arch := t.arch.Name(); arch { // rhel-ec2-x86_64, rhel-ha-ec2 case distro.X86_64ArchName: - treePipeline, err = ec2X86_64BaseTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, withRHUI, t.arch.distro.isRHEL(), &partitionTable) + treePipeline, err = ec2X86_64BaseTreePipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, withRHUI, &partitionTable) // rhel-ec2-aarch64 case distro.Aarch64ArchName: - treePipeline, err = ec2BaseTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, withRHUI, t.arch.distro.isRHEL(), &partitionTable) + treePipeline, err = ec2BaseTreePipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, withRHUI, &partitionTable) default: return nil, fmt.Errorf("ec2CommonPipelines: unsupported image architecture: %q", arch) } @@ -454,7 +424,7 @@ func ec2SapPipelines(t *imageType, customizations *blueprint.Customizations, opt switch arch := t.arch.Name(); arch { // rhel-sap-ec2 case distro.X86_64ArchName: - treePipeline, err = ec2X86_64BaseTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, withRHUI, t.arch.distro.isRHEL(), &partitionTable) + treePipeline, err = ec2X86_64BaseTreePipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, withRHUI, &partitionTable) default: return nil, fmt.Errorf("ec2SapPipelines: unsupported image architecture: %q", arch) } @@ -619,7 +589,7 @@ func tarPipelines(t *imageType, customizations *blueprint.Customizations, option pipelines := make([]osbuild.Pipeline, 0) pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner)) - treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, nil) + treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, nil) if err != nil { return nil, err } @@ -662,7 +632,7 @@ func tarInstallerPipelines(t *imageType, customizations *blueprint.Customization pipelines := make([]osbuild.Pipeline, 0) pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner)) - treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, nil) + treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, nil) if err != nil { return nil, err } @@ -700,7 +670,7 @@ func edgeCorePipelines(t *imageType, customizations *blueprint.Customizations, o pipelines := make([]osbuild.Pipeline, 0) pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner)) - treePipeline, err := ostreeTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) + treePipeline, err := ostreeTreePipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options) if err != nil { return nil, err } @@ -789,34 +759,39 @@ func buildPipeline(repos []rpmmd.RepoConfig, buildPackageSpecs []rpmmd.PackageSp return p } -func osPipeline(repos []rpmmd.RepoConfig, +func osPipeline(t *imageType, + repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, - enabledServices, disabledServices []string, - defaultTarget string, pt *disk.PartitionTable) (*osbuild.Pipeline, error) { + imageConfig := t.getDefaultImageConfig() p := new(osbuild.Pipeline) p.Name = "os" p.Build = "name:build" packages = append(packages, bpPackages...) p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) + // If the /boot is on a separate partition, the prefix for the BLS stage must be "" if pt == nil || pt.BootPartition() == nil { p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{})) } else { p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{Prefix: common.StringToPtr("")})) } + language, keyboard := c.GetPrimaryLocale() if language != nil { p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) } else { - p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: imageConfig.Locale})) } if keyboard != nil { p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard})) + } else if imageConfig.Keyboard != nil { + p.AddStage(osbuild.NewKeymapStage(imageConfig.Keyboard)) } + if hostname := c.GetHostname(); hostname != nil { p.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: *hostname})) } @@ -825,11 +800,13 @@ func osPipeline(repos []rpmmd.RepoConfig, if timezone != nil { p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone})) } else { - p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: "America/New_York"})) + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: imageConfig.Timezone})) } if len(ntpServers) > 0 { p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) + } else if imageConfig.TimeSynchronization != nil { + p.AddStage(osbuild.NewChronyStage(imageConfig.TimeSynchronization)) } if groups := c.GetGroups(); len(groups) > 0 { @@ -844,25 +821,23 @@ func osPipeline(repos []rpmmd.RepoConfig, p.AddStage(osbuild.NewUsersStage(userOptions)) } - if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { - p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) + if services := c.GetServices(); services != nil || imageConfig.EnabledServices != nil || + imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != "" { + p.AddStage(osbuild.NewSystemdStage(systemdStageOptions( + imageConfig.EnabledServices, + imageConfig.DisabledServices, + services, + imageConfig.DefaultTarget, + ))) } if firewall := c.GetFirewall(); firewall != nil { p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) } - // These are the current defaults for the sysconfig stage. This can be changed to be image type exclusive if different configs are needed. - p.AddStage(osbuild.NewSysconfigStage(&osbuild.SysconfigStageOptions{ - Kernel: &osbuild.SysconfigKernelOptions{ - UpdateDefault: true, - DefaultKernel: "kernel", - }, - Network: &osbuild.SysconfigNetworkOptions{ - Networking: true, - NoZeroConf: true, - }, - })) + for _, sysconfigConfig := range imageConfig.Sysconfig { + p.AddStage(osbuild.NewSysconfigStage(sysconfigConfig)) + } if options.Subscription != nil { commands := []string{ @@ -881,7 +856,8 @@ func osPipeline(repos []rpmmd.RepoConfig, return p, nil } -func ostreeTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, defaultTarget string) (*osbuild.Pipeline, error) { +func ostreeTreePipeline(t *imageType, repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions) (*osbuild.Pipeline, error) { + imageConfig := t.getDefaultImageConfig() p := new(osbuild.Pipeline) p.Name = "ostree-tree" p.Build = "name:build" @@ -894,15 +870,19 @@ func ostreeTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{})) + language, keyboard := c.GetPrimaryLocale() if language != nil { p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) } else { - p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: imageConfig.Locale})) } if keyboard != nil { p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard})) + } else if imageConfig.Keyboard != nil { + p.AddStage(osbuild.NewKeymapStage(imageConfig.Keyboard)) } + if hostname := c.GetHostname(); hostname != nil { p.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: *hostname})) } @@ -911,11 +891,13 @@ func ostreeTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, if timezone != nil { p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone})) } else { - p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: "America/New_York"})) + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: imageConfig.Timezone})) } if len(ntpServers) > 0 { p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) + } else if imageConfig.TimeSynchronization != nil { + p.AddStage(osbuild.NewChronyStage(imageConfig.TimeSynchronization)) } if groups := c.GetGroups(); len(groups) > 0 { @@ -931,25 +913,23 @@ func ostreeTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, p.AddStage(osbuild.NewFirstBootStage(usersFirstBootOptions(userOptions))) } - if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { - p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) + if services := c.GetServices(); services != nil || imageConfig.EnabledServices != nil || + imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != "" { + p.AddStage(osbuild.NewSystemdStage(systemdStageOptions( + imageConfig.EnabledServices, + imageConfig.DisabledServices, + services, + imageConfig.DefaultTarget, + ))) } if firewall := c.GetFirewall(); firewall != nil { p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) } - // These are the current defaults for the sysconfig stage. This can be changed to be image type exclusive if different configs are needed. - p.AddStage(osbuild.NewSysconfigStage(&osbuild.SysconfigStageOptions{ - Kernel: &osbuild.SysconfigKernelOptions{ - UpdateDefault: true, - DefaultKernel: "kernel", - }, - Network: &osbuild.SysconfigNetworkOptions{ - Networking: true, - NoZeroConf: true, - }, - })) + for _, sysconfigConfig := range imageConfig.Sysconfig { + p.AddStage(osbuild.NewSysconfigStage(sysconfigConfig)) + } if options.Subscription != nil { commands := []string{ @@ -975,6 +955,7 @@ func ostreeTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, })) return p, nil } + func ostreeCommitPipeline(options distro.ImageOptions, osVersion string) *osbuild.Pipeline { p := new(osbuild.Pipeline) p.Name = "ostree-commit" diff --git a/test/data/manifests/rhel_90-aarch64-ami-boot.json b/test/data/manifests/rhel_90-aarch64-ami-boot.json index d4ed9dc67..8712da5f0 100644 --- a/test/data/manifests/rhel_90-aarch64-ami-boot.json +++ b/test/data/manifests/rhel_90-aarch64-ami-boot.json @@ -876,17 +876,6 @@ "default_target": "multi-user.target" } }, - { - "type": "org.osbuild.systemd-logind", - "options": { - "filename": "00-getty-fixes.conf", - "config": { - "Login": { - "NAutoVTs": 0 - } - } - } - }, { "type": "org.osbuild.sysconfig", "options": { @@ -913,6 +902,17 @@ } } }, + { + "type": "org.osbuild.systemd-logind", + "options": { + "filename": "00-getty-fixes.conf", + "config": { + "Login": { + "NAutoVTs": 0 + } + } + } + }, { "type": "org.osbuild.cloud-init", "options": { diff --git a/test/data/manifests/rhel_90-aarch64-ec2-boot.json b/test/data/manifests/rhel_90-aarch64-ec2-boot.json index 8791d3ca1..65f078d43 100644 --- a/test/data/manifests/rhel_90-aarch64-ec2-boot.json +++ b/test/data/manifests/rhel_90-aarch64-ec2-boot.json @@ -887,17 +887,6 @@ "default_target": "multi-user.target" } }, - { - "type": "org.osbuild.systemd-logind", - "options": { - "filename": "00-getty-fixes.conf", - "config": { - "Login": { - "NAutoVTs": 0 - } - } - } - }, { "type": "org.osbuild.sysconfig", "options": { @@ -924,6 +913,17 @@ } } }, + { + "type": "org.osbuild.systemd-logind", + "options": { + "filename": "00-getty-fixes.conf", + "config": { + "Login": { + "NAutoVTs": 0 + } + } + } + }, { "type": "org.osbuild.cloud-init", "options": { diff --git a/test/data/manifests/rhel_90-x86_64-ami-boot.json b/test/data/manifests/rhel_90-x86_64-ami-boot.json index e7bd5cac9..7cb8087e6 100644 --- a/test/data/manifests/rhel_90-x86_64-ami-boot.json +++ b/test/data/manifests/rhel_90-x86_64-ami-boot.json @@ -859,17 +859,6 @@ "default_target": "multi-user.target" } }, - { - "type": "org.osbuild.systemd-logind", - "options": { - "filename": "00-getty-fixes.conf", - "config": { - "Login": { - "NAutoVTs": 0 - } - } - } - }, { "type": "org.osbuild.sysconfig", "options": { @@ -896,6 +885,17 @@ } } }, + { + "type": "org.osbuild.systemd-logind", + "options": { + "filename": "00-getty-fixes.conf", + "config": { + "Login": { + "NAutoVTs": 0 + } + } + } + }, { "type": "org.osbuild.cloud-init", "options": { diff --git a/test/data/manifests/rhel_90-x86_64-ec2-boot.json b/test/data/manifests/rhel_90-x86_64-ec2-boot.json index aba1aa8a8..c10f2ac4b 100644 --- a/test/data/manifests/rhel_90-x86_64-ec2-boot.json +++ b/test/data/manifests/rhel_90-x86_64-ec2-boot.json @@ -872,17 +872,6 @@ "default_target": "multi-user.target" } }, - { - "type": "org.osbuild.systemd-logind", - "options": { - "filename": "00-getty-fixes.conf", - "config": { - "Login": { - "NAutoVTs": 0 - } - } - } - }, { "type": "org.osbuild.sysconfig", "options": { @@ -909,6 +898,17 @@ } } }, + { + "type": "org.osbuild.systemd-logind", + "options": { + "filename": "00-getty-fixes.conf", + "config": { + "Login": { + "NAutoVTs": 0 + } + } + } + }, { "type": "org.osbuild.cloud-init", "options": { diff --git a/test/data/manifests/rhel_90-x86_64-ec2_ha-boot.json b/test/data/manifests/rhel_90-x86_64-ec2_ha-boot.json index aed55bd96..39ec872bc 100644 --- a/test/data/manifests/rhel_90-x86_64-ec2_ha-boot.json +++ b/test/data/manifests/rhel_90-x86_64-ec2_ha-boot.json @@ -1066,17 +1066,6 @@ "default_target": "multi-user.target" } }, - { - "type": "org.osbuild.systemd-logind", - "options": { - "filename": "00-getty-fixes.conf", - "config": { - "Login": { - "NAutoVTs": 0 - } - } - } - }, { "type": "org.osbuild.sysconfig", "options": { @@ -1103,6 +1092,17 @@ } } }, + { + "type": "org.osbuild.systemd-logind", + "options": { + "filename": "00-getty-fixes.conf", + "config": { + "Login": { + "NAutoVTs": 0 + } + } + } + }, { "type": "org.osbuild.cloud-init", "options": { diff --git a/test/data/manifests/rhel_90-x86_64-ec2_sap-boot.json b/test/data/manifests/rhel_90-x86_64-ec2_sap-boot.json index 07efe56b3..3a9ffdcdb 100644 --- a/test/data/manifests/rhel_90-x86_64-ec2_sap-boot.json +++ b/test/data/manifests/rhel_90-x86_64-ec2_sap-boot.json @@ -1120,17 +1120,6 @@ "default_target": "multi-user.target" } }, - { - "type": "org.osbuild.systemd-logind", - "options": { - "filename": "00-getty-fixes.conf", - "config": { - "Login": { - "NAutoVTs": 0 - } - } - } - }, { "type": "org.osbuild.sysconfig", "options": { @@ -1157,6 +1146,17 @@ } } }, + { + "type": "org.osbuild.systemd-logind", + "options": { + "filename": "00-getty-fixes.conf", + "config": { + "Login": { + "NAutoVTs": 0 + } + } + } + }, { "type": "org.osbuild.cloud-init", "options": {