Each pipeline constructor receives the full list of repositories and filters the list based on its own internal name.
720 lines
22 KiB
Go
720 lines
22 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/fsnode"
|
|
"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
|
|
|
|
// Custom directories and files to create in the image
|
|
Directories []*fsnode.Directory
|
|
Files []*fsnode.File
|
|
}
|
|
|
|
// 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 {
|
|
name := "os"
|
|
p := &OS{
|
|
Base: NewBase(m, name, buildPipeline),
|
|
repos: filterRepos(repos, name),
|
|
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")
|
|
}
|
|
|
|
// Make sure the right packages are included for subscriptions
|
|
// rhc always uses insights, and depends on subscription-manager
|
|
// non-rhc uses subscription-manager and optionally includes Insights
|
|
if p.Subscription != nil {
|
|
packages = append(packages, "subscription-manager")
|
|
if p.Subscription.Rhc {
|
|
packages = append(packages, "rhc", "insights-client")
|
|
} else if p.Subscription.Insights {
|
|
packages = append(packages, "insights-client")
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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 subscription settings are included there are 3 possible setups:
|
|
// - Register the system with rhc and enable Insights
|
|
// - Register with subscription-manager, no Insights or rhc
|
|
// - Register with subscription-manager and enable Insights, no rhc
|
|
if p.Subscription != nil {
|
|
var commands []string
|
|
if p.Subscription.Rhc {
|
|
// Use rhc for registration instead of subscription manager
|
|
commands = []string{fmt.Sprintf("/usr/bin/rhc connect -o=%s -a=%s --server %s", p.Subscription.Organization, p.Subscription.ActivationKey, p.Subscription.ServerUrl)}
|
|
|
|
// Always enable Insights when using rhc
|
|
commands = append(commands, "/usr/bin/insights-client --register")
|
|
} else {
|
|
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)}
|
|
|
|
// Insights is optional when using subscription-manager
|
|
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,
|
|
},
|
|
},
|
|
}))
|
|
}
|
|
|
|
// First create custom directories, because some of the custom files may depend on them
|
|
if len(p.Directories) > 0 {
|
|
pipeline.AddStages(osbuild.GenDirectoryNodesStages(p.Directories)...)
|
|
}
|
|
|
|
if len(p.Files) > 0 {
|
|
pipeline.AddStages(osbuild.GenFileNodesStages(p.Files)...)
|
|
}
|
|
|
|
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.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
|
|
}
|
|
|
|
func (p *OS) getInline() []string {
|
|
inlineData := []string{}
|
|
|
|
// inline data for custom files
|
|
for _, file := range p.Files {
|
|
inlineData = append(inlineData, string(file.Data()))
|
|
}
|
|
|
|
return inlineData
|
|
}
|