mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-19 05:31:14 +02:00
This change aims to make installations more unified and reliable. It introduces the concept of a mountpoint manager that is capable of mounting, unmounting, and moving a set of mountpoints in the correct order. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
253 lines
6.2 KiB
Go
253 lines
6.2 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 installer
|
|
|
|
import (
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"unsafe"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
|
"github.com/talos-systems/talos/internal/pkg/installer/bootloader/syslinux"
|
|
"github.com/talos-systems/talos/internal/pkg/installer/manifest"
|
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
|
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
|
"github.com/talos-systems/talos/internal/pkg/mount/manager/owned"
|
|
"github.com/talos-systems/talos/internal/pkg/version"
|
|
"github.com/talos-systems/talos/pkg/userdata"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const (
|
|
// DefaultSizeBootDevice is the default size of the boot partition.
|
|
// TODO(andrewrynhard): We should inspect the sizes of the artifacts and dynamically set the boot partition's size.
|
|
DefaultSizeBootDevice = 512 * 1000 * 1000
|
|
)
|
|
|
|
var (
|
|
// DefaultURLBase is the base URL for all default artifacts.
|
|
// TODO(andrewrynhard): We need to setup infrastructure for publishing artifacts and not depend on GitHub.
|
|
DefaultURLBase = "https://github.com/talos-systems/talos/releases/download/" + version.Tag
|
|
|
|
// DefaultKernelURL is the URL to the kernel.
|
|
DefaultKernelURL = DefaultURLBase + "/vmlinuz"
|
|
|
|
// DefaultInitramfsURL is the URL to the initramfs.
|
|
DefaultInitramfsURL = DefaultURLBase + "/initramfs.xz"
|
|
)
|
|
|
|
// Installer represents the installer logic. It serves as the entrypoint to all
|
|
// installation methods.
|
|
type Installer struct {
|
|
cmdline *kernel.Cmdline
|
|
data *userdata.UserData
|
|
manifest *manifest.Manifest
|
|
}
|
|
|
|
// NewInstaller initializes and returns an Installer.
|
|
func NewInstaller(cmdline *kernel.Cmdline, data *userdata.UserData) *Installer {
|
|
i := &Installer{
|
|
cmdline: cmdline,
|
|
data: data,
|
|
}
|
|
|
|
i.manifest = manifest.NewManifest(data)
|
|
|
|
return i
|
|
}
|
|
|
|
// Install fetches the necessary data locations and copies or extracts
|
|
// to the target locations.
|
|
// nolint: gocyclo
|
|
func (i *Installer) Install() (err error) {
|
|
if i.data.Install == nil {
|
|
return nil
|
|
}
|
|
|
|
if i.data.Install.Boot != nil {
|
|
var ok bool
|
|
if ok, err = exists(i.data.Install.Boot.InstallDevice.Device); err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
log.Println("found existing installation")
|
|
var mountpoints *mount.Points
|
|
mountpoints, err = owned.MountPointsFromLabels()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m := manager.NewManager(mountpoints)
|
|
if err = m.MountAll(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if err = VerifyBootDevice(i.data); err != nil {
|
|
return errors.Wrap(err, "failed to prepare boot device")
|
|
}
|
|
}
|
|
|
|
// Verify that the target device(s) can satisify the requested options.
|
|
|
|
if err = VerifyDataDevice(i.data); err != nil {
|
|
return errors.Wrap(err, "failed to prepare data device")
|
|
}
|
|
|
|
if i.data.Install.Wipe {
|
|
if err = wipe(i.manifest); err != nil {
|
|
return errors.Wrap(err, "failed to wipe device(s)")
|
|
}
|
|
}
|
|
|
|
// Partition and format the block device(s).
|
|
|
|
if err = i.manifest.ExecuteManifest(i.data, i.manifest); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Mount the partitions.
|
|
|
|
var mountpoints *mount.Points
|
|
if i.data.Install.Boot != nil {
|
|
mountpoints, err = owned.MountPointsForDevice(i.data.Install.Boot.InstallDevice.Device)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
mountpoints, err = owned.MountPointsFromLabels()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
m := manager.NewManager(mountpoints)
|
|
if err = m.MountAll(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Install the assets.
|
|
|
|
for _, targets := range i.manifest.Targets {
|
|
for _, target := range targets {
|
|
switch target.Label {
|
|
case constants.BootPartitionLabel:
|
|
// Install the bootloader.
|
|
if err = syslinux.Prepare(target.Device); err != nil {
|
|
return err
|
|
}
|
|
case constants.DataPartitionLabel:
|
|
continue
|
|
}
|
|
|
|
// Handle the download and extraction of assets.
|
|
if err = target.Save(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Install the bootloader.
|
|
|
|
if i.data.Install.Boot == nil {
|
|
return nil
|
|
}
|
|
|
|
syslinuxcfg := &syslinux.Cfg{
|
|
Default: "default",
|
|
Labels: []*syslinux.Label{
|
|
{
|
|
Root: "default",
|
|
Kernel: filepath.Join("/", "default", filepath.Base(i.data.Install.Boot.Kernel)),
|
|
Initrd: filepath.Join("/", "default", filepath.Base(i.data.Install.Boot.Initramfs)),
|
|
Append: i.cmdline.String(),
|
|
},
|
|
},
|
|
}
|
|
|
|
if err = syslinux.Install(filepath.Join(constants.BootMountPoint), syslinuxcfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = ioutil.WriteFile(filepath.Join(constants.BootMountPoint, "installed"), []byte{}, 0400); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func wipe(manifest *manifest.Manifest) (err error) {
|
|
var zero *os.File
|
|
if zero, err = os.Open("/dev/zero"); err != nil {
|
|
return err
|
|
}
|
|
|
|
for dev := range manifest.Targets {
|
|
var f *os.File
|
|
if f, err = os.OpenFile(dev, os.O_RDWR, os.ModeDevice); err != nil {
|
|
return err
|
|
}
|
|
var size uint64
|
|
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); ret != 0 {
|
|
return errors.Errorf("failed to got block device size: %v", ret)
|
|
}
|
|
if _, err = io.CopyN(f, zero, int64(size)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = f.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err = zero.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func exists(devpath string) (bool, error) {
|
|
var (
|
|
err error
|
|
dev *probe.ProbedBlockDevice
|
|
)
|
|
|
|
if dev, err = probe.DevForFileSystemLabel(devpath, constants.BootPartitionLabel); err == nil {
|
|
// nolint: errcheck
|
|
defer dev.Close()
|
|
if dev.SuperBlock != nil {
|
|
mountpoint := mount.NewMountPoint(dev.Path, "/tmp", dev.SuperBlock.Type(), 0, "")
|
|
if err = mountpoint.Mount(); err != nil {
|
|
return false, err
|
|
}
|
|
defer func() {
|
|
if err = mountpoint.Unmount(); err != nil {
|
|
log.Printf("WARNING: failed to unmount %s from /tmp", dev.Path)
|
|
}
|
|
}()
|
|
_, err = os.Stat(filepath.Join("tmp", "installed"))
|
|
switch {
|
|
case err == nil:
|
|
return true, nil
|
|
case os.IsNotExist(err):
|
|
return false, nil
|
|
default:
|
|
return false, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|