mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-17 12:07:05 +02:00
This change ensures that any symlinks under `/dev/disks` are created by udevd early enough so that users can specify disks identified by these symlinks in `machine.disks` entries. This change also moves the udevd-trigger from a service to a post-startup task for udevd. This is a one time command that doesn't need to be a service. Some other minor changes were made around parsing the partition name that makes use of the symlinks created by udevd. This also ensures that target mount points in `machine.disks` entries are created, fixing a preexisting bug. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
356 lines
8.1 KiB
Go
356 lines
8.1 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 (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"unsafe"
|
|
|
|
"github.com/talos-systems/go-procfs/procfs"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
|
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/syslinux"
|
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
|
"github.com/talos-systems/talos/pkg/blockdevice"
|
|
"github.com/talos-systems/talos/pkg/blockdevice/probe"
|
|
"github.com/talos-systems/talos/pkg/blockdevice/table"
|
|
"github.com/talos-systems/talos/pkg/blockdevice/util"
|
|
"github.com/talos-systems/talos/pkg/constants"
|
|
"github.com/talos-systems/talos/pkg/version"
|
|
)
|
|
|
|
// Options represents the set of options available for an install.
|
|
type Options struct {
|
|
ConfigSource string
|
|
Disk string
|
|
Platform string
|
|
ExtraKernelArgs []string
|
|
Bootloader bool
|
|
Upgrade bool
|
|
Force bool
|
|
Zero bool
|
|
Save bool
|
|
}
|
|
|
|
// Install installs Talos.
|
|
func Install(p runtime.Platform, seq runtime.Sequence, opts *Options) (err error) {
|
|
cmdline := procfs.NewCmdline("")
|
|
cmdline.Append(constants.KernelParamPlatform, p.Name())
|
|
cmdline.Append(constants.KernelParamConfig, opts.ConfigSource)
|
|
|
|
if err = cmdline.AppendAll(p.KernelArgs().Strings()); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = cmdline.AppendAll(opts.ExtraKernelArgs); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmdline.AppendDefaults()
|
|
|
|
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
|
|
Current string
|
|
Next string
|
|
|
|
bootPartitionFound bool
|
|
}
|
|
|
|
// NewInstaller initializes and returns an Installer.
|
|
//
|
|
// nolint: gocyclo
|
|
func NewInstaller(cmdline *procfs.Cmdline, seq runtime.Sequence, opts *Options) (i *Installer, err error) {
|
|
i = &Installer{
|
|
cmdline: cmdline,
|
|
options: opts,
|
|
}
|
|
|
|
var dev *probe.ProbedBlockDevice
|
|
|
|
if dev, err = probe.GetDevWithFileSystemLabel(constants.BootPartitionLabel); err != nil {
|
|
i.bootPartitionFound = false
|
|
} else {
|
|
i.bootPartitionFound = true
|
|
}
|
|
|
|
if seq == runtime.SequenceUpgrade && i.bootPartitionFound {
|
|
if err = os.MkdirAll("/boot", 0777); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = unix.Mount(dev.Path, "/boot", dev.SuperBlock.Type(), 0, ""); err != nil {
|
|
return nil, fmt.Errorf("failed to mount /boot: %w", err)
|
|
}
|
|
}
|
|
|
|
i.Current, i.Next, err = syslinux.Labels()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
label := i.Current
|
|
|
|
if seq == runtime.SequenceUpgrade && i.bootPartitionFound {
|
|
label = i.Next
|
|
}
|
|
|
|
i.manifest, err = NewManifest(label, seq, i.options)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create installation manifest: %w", err)
|
|
}
|
|
|
|
return i, nil
|
|
}
|
|
|
|
// Install fetches the necessary data locations and copies or extracts
|
|
// to the target locations.
|
|
//
|
|
// nolint: gocyclo
|
|
func (i *Installer) Install(seq runtime.Sequence) (err error) {
|
|
if seq != runtime.SequenceUpgrade || !i.bootPartitionFound {
|
|
if i.options.Zero {
|
|
if err = zero(i.manifest); err != nil {
|
|
return fmt.Errorf("failed to wipe device(s): %w", err)
|
|
}
|
|
}
|
|
|
|
// Partition and format the block device(s).
|
|
if err = i.manifest.ExecuteManifest(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Mount the partitions.
|
|
|
|
mountpoints := mount.NewMountPoints()
|
|
// look for mountpoints across all target devices
|
|
for dev := range i.manifest.Targets {
|
|
var mp *mount.Points
|
|
|
|
mp, err = mount.SystemMountPointsForDevice(dev)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
iter := mp.Iter()
|
|
for iter.Next() {
|
|
mountpoints.Set(iter.Key(), iter.Value())
|
|
}
|
|
}
|
|
|
|
if err = mount.Mount(mountpoints); err != nil {
|
|
return err
|
|
}
|
|
|
|
// nolint: errcheck
|
|
defer mount.Unmount(mountpoints)
|
|
}
|
|
|
|
if seq == runtime.SequenceUpgrade && i.bootPartitionFound && i.options.Force {
|
|
for dev, targets := range i.manifest.Targets {
|
|
var bd *blockdevice.BlockDevice
|
|
|
|
if bd, err = blockdevice.Open(dev); err != nil {
|
|
return err
|
|
}
|
|
|
|
// nolint: errcheck
|
|
defer bd.Close()
|
|
|
|
var pt table.PartitionTable
|
|
|
|
pt, err = bd.PartitionTable(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, target := range targets {
|
|
for _, part := range pt.Partitions() {
|
|
switch target.Label {
|
|
case constants.BootPartitionLabel, constants.EphemeralPartitionLabel:
|
|
target.PartitionName, err = util.PartPath(target.Device, int(part.No()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if target.Label == constants.BootPartitionLabel {
|
|
continue
|
|
}
|
|
|
|
if err = target.Format(); err != nil {
|
|
return fmt.Errorf("failed to format device: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Install the assets.
|
|
|
|
for _, targets := range i.manifest.Targets {
|
|
for _, target := range targets {
|
|
switch target.Label {
|
|
case constants.BootPartitionLabel:
|
|
if err = syslinux.Prepare(target.Device); err != nil {
|
|
return err
|
|
}
|
|
case constants.EphemeralPartitionLabel:
|
|
continue
|
|
}
|
|
|
|
// Handle the download and extraction of assets.
|
|
if err = target.Save(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Install the bootloader.
|
|
|
|
if !i.options.Bootloader {
|
|
return nil
|
|
}
|
|
|
|
if seq != runtime.SequenceUpgrade || !i.bootPartitionFound {
|
|
i.cmdline.Append("initrd", filepath.Join("/", i.Current, constants.InitramfsAsset))
|
|
|
|
syslinuxcfg := &syslinux.Cfg{
|
|
Default: i.Current,
|
|
Labels: []*syslinux.Label{
|
|
{
|
|
Root: i.Current,
|
|
Initrd: filepath.Join("/", i.Current, constants.InitramfsAsset),
|
|
Kernel: filepath.Join("/", i.Current, constants.KernelAsset),
|
|
Append: i.cmdline.String(),
|
|
},
|
|
},
|
|
}
|
|
|
|
if err = syslinux.Install("", syslinuxcfg, seq, i.bootPartitionFound); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
i.cmdline.Append("initrd", filepath.Join("/", i.Next, constants.InitramfsAsset))
|
|
|
|
syslinuxcfg := &syslinux.Cfg{
|
|
Default: i.Next,
|
|
Labels: []*syslinux.Label{
|
|
{
|
|
Root: i.Next,
|
|
Initrd: filepath.Join("/", i.Next, constants.InitramfsAsset),
|
|
Kernel: filepath.Join("/", i.Next, constants.KernelAsset),
|
|
Append: i.cmdline.String(),
|
|
},
|
|
{
|
|
Root: i.Current,
|
|
Initrd: filepath.Join("/", i.Current, constants.InitramfsAsset),
|
|
Kernel: filepath.Join("/", i.Current, constants.KernelAsset),
|
|
Append: procfs.ProcCmdline().String(),
|
|
},
|
|
},
|
|
}
|
|
|
|
if err = syslinux.Install(i.Current, syslinuxcfg, seq, i.bootPartitionFound); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if i.options.Save {
|
|
u, err := url.Parse(i.options.ConfigSource)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if u.Scheme != "file" {
|
|
return fmt.Errorf("file:// scheme must be used with the save option, have %s", u.Scheme)
|
|
}
|
|
|
|
src, err := os.Open(u.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// nolint: errcheck
|
|
defer src.Close()
|
|
|
|
dst, err := os.OpenFile(constants.ConfigPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// nolint: errcheck
|
|
defer dst.Close()
|
|
|
|
_, err = io.Copy(dst, src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func zero(manifest *Manifest) (err error) {
|
|
var zero *os.File
|
|
|
|
if zero, err = os.Open("/dev/zero"); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer zero.Close() //nolint: errcheck
|
|
|
|
for dev := range manifest.Targets {
|
|
if err = func(dev string) error {
|
|
var f *os.File
|
|
|
|
if f, err = os.OpenFile(dev, os.O_RDWR, os.ModeDevice); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer f.Close() //nolint: errcheck
|
|
|
|
var size uint64
|
|
|
|
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); ret != 0 {
|
|
return fmt.Errorf("failed to got block device size: %v", ret)
|
|
}
|
|
|
|
if _, err = io.CopyN(f, zero, int64(size)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return f.Close()
|
|
}(dev); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return zero.Close()
|
|
}
|