mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-19 13:41:13 +02:00
355 lines
10 KiB
Go
355 lines
10 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 (
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
|
|
"github.com/autonomy/talos/internal/pkg/blockdevice"
|
|
"github.com/autonomy/talos/internal/pkg/blockdevice/bootloader/syslinux"
|
|
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/vfat"
|
|
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/xfs"
|
|
"github.com/autonomy/talos/internal/pkg/blockdevice/probe"
|
|
"github.com/autonomy/talos/internal/pkg/blockdevice/table"
|
|
"github.com/autonomy/talos/internal/pkg/blockdevice/table/gpt/partition"
|
|
"github.com/autonomy/talos/internal/pkg/constants"
|
|
"github.com/autonomy/talos/internal/pkg/userdata"
|
|
"github.com/autonomy/talos/internal/pkg/version"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Prepare handles setting/consolidating/defaulting userdata pieces specific to
|
|
// installation
|
|
// TODO: See if this would be more appropriate in userdata
|
|
// nolint: dupl, gocyclo
|
|
func Prepare(data *userdata.UserData) (err error) {
|
|
if data.Install == nil {
|
|
return nil
|
|
}
|
|
|
|
// Root Device Init
|
|
if data.Install.Root.Device == "" {
|
|
return errors.Errorf("%s", "install.rootdevice is required")
|
|
}
|
|
|
|
if data.Install.Root.Size == 0 {
|
|
// Set to 1G default for funzies
|
|
data.Install.Root.Size = 2048 * 1000 * 1000
|
|
}
|
|
|
|
if len(data.Install.Root.Data) == 0 {
|
|
// Should probably have a canonical location to fetch rootfs - github?/s3?
|
|
// need to figure out how to download latest instead of hardcoding
|
|
data.Install.Root.Data = append(data.Install.Root.Data, "https://github.com/autonomy/talos/releases/download/"+version.Tag+"/rootfs.tar.gz")
|
|
}
|
|
|
|
// Data Device Init
|
|
if data.Install.Data.Device == "" {
|
|
data.Install.Data.Device = data.Install.Root.Device
|
|
}
|
|
|
|
if data.Install.Data.Size == 0 {
|
|
// Set to 1G default for funzies
|
|
data.Install.Data.Size = 1024 * 1000 * 1000
|
|
}
|
|
|
|
// Boot Device Init
|
|
if data.Install.Boot != nil {
|
|
if data.Install.Boot.Device == "" {
|
|
data.Install.Boot.Device = data.Install.Root.Device
|
|
}
|
|
if data.Install.Boot.Size == 0 {
|
|
// Set to 512MB default for funzies
|
|
data.Install.Boot.Size = 512 * 1000 * 1000
|
|
}
|
|
if len(data.Install.Boot.Data) == 0 {
|
|
data.Install.Boot.Data = append(data.Install.Boot.Data, "https://github.com/autonomy/talos/releases/download/"+version.Tag+"/vmlinuz")
|
|
data.Install.Boot.Data = append(data.Install.Boot.Data, "https://github.com/autonomy/talos/releases/download/"+version.Tag+"/initramfs.xz")
|
|
|
|
}
|
|
}
|
|
|
|
// Verify that the disks are unused
|
|
// Maybe a simple check against bd.UUID is more appropriate?
|
|
if !data.Install.Wipe {
|
|
var dev *probe.ProbedBlockDevice
|
|
for _, device := range []string{data.Install.Boot.Device, data.Install.Root.Device, data.Install.Data.Device} {
|
|
dev, err = probe.GetDevWithFileSystemLabel(device)
|
|
if err != nil {
|
|
// We continue here because we only care if we can discover the
|
|
// device successfully and confirm that the disk is not in use.
|
|
// TODO(andrewrynhard): We should return a custom error type here
|
|
// that we can use to confirm the device was not found.
|
|
continue
|
|
}
|
|
if dev.SuperBlock != nil {
|
|
return errors.Errorf("target install device %s is not empty, found existing %s file system", device, dev.SuperBlock.Type())
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a map of all the devices we need to be concerned with
|
|
devices := make(map[string]*Device)
|
|
labeldev := make(map[string]string)
|
|
|
|
// PR: Should we only allow boot device creation if data.Install.Wipe?
|
|
if data.Install.Boot.Device != "" {
|
|
devices[constants.BootPartitionLabel] = NewDevice(data.Install.Boot.Device,
|
|
constants.BootPartitionLabel,
|
|
data.Install.Boot.Size,
|
|
data.Install.Wipe,
|
|
false,
|
|
data.Install.Boot.Data)
|
|
labeldev[constants.BootPartitionLabel] = data.Install.Boot.Device
|
|
}
|
|
|
|
devices[constants.RootPartitionLabel] = NewDevice(data.Install.Root.Device,
|
|
constants.RootPartitionLabel,
|
|
data.Install.Root.Size,
|
|
data.Install.Wipe,
|
|
false,
|
|
data.Install.Root.Data)
|
|
labeldev[constants.RootPartitionLabel] = data.Install.Root.Device
|
|
|
|
devices[constants.DataPartitionLabel] = NewDevice(data.Install.Data.Device,
|
|
constants.DataPartitionLabel,
|
|
data.Install.Data.Size,
|
|
data.Install.Wipe,
|
|
false,
|
|
data.Install.Data.Data)
|
|
|
|
labeldev[constants.DataPartitionLabel] = data.Install.Data.Device
|
|
|
|
if data.Install.Wipe {
|
|
log.Println("Preparing to zero out devices")
|
|
var zero *os.File
|
|
zero, err = os.Open("/dev/zero")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Println("Calculating total disk usage")
|
|
diskSizes := make(map[string]uint, len(devices))
|
|
for _, dev := range devices {
|
|
// Adding 264*512b to cover partition table size
|
|
// In theory, a GUID Partition Table disk can be up to 264 sectors in a single logical block in length.
|
|
// Logical blocks are commonly 512 bytes or one sector in size.
|
|
// TODO verify this against gpt.go
|
|
diskSizes[dev.Name] += dev.Size + 164010
|
|
}
|
|
|
|
log.Println("Zeroing out each disk")
|
|
var f *os.File
|
|
for dev, size := range diskSizes {
|
|
f, err = os.OpenFile(dev, os.O_RDWR, os.ModeDevice)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
// Use the below to only open a block device once
|
|
uniqueDevices := make(map[string]*blockdevice.BlockDevice)
|
|
|
|
// Associate block device to a partition table. This allows us to
|
|
// make use of a single partition table across an entire block device.
|
|
log.Println("Opening block devices in preparation for partitioning")
|
|
partitionTables := make(map[*blockdevice.BlockDevice]table.PartitionTable)
|
|
for label, device := range labeldev {
|
|
if dev, ok := uniqueDevices[device]; ok {
|
|
devices[label].BlockDevice = dev
|
|
devices[label].PartitionTable = partitionTables[dev]
|
|
continue
|
|
}
|
|
|
|
if label == constants.BootPartitionLabel {
|
|
if err = syslinux.Prepare(device); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
var bd *blockdevice.BlockDevice
|
|
|
|
bd, err = blockdevice.Open(device, blockdevice.WithNewGPT(data.Install.Wipe))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// nolint: errcheck
|
|
defer bd.Close()
|
|
|
|
var pt table.PartitionTable
|
|
pt, err = bd.PartitionTable(!data.Install.Wipe)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
uniqueDevices[device] = bd
|
|
partitionTables[bd] = pt
|
|
|
|
devices[label].BlockDevice = bd
|
|
devices[label].PartitionTable = pt
|
|
}
|
|
|
|
// devices = Device
|
|
if data.Install.Wipe {
|
|
for _, label := range []string{constants.BootPartitionLabel, constants.RootPartitionLabel, constants.DataPartitionLabel} {
|
|
// Wipe disk
|
|
// Partition the disk
|
|
log.Printf("Partitioning %s - %s\n", devices[label].Name, label)
|
|
err = devices[label].Partition()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Installation/preparation necessary
|
|
if data.Install != nil {
|
|
|
|
// uniqueDevices = blockdevice
|
|
seen := make(map[string]interface{})
|
|
for _, dev := range devices {
|
|
if _, ok := seen[dev.Name]; ok {
|
|
continue
|
|
}
|
|
seen[dev.Name] = nil
|
|
|
|
err = dev.PartitionTable.Write()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create the device files
|
|
log.Printf("Reread Partition Table %s\n", dev.Name)
|
|
if err = dev.BlockDevice.RereadPartitionTable(); err != nil {
|
|
log.Println("break here?")
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
for _, dev := range devices {
|
|
// Create the filesystem
|
|
log.Printf("Formatting Partition %s - %s\n", dev.PartitionName, dev.Label)
|
|
err = dev.Format()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Device represents a single partition.
|
|
type Device struct {
|
|
DataURLs []string
|
|
Label string
|
|
MountBase string
|
|
Name string
|
|
|
|
// This seems overkill to save partition table
|
|
// when we can get partition table from BlockDevice
|
|
// but we want to have a shared partition table for each
|
|
// device so we can properly append partitions and have
|
|
// an atomic write partition operation
|
|
PartitionTable table.PartitionTable
|
|
|
|
// This guy might be overkill but we can clean up later
|
|
// Made up of Name + part.No(), so maybe it's worth
|
|
// just storing part.No() and adding a method d.PartName()
|
|
PartitionName string
|
|
|
|
Size uint
|
|
|
|
BlockDevice *blockdevice.BlockDevice
|
|
|
|
Force bool
|
|
Test bool
|
|
}
|
|
|
|
// NewDevice creates a Device with basic metadata. BlockDevice and PartitionTable
|
|
// need to be set outsite of this.
|
|
func NewDevice(name string, label string, size uint, force bool, test bool, data []string) *Device {
|
|
return &Device{
|
|
DataURLs: data,
|
|
Force: force,
|
|
Label: label,
|
|
MountBase: "/tmp",
|
|
Name: name,
|
|
Size: size,
|
|
Test: test,
|
|
}
|
|
}
|
|
|
|
// Partition creates a new partition on the specified device
|
|
// nolint: dupl
|
|
func (d *Device) Partition() error {
|
|
var (
|
|
typeID string
|
|
legacyBIOSBootable bool
|
|
)
|
|
switch d.Label {
|
|
case constants.BootPartitionLabel:
|
|
// EFI System Partition
|
|
typeID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
|
legacyBIOSBootable = true
|
|
case constants.RootPartitionLabel:
|
|
// Root Partition
|
|
switch runtime.GOARCH {
|
|
case "386":
|
|
typeID = "44479540-F297-41B2-9AF7-D131D5F0458A"
|
|
case "amd64":
|
|
typeID = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709"
|
|
default:
|
|
return errors.Errorf("%s", "unsupported cpu architecture")
|
|
}
|
|
case constants.DataPartitionLabel:
|
|
// Data Partition
|
|
typeID = "AF3DC60F-8384-7247-8E79-3D69D8477DE4"
|
|
default:
|
|
return errors.Errorf("%s", "unknown partition label")
|
|
}
|
|
|
|
part, err := d.PartitionTable.Add(
|
|
uint64(d.Size),
|
|
partition.WithPartitionType(typeID),
|
|
partition.WithPartitionName(d.Label),
|
|
partition.WithLegacyBIOSBootableAttribute(legacyBIOSBootable),
|
|
partition.WithPartitionTest(d.Test),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.PartitionName = d.Name + strconv.Itoa(int(part.No()))
|
|
|
|
return nil
|
|
}
|
|
|
|
// Format creates a xfs filesystem on the device/partition
|
|
func (d *Device) Format() error {
|
|
if d.Label == constants.BootPartitionLabel {
|
|
return vfat.MakeFS(d.PartitionName, vfat.WithLabel(d.Label))
|
|
}
|
|
return xfs.MakeFS(d.PartitionName, xfs.WithLabel(d.Label), xfs.WithForce(d.Force))
|
|
}
|