mirror of
				https://github.com/siderolabs/talos.git
				synced 2025-10-31 00:11:36 +01:00 
			
		
		
		
	Move META constants out to machinery, and fix up imports. The internal `pkg/meta` package shold not be consumed in public-facing commands. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
		
			
				
	
	
		
			588 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			588 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // This Source Code Form is subject to the terms of the Mozilla Public
 | |
| // License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
| // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | |
| 
 | |
| package install
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"slices"
 | |
| 
 | |
| 	"github.com/google/uuid"
 | |
| 	"github.com/siderolabs/go-blockdevice/v2/blkid"
 | |
| 	"github.com/siderolabs/go-blockdevice/v2/block"
 | |
| 	"github.com/siderolabs/go-blockdevice/v2/partitioning"
 | |
| 	"github.com/siderolabs/go-blockdevice/v2/partitioning/gpt"
 | |
| 	"github.com/siderolabs/go-pointer"
 | |
| 	"github.com/siderolabs/go-procfs/procfs"
 | |
| 	"gopkg.in/yaml.v3"
 | |
| 
 | |
| 	"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
 | |
| 	"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/board"
 | |
| 	"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
 | |
| 	bootloaderoptions "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
 | |
| 	"github.com/siderolabs/talos/internal/pkg/meta"
 | |
| 	"github.com/siderolabs/talos/internal/pkg/partition"
 | |
| 	"github.com/siderolabs/talos/pkg/imager/overlay/executor"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/compatibility"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/constants"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/kernel"
 | |
| 	metaconsts "github.com/siderolabs/talos/pkg/machinery/meta"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/overlay"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/version"
 | |
| )
 | |
| 
 | |
| // Options represents the set of options available for an install.
 | |
| type Options struct {
 | |
| 	ConfigSource        string
 | |
| 	Disk                string
 | |
| 	Platform            string
 | |
| 	Arch                string
 | |
| 	Board               string
 | |
| 	ExtraKernelArgs     []string
 | |
| 	Upgrade             bool
 | |
| 	Force               bool
 | |
| 	Zero                bool
 | |
| 	LegacyBIOSSupport   bool
 | |
| 	MetaValues          MetaValues
 | |
| 	OverlayInstaller    overlay.Installer[overlay.ExtraOptions]
 | |
| 	OverlayName         string
 | |
| 	OverlayExtractedDir string
 | |
| 	ExtraOptions        overlay.ExtraOptions
 | |
| 
 | |
| 	// Options specific for the image creation mode.
 | |
| 	ImageSecureboot bool
 | |
| 	Version         string
 | |
| 	BootAssets      bootloaderoptions.BootAssets
 | |
| 	Printf          func(string, ...any)
 | |
| 	MountPrefix     string
 | |
| }
 | |
| 
 | |
| // Mode is the install mode.
 | |
| type Mode int
 | |
| 
 | |
| const (
 | |
| 	// ModeInstall is the install mode.
 | |
| 	ModeInstall Mode = iota
 | |
| 	// ModeUpgrade is the upgrade mode.
 | |
| 	ModeUpgrade
 | |
| 	// ModeImage is the image creation mode.
 | |
| 	ModeImage
 | |
| )
 | |
| 
 | |
| // IsImage returns true if the mode is image creation.
 | |
| func (m Mode) IsImage() bool {
 | |
| 	return m == ModeImage
 | |
| }
 | |
| 
 | |
| const typeGPT = "gpt"
 | |
| 
 | |
| // Install installs Talos.
 | |
| //
 | |
| //nolint:gocyclo
 | |
| func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options) error {
 | |
| 	overlayPresent := overlayPresent()
 | |
| 
 | |
| 	if b := getBoard(); b != constants.BoardNone && !overlayPresent {
 | |
| 		return fmt.Errorf("using standard installer image is not supported for board: %s, use an installer with overlay", b)
 | |
| 	}
 | |
| 
 | |
| 	if overlayPresent {
 | |
| 		extraOptionsBytes, err := os.ReadFile(constants.ImagerOverlayExtraOptionsPath)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		var extraOptions overlay.ExtraOptions
 | |
| 
 | |
| 		decoder := yaml.NewDecoder(bytes.NewReader(extraOptionsBytes))
 | |
| 		decoder.KnownFields(true)
 | |
| 
 | |
| 		if err := decoder.Decode(&extraOptions); err != nil {
 | |
| 			return fmt.Errorf("failed to decode extra options: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		opts.OverlayInstaller = executor.New(constants.ImagerOverlayInstallerDefaultPath)
 | |
| 		opts.ExtraOptions = extraOptions
 | |
| 	}
 | |
| 
 | |
| 	cmdline := procfs.NewCmdline("")
 | |
| 	cmdline.Append(constants.KernelParamPlatform, p.Name())
 | |
| 
 | |
| 	if opts.ConfigSource != "" {
 | |
| 		cmdline.Append(constants.KernelParamConfig, opts.ConfigSource)
 | |
| 	}
 | |
| 
 | |
| 	cmdline.SetAll(p.KernelArgs(opts.Arch).Strings())
 | |
| 
 | |
| 	// first defaults, then extra kernel args to allow extra kernel args to override defaults
 | |
| 	if err := cmdline.AppendAll(kernel.DefaultArgs); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if opts.Board != constants.BoardNone {
 | |
| 		// board 'rpi_4' was removed in Talos 1.5 in favor of `rpi_generic`
 | |
| 		if opts.Board == "rpi_4" {
 | |
| 			opts.Board = constants.BoardRPiGeneric
 | |
| 		}
 | |
| 
 | |
| 		var b runtime.Board
 | |
| 
 | |
| 		b, err := board.NewBoard(opts.Board)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		cmdline.Append(constants.KernelParamBoard, b.Name())
 | |
| 
 | |
| 		cmdline.SetAll(b.KernelArgs().Strings())
 | |
| 	}
 | |
| 
 | |
| 	if opts.OverlayInstaller != nil {
 | |
| 		overlayOpts, getOptsErr := opts.OverlayInstaller.GetOptions(opts.ExtraOptions)
 | |
| 		if getOptsErr != nil {
 | |
| 			return fmt.Errorf("failed to get overlay installer options: %w", getOptsErr)
 | |
| 		}
 | |
| 
 | |
| 		opts.OverlayName = overlayOpts.Name
 | |
| 
 | |
| 		cmdline.SetAll(overlayOpts.KernelArgs)
 | |
| 	}
 | |
| 
 | |
| 	// preserve console=ttyS0 if it was already present in cmdline for metal platform
 | |
| 	existingCmdline := procfs.ProcCmdline()
 | |
| 
 | |
| 	if *existingCmdline.Get(constants.KernelParamPlatform).First() == constants.PlatformMetal && existingCmdline.Get("console").Contains("ttyS0") {
 | |
| 		if !slices.Contains(opts.ExtraKernelArgs, "console=ttyS0") {
 | |
| 			cmdline.Append("console", "ttyS0")
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err := cmdline.AppendAll(
 | |
| 		opts.ExtraKernelArgs,
 | |
| 		procfs.WithOverwriteArgs("console"),
 | |
| 		procfs.WithOverwriteArgs(constants.KernelParamPlatform),
 | |
| 		procfs.WithDeleteNegatedArgs(),
 | |
| 	); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	i, err := NewInstaller(ctx, cmdline, mode, opts)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err = i.Install(ctx, mode); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	i.options.Printf("installation of %s complete", version.Tag)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Installer represents the installer logic. It serves as the entrypoint to all
 | |
| // installation methods.
 | |
| type Installer struct {
 | |
| 	cmdline *procfs.Cmdline
 | |
| 	options *Options
 | |
| }
 | |
| 
 | |
| // NewInstaller initializes and returns an Installer.
 | |
| func NewInstaller(ctx context.Context, cmdline *procfs.Cmdline, mode Mode, opts *Options) (i *Installer, err error) {
 | |
| 	i = &Installer{
 | |
| 		cmdline: cmdline,
 | |
| 		options: opts,
 | |
| 	}
 | |
| 
 | |
| 	if i.options.Version == "" {
 | |
| 		i.options.Version = version.Tag
 | |
| 	}
 | |
| 
 | |
| 	if i.options.Printf == nil {
 | |
| 		i.options.Printf = log.Printf
 | |
| 	}
 | |
| 
 | |
| 	if mode == ModeUpgrade && i.options.Force {
 | |
| 		i.options.Printf("system disk wipe on upgrade is not supported anymore, option ignored")
 | |
| 	}
 | |
| 
 | |
| 	if i.options.Zero && mode != ModeInstall {
 | |
| 		i.options.Printf("zeroing of the disk is only supported for the initial installation, option ignored")
 | |
| 	}
 | |
| 
 | |
| 	i.options.BootAssets.FillDefaults(opts.Arch)
 | |
| 
 | |
| 	return i, nil
 | |
| }
 | |
| 
 | |
| // Install fetches the necessary data locations and copies or extracts
 | |
| // to the target locations.
 | |
| //
 | |
| //nolint:gocyclo,cyclop
 | |
| func (i *Installer) Install(ctx context.Context, mode Mode) (err error) {
 | |
| 	// pre-flight checks, erratas
 | |
| 	hostTalosVersion, err := readHostTalosVersion()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if mode == ModeUpgrade {
 | |
| 		errataArm64ZBoot()
 | |
| 
 | |
| 		i.errataNetIfnames(hostTalosVersion)
 | |
| 	}
 | |
| 
 | |
| 	if err = i.runPreflightChecks(mode); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// prepare extensions if legacy machine.install.extensions is present
 | |
| 	if err = i.installExtensions(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// open and lock the blockdevice for the installation disk for the whole duration of the installer
 | |
| 	bd, err := block.NewFromPath(i.options.Disk, block.OpenForWrite())
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to open blockdevice %s: %w", i.options.Disk, err)
 | |
| 	}
 | |
| 
 | |
| 	defer bd.Close() //nolint:errcheck
 | |
| 
 | |
| 	if err = bd.Lock(true); err != nil {
 | |
| 		return fmt.Errorf("failed to lock blockdevice %s: %w", i.options.Disk, err)
 | |
| 	}
 | |
| 
 | |
| 	defer bd.Unlock() //nolint:errcheck
 | |
| 
 | |
| 	var bootlder bootloader.Bootloader
 | |
| 
 | |
| 	// if running in install/image mode, just create new GPT partition table
 | |
| 	// if running in upgrade mode, verify that install disk has correct GPT partition table and expected partitions
 | |
| 
 | |
| 	// probe the disk anyways
 | |
| 	info, err := blkid.Probe(bd.File(), blkid.WithSkipLocking(true))
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to probe blockdevice %s: %w", i.options.Disk, err)
 | |
| 	}
 | |
| 
 | |
| 	gptdev, err := gpt.DeviceFromBlockDevice(bd)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error getting GPT device: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	switch mode {
 | |
| 	case ModeImage:
 | |
| 		// on image creation, we don't care about disk contents
 | |
| 		bootlder = bootloader.New(i.options.ImageSecureboot, i.options.Version)
 | |
| 	case ModeInstall:
 | |
| 		if !i.options.Zero && !i.options.Force {
 | |
| 			// verify that the disk is either empty or has an empty GPT partition table, otherwise fail the install
 | |
| 			switch {
 | |
| 			case info.Name == "":
 | |
| 				// empty, ok
 | |
| 			case info.Name == typeGPT && len(info.Parts) == 0:
 | |
| 				// GPT, no partitions, ok
 | |
| 			default:
 | |
| 				return fmt.Errorf("disk %s is not empty, skipping install, detected %q", i.options.Disk, info.Name)
 | |
| 			}
 | |
| 		} else {
 | |
| 			// zero the disk
 | |
| 			if err = bd.FastWipe(); err != nil {
 | |
| 				return fmt.Errorf("failed to zero blockdevice %s: %w", i.options.Disk, err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// on install, automatically detect the bootloader
 | |
| 		bootlder = bootloader.NewAuto()
 | |
| 	case ModeUpgrade:
 | |
| 		// on upgrade, we don't touch the disk partitions, but we need to verify that the disk has the expected GPT partition table
 | |
| 		if info.Name != typeGPT {
 | |
| 			return fmt.Errorf("disk %s has an unexpected format %q", i.options.Disk, info.Name)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if mode == ModeImage || mode == ModeInstall {
 | |
| 		// create partitions and re-probe the device
 | |
| 		info, err = i.createPartitions(gptdev, mode, hostTalosVersion, bootlder)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("failed to create partitions: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if mode == ModeUpgrade {
 | |
| 		// on upgrade, probe the bootloader
 | |
| 		bootlder, err = bootloader.Probe(i.options.Disk, bootloaderoptions.ProbeOptions{
 | |
| 			// the disk is already locked
 | |
| 			BlockProbeOptions: []blkid.ProbeOption{
 | |
| 				blkid.WithSkipLocking(true),
 | |
| 			},
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("failed to probe bootloader on upgrade: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Install the bootloader.
 | |
| 	bootInstallResult, err := bootlder.Install(bootloaderoptions.InstallOptions{
 | |
| 		BootDisk:    i.options.Disk,
 | |
| 		Arch:        i.options.Arch,
 | |
| 		Cmdline:     i.cmdline.String(),
 | |
| 		Version:     i.options.Version,
 | |
| 		ImageMode:   mode.IsImage(),
 | |
| 		MountPrefix: i.options.MountPrefix,
 | |
| 		BootAssets:  i.options.BootAssets,
 | |
| 		Printf:      i.options.Printf,
 | |
| 		BlkidInfo:   info,
 | |
| 
 | |
| 		ExtraInstallStep: func() error {
 | |
| 			if i.options.Board != constants.BoardNone {
 | |
| 				var b runtime.Board
 | |
| 
 | |
| 				b, err = board.NewBoard(i.options.Board)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 
 | |
| 				i.options.Printf("installing U-Boot for %q", b.Name())
 | |
| 
 | |
| 				if err = b.Install(runtime.BoardInstallOptions{
 | |
| 					InstallDisk:     i.options.Disk,
 | |
| 					MountPrefix:     i.options.MountPrefix,
 | |
| 					UBootPath:       i.options.BootAssets.UBootPath,
 | |
| 					DTBPath:         i.options.BootAssets.DTBPath,
 | |
| 					RPiFirmwarePath: i.options.BootAssets.RPiFirmwarePath,
 | |
| 					Printf:          i.options.Printf,
 | |
| 				}); err != nil {
 | |
| 					return fmt.Errorf("failed to install for board %s: %w", b.Name(), err)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if i.options.OverlayInstaller != nil {
 | |
| 				i.options.Printf("running overlay installer %q", i.options.OverlayName)
 | |
| 
 | |
| 				if err = i.options.OverlayInstaller.Install(overlay.InstallOptions[overlay.ExtraOptions]{
 | |
| 					InstallDisk:   i.options.Disk,
 | |
| 					MountPrefix:   i.options.MountPrefix,
 | |
| 					ArtifactsPath: filepath.Join(i.options.OverlayExtractedDir, constants.ImagerOverlayArtifactsPath),
 | |
| 					ExtraOptions:  i.options.ExtraOptions,
 | |
| 				}); err != nil {
 | |
| 					return fmt.Errorf("failed to run overlay installer: %w", err)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return nil
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to install bootloader: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if mode == ModeUpgrade || len(i.options.MetaValues.values) > 0 {
 | |
| 		var (
 | |
| 			metaState         *meta.Meta
 | |
| 			metaPartitionName string
 | |
| 		)
 | |
| 
 | |
| 		for _, partition := range info.Parts {
 | |
| 			if pointer.SafeDeref(partition.PartitionLabel) == constants.MetaPartitionLabel {
 | |
| 				metaPartitionName = partitioning.DevName(i.options.Disk, partition.PartitionIndex)
 | |
| 
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if metaPartitionName == "" {
 | |
| 			return errors.New("failed to detect META partition")
 | |
| 		}
 | |
| 
 | |
| 		if metaState, err = meta.New(ctx, nil, meta.WithPrinter(i.options.Printf), meta.WithFixedPath(metaPartitionName)); err != nil {
 | |
| 			return fmt.Errorf("failed to open META: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		var ok bool
 | |
| 
 | |
| 		if mode == ModeUpgrade {
 | |
| 			if ok, err = metaState.SetTag(ctx, metaconsts.Upgrade, bootInstallResult.PreviousLabel); !ok || err != nil {
 | |
| 				return fmt.Errorf("failed to set upgrade tag: %q", bootInstallResult.PreviousLabel)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for _, v := range i.options.MetaValues.values {
 | |
| 			if ok, err = metaState.SetTag(ctx, v.Key, v.Value); !ok || err != nil {
 | |
| 				return fmt.Errorf("failed to set meta tag: %q -> %q", v.Key, v.Value)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if err = metaState.Flush(); err != nil {
 | |
| 			return fmt.Errorf("failed to flush META: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| //nolint:gocyclo,cyclop
 | |
| func (i *Installer) createPartitions(gptdev gpt.Device, mode Mode, hostTalosVersion *compatibility.TalosVersion, bootlder bootloader.Bootloader) (*blkid.Info, error) {
 | |
| 	var partitionOptions *runtime.PartitionOptions
 | |
| 
 | |
| 	if i.options.Board != constants.BoardNone && !quirks.New(i.options.Version).SupportsOverlay() {
 | |
| 		var b runtime.Board
 | |
| 
 | |
| 		b, err := board.NewBoard(i.options.Board)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		partitionOptions = b.PartitionOptions()
 | |
| 	}
 | |
| 
 | |
| 	if i.options.OverlayInstaller != nil {
 | |
| 		overlayOpts, getOptsErr := i.options.OverlayInstaller.GetOptions(i.options.ExtraOptions)
 | |
| 		if getOptsErr != nil {
 | |
| 			return nil, fmt.Errorf("failed to get overlay installer options: %w", getOptsErr)
 | |
| 		}
 | |
| 
 | |
| 		if overlayOpts.PartitionOptions.Offset != 0 {
 | |
| 			partitionOptions = &runtime.PartitionOptions{
 | |
| 				PartitionsOffset: overlayOpts.PartitionOptions.Offset,
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var gptOptions []gpt.Option
 | |
| 
 | |
| 	if partitionOptions != nil && partitionOptions.PartitionsOffset != 0 {
 | |
| 		gptOptions = append(gptOptions, gpt.WithSkipLBAs(uint(partitionOptions.PartitionsOffset)))
 | |
| 	}
 | |
| 
 | |
| 	if i.options.LegacyBIOSSupport {
 | |
| 		gptOptions = append(gptOptions, gpt.WithMarkPMBRBootable())
 | |
| 	}
 | |
| 
 | |
| 	pt, err := gpt.New(gptdev, gptOptions...)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to initialize GPT: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// boot partitions
 | |
| 	partitions := bootlder.RequiredPartitions()
 | |
| 
 | |
| 	// META partition
 | |
| 	partitions = append(partitions,
 | |
| 		partition.NewPartitionOptions(constants.MetaPartitionLabel, false),
 | |
| 	)
 | |
| 
 | |
| 	legacyImage := mode == ModeImage && !quirks.New(i.options.Version).SkipDataPartitions()
 | |
| 
 | |
| 	// compatibility when installing on Talos < 1.8
 | |
| 	if legacyImage || (hostTalosVersion != nil && hostTalosVersion.PrecreateStatePartition()) {
 | |
| 		partitions = append(partitions,
 | |
| 			partition.NewPartitionOptions(constants.StatePartitionLabel, false),
 | |
| 		)
 | |
| 	}
 | |
| 
 | |
| 	if legacyImage {
 | |
| 		partitions = append(partitions,
 | |
| 			partition.NewPartitionOptions(constants.EphemeralPartitionLabel, false),
 | |
| 		)
 | |
| 	}
 | |
| 
 | |
| 	for _, p := range partitions {
 | |
| 		size := p.Size
 | |
| 
 | |
| 		if size == 0 {
 | |
| 			size = pt.LargestContiguousAllocatable()
 | |
| 		}
 | |
| 
 | |
| 		partitionTyp := uuid.MustParse(p.PartitionType)
 | |
| 
 | |
| 		_, _, err = pt.AllocatePartition(size, p.PartitionLabel, partitionTyp, p.PartitionOpts...)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("failed to allocate partition %s: %w", p.PartitionLabel, err)
 | |
| 		}
 | |
| 
 | |
| 		i.options.Printf("created %s (%s) size %d bytes", p.PartitionLabel, p.PartitionType, size)
 | |
| 	}
 | |
| 
 | |
| 	if err = pt.Write(); err != nil {
 | |
| 		return nil, fmt.Errorf("failed to write GPT: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// now format all partitions
 | |
| 	for idx, p := range partitions {
 | |
| 		devName := partitioning.DevName(i.options.Disk, uint(idx+1))
 | |
| 
 | |
| 		if err = partition.Format(devName, &p.FormatOptions, i.options.Printf); err != nil {
 | |
| 			return nil, fmt.Errorf("failed to format partition %s: %w", devName, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	info, err := blkid.ProbePath(i.options.Disk, blkid.WithSkipLocking(true))
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to probe blockdevice %s: %w", i.options.Disk, err)
 | |
| 	}
 | |
| 
 | |
| 	if len(info.Parts) != len(partitions) {
 | |
| 		return nil, fmt.Errorf("expected %d partitions, got %d", len(partitions), len(info.Parts))
 | |
| 	}
 | |
| 
 | |
| 	// this is weird, but sometimes blkid doesn't return the filesystem type for freshly formatted partitions
 | |
| 	for idx, p := range partitions {
 | |
| 		if p.FormatOptions.FileSystemType == partition.FilesystemTypeNone || p.FormatOptions.FileSystemType == partition.FilesystemTypeZeroes {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		info.Parts[idx].Name = p.FormatOptions.FileSystemType
 | |
| 	}
 | |
| 
 | |
| 	return info, nil
 | |
| }
 | |
| 
 | |
| func (i *Installer) runPreflightChecks(mode Mode) error {
 | |
| 	if mode != ModeUpgrade {
 | |
| 		// pre-flight checks only apply to upgrades
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	ctx, cancel := context.WithCancel(context.Background())
 | |
| 	defer cancel()
 | |
| 
 | |
| 	checks, err := NewPreflightChecks(ctx)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error initializing pre-flight checks: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	defer checks.Close() //nolint:errcheck
 | |
| 
 | |
| 	return checks.Run(ctx)
 | |
| }
 | |
| 
 | |
| func overlayPresent() bool {
 | |
| 	_, err := os.Stat(constants.ImagerOverlayInstallerDefaultPath)
 | |
| 
 | |
| 	return err == nil
 | |
| }
 | |
| 
 | |
| func getBoard() string {
 | |
| 	cmdline := procfs.ProcCmdline()
 | |
| 	if cmdline == nil {
 | |
| 		return constants.BoardNone
 | |
| 	}
 | |
| 
 | |
| 	board := cmdline.Get(constants.KernelParamBoard)
 | |
| 	if board == nil {
 | |
| 		return constants.BoardNone
 | |
| 	}
 | |
| 
 | |
| 	return *board.First()
 | |
| }
 |