YUMConfig is supported in ImageConfig for RHEL 7. We now copy the options over to OSCustomizations and create the stage when necessary.
668 lines
20 KiB
Go
668 lines
20 KiB
Go
package manifest
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/common"
|
|
"github.com/osbuild/osbuild-composer/internal/container"
|
|
"github.com/osbuild/osbuild-composer/internal/disk"
|
|
"github.com/osbuild/osbuild-composer/internal/distro"
|
|
"github.com/osbuild/osbuild-composer/internal/environment"
|
|
"github.com/osbuild/osbuild-composer/internal/osbuild"
|
|
"github.com/osbuild/osbuild-composer/internal/ostree"
|
|
"github.com/osbuild/osbuild-composer/internal/platform"
|
|
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
|
"github.com/osbuild/osbuild-composer/internal/users"
|
|
"github.com/osbuild/osbuild-composer/internal/workload"
|
|
)
|
|
|
|
// OSCustomizations encapsulates all configuration applied to the base
|
|
// operating system independently of where and how it is integrated and what
|
|
// workload it is running.
|
|
// TODO: move out kernel/bootloader/cloud-init/... to other
|
|
//
|
|
// abstractions, this should ideally only contain things that
|
|
// can always be applied.
|
|
type OSCustomizations struct {
|
|
|
|
// Packages to install in addition to the ones required by the
|
|
// pipeline.
|
|
ExtraBasePackages []string
|
|
|
|
// Packages to exclude from the base package set. This is useful in
|
|
// case of weak dependencies, comps groups, or where multiple packages
|
|
// can satisfy a dependency. Must not conflict with the included base
|
|
// package set.
|
|
ExcludeBasePackages []string
|
|
|
|
// Additional repos to install the base packages from.
|
|
ExtraBaseRepos []rpmmd.RepoConfig
|
|
|
|
// Containers to embed in the image
|
|
Containers []container.Spec
|
|
|
|
// KernelName indicates that a kernel is installed, and names the kernel
|
|
// package.
|
|
KernelName string
|
|
|
|
// KernelOptionsAppend are appended to the kernel commandline
|
|
KernelOptionsAppend []string
|
|
|
|
// KernelOptionsBootloader controls whether kernel command line options
|
|
// should be specified in the bootloader configuration. Otherwise they are
|
|
// specified in /etc/kernel/cmdline (default).
|
|
// This should only be used for RHEL 8 and CentOS 8 images that use grub
|
|
// (non s390x). Newer releases (9+) should keep this disabled.
|
|
KernelOptionsBootloader bool
|
|
|
|
GPGKeyFiles []string
|
|
Language string
|
|
Keyboard *string
|
|
X11KeymapLayouts []string
|
|
Hostname string
|
|
Timezone string
|
|
EnabledServices []string
|
|
DisabledServices []string
|
|
DefaultTarget string
|
|
|
|
// SELinux policy, when set it enables the labeling of the tree with the
|
|
// selected profile
|
|
SElinux string
|
|
|
|
SELinuxForceRelabel *bool
|
|
|
|
// Do not install documentation
|
|
ExcludeDocs bool
|
|
|
|
Groups []users.Group
|
|
Users []users.User
|
|
// TODO: drop osbuild types from the API
|
|
Firewall *osbuild.FirewallStageOptions
|
|
Grub2Config *osbuild.GRUB2Config
|
|
Sysconfig []*osbuild.SysconfigStageOptions
|
|
SystemdLogind []*osbuild.SystemdLogindStageOptions
|
|
CloudInit []*osbuild.CloudInitStageOptions
|
|
Modprobe []*osbuild.ModprobeStageOptions
|
|
DracutConf []*osbuild.DracutConfStageOptions
|
|
SystemdUnit []*osbuild.SystemdUnitStageOptions
|
|
Authselect *osbuild.AuthselectStageOptions
|
|
SELinuxConfig *osbuild.SELinuxConfigStageOptions
|
|
Tuned *osbuild.TunedStageOptions
|
|
Tmpfilesd []*osbuild.TmpfilesdStageOptions
|
|
PamLimitsConf []*osbuild.PamLimitsConfStageOptions
|
|
Sysctld []*osbuild.SysctldStageOptions
|
|
DNFConfig []*osbuild.DNFConfigStageOptions
|
|
DNFAutomaticConfig *osbuild.DNFAutomaticConfigStageOptions
|
|
YUMConfig *osbuild.YumConfigStageOptions
|
|
YUMRepos []*osbuild.YumReposStageOptions
|
|
SshdConfig *osbuild.SshdConfigStageOptions
|
|
GCPGuestAgentConfig *osbuild.GcpGuestAgentConfigOptions
|
|
AuthConfig *osbuild.AuthconfigStageOptions
|
|
PwQuality *osbuild.PwqualityConfStageOptions
|
|
OpenSCAPConfig *osbuild.OscapRemediationStageOptions
|
|
NTPServers []osbuild.ChronyConfigServer
|
|
WAAgentConfig *osbuild.WAAgentConfStageOptions
|
|
UdevRules *osbuild.UdevRulesStageOptions
|
|
LeapSecTZ *string
|
|
FactAPIType string
|
|
|
|
Subscription *distro.SubscriptionImageOptions
|
|
RHSMConfig map[distro.RHSMSubscriptionStatus]*osbuild.RHSMStageOptions
|
|
}
|
|
|
|
// OS represents the filesystem tree of the target image. This roughly
|
|
// corresponds to the root filesystem once an instance of the image is running.
|
|
type OS struct {
|
|
Base
|
|
// Customizations to apply to the base OS
|
|
OSCustomizations
|
|
// Environment the system will run in
|
|
Environment environment.Environment
|
|
// Workload to install on top of the base system
|
|
Workload workload.Workload
|
|
// Ref of ostree commit, if empty the tree cannot be in an ostree commit
|
|
OSTreeRef string
|
|
// OSTree parent spec, if nil the new commit (if applicable) will have no parent
|
|
OSTreeParent *ostree.CommitSpec
|
|
// Partition table, if nil the tree cannot be put on a partitioned disk
|
|
PartitionTable *disk.PartitionTable
|
|
|
|
repos []rpmmd.RepoConfig
|
|
packageSpecs []rpmmd.PackageSpec
|
|
platform platform.Platform
|
|
kernelVer string
|
|
|
|
// NoBLS configures the image bootloader with traditional menu entries
|
|
// instead of BLS. Required for legacy systems like RHEL 7.
|
|
NoBLS bool
|
|
OSProduct string
|
|
OSVersion string
|
|
OSNick string
|
|
}
|
|
|
|
// NewOS creates a new OS pipeline. build is the build pipeline to use for
|
|
// building the OS pipeline. platform is the target platform for the final
|
|
// image. repos are the repositories to install RPMs from.
|
|
func NewOS(m *Manifest,
|
|
buildPipeline *Build,
|
|
platform platform.Platform,
|
|
repos []rpmmd.RepoConfig) *OS {
|
|
p := &OS{
|
|
Base: NewBase(m, "os", buildPipeline),
|
|
repos: repos,
|
|
platform: platform,
|
|
}
|
|
buildPipeline.addDependent(p)
|
|
m.addPipeline(p)
|
|
return p
|
|
}
|
|
|
|
func (p *OS) getPackageSetChain() []rpmmd.PackageSet {
|
|
packages := p.platform.GetPackages()
|
|
|
|
if p.KernelName != "" {
|
|
packages = append(packages, p.KernelName)
|
|
}
|
|
|
|
// If we have a logical volume we need to include the lvm2 package.
|
|
// OSTree-based images (commit and container) aren't bootable images and
|
|
// don't have partition tables.
|
|
if p.PartitionTable != nil && p.OSTreeRef == "" {
|
|
packages = append(packages, p.PartitionTable.GetBuildPackages()...)
|
|
}
|
|
|
|
if p.Environment != nil {
|
|
packages = append(packages, p.Environment.GetPackages()...)
|
|
}
|
|
|
|
if len(p.NTPServers) > 0 {
|
|
packages = append(packages, "chrony")
|
|
}
|
|
|
|
if p.SElinux != "" {
|
|
packages = append(packages, fmt.Sprintf("selinux-policy-%s", p.SElinux))
|
|
}
|
|
|
|
if p.OpenSCAPConfig != nil {
|
|
packages = append(packages, "openscap-scanner", "scap-security-guide")
|
|
}
|
|
|
|
chain := []rpmmd.PackageSet{
|
|
{
|
|
Include: append(packages, p.ExtraBasePackages...),
|
|
Exclude: p.ExcludeBasePackages,
|
|
Repositories: append(p.repos, p.ExtraBaseRepos...),
|
|
},
|
|
}
|
|
|
|
if p.Workload != nil {
|
|
workloadPackages := p.Workload.GetPackages()
|
|
if len(workloadPackages) > 0 {
|
|
chain = append(chain, rpmmd.PackageSet{
|
|
Include: workloadPackages,
|
|
Repositories: append(p.repos, p.Workload.GetRepos()...),
|
|
})
|
|
}
|
|
}
|
|
|
|
return chain
|
|
}
|
|
|
|
func (p *OS) getBuildPackages() []string {
|
|
packages := p.platform.GetBuildPackages()
|
|
if p.PartitionTable != nil {
|
|
packages = append(packages, p.PartitionTable.GetBuildPackages()...)
|
|
}
|
|
packages = append(packages, "rpm")
|
|
if p.OSTreeRef != "" {
|
|
packages = append(packages, "rpm-ostree")
|
|
}
|
|
if p.SElinux != "" {
|
|
packages = append(packages, "policycoreutils", fmt.Sprintf("selinux-policy-%s", p.SElinux))
|
|
}
|
|
if len(p.CloudInit) > 0 {
|
|
packages = append(packages, "python3-pyyaml")
|
|
}
|
|
if len(p.DNFConfig) > 0 || len(p.RHSMConfig) > 0 {
|
|
packages = append(packages, "python3-iniparse")
|
|
}
|
|
|
|
if len(p.OSCustomizations.Containers) > 0 {
|
|
if p.OSTreeRef != "" {
|
|
packages = append(packages, "python3-toml")
|
|
}
|
|
packages = append(packages, "skopeo")
|
|
}
|
|
|
|
return packages
|
|
}
|
|
|
|
func (p *OS) getOSTreeCommits() []ostree.CommitSpec {
|
|
if p.OSTreeParent == nil {
|
|
return nil
|
|
}
|
|
return []ostree.CommitSpec{*p.OSTreeParent}
|
|
}
|
|
|
|
func (p *OS) getPackageSpecs() []rpmmd.PackageSpec {
|
|
return p.packageSpecs
|
|
}
|
|
|
|
func (p *OS) getContainerSpecs() []container.Spec {
|
|
return p.Containers
|
|
}
|
|
|
|
func (p *OS) serializeStart(packages []rpmmd.PackageSpec) {
|
|
if len(p.packageSpecs) > 0 {
|
|
panic("double call to serializeStart()")
|
|
}
|
|
p.packageSpecs = packages
|
|
if p.KernelName != "" {
|
|
p.kernelVer = rpmmd.GetVerStrFromPackageSpecListPanic(p.packageSpecs, p.KernelName)
|
|
}
|
|
}
|
|
|
|
func (p *OS) serializeEnd() {
|
|
if len(p.packageSpecs) == 0 {
|
|
panic("serializeEnd() call when serialization not in progress")
|
|
}
|
|
p.kernelVer = ""
|
|
p.packageSpecs = nil
|
|
}
|
|
|
|
func (p *OS) serialize() osbuild.Pipeline {
|
|
if len(p.packageSpecs) == 0 {
|
|
panic("serialization not started")
|
|
}
|
|
|
|
pipeline := p.Base.serialize()
|
|
|
|
if p.OSTreeRef != "" && p.OSTreeParent != nil {
|
|
pipeline.AddStage(osbuild.NewOSTreePasswdStage("org.osbuild.source", p.OSTreeParent.Checksum))
|
|
}
|
|
|
|
rpmOptions := osbuild.NewRPMStageOptions(p.repos)
|
|
if p.ExcludeDocs {
|
|
if rpmOptions.Exclude == nil {
|
|
rpmOptions.Exclude = &osbuild.Exclude{}
|
|
}
|
|
rpmOptions.Exclude.Docs = true
|
|
}
|
|
rpmOptions.GPGKeysFromTree = p.GPGKeyFiles
|
|
if p.OSTreeRef != "" {
|
|
rpmOptions.OSTreeBooted = common.ToPtr(true)
|
|
rpmOptions.DBPath = "/usr/share/rpm"
|
|
}
|
|
pipeline.AddStage(osbuild.NewRPMStage(rpmOptions, osbuild.NewRpmStageSourceFilesInputs(p.packageSpecs)))
|
|
|
|
if !p.NoBLS {
|
|
// If the /boot is on a separate partition, the prefix for the BLS stage must be ""
|
|
if p.PartitionTable == nil || p.PartitionTable.FindMountable("/boot") == nil {
|
|
pipeline.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{}))
|
|
} else {
|
|
pipeline.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{Prefix: common.ToPtr("")}))
|
|
}
|
|
}
|
|
|
|
if len(p.Containers) > 0 {
|
|
images := osbuild.NewContainersInputForSources(p.Containers)
|
|
|
|
var storagePath string
|
|
|
|
// OSTree commits do not include data in `/var` since that is tied to the
|
|
// deployment, rather than the commit. Therefore the containers need to be
|
|
// stored in a different location, like `/usr/share`, and the container
|
|
// storage engine configured accordingly.
|
|
if p.OSTreeRef != "" {
|
|
storagePath = "/usr/share/containers/storage"
|
|
storageConf := "/etc/containers/storage.conf"
|
|
|
|
containerStoreOpts := osbuild.NewContainerStorageOptions(storageConf, storagePath)
|
|
pipeline.AddStage(osbuild.NewContainersStorageConfStage(containerStoreOpts))
|
|
}
|
|
|
|
skopeo := osbuild.NewSkopeoStage(images, storagePath)
|
|
pipeline.AddStage(skopeo)
|
|
}
|
|
|
|
pipeline.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: p.Language}))
|
|
|
|
if p.Keyboard != nil {
|
|
keymapOptions := &osbuild.KeymapStageOptions{Keymap: *p.Keyboard}
|
|
if len(p.X11KeymapLayouts) > 0 {
|
|
keymapOptions.X11Keymap = &osbuild.X11KeymapOptions{Layouts: p.X11KeymapLayouts}
|
|
}
|
|
pipeline.AddStage(osbuild.NewKeymapStage(keymapOptions))
|
|
}
|
|
|
|
if p.Hostname != "" {
|
|
pipeline.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: p.Hostname}))
|
|
}
|
|
pipeline.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: p.Timezone}))
|
|
|
|
if len(p.NTPServers) > 0 {
|
|
chronyOptions := &osbuild.ChronyStageOptions{Servers: p.NTPServers}
|
|
if p.LeapSecTZ != nil {
|
|
chronyOptions.LeapsecTz = p.LeapSecTZ
|
|
}
|
|
pipeline.AddStage(osbuild.NewChronyStage(chronyOptions))
|
|
}
|
|
|
|
if len(p.Groups) > 0 {
|
|
pipeline.AddStage(osbuild.GenGroupsStage(p.Groups))
|
|
}
|
|
|
|
if len(p.Users) > 0 {
|
|
if p.OSTreeRef != "" {
|
|
// for ostree, writing the key during user creation is
|
|
// redundant and can cause issues so create users without keys
|
|
// and write them on first boot
|
|
usersStageSansKeys, err := osbuild.GenUsersStage(p.Users, true)
|
|
if err != nil {
|
|
// TODO: move encryption into weldr
|
|
panic("password encryption failed")
|
|
}
|
|
pipeline.AddStage(usersStageSansKeys)
|
|
pipeline.AddStage(osbuild.NewFirstBootStage(usersFirstBootOptions(p.Users)))
|
|
} else {
|
|
usersStage, err := osbuild.GenUsersStage(p.Users, false)
|
|
if err != nil {
|
|
// TODO: move encryption into weldr
|
|
panic("password encryption failed")
|
|
}
|
|
pipeline.AddStage(usersStage)
|
|
}
|
|
}
|
|
|
|
enabledServices := []string{}
|
|
disabledServices := []string{}
|
|
enabledServices = append(enabledServices, p.EnabledServices...)
|
|
disabledServices = append(disabledServices, p.DisabledServices...)
|
|
if p.Environment != nil {
|
|
enabledServices = append(enabledServices, p.Environment.GetServices()...)
|
|
}
|
|
if p.Workload != nil {
|
|
enabledServices = append(enabledServices, p.Workload.GetServices()...)
|
|
disabledServices = append(disabledServices, p.Workload.GetDisabledServices()...)
|
|
}
|
|
if len(enabledServices) != 0 ||
|
|
len(disabledServices) != 0 || p.DefaultTarget != "" {
|
|
pipeline.AddStage(osbuild.NewSystemdStage(&osbuild.SystemdStageOptions{
|
|
EnabledServices: enabledServices,
|
|
DisabledServices: disabledServices,
|
|
DefaultTarget: p.DefaultTarget,
|
|
}))
|
|
}
|
|
|
|
if p.Firewall != nil {
|
|
pipeline.AddStage(osbuild.NewFirewallStage(p.Firewall))
|
|
}
|
|
|
|
for _, sysconfigConfig := range p.Sysconfig {
|
|
pipeline.AddStage(osbuild.NewSysconfigStage(sysconfigConfig))
|
|
}
|
|
|
|
for _, systemdLogindConfig := range p.SystemdLogind {
|
|
pipeline.AddStage(osbuild.NewSystemdLogindStage(systemdLogindConfig))
|
|
}
|
|
|
|
for _, cloudInitConfig := range p.CloudInit {
|
|
pipeline.AddStage(osbuild.NewCloudInitStage(cloudInitConfig))
|
|
}
|
|
|
|
for _, modprobeConfig := range p.Modprobe {
|
|
pipeline.AddStage(osbuild.NewModprobeStage(modprobeConfig))
|
|
}
|
|
|
|
for _, dracutConfConfig := range p.DracutConf {
|
|
pipeline.AddStage(osbuild.NewDracutConfStage(dracutConfConfig))
|
|
}
|
|
|
|
for _, systemdUnitConfig := range p.SystemdUnit {
|
|
pipeline.AddStage(osbuild.NewSystemdUnitStage(systemdUnitConfig))
|
|
}
|
|
|
|
if p.Authselect != nil {
|
|
pipeline.AddStage(osbuild.NewAuthselectStage(p.Authselect))
|
|
}
|
|
|
|
if p.SELinuxConfig != nil {
|
|
pipeline.AddStage(osbuild.NewSELinuxConfigStage(p.SELinuxConfig))
|
|
}
|
|
|
|
if p.Tuned != nil {
|
|
pipeline.AddStage(osbuild.NewTunedStage(p.Tuned))
|
|
}
|
|
|
|
for _, tmpfilesdConfig := range p.Tmpfilesd {
|
|
pipeline.AddStage(osbuild.NewTmpfilesdStage(tmpfilesdConfig))
|
|
}
|
|
|
|
for _, pamLimitsConfConfig := range p.PamLimitsConf {
|
|
pipeline.AddStage(osbuild.NewPamLimitsConfStage(pamLimitsConfConfig))
|
|
}
|
|
|
|
for _, sysctldConfig := range p.Sysctld {
|
|
pipeline.AddStage(osbuild.NewSysctldStage(sysctldConfig))
|
|
}
|
|
|
|
for _, dnfConfig := range p.DNFConfig {
|
|
pipeline.AddStage(osbuild.NewDNFConfigStage(dnfConfig))
|
|
}
|
|
|
|
if p.DNFAutomaticConfig != nil {
|
|
pipeline.AddStage(osbuild.NewDNFAutomaticConfigStage(p.DNFAutomaticConfig))
|
|
}
|
|
|
|
for _, yumRepo := range p.YUMRepos {
|
|
pipeline.AddStage(osbuild.NewYumReposStage(yumRepo))
|
|
}
|
|
|
|
if p.YUMConfig != nil {
|
|
pipeline.AddStage(osbuild.NewYumConfigStage(p.YUMConfig))
|
|
}
|
|
|
|
if p.GCPGuestAgentConfig != nil {
|
|
pipeline.AddStage(osbuild.NewGcpGuestAgentConfigStage(p.GCPGuestAgentConfig))
|
|
}
|
|
|
|
if p.SshdConfig != nil {
|
|
pipeline.AddStage((osbuild.NewSshdConfigStage(p.SshdConfig)))
|
|
}
|
|
|
|
if p.AuthConfig != nil {
|
|
pipeline.AddStage(osbuild.NewAuthconfigStage(p.AuthConfig))
|
|
}
|
|
|
|
if p.PwQuality != nil {
|
|
pipeline.AddStage(osbuild.NewPwqualityConfStage(p.PwQuality))
|
|
}
|
|
|
|
if p.Subscription != nil {
|
|
commands := []string{
|
|
fmt.Sprintf("/usr/sbin/subscription-manager register --org=%s --activationkey=%s --serverurl %s --baseurl %s", p.Subscription.Organization, p.Subscription.ActivationKey, p.Subscription.ServerUrl, p.Subscription.BaseUrl),
|
|
}
|
|
if p.Subscription.Insights {
|
|
commands = append(commands, "/usr/bin/insights-client --register")
|
|
}
|
|
pipeline.AddStage(osbuild.NewFirstBootStage(&osbuild.FirstBootStageOptions{
|
|
Commands: commands,
|
|
WaitForNetwork: true,
|
|
}))
|
|
|
|
if rhsmConfig, exists := p.RHSMConfig[distro.RHSMConfigWithSubscription]; exists {
|
|
pipeline.AddStage(osbuild.NewRHSMStage(rhsmConfig))
|
|
}
|
|
} else {
|
|
if rhsmConfig, exists := p.RHSMConfig[distro.RHSMConfigNoSubscription]; exists {
|
|
pipeline.AddStage(osbuild.NewRHSMStage(rhsmConfig))
|
|
}
|
|
}
|
|
|
|
if waConfig := p.WAAgentConfig; waConfig != nil {
|
|
pipeline.AddStage(osbuild.NewWAAgentConfStage(waConfig))
|
|
}
|
|
|
|
if udevRules := p.UdevRules; udevRules != nil {
|
|
pipeline.AddStage(osbuild.NewUdevRulesStage(udevRules))
|
|
}
|
|
|
|
if pt := p.PartitionTable; pt != nil {
|
|
kernelOptions := osbuild.GenImageKernelOptions(p.PartitionTable)
|
|
kernelOptions = append(kernelOptions, p.KernelOptionsAppend...)
|
|
if !p.KernelOptionsBootloader {
|
|
pipeline = prependKernelCmdlineStage(pipeline, strings.Join(kernelOptions, " "), pt)
|
|
}
|
|
|
|
pipeline.AddStage(osbuild.NewFSTabStage(osbuild.NewFSTabStageOptions(pt)))
|
|
|
|
var bootloader *osbuild.Stage
|
|
switch p.platform.GetArch() {
|
|
case platform.ARCH_S390X:
|
|
bootloader = osbuild.NewZiplStage(new(osbuild.ZiplStageOptions))
|
|
default:
|
|
if p.NoBLS {
|
|
// BLS entries not supported: use grub2.legacy
|
|
id := "76a22bf4-f153-4541-b6c7-0332c0dfaeac"
|
|
product := osbuild.GRUB2Product{
|
|
Name: p.OSProduct,
|
|
Version: p.OSVersion,
|
|
Nick: p.OSNick,
|
|
}
|
|
|
|
rescueVer, _ := rpmmd.GetVerStrFromPackageSpecList(p.packageSpecs, "dracut-config-rescue")
|
|
hasRescue := rescueVer != ""
|
|
bootloader = osbuild.NewGrub2LegacyStage(
|
|
osbuild.NewGrub2LegacyStageOptions(
|
|
p.Grub2Config,
|
|
p.PartitionTable,
|
|
kernelOptions,
|
|
p.platform.GetBIOSPlatform(),
|
|
p.platform.GetUEFIVendor(),
|
|
osbuild.MakeGrub2MenuEntries(id, p.kernelVer, product, hasRescue),
|
|
),
|
|
)
|
|
} else {
|
|
options := osbuild.NewGrub2StageOptionsUnified(pt,
|
|
p.kernelVer,
|
|
p.platform.GetUEFIVendor() != "",
|
|
p.platform.GetBIOSPlatform(),
|
|
p.platform.GetUEFIVendor(), false)
|
|
if cfg := p.Grub2Config; cfg != nil {
|
|
// TODO: don't store Grub2Config in OSPipeline, making the overrides unnecessary
|
|
// grub2.Config.Default is owned and set by `NewGrub2StageOptionsUnified`
|
|
// and thus we need to preserve it
|
|
if options.Config != nil {
|
|
cfg.Default = options.Config.Default
|
|
}
|
|
|
|
options.Config = cfg
|
|
}
|
|
if p.KernelOptionsBootloader {
|
|
options.WriteCmdLine = nil
|
|
if options.UEFI != nil {
|
|
options.UEFI.Unified = false
|
|
}
|
|
options.KernelOptions = strings.Join(kernelOptions, " ")
|
|
}
|
|
bootloader = osbuild.NewGRUB2Stage(options)
|
|
}
|
|
}
|
|
|
|
pipeline.AddStage(bootloader)
|
|
}
|
|
|
|
if p.OpenSCAPConfig != nil {
|
|
pipeline.AddStage(osbuild.NewOscapRemediationStage(p.OpenSCAPConfig))
|
|
}
|
|
|
|
if p.FactAPIType != "" {
|
|
pipeline.AddStage(osbuild.NewRHSMFactsStage(&osbuild.RHSMFactsStageOptions{
|
|
Facts: osbuild.RHSMFacts{
|
|
ApiType: p.FactAPIType,
|
|
},
|
|
}))
|
|
}
|
|
|
|
if p.OSTreeRef != "" {
|
|
pipeline.AddStage(osbuild.NewSystemdJournaldStage(
|
|
&osbuild.SystemdJournaldStageOptions{
|
|
Filename: "10-persistent.conf",
|
|
Config: osbuild.SystemdJournaldConfigDropin{
|
|
Journal: osbuild.SystemdJournaldConfigJournalSection{
|
|
Storage: osbuild.StoragePresistent,
|
|
},
|
|
},
|
|
}))
|
|
}
|
|
|
|
if p.SElinux != "" {
|
|
pipeline.AddStage(osbuild.NewSELinuxStage(&osbuild.SELinuxStageOptions{
|
|
FileContexts: fmt.Sprintf("etc/selinux/%s/contexts/files/file_contexts", p.SElinux),
|
|
ForceAutorelabel: p.SELinuxForceRelabel,
|
|
}))
|
|
}
|
|
|
|
if p.OSTreeRef != "" {
|
|
pipeline.AddStage(osbuild.NewOSTreePrepTreeStage(&osbuild.OSTreePrepTreeStageOptions{
|
|
EtcGroupMembers: []string{
|
|
// NOTE: We may want to make this configurable.
|
|
"wheel", "docker",
|
|
},
|
|
}))
|
|
}
|
|
|
|
return pipeline
|
|
}
|
|
|
|
func prependKernelCmdlineStage(pipeline osbuild.Pipeline, kernelOptions string, pt *disk.PartitionTable) osbuild.Pipeline {
|
|
rootFs := pt.FindMountable("/")
|
|
if rootFs == nil {
|
|
panic("root filesystem must be defined for kernel-cmdline stage, this is a programming error")
|
|
}
|
|
rootFsUUID := rootFs.GetFSSpec().UUID
|
|
kernelStage := osbuild.NewKernelCmdlineStage(osbuild.NewKernelCmdlineStageOptions(rootFsUUID, kernelOptions))
|
|
pipeline.Stages = append([]*osbuild.Stage{kernelStage}, pipeline.Stages...)
|
|
return pipeline
|
|
}
|
|
|
|
func usersFirstBootOptions(users []users.User) *osbuild.FirstBootStageOptions {
|
|
cmds := make([]string, 0, 3*len(users)+2)
|
|
// workaround for creating authorized_keys file for user
|
|
// need to special case the root user, which has its home in a different place
|
|
varhome := filepath.Join("/var", "home")
|
|
roothome := filepath.Join("/var", "roothome")
|
|
|
|
for _, user := range users {
|
|
if user.Key != nil {
|
|
var home string
|
|
|
|
if user.Name == "root" {
|
|
home = roothome
|
|
} else {
|
|
home = filepath.Join(varhome, user.Name)
|
|
}
|
|
|
|
sshdir := filepath.Join(home, ".ssh")
|
|
|
|
cmds = append(cmds, fmt.Sprintf("mkdir -p %s", sshdir))
|
|
cmds = append(cmds, fmt.Sprintf("sh -c 'echo %q >> %q'", *user.Key, filepath.Join(sshdir, "authorized_keys")))
|
|
cmds = append(cmds, fmt.Sprintf("chown %s:%s -Rc %s", user.Name, user.Name, sshdir))
|
|
}
|
|
}
|
|
cmds = append(cmds, fmt.Sprintf("restorecon -rvF %s", varhome))
|
|
cmds = append(cmds, fmt.Sprintf("restorecon -rvF %s", roothome))
|
|
|
|
options := &osbuild.FirstBootStageOptions{
|
|
Commands: cmds,
|
|
WaitForNetwork: false,
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
func (p *OS) GetPlatform() platform.Platform {
|
|
return p.platform
|
|
}
|