blob: 31b7328216b647557585b83303c1678aa7a6cd3f [file] [log] [blame]
Lorenz Brun57479bb2021-10-26 14:01:06 +02001package core
2
3import (
Lorenz Brunad131882023-06-28 16:42:20 +02004 "bytes"
Lorenz Brun57479bb2021-10-26 14:01:06 +02005 "errors"
6 "fmt"
Lorenz Brunad131882023-06-28 16:42:20 +02007 "math"
Lorenz Brun57479bb2021-10-26 14:01:06 +02008 "os"
9
Lorenz Brun57479bb2021-10-26 14:01:06 +020010 "google.golang.org/protobuf/proto"
Serge Bazanski44d2ad42021-11-10 17:05:34 +010011
Lorenz Brunad131882023-06-28 16:42:20 +020012 "source.monogon.dev/metropolis/pkg/blockdev"
13 "source.monogon.dev/metropolis/pkg/fat32"
14 "source.monogon.dev/metropolis/pkg/gpt"
Lorenz Brun57479bb2021-10-26 14:01:06 +020015 "source.monogon.dev/metropolis/proto/api"
16)
17
Lorenz Brun57479bb2021-10-26 14:01:06 +020018type MakeInstallerImageArgs struct {
19 // Path to either a file or a disk which will contain the installer data.
20 TargetPath string
21
22 // Reader for the installer EFI executable. Mandatory.
Lorenz Brunad131882023-06-28 16:42:20 +020023 Installer fat32.SizedReader
Lorenz Brun57479bb2021-10-26 14:01:06 +020024
25 // Optional NodeParameters to be embedded for use by the installer.
26 NodeParams *api.NodeParameters
27
28 // Optional Reader for a Metropolis bundle for use by the installer.
Lorenz Brunad131882023-06-28 16:42:20 +020029 Bundle fat32.SizedReader
Lorenz Brun57479bb2021-10-26 14:01:06 +020030}
31
Lorenz Brunad131882023-06-28 16:42:20 +020032// MakeInstallerImage generates an installer disk image containing a Table
Lorenz Brun57479bb2021-10-26 14:01:06 +020033// partition table and a single FAT32 partition with an installer and optionally
34// with a bundle and/or Node Parameters.
35func MakeInstallerImage(args MakeInstallerImageArgs) error {
36 if args.Installer == nil {
37 return errors.New("Installer is mandatory")
38 }
Lorenz Brun57479bb2021-10-26 14:01:06 +020039
Lorenz Brunad131882023-06-28 16:42:20 +020040 espRoot := fat32.Inode{Attrs: fat32.AttrDirectory}
Lorenz Brun57479bb2021-10-26 14:01:06 +020041
Lorenz Brun57479bb2021-10-26 14:01:06 +020042 // This needs to be a "Removable Media" according to the UEFI Specification
43 // V2.9 Section 3.5.1.1. This file is booted by any compliant UEFI firmware
44 // in absence of another bootable boot entry.
Lorenz Brunad131882023-06-28 16:42:20 +020045 if err := espRoot.PlaceFile("EFI/BOOT/BOOTx64.EFI", args.Installer); err != nil {
46 return err
Lorenz Brun57479bb2021-10-26 14:01:06 +020047 }
Lorenz Brunad131882023-06-28 16:42:20 +020048
Lorenz Brun57479bb2021-10-26 14:01:06 +020049 if args.NodeParams != nil {
Lorenz Brunad131882023-06-28 16:42:20 +020050 nodeParamsRaw, err := proto.Marshal(args.NodeParams)
Lorenz Brun57479bb2021-10-26 14:01:06 +020051 if err != nil {
Lorenz Brunad131882023-06-28 16:42:20 +020052 return fmt.Errorf("failed to marshal node params: %w", err)
Lorenz Brun57479bb2021-10-26 14:01:06 +020053 }
Lorenz Brunad131882023-06-28 16:42:20 +020054 if err := espRoot.PlaceFile("metropolis-installer/nodeparams.pb", bytes.NewReader(nodeParamsRaw)); err != nil {
55 return err
Lorenz Brun57479bb2021-10-26 14:01:06 +020056 }
57 }
58 if args.Bundle != nil {
Lorenz Brunad131882023-06-28 16:42:20 +020059 if err := espRoot.PlaceFile("metropolis-installer/bundle.bin", args.Bundle); err != nil {
60 return err
61 }
62 }
63 var targetDev blockdev.BlockDev
64 var err error
65 targetDev, err = blockdev.Open(args.TargetPath)
66 if err != nil {
67 if errors.Is(err, os.ErrNotExist) {
68 targetDev, err = blockdev.CreateFile(args.TargetPath, 512, 1024*1024+4096)
69 }
Lorenz Brun57479bb2021-10-26 14:01:06 +020070 if err != nil {
Lorenz Brunad131882023-06-28 16:42:20 +020071 return fmt.Errorf("unable to open target device: %w", err)
Lorenz Brun57479bb2021-10-26 14:01:06 +020072 }
Lorenz Brunad131882023-06-28 16:42:20 +020073 }
74 partTable, err := gpt.New(targetDev)
75 if err != nil {
76 return fmt.Errorf("target device has invalid geometry: %w", err)
77 }
78 esp := gpt.Partition{
79 Type: gpt.PartitionTypeEFISystem,
80 Name: "MetropolisInstaller",
81 }
82 fatOpts := fat32.Options{Label: "METRO_INST"}
83 // TODO(#254): Build and use dynamically-grown block devices
84 var espSize int64 = 512 * 1024 * 1024
85 if err := partTable.AddPartition(&esp, espSize); err != nil {
86 return fmt.Errorf("unable to create partition layout: %w", err)
87 }
88 if esp.BlockSize() > math.MaxUint16 {
89 return fmt.Errorf("block size (%d) too large for FAT32", esp.BlockSize())
90 }
91 fatOpts.BlockSize = uint16(esp.BlockSize())
92 fatOpts.BlockCount = uint32(esp.BlockCount())
93 if err := fat32.WriteFS(blockdev.NewRWS(esp), espRoot, fatOpts); err != nil {
94 return fmt.Errorf("failed to write FAT32: %w", err)
95 }
96 if err := partTable.Write(); err != nil {
97 return fmt.Errorf("unable to write partition table: %w", err)
Lorenz Brun57479bb2021-10-26 14:01:06 +020098 }
99 return nil
100}