Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2281 - target manually partitioned disk #235

Merged
merged 9 commits into from
Apr 10, 2024
38 changes: 0 additions & 38 deletions go.sum

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions internal/agent/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func Install(sourceImgURL string, dir ...string) error {
return err
}

if cc.Install.Reboot == false && cc.Install.Poweroff == false {
if !cc.Install.Reboot && !cc.Install.Poweroff {
pterm.DefaultInteractiveContinue.Show("Installation completed, press enter to go back to the shell.")
svc, err := machine.Getty(1)
if err == nil {
Expand Down Expand Up @@ -194,7 +194,7 @@ func Install(sourceImgURL string, dir ...string) error {

// If neither reboot and poweroff are enabled let the user insert enter to go back to a new shell
// This is helpful to see the installation messages instead of just cleaning the screen with a new tty
if cc.Install.Reboot == false && cc.Install.Poweroff == false {
if !cc.Install.Reboot && !cc.Install.Poweroff {
pterm.DefaultInteractiveContinue.Show("Installation completed, press enter to go back to the shell.")

utils.Prompt("") //nolint:errcheck
Expand All @@ -213,10 +213,6 @@ func RunInstall(c *config.Config) error {
utils.SetEnv(c.Env)
utils.SetEnv(c.Install.Env)

if c.Install.Device == "" || c.Install.Device == "auto" {
c.Install.Device = detectDevice()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// UKI path. Check if we are on UKI AND if we are running off a cd, otherwise it makes no sense to run the install
// From the installed system
if internalutils.IsUkiWithFs(c.Fs) {
Expand Down Expand Up @@ -266,9 +262,6 @@ func runInstall(c *config.Config) error {
return err
}

// TODO: This should not be neccessary
installSpec.NoFormat = c.Install.NoFormat

// Set our cloud-init to the file we just created
f, err := dumpCCStringToFile(c)
if err == nil {
Expand Down
17 changes: 0 additions & 17 deletions internal/agent/interactive_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,6 @@ func promptToUnstructured(p events.YAMLPrompt, unstructuredYAML map[string]inter
return unstructuredYAML, nil
}

func detectDevice() string {
preferedDevice := "/dev/sda"
maxSize := float64(0)

block, err := ghw.Block()
if err == nil {
for _, disk := range block.Disks {
size := float64(disk.SizeBytes) / float64(GiB)
if size > maxSize {
maxSize = size
preferedDevice = "/dev/" + disk.Name
}
}
}
return preferedDevice
}

func InteractiveInstall(debug, spawnShell bool, sourceImgURL string) error {
var sshUsers []string
bus.Manager.Initialize()
Expand Down
13 changes: 12 additions & 1 deletion pkg/action/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,24 @@ func (i InstallAction) Run() (err error) {
}
}

// Check no-format flag
if i.spec.NoFormat {
i.cfg.Logger.Infof("NoFormat is true, skipping format and partitioning")
// Check force flag against current device
labels := []string{i.spec.Active.Label, i.spec.Recovery.Label}
if e.CheckActiveDeployment(labels) && !i.spec.Force {
return fmt.Errorf("use `force` flag to run an installation over the current running deployment")
}

if i.spec.Target == "" || i.spec.Target == "auto" {
// This needs to run after the pre-install stage to give the user the
// opportunity to prepare the target disk in the pre-install stage.
device, err := config.DetectPreConfiguredDevice(i.cfg.Logger)
if err != nil {
return fmt.Errorf("no target device specified and no device found: %s", err)
}
i.cfg.Logger.Infof("No target device specified, using pre-configured device: %s", device)
i.spec.Target = device
}
} else {
// Deactivate any active volume on target
err = e.DeactivateDevices()
Expand Down
4 changes: 2 additions & 2 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const (
type Install struct {
Auto bool `yaml:"auto,omitempty"`
Reboot bool `yaml:"reboot,omitempty"`
NoFormat bool `yaml:"no_format,omitempty"`
NoFormat bool `yaml:"no-format,omitempty"`
Device string `yaml:"device,omitempty"`
Poweroff bool `yaml:"poweroff,omitempty"`
GrubOptions map[string]string `yaml:"grub_options,omitempty"`
Expand Down Expand Up @@ -353,8 +353,8 @@ func scan(result *Config, opts ...collector.Option) (c *Config, err error) {
genericConfig, err := collector.Scan(o, FilterKeys)
if err != nil {
return result, err

}

result.Config = *genericConfig
configStr, err := genericConfig.String()
if err != nil {
Expand Down
76 changes: 57 additions & 19 deletions pkg/config/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ package config

import (
"fmt"
sdkTypes "github.com/kairos-io/kairos-sdk/types"
"io/fs"
"os"
"path/filepath"
"reflect"
"strings"

sdkTypes "github.com/kairos-io/kairos-sdk/types"

"github.com/google/go-containerregistry/pkg/crane"
"github.com/jaypipes/ghw"
"golang.org/x/sys/unix"

"github.com/kairos-io/kairos-agent/v2/pkg/constants"
Expand All @@ -37,6 +39,14 @@ import (
"github.com/spf13/viper"
)

const (
_ = 1 << (10 * iota)
KiB
MiB
GiB
TiB
)

// NewInstallSpec returns an InstallSpec struct all based on defaults and basic host checks (e.g. EFI vs BIOS)
func NewInstallSpec(cfg *Config) (*v1.InstallSpec, error) {
var firmware string
Expand Down Expand Up @@ -556,16 +566,11 @@ func ReadInstallSpecFromConfig(c *Config) (*v1.InstallSpec, error) {
return &v1.InstallSpec{}, err
}
installSpec := sp.(*v1.InstallSpec)
// Workaround!
// If we set the "auto" for the device in the cloudconfig the value will be proper in the Config.Install.Device
// But on the cloud-config it will still appear as "auto" as we dont modify that
// Unfortunately as we load the full cloud-config and unmarshall it into our spec, we cannot infer from there
// What device was choosen, and re-choosing again could lead to different results
// So instead we do the check here and override the installSpec.Target with the Config.Install.Device
// as its the soonest we have access to both
if installSpec.Target == "auto" {
installSpec.Target = c.Install.Device

if (installSpec.Target == "" || installSpec.Target == "auto") && !installSpec.NoFormat {
installSpec.Target = detectLargestDevice()
}

return installSpec, nil
}

Expand Down Expand Up @@ -656,16 +661,11 @@ func ReadUkiInstallSpecFromConfig(c *Config) (*v1.InstallUkiSpec, error) {
return &v1.InstallUkiSpec{}, err
}
installSpec := sp.(*v1.InstallUkiSpec)
// Workaround!
// If we set the "auto" for the device in the cloudconfig the value will be proper in the Config.Install.Device
// But on the cloud-config it will still appear as "auto" as we dont modify that
// Unfortunately as we load the full cloud-config and unmarshall it into our spec, we cannot infer from there
// What device was choosen, and re-choosing again could lead to different results
// So instead we do the check here and override the installSpec.Target with the Config.Install.Device
// as its the soonest we have access to both
if installSpec.Target == "auto" {
installSpec.Target = c.Install.Device

if (installSpec.Target == "" || installSpec.Target == "auto") && !installSpec.NoFormat {
installSpec.Target = detectLargestDevice()
}

return installSpec, nil
}

Expand Down Expand Up @@ -969,3 +969,41 @@ func unmarshallFullSpec(r *Config, subkey string, sp v1.Spec) error {

return nil
}

// detectLargestDevice returns the largest disk found
func detectLargestDevice() string {
preferedDevice := "/dev/sda"
maxSize := float64(0)

block, err := ghw.Block()
if err == nil {
for _, disk := range block.Disks {
size := float64(disk.SizeBytes) / float64(GiB)
if size > maxSize {
maxSize = size
preferedDevice = "/dev/" + disk.Name
}
}
}
return preferedDevice
}

// DetectPreConfiguredDevice returns a disk that has partitions labeled with
// Kairos labels. It can be used to detect a pre-configured device.
func DetectPreConfiguredDevice(logger sdkTypes.KairosLogger) (string, error) {
block, err := ghw.Block()
if err != nil {
logger.Errorf("failed getting block devices: %s", err.Error())
return "", err
}

for _, disk := range block.Disks {
for _, p := range disk.Partitions {
if p.FilesystemLabel == "COS_STATE" {
return filepath.Join("/", "dev", disk.Name), nil
}
}
}

return "", nil
}
1 change: 1 addition & 0 deletions pkg/types/v1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ type InstallUkiSpec struct {
PowerOff bool `yaml:"poweroff,omitempty" mapstructure:"poweroff"`
Partitions ElementalPartitions `yaml:"partitions,omitempty" mapstructure:"partitions"`
ExtraPartitions PartitionList `yaml:"extra-partitions,omitempty" mapstructure:"extra-partitions"`
NoFormat bool `yaml:"no-format,omitempty" mapstructure:"no-format"`
CloudInit []string `yaml:"cloud-init,omitempty" mapstructure:"cloud-init"`
SkipEntries []string `yaml:"skip-entries,omitempty" mapstructure:"skip-entries"`
}
Expand Down
37 changes: 26 additions & 11 deletions pkg/uki/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,32 @@ func (i *InstallAction) Run() (err error) {
i.cfg.Logger.Errorf("running kairos-uki-install.pre hook script: %s", err.Error())
}

// Deactivate any active volume on target
err = e.DeactivateDevices()
if err != nil {
i.cfg.Logger.Errorf("deactivating devices: %s", err.Error())
return err
}
// Partition device
err = e.PartitionAndFormatDevice(i.spec)
if err != nil {
i.cfg.Logger.Errorf("partitioning and formating devices: %s", err.Error())
return err
if i.spec.NoFormat {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to add a test for the UKI case too here: kairos-io/kairos#2291
to be complete.

Copy link
Contributor Author

@jimmykarily jimmykarily Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manually creating the disk layout that the UKI installation creates is tricky:

[kairos@fedora ~]$ sudo blkid
/dev/sr0: BLOCK_SIZE="2048" UUID="2024-04-09-09-34-22-00" LABEL="UKI_ISO_INSTALL" TYPE="iso9660"
/dev/loop0: UUID="B9E2-794E" BLOCK_SIZE="512" TYPE="vfat"
/dev/vda2: UUID="8bfa06f9-ca4f-56dc-90c9-49cf20f4f45e" TYPE="crypto_LUKS" PARTLABEL="oem" PARTUUID="d0be3cb8-e04d-4bc9-9bde-311a516eece2"
/dev/vda3: UUID="85c39d0f-4867-5227-8334-f5eec606d9eb" TYPE="crypto_LUKS" PARTLABEL="persistent" PARTUUID="bc2d3f7d-54c6-4fc9-9918-595b19129d50"
/dev/vda1: LABEL_FATBOOT="COS_GRUB" LABEL="COS_GRUB" UUID="E9F3-549E" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="efi" PARTUUID="feb38d63-49d4-4b2d-a1d6-2f340a27e4c5"

We can keep the code around here but writing a test for it is rather complex (creating a layout with encrypted partitions using the tpm). I'm not sure if anyone will ever be interested in this feature in UKI mode.

i.cfg.Logger.Infof("NoFormat is true, skipping format and partitioning")
if i.spec.Target == "" || i.spec.Target == "auto" {
// This needs to run after the pre-install stage to give the user the
// opportunity to prepare the target disk in the pre-install stage.
device, err := config.DetectPreConfiguredDevice(i.cfg.Logger)
if err != nil {
return fmt.Errorf("no target device specified and no device found: %s", err)
}
i.cfg.Logger.Infof("No target device specified, using pre-configured device: %s", device)
i.spec.Target = device
}
} else {
// Deactivate any active volume on target
err = e.DeactivateDevices()
if err != nil {
i.cfg.Logger.Errorf("deactivating devices: %s", err.Error())
return err
}

// Partition device
err = e.PartitionAndFormatDevice(i.spec)
if err != nil {
i.cfg.Logger.Errorf("partitioning and formating devices: %s", err.Error())
return err
}
}

err = e.MountPartitions(i.spec.GetPartitions().PartitionsByMountPoint(false))
Expand Down
Loading