debian-forge-composer/internal/manifest/commit_deployment.go
Tomáš Hozza dc637d9a57 Manifest: always set kernel options in grub2 stage
It turned out that when we stopped setting the kernel options in grubenv
file, we also stopped setting them in /etc/default/grub under
`GRUB_CMDLINE_LINUX`. This file is used by grub2-mkconfig when
generating grub configuration.

10_linux script executed by grub2-mkconfig recently started to overwrite
the /etc/kernel/cmdline, if its timestamp is older than the timestamp of
/etc/default/grub [1]. As a result, all kernel options were wiped out from
/etc/kernel/cmdline.

Make sure that we always set the `KernelOptions` in the grub2 stage
options, even if the `WriteCmdLine` is set to `false`.

In addition, unify the way we concatenate kernel options set in the
grub2 stage options. Some pipeline implementations were previously using
space, other were using comma. Space is now used everywhere.

Regenerate all affected image manifests.

[1] https://src.fedoraproject.org/rpms/grub2/c/fc76aed5333f56dd05400521a35b944a5df52ebc

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
2023-03-23 16:32:34 +01:00

267 lines
6.9 KiB
Go

package manifest
import (
"os"
"strings"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"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/users"
)
// OSTreeDeployment represents the filesystem tree of a target image based
// on a deployed ostree commit.
type OSTreeDeployment struct {
Base
Remote ostree.Remote
OSVersion string
commit ostree.CommitSpec
SysrootReadOnly bool
osName string
KernelOptionsAppend []string
Keyboard string
Locale string
Users []users.User
Groups []users.Group
platform platform.Platform
PartitionTable *disk.PartitionTable
// Whether ignition is in use or not
ignition bool
}
// NewOSTreeDeployment creates a pipeline for an ostree deployment from a
// commit.
func NewOSTreeDeployment(m *Manifest,
buildPipeline *Build,
commit ostree.CommitSpec,
osName string,
ignition bool,
platform platform.Platform) *OSTreeDeployment {
p := &OSTreeDeployment{
Base: NewBase(m, "image-tree", buildPipeline),
commit: commit,
osName: osName,
platform: platform,
ignition: ignition,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *OSTreeDeployment) getBuildPackages() []string {
packages := []string{
"rpm-ostree",
}
return packages
}
func (p *OSTreeDeployment) getOSTreeCommits() []ostree.CommitSpec {
return []ostree.CommitSpec{p.commit}
}
func (p *OSTreeDeployment) serialize() osbuild.Pipeline {
const repoPath = "/ostree/repo"
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.OSTreeInitFsStage())
pipeline.AddStage(osbuild.NewOSTreePullStage(
&osbuild.OSTreePullStageOptions{Repo: repoPath, Remote: p.Remote.Name},
osbuild.NewOstreePullStageInputs("org.osbuild.source", p.commit.Checksum, p.commit.Ref),
))
pipeline.AddStage(osbuild.NewOSTreeOsInitStage(
&osbuild.OSTreeOsInitStageOptions{
OSName: p.osName,
},
))
pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: []osbuild.MkdirStagePath{
{
Path: "/boot/efi",
Mode: common.ToPtr(os.FileMode(0700)),
},
},
}))
kernelOpts := osbuild.GenImageKernelOptions(p.PartitionTable)
kernelOpts = append(kernelOpts, p.KernelOptionsAppend...)
if p.ignition {
kernelOpts = append(kernelOpts,
"coreos.no_persist_ip", // users cannot add connections as we don't have a live iso, this prevents connections to bleed into the system from the ign initrd
"ignition.platform.id=metal",
"$ignition_firstboot",
)
}
pipeline.AddStage(osbuild.NewOSTreeDeployStage(
&osbuild.OSTreeDeployStageOptions{
OsName: p.osName,
Ref: p.commit.Ref,
Remote: p.Remote.Name,
Mounts: []string{"/boot", "/boot/efi"},
Rootfs: osbuild.Rootfs{
Label: "root",
},
KernelOpts: kernelOpts,
},
))
remoteURL := p.Remote.URL
if remoteURL == "" {
// if the remote URL for the image is not specified, use the source commit URL
remoteURL = p.commit.URL
}
pipeline.AddStage(osbuild.NewOSTreeRemotesStage(
&osbuild.OSTreeRemotesStageOptions{
Repo: "/ostree/repo",
Remotes: []osbuild.OSTreeRemote{
{
Name: p.Remote.Name,
URL: remoteURL,
ContentURL: p.Remote.ContentURL,
GPGKeyPaths: p.Remote.GPGKeyPaths,
},
},
},
))
pipeline.AddStage(osbuild.NewOSTreeFillvarStage(
&osbuild.OSTreeFillvarStageOptions{
Deployment: osbuild.OSTreeDeployment{
OSName: p.osName,
Ref: p.commit.Ref,
},
},
))
configStage := osbuild.NewOSTreeConfigStage(
&osbuild.OSTreeConfigStageOptions{
Repo: repoPath,
Config: &osbuild.OSTreeConfig{
Sysroot: &osbuild.SysrootOptions{
ReadOnly: &p.SysrootReadOnly,
Bootloader: "none",
},
},
},
)
configStage.MountOSTree(p.osName, p.commit.Ref, 0)
pipeline.AddStage(configStage)
fstabOptions := osbuild.NewFSTabStageOptions(p.PartitionTable)
fstabStage := osbuild.NewFSTabStage(fstabOptions)
fstabStage.MountOSTree(p.osName, p.commit.Ref, 0)
pipeline.AddStage(fstabStage)
if len(p.Users) > 0 {
usersStage, err := osbuild.GenUsersStage(p.Users, false)
if err != nil {
panic("password encryption failed")
}
usersStage.MountOSTree(p.osName, p.commit.Ref, 0)
pipeline.AddStage(usersStage)
}
if len(p.Groups) > 0 {
grpStage := osbuild.GenGroupsStage(p.Groups)
grpStage.MountOSTree(p.osName, p.commit.Ref, 0)
pipeline.AddStage(grpStage)
}
if p.ignition {
pipeline.AddStage(osbuild.NewIgnitionStage(&osbuild.IgnitionStageOptions{
// This is a workaround to make the systemd believe it's firstboot when ignition runs on real firstboot.
// Right now, since we ship /etc/machine-id, systemd thinks it's not firstboot and ignition depends on it
// to run on the real firstboot to enable services from presets.
// Since this only applies to artifacts with ignition and changing machineid-compat at commit creation time may
// have undesiderable effect, we're doing it here as a stopgap. We may revisit this in the future.
Network: []string{
"systemd.firstboot=off",
"systemd.condition-first-boot=true",
},
}))
}
// if no root password is set, lock the root account
hasRoot := false
for _, user := range p.Users {
if user.Name == "root" {
hasRoot = true
break
}
}
if !hasRoot {
userOptions := &osbuild.UsersStageOptions{
Users: map[string]osbuild.UsersStageOptionsUser{
"root": {
Password: common.ToPtr("!locked"), // this is treated as crypted and locks/disables the password
},
},
}
rootLockStage := osbuild.NewUsersStage(userOptions)
rootLockStage.MountOSTree(p.osName, p.commit.Ref, 0)
pipeline.AddStage(rootLockStage)
}
if p.Keyboard != "" {
options := &osbuild.KeymapStageOptions{
Keymap: p.Keyboard,
}
keymapStage := osbuild.NewKeymapStage(options)
keymapStage.MountOSTree(p.osName, p.commit.Ref, 0)
pipeline.AddStage(keymapStage)
}
if p.Locale != "" {
options := &osbuild.LocaleStageOptions{
Language: p.Locale,
}
localeStage := osbuild.NewLocaleStage(options)
localeStage.MountOSTree(p.osName, p.commit.Ref, 0)
pipeline.AddStage(localeStage)
}
grubOptions := osbuild.NewGrub2StageOptionsUnified(p.PartitionTable,
strings.Join(kernelOpts, " "),
"",
p.platform.GetUEFIVendor() != "",
p.platform.GetBIOSPlatform(),
p.platform.GetUEFIVendor(), true)
grubOptions.Greenboot = true
grubOptions.Ignition = p.ignition
grubOptions.Config = &osbuild.GRUB2Config{
Default: "saved",
Timeout: 1,
TerminalOutput: []string{"console"},
}
bootloader := osbuild.NewGRUB2Stage(grubOptions)
bootloader.MountOSTree(p.osName, p.commit.Ref, 0)
pipeline.AddStage(bootloader)
pipeline.AddStage(osbuild.NewOSTreeSelinuxStage(
&osbuild.OSTreeSelinuxStageOptions{
Deployment: osbuild.OSTreeDeployment{
OSName: p.osName,
Ref: p.commit.Ref,
},
},
))
return pipeline
}