mirror of
				https://github.com/siderolabs/talos.git
				synced 2025-10-31 08:21:25 +01:00 
			
		
		
		
	Use `udevd` rules to create stable interface names. Link controllers should wait for `udevd` to settle down, otherwise link rename will fail (interface should not be UP). Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
		
			
				
	
	
		
			380 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			380 lines
		
	
	
		
			8.5 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 (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 
 | |
| 	"github.com/siderolabs/go-blockdevice/blockdevice"
 | |
| 	"github.com/siderolabs/go-procfs/procfs"
 | |
| 	"golang.org/x/sys/unix"
 | |
| 
 | |
| 	"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"
 | |
| 	"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
 | |
| 	"github.com/siderolabs/talos/internal/pkg/meta"
 | |
| 	"github.com/siderolabs/talos/internal/pkg/mount"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/constants"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/kernel"
 | |
| 	"github.com/siderolabs/talos/pkg/version"
 | |
| )
 | |
| 
 | |
| // Options represents the set of options available for an install.
 | |
| type Options struct {
 | |
| 	ConfigSource      string
 | |
| 	Disk              string
 | |
| 	DiskSize          int
 | |
| 	Platform          string
 | |
| 	Arch              string
 | |
| 	Board             string
 | |
| 	ExtraKernelArgs   []string
 | |
| 	Bootloader        bool
 | |
| 	Upgrade           bool
 | |
| 	Force             bool
 | |
| 	Zero              bool
 | |
| 	LegacyBIOSSupport bool
 | |
| 	MetaValues        MetaValues
 | |
| }
 | |
| 
 | |
| // Install installs Talos.
 | |
| func Install(p runtime.Platform, seq runtime.Sequence, opts *Options) (err error) {
 | |
| 	cmdline := procfs.NewCmdline("")
 | |
| 	cmdline.Append(constants.KernelParamPlatform, p.Name())
 | |
| 
 | |
| 	if opts.ConfigSource != "" {
 | |
| 		cmdline.Append(constants.KernelParamConfig, opts.ConfigSource)
 | |
| 	}
 | |
| 
 | |
| 	cmdline.SetAll(p.KernelArgs().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 err = cmdline.AppendAll(
 | |
| 		opts.ExtraKernelArgs,
 | |
| 		procfs.WithOverwriteArgs("console"),
 | |
| 		procfs.WithOverwriteArgs(constants.KernelParamPlatform),
 | |
| 	); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	i, err := NewInstaller(cmdline, seq, opts)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err = i.Install(seq); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	log.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
 | |
| 	manifest   *Manifest
 | |
| 	bootloader bootloader.Bootloader
 | |
| 
 | |
| 	bootPartitionFound bool
 | |
| 
 | |
| 	Current grub.BootLabel
 | |
| 	Next    grub.BootLabel
 | |
| }
 | |
| 
 | |
| // NewInstaller initializes and returns an Installer.
 | |
| func NewInstaller(cmdline *procfs.Cmdline, seq runtime.Sequence, opts *Options) (i *Installer, err error) {
 | |
| 	i = &Installer{
 | |
| 		cmdline: cmdline,
 | |
| 		options: opts,
 | |
| 	}
 | |
| 
 | |
| 	if err = i.probeBootPartition(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	i.manifest, err = NewManifest(string(i.Next), seq, i.bootPartitionFound, i.options)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to create installation manifest: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return i, nil
 | |
| }
 | |
| 
 | |
| // Verify existence of boot partition.
 | |
| func (i *Installer) probeBootPartition() error {
 | |
| 	// there's no reason to discover boot partition if the disk is about to be wiped
 | |
| 	if !i.options.Zero {
 | |
| 		dev, err := blockdevice.Open(i.options.Disk)
 | |
| 		if err != nil {
 | |
| 			i.bootPartitionFound = false
 | |
| 
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		defer dev.Close() //nolint:errcheck
 | |
| 
 | |
| 		if part, err := dev.GetPartition(constants.BootPartitionLabel); err != nil {
 | |
| 			i.bootPartitionFound = false
 | |
| 		} else {
 | |
| 			i.bootPartitionFound = true
 | |
| 
 | |
| 			// mount the boot partition temporarily to find the bootloader labels
 | |
| 			mountpoints := mount.NewMountPoints()
 | |
| 
 | |
| 			partPath, err := part.Path()
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			fsType, err := part.Filesystem()
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			mountpoint := mount.NewMountPoint(partPath, constants.BootMountPoint, fsType, unix.MS_NOATIME|unix.MS_RDONLY, "")
 | |
| 			mountpoints.Set(constants.BootPartitionLabel, mountpoint)
 | |
| 
 | |
| 			if err := mount.Mount(mountpoints); err != nil {
 | |
| 				log.Printf("warning: failed to mount boot partition %q: %s", partPath, err)
 | |
| 			} else {
 | |
| 				defer mount.Unmount(mountpoints) //nolint:errcheck
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	grubConf, err := grub.Read(grub.ConfigPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	next := grub.BootA
 | |
| 
 | |
| 	if grubConf != nil {
 | |
| 		i.Current = grubConf.Default
 | |
| 
 | |
| 		next, err = grub.FlipBootLabel(grubConf.Default)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		i.bootloader = grubConf
 | |
| 	}
 | |
| 
 | |
| 	i.Next = next
 | |
| 
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // Install fetches the necessary data locations and copies or extracts
 | |
| // to the target locations.
 | |
| //
 | |
| //nolint:gocyclo,cyclop
 | |
| func (i *Installer) Install(seq runtime.Sequence) (err error) {
 | |
| 	errataBTF()
 | |
| 
 | |
| 	if seq == runtime.SequenceUpgrade {
 | |
| 		if err = i.errataNetIfnames(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err = i.runPreflightChecks(seq); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err = i.installExtensions(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if i.options.Board != constants.BoardNone {
 | |
| 		var b runtime.Board
 | |
| 
 | |
| 		b, err = board.NewBoard(i.options.Board)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		i.cmdline.Append(constants.KernelParamBoard, b.Name())
 | |
| 
 | |
| 		i.cmdline.SetAll(b.KernelArgs().Strings())
 | |
| 	}
 | |
| 
 | |
| 	if err = i.manifest.Execute(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Mount the partitions.
 | |
| 	mountpoints := mount.NewMountPoints()
 | |
| 
 | |
| 	for _, label := range []string{constants.BootPartitionLabel, constants.EFIPartitionLabel} {
 | |
| 		err = func() error {
 | |
| 			var device string
 | |
| 			// searching targets for the device to be used
 | |
| 		OuterLoop:
 | |
| 			for dev, targets := range i.manifest.Targets {
 | |
| 				for _, target := range targets {
 | |
| 					if target.Label == label {
 | |
| 						device = dev
 | |
| 
 | |
| 						break OuterLoop
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if device == "" {
 | |
| 				return fmt.Errorf("failed to detect %s target device", label)
 | |
| 			}
 | |
| 
 | |
| 			var bd *blockdevice.BlockDevice
 | |
| 
 | |
| 			bd, err = blockdevice.Open(device)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			defer bd.Close() //nolint:errcheck
 | |
| 
 | |
| 			var mountpoint *mount.Point
 | |
| 
 | |
| 			mountpoint, err = mount.SystemMountPointForLabel(bd, label)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			mountpoints.Set(label, mountpoint)
 | |
| 
 | |
| 			return nil
 | |
| 		}()
 | |
| 
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err = mount.Mount(mountpoints); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	defer func() {
 | |
| 		e := mount.Unmount(mountpoints)
 | |
| 		if e != nil {
 | |
| 			log.Printf("failed to unmount: %v", e)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	// Install the assets.
 | |
| 
 | |
| 	for _, targets := range i.manifest.Targets {
 | |
| 		for _, target := range targets {
 | |
| 			// Handle the download and extraction of assets.
 | |
| 			if err = target.Save(); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Install the bootloader.
 | |
| 
 | |
| 	if !i.options.Bootloader {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var conf *grub.Config
 | |
| 	if i.bootloader == nil {
 | |
| 		conf = grub.NewConfig(i.cmdline.String())
 | |
| 	} else {
 | |
| 		existingConf, ok := i.bootloader.(*grub.Config)
 | |
| 		if !ok {
 | |
| 			return fmt.Errorf("unsupported bootloader type: %T", i.bootloader)
 | |
| 		}
 | |
| 		if err = existingConf.Put(i.Next, i.cmdline.String()); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		existingConf.Default = i.Next
 | |
| 		existingConf.Fallback = i.Current
 | |
| 
 | |
| 		conf = existingConf
 | |
| 	}
 | |
| 
 | |
| 	i.bootloader = conf
 | |
| 
 | |
| 	err = i.bootloader.Install(i.options.Disk, i.options.Arch)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if i.options.Board != constants.BoardNone {
 | |
| 		var b runtime.Board
 | |
| 
 | |
| 		b, err = board.NewBoard(i.options.Board)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		log.Printf("installing U-Boot for %q", b.Name())
 | |
| 
 | |
| 		if err = b.Install(i.options.Disk); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if seq == runtime.SequenceUpgrade || len(i.options.MetaValues.values) > 0 {
 | |
| 		var metaState *meta.Meta
 | |
| 
 | |
| 		if metaState, err = meta.New(context.Background(), nil); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		var ok bool
 | |
| 
 | |
| 		if seq == runtime.SequenceUpgrade {
 | |
| 			if ok, err = metaState.SetTag(context.Background(), meta.Upgrade, string(i.Current)); !ok || err != nil {
 | |
| 				return fmt.Errorf("failed to set upgrade tag: %q", i.Current)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for _, v := range i.options.MetaValues.values {
 | |
| 			if ok, err = metaState.SetTag(context.Background(), 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 err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (i *Installer) runPreflightChecks(seq runtime.Sequence) error {
 | |
| 	if seq != runtime.SequenceUpgrade {
 | |
| 		// 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)
 | |
| }
 |