mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-11 07:31:18 +02:00
The extra disks functionality was completely broken. One fundamental issue was that we were attempting to create and mount the partitions before the system disk was created. This moves the extra disks tasks to the correct part of the boot sequnce. This also adds a simple check that refuses to operate on a disk if any partitions are found. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
248 lines
6.3 KiB
Go
248 lines
6.3 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 manifest
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/talos-systems/talos/pkg/blockdevice"
|
|
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/vfat"
|
|
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/xfs"
|
|
"github.com/talos-systems/talos/pkg/blockdevice/table"
|
|
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt/partition"
|
|
"github.com/talos-systems/talos/pkg/blockdevice/util"
|
|
"github.com/talos-systems/talos/pkg/config/machine"
|
|
"github.com/talos-systems/talos/pkg/constants"
|
|
)
|
|
|
|
const (
|
|
// DefaultSizeBootDevice is the default size of the boot partition.
|
|
DefaultSizeBootDevice = 512 * 1000 * 1000
|
|
)
|
|
|
|
// Manifest represents the instructions for preparing all block devices
|
|
// for an installation.
|
|
type Manifest struct {
|
|
Targets map[string][]*Target
|
|
}
|
|
|
|
// Target represents an installation partition.
|
|
type Target struct {
|
|
Label string
|
|
Device string
|
|
FileSystemType string
|
|
PartitionName string
|
|
Size uint
|
|
Force bool
|
|
Test bool
|
|
Assets []*Asset
|
|
BlockDevice *blockdevice.BlockDevice
|
|
}
|
|
|
|
// Asset represents a file required by a target.
|
|
type Asset struct {
|
|
Source string
|
|
Destination string
|
|
}
|
|
|
|
// NewManifest initializes and returns a Manifest.
|
|
func NewManifest(install machine.Install) (manifest *Manifest, err error) {
|
|
manifest = &Manifest{
|
|
Targets: map[string][]*Target{},
|
|
}
|
|
|
|
// Verify that the target device(s) can satisify the requested options.
|
|
|
|
if err = VerifyDataDevice(install); err != nil {
|
|
return nil, fmt.Errorf("failed to prepare ephemeral partition: %w", err)
|
|
}
|
|
|
|
if err = VerifyBootDevice(install); err != nil {
|
|
return nil, fmt.Errorf("failed to prepare boot partition: %w", err)
|
|
}
|
|
|
|
// Initialize any slices we need. Note that a boot paritition is not
|
|
// required.
|
|
|
|
if manifest.Targets[install.Disk()] == nil {
|
|
manifest.Targets[install.Disk()] = []*Target{}
|
|
}
|
|
|
|
var bootTarget *Target
|
|
if install.WithBootloader() {
|
|
bootTarget = &Target{
|
|
Device: install.Disk(),
|
|
Label: constants.BootPartitionLabel,
|
|
Size: 512 * 1024 * 1024,
|
|
Force: true,
|
|
Test: false,
|
|
Assets: []*Asset{
|
|
{
|
|
Source: constants.KernelAssetPath,
|
|
Destination: filepath.Join(constants.BootMountPoint, "default", constants.KernelAsset),
|
|
},
|
|
{
|
|
Source: constants.InitramfsAssetPath,
|
|
Destination: filepath.Join(constants.BootMountPoint, "default", constants.InitramfsAsset),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
ephemeralTarget := &Target{
|
|
Device: install.Disk(),
|
|
Label: constants.EphemeralPartitionLabel,
|
|
Size: 16 * 1024 * 1024,
|
|
Force: true,
|
|
Test: false,
|
|
}
|
|
|
|
for _, target := range []*Target{bootTarget, ephemeralTarget} {
|
|
if target == nil {
|
|
continue
|
|
}
|
|
|
|
manifest.Targets[target.Device] = append(manifest.Targets[target.Device], target)
|
|
}
|
|
|
|
return manifest, nil
|
|
}
|
|
|
|
// ExecuteManifest partitions and formats all disks in a manifest.
|
|
func (m *Manifest) ExecuteManifest() (err error) {
|
|
for dev, targets := range m.Targets {
|
|
var bd *blockdevice.BlockDevice
|
|
|
|
if bd, err = blockdevice.Open(dev, blockdevice.WithNewGPT(true)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// nolint: errcheck
|
|
defer bd.Close()
|
|
|
|
for _, target := range targets {
|
|
if err = target.Partition(bd); err != nil {
|
|
return fmt.Errorf("failed to partition device: %w", err)
|
|
}
|
|
}
|
|
|
|
for _, target := range targets {
|
|
if err = target.Format(); err != nil {
|
|
return fmt.Errorf("failed to format device: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Partition creates a new partition on the specified device.
|
|
// nolint: dupl, gocyclo
|
|
func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
|
|
log.Printf("partitioning %s - %s\n", t.Device, t.Label)
|
|
|
|
var pt table.PartitionTable
|
|
|
|
if pt, err = bd.PartitionTable(true); err != nil {
|
|
return err
|
|
}
|
|
|
|
opts := []interface{}{}
|
|
|
|
switch t.Label {
|
|
case constants.BootPartitionLabel:
|
|
// EFI System Partition
|
|
typeID := "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
|
opts = append(opts, partition.WithPartitionType(typeID), partition.WithPartitionName(t.Label), partition.WithLegacyBIOSBootableAttribute(true))
|
|
case constants.EphemeralPartitionLabel:
|
|
// Ephemeral Partition
|
|
typeID := "AF3DC60F-8384-7247-8E79-3D69D8477DE4"
|
|
opts = append(opts, partition.WithPartitionType(typeID), partition.WithPartitionName(t.Label))
|
|
default:
|
|
typeID := "AF3DC60F-8384-7247-8E79-3D69D8477DE4"
|
|
opts = append(opts, partition.WithPartitionType(typeID))
|
|
}
|
|
|
|
part, err := pt.Add(uint64(t.Size), opts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = pt.Write(); err != nil {
|
|
return err
|
|
}
|
|
|
|
t.PartitionName = util.PartPath(t.Device, int(part.No()))
|
|
|
|
return nil
|
|
}
|
|
|
|
// Format creates a filesystem on the device/partition.
|
|
func (t *Target) Format() error {
|
|
if t.Label == constants.BootPartitionLabel {
|
|
log.Printf("formatting partition %s - %s as %s\n", t.PartitionName, t.Label, "fat")
|
|
return vfat.MakeFS(t.PartitionName, vfat.WithLabel(t.Label))
|
|
}
|
|
|
|
log.Printf("formatting partition %s - %s as %s\n", t.PartitionName, t.Label, "xfs")
|
|
opts := []xfs.Option{xfs.WithForce(t.Force)}
|
|
|
|
if t.Label != "" {
|
|
opts = append(opts, xfs.WithLabel(t.Label))
|
|
}
|
|
|
|
return xfs.MakeFS(t.PartitionName, opts...)
|
|
}
|
|
|
|
// Save copies the assets to the bootloader partition.
|
|
func (t *Target) Save() (err error) {
|
|
for _, asset := range t.Assets {
|
|
var (
|
|
sourceFile *os.File
|
|
destFile *os.File
|
|
)
|
|
|
|
if sourceFile, err = os.Open(asset.Source); err != nil {
|
|
return err
|
|
}
|
|
// nolint: errcheck
|
|
defer sourceFile.Close()
|
|
|
|
if err = os.MkdirAll(filepath.Dir(asset.Destination), os.ModeDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
if destFile, err = os.Create(asset.Destination); err != nil {
|
|
return err
|
|
}
|
|
|
|
// nolint: errcheck
|
|
defer destFile.Close()
|
|
|
|
log.Printf("copying %s to %s\n", sourceFile.Name(), destFile.Name())
|
|
|
|
if _, err = io.Copy(destFile, sourceFile); err != nil {
|
|
log.Printf("failed to copy %s to %s\n", sourceFile.Name(), destFile.Name())
|
|
return err
|
|
}
|
|
|
|
if err = destFile.Close(); err != nil {
|
|
log.Printf("failed to close %s", destFile.Name())
|
|
return err
|
|
}
|
|
|
|
if err = sourceFile.Close(); err != nil {
|
|
log.Printf("failed to close %s", sourceFile.Name())
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|