2019-10-25 23:45:41 +03:00
// 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/.
2019-07-31 17:16:10 +00:00
2020-04-23 21:12:44 -07:00
package install
2019-07-31 17:16:10 +00:00
import (
2024-02-29 20:26:46 +05:30
"bytes"
2022-11-24 22:48:22 +04:00
"context"
2024-02-13 15:32:06 +03:00
"errors"
2019-10-16 04:50:06 +00:00
"fmt"
2020-03-10 07:52:10 -07:00
"log"
2023-06-14 17:33:11 +04:00
"os"
2024-02-29 20:26:46 +05:30
"path/filepath"
2024-02-27 16:15:10 +04:00
"syscall"
2024-01-31 22:17:20 +04:00
"time"
2019-07-31 17:16:10 +00:00
2022-09-20 22:51:04 +05:30
"github.com/siderolabs/go-blockdevice/blockdevice"
2022-11-01 12:06:37 +04:00
"github.com/siderolabs/go-procfs/procfs"
2024-01-31 22:17:20 +04:00
"github.com/siderolabs/go-retry/retry"
2024-02-29 20:26:46 +05:30
"gopkg.in/yaml.v3"
2020-10-05 20:35:41 +03:00
2022-11-02 15:06:45 +04:00
"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"
2023-07-27 12:50:06 +04:00
bootloaderoptions "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
2023-03-14 14:33:11 +04:00
"github.com/siderolabs/talos/internal/pkg/meta"
2022-11-02 15:06:45 +04:00
"github.com/siderolabs/talos/internal/pkg/mount"
2024-02-29 20:26:46 +05:30
"github.com/siderolabs/talos/pkg/imager/overlay/executor"
2022-11-02 15:06:45 +04:00
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/kernel"
2024-02-29 20:26:46 +05:30
"github.com/siderolabs/talos/pkg/machinery/overlay"
2024-03-05 18:04:11 +03:00
"github.com/siderolabs/talos/pkg/machinery/version"
2019-07-31 17:16:10 +00:00
)
2020-04-23 21:12:44 -07:00
// Options represents the set of options available for an install.
type Options struct {
2024-02-29 20:26:46 +05:30
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 ]
OverlayExtractedDir string
ExtraOptions overlay . ExtraOptions
2023-07-27 12:50:06 +04:00
// Options specific for the image creation mode.
ImageSecureboot bool
Version string
BootAssets bootloaderoptions . BootAssets
2023-08-04 23:11:16 +04:00
Printf func ( string , ... any )
2023-08-31 22:21:41 +04:00
MountPrefix string
2023-07-27 12:50:06 +04:00
}
// 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
2020-04-23 21:12:44 -07:00
}
// Install installs Talos.
2024-02-29 20:26:46 +05:30
//
//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 )
}
2020-04-23 21:12:44 -07:00
cmdline := procfs . NewCmdline ( "" )
cmdline . Append ( constants . KernelParamPlatform , p . Name ( ) )
2020-11-25 18:00:02 +03:00
if opts . ConfigSource != "" {
cmdline . Append ( constants . KernelParamConfig , opts . ConfigSource )
}
2020-04-23 21:12:44 -07:00
2020-12-23 18:11:41 +03:00
cmdline . SetAll ( p . KernelArgs ( ) . Strings ( ) )
2020-04-23 21:12:44 -07:00
2020-12-10 17:10:06 +03:00
// first defaults, then extra kernel args to allow extra kernel args to override defaults
2024-02-29 20:26:46 +05:30
if err := cmdline . AppendAll ( kernel . DefaultArgs ) ; err != nil {
2020-12-24 15:19:41 +03:00
return err
}
2020-12-10 17:10:06 +03:00
2023-07-27 12:50:06 +04:00
if opts . Board != constants . BoardNone {
2023-08-21 18:05:42 +04:00
// board 'rpi_4' was removed in Talos 1.5 in favor of `rpi_generic`
if opts . Board == "rpi_4" {
opts . Board = constants . BoardRPiGeneric
}
2023-07-27 12:50:06 +04:00
var b runtime . Board
2024-02-29 20:26:46 +05:30
b , err := board . NewBoard ( opts . Board )
2023-07-27 12:50:06 +04:00
if err != nil {
return err
}
cmdline . Append ( constants . KernelParamBoard , b . Name ( ) )
cmdline . SetAll ( b . KernelArgs ( ) . Strings ( ) )
}
2024-02-29 20:26:46 +05:30
if err := cmdline . AppendAll (
2022-07-05 10:01:12 +03:00
opts . ExtraKernelArgs ,
procfs . WithOverwriteArgs ( "console" ) ,
procfs . WithOverwriteArgs ( constants . KernelParamPlatform ) ,
2023-11-23 10:51:01 +05:30
procfs . WithDeleteNegatedArgs ( ) ,
2022-07-05 10:01:12 +03:00
) ; err != nil {
2020-04-23 21:12:44 -07:00
return err
}
2024-02-29 20:26:46 +05:30
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
}
2023-07-27 12:50:06 +04:00
i , err := NewInstaller ( ctx , cmdline , mode , opts )
2020-04-23 21:12:44 -07:00
if err != nil {
return err
}
2023-07-27 12:50:06 +04:00
if err = i . Install ( ctx , mode ) ; err != nil {
2020-04-23 21:12:44 -07:00
return err
}
2023-08-04 23:11:16 +04:00
i . options . Printf ( "installation of %s complete" , version . Tag )
2020-04-23 21:12:44 -07:00
return nil
}
2019-07-31 17:16:10 +00:00
// Installer represents the installer logic. It serves as the entrypoint to all
// installation methods.
type Installer struct {
2020-08-18 15:52:26 -07:00
cmdline * procfs . Cmdline
options * Options
manifest * Manifest
bootloader bootloader . Bootloader
2019-07-31 17:16:10 +00:00
}
// NewInstaller initializes and returns an Installer.
2023-07-27 12:50:06 +04:00
func NewInstaller ( ctx context . Context , cmdline * procfs . Cmdline , mode Mode , opts * Options ) ( i * Installer , err error ) {
2019-08-15 15:21:55 +00:00
i = & Installer {
2019-07-31 17:16:10 +00:00
cmdline : cmdline ,
2020-04-23 21:12:44 -07:00
options : opts ,
2019-07-31 17:16:10 +00:00
}
2023-07-27 12:50:06 +04:00
if i . options . Version == "" {
i . options . Version = version . Tag
}
2023-08-04 23:11:16 +04:00
if i . options . Printf == nil {
i . options . Printf = log . Printf
}
2023-06-14 17:33:11 +04:00
if ! i . options . Zero {
2023-06-29 23:54:09 +03:00
i . bootloader , err = bootloader . Probe ( ctx , i . options . Disk )
2023-06-14 17:33:11 +04:00
if err != nil && ! os . IsNotExist ( err ) {
return nil , fmt . Errorf ( "failed to probe bootloader: %w" , err )
}
2019-08-15 15:21:55 +00:00
}
2019-07-31 17:16:10 +00:00
2023-12-01 17:24:34 +04:00
i . options . BootAssets . FillDefaults ( opts . Arch )
2023-06-14 17:33:11 +04:00
bootLoaderPresent := i . bootloader != nil
if ! bootLoaderPresent {
2023-07-27 12:50:06 +04:00
if mode . IsImage ( ) {
// on image creation, use the bootloader based on options
2023-12-19 21:23:37 +04:00
i . bootloader = bootloader . New ( opts . ImageSecureboot , opts . Version )
2023-07-27 12:50:06 +04:00
} else {
// on install/upgrade perform automatic detection
i . bootloader = bootloader . NewAuto ( )
2020-10-20 15:49:45 +03:00
}
}
2023-07-27 12:50:06 +04:00
i . manifest , err = NewManifest ( mode , i . bootloader . UEFIBoot ( ) , bootLoaderPresent , i . options )
2023-06-07 20:45:53 +05:30
if err != nil {
return nil , fmt . Errorf ( "failed to create installation manifest: %w" , err )
2022-02-09 21:11:42 +01:00
}
2023-06-07 20:45:53 +05:30
return i , nil
2020-10-20 15:49:45 +03:00
}
2019-07-31 17:16:10 +00:00
// Install fetches the necessary data locations and copies or extracts
// to the target locations.
2020-03-10 07:52:10 -07:00
//
2021-03-12 02:17:28 +03:00
//nolint:gocyclo,cyclop
2023-07-27 12:50:06 +04:00
func ( i * Installer ) Install ( ctx context . Context , mode Mode ) ( err error ) {
2022-11-24 21:45:08 +04:00
errataBTF ( )
2023-07-27 12:50:06 +04:00
if mode == ModeUpgrade {
2023-05-29 23:00:13 +04:00
if err = i . errataNetIfnames ( ) ; err != nil {
return err
}
}
2023-07-27 12:50:06 +04:00
if err = i . runPreflightChecks ( mode ) ; err != nil {
2022-11-24 22:48:22 +04:00
return err
}
2022-01-20 20:53:28 +03:00
if err = i . installExtensions ( ) ; err != nil {
return err
}
2020-10-16 17:00:40 +03:00
if err = i . manifest . Execute ( ) ; err != nil {
return err
2019-07-31 17:16:10 +00:00
}
2020-08-18 15:52:26 -07:00
// Mount the partitions.
2021-02-16 22:50:18 +03:00
mountpoints := mount . NewMountPoints ( )
2023-06-14 22:35:33 +04:00
var bootLabels [ ] string
if i . bootloader . UEFIBoot ( ) {
bootLabels = [ ] string { constants . EFIPartitionLabel }
} else {
bootLabels = [ ] string { constants . BootPartitionLabel , constants . EFIPartitionLabel }
}
for _ , label := range bootLabels {
2024-02-09 17:42:50 +03:00
if err = func ( ) error {
2021-02-16 22:50:18 +03:00
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
}
}
}
2020-03-10 07:52:10 -07:00
2021-02-16 22:50:18 +03:00
if device == "" {
return fmt . Errorf ( "failed to detect %s target device" , label )
}
var bd * blockdevice . BlockDevice
2024-01-31 22:17:20 +04:00
bd , err = retryBlockdeviceOpen ( device )
2021-02-16 22:50:18 +03:00
if err != nil {
return err
}
defer bd . Close ( ) //nolint:errcheck
var mountpoint * mount . Point
2023-08-31 22:21:41 +04:00
mountpoint , err = mount . SystemMountPointForLabel ( ctx , bd , label , mount . WithPrefix ( i . options . MountPrefix ) )
2021-02-16 22:50:18 +03:00
if err != nil {
return err
}
mountpoints . Set ( label , mountpoint )
return nil
2024-02-09 17:42:50 +03:00
} ( ) ; err != nil {
2021-02-16 22:50:18 +03:00
return err
}
2020-03-10 07:52:10 -07:00
}
2019-08-08 00:16:43 -05:00
2020-08-18 15:52:26 -07:00
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 )
}
} ( )
2019-07-31 17:16:10 +00:00
// Install the bootloader.
2023-07-27 12:50:06 +04:00
if err = i . bootloader . Install ( bootloaderoptions . InstallOptions {
2023-08-31 22:21:41 +04:00
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 ,
2023-07-27 12:50:06 +04:00
} ) ; err != nil {
2020-08-18 15:52:26 -07:00
return err
2020-03-10 07:52:10 -07:00
}
2019-11-05 23:13:50 +00:00
2020-11-25 18:00:02 +03:00
if i . options . Board != constants . BoardNone {
var b runtime . Board
b , err = board . NewBoard ( i . options . Board )
if err != nil {
return err
}
2023-08-04 23:11:16 +04:00
i . options . Printf ( "installing U-Boot for %q" , b . Name ( ) )
2020-11-25 18:00:02 +03:00
2023-10-27 21:55:38 +04:00
if err = b . Install ( runtime . BoardInstallOptions {
InstallDisk : i . options . Disk ,
2023-12-15 19:03:08 +04:00
MountPrefix : i . options . MountPrefix ,
2023-10-27 21:55:38 +04:00
UBootPath : i . options . BootAssets . UBootPath ,
DTBPath : i . options . BootAssets . DTBPath ,
RPiFirmwarePath : i . options . BootAssets . RPiFirmwarePath ,
Printf : i . options . Printf ,
} ) ; err != nil {
2020-11-25 18:00:02 +03:00
return err
}
}
2024-02-29 20:26:46 +05:30
if i . options . OverlayInstaller != nil {
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 err
}
}
2023-07-27 12:50:06 +04:00
if mode == ModeUpgrade || len ( i . options . MetaValues . values ) > 0 {
2023-11-01 20:34:41 +04:00
var (
metaState * meta . Meta
metaPartitionName string
)
2020-10-16 17:00:40 +03:00
2023-11-01 20:34:41 +04:00
for _ , targets := range i . manifest . Targets {
for _ , target := range targets {
if target . Label == constants . MetaPartitionLabel {
metaPartitionName = target . PartitionName
break
}
}
if metaPartitionName != "" {
break
}
}
if metaPartitionName == "" {
2024-02-13 15:32:06 +03:00
return errors . New ( "failed to detect META partition" )
2023-11-01 20:34:41 +04:00
}
if metaState , err = meta . New ( context . Background ( ) , nil , meta . WithPrinter ( i . options . Printf ) , meta . WithFixedPath ( metaPartitionName ) ) ; err != nil {
2020-10-16 17:00:40 +03:00
return err
}
2023-03-14 14:33:11 +04:00
var ok bool
2020-10-16 17:00:40 +03:00
2023-07-27 12:50:06 +04:00
if mode == ModeUpgrade {
2023-06-07 20:45:53 +05:30
if ok , err = metaState . SetTag ( context . Background ( ) , meta . Upgrade , i . bootloader . PreviousLabel ( ) ) ; ! ok || err != nil {
return fmt . Errorf ( "failed to set upgrade tag: %q" , i . bootloader . PreviousLabel ( ) )
2023-03-27 14:16:06 +04:00
}
}
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 )
}
2020-10-16 17:00:40 +03:00
}
2023-03-14 14:33:11 +04:00
if err = metaState . Flush ( ) ; err != nil {
2020-10-16 17:00:40 +03:00
return err
}
}
2020-03-10 07:52:10 -07:00
return nil
2019-07-31 17:16:10 +00:00
}
2022-11-24 22:48:22 +04:00
2023-07-27 12:50:06 +04:00
func ( i * Installer ) runPreflightChecks ( mode Mode ) error {
if mode != ModeUpgrade {
2022-11-24 22:48:22 +04:00
// 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 )
}
2024-01-31 22:17:20 +04:00
func retryBlockdeviceOpen ( device string ) ( * blockdevice . BlockDevice , error ) {
var bd * blockdevice . BlockDevice
err := retry . Constant ( 10 * time . Second , retry . WithUnits ( 100 * time . Millisecond ) ) . Retry ( func ( ) error {
var openErr error
bd , openErr = blockdevice . Open ( device )
2024-02-26 20:00:05 +04:00
if openErr == nil {
return nil
2024-01-31 22:17:20 +04:00
}
2024-02-26 20:00:05 +04:00
switch {
case os . IsNotExist ( openErr ) :
return retry . ExpectedError ( openErr )
2024-02-27 16:15:10 +04:00
case errors . Is ( openErr , syscall . ENODEV ) :
2024-02-26 20:00:05 +04:00
return retry . ExpectedError ( openErr )
default :
return nil
}
2024-01-31 22:17:20 +04:00
} )
return bd , err
}
2024-02-29 20:26:46 +05:30
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 ( )
}