| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame] | 1 | // Copyright The Monogon Project Authors. |
| 2 | // SPDX-License-Identifier: Apache-2.0 |
| 3 | |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 4 | package main |
| 5 | |
| 6 | import ( |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 7 | "context" |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 8 | _ "embed" |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 9 | "errors" |
| 10 | "fmt" |
| Tim Windelschmidt | 5832112 | 2024-09-10 02:26:03 +0200 | [diff] [blame] | 11 | "os" |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 12 | "path/filepath" |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 13 | "time" |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 14 | |
| 15 | "github.com/cenkalti/backoff/v4" |
| 16 | "google.golang.org/protobuf/proto" |
| 17 | |
| Tim Windelschmidt | b21bdf9 | 2025-05-28 18:37:35 +0200 | [diff] [blame] | 18 | apb "source.monogon.dev/cloud/agent/api" |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 19 | npb "source.monogon.dev/osbase/net/proto" |
| 20 | |
| Jan Schär | e19d279 | 2025-06-23 12:37:58 +0000 | [diff] [blame^] | 21 | metropolisInstall "source.monogon.dev/metropolis/installer/install" |
| Tim Windelschmidt | 9f21f53 | 2024-05-07 15:14:20 +0200 | [diff] [blame] | 22 | "source.monogon.dev/osbase/blockdev" |
| 23 | "source.monogon.dev/osbase/efivarfs" |
| Jan Schär | e19d279 | 2025-06-23 12:37:58 +0000 | [diff] [blame^] | 24 | "source.monogon.dev/osbase/oci/osimage" |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 25 | "source.monogon.dev/osbase/oci/registry" |
| Jan Schär | c1b6df4 | 2025-03-20 08:52:18 +0000 | [diff] [blame] | 26 | "source.monogon.dev/osbase/structfs" |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 27 | "source.monogon.dev/osbase/supervisor" |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 28 | ) |
| 29 | |
| Jan Schär | 69b7687 | 2025-05-14 16:39:47 +0000 | [diff] [blame] | 30 | //go:embed metropolis/node/abloader/abloader_bin.efi |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 31 | var abloader []byte |
| 32 | |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 33 | // install dispatches OSInstallationRequests to the appropriate installer |
| 34 | // method |
| Tim Windelschmidt | b21bdf9 | 2025-05-28 18:37:35 +0200 | [diff] [blame] | 35 | func install(ctx context.Context, req *apb.OSInstallationRequest, netConfig *npb.Net) error { |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 36 | switch reqT := req.Type.(type) { |
| Tim Windelschmidt | b21bdf9 | 2025-05-28 18:37:35 +0200 | [diff] [blame] | 37 | case *apb.OSInstallationRequest_Metropolis: |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 38 | return installMetropolis(ctx, reqT.Metropolis, netConfig) |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 39 | default: |
| 40 | return errors.New("unknown installation request type") |
| 41 | } |
| 42 | } |
| 43 | |
| Tim Windelschmidt | b21bdf9 | 2025-05-28 18:37:35 +0200 | [diff] [blame] | 44 | func installMetropolis(ctx context.Context, req *apb.MetropolisInstallationRequest, netConfig *npb.Net) error { |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 45 | l := supervisor.Logger(ctx) |
| Tim Windelschmidt | 5832112 | 2024-09-10 02:26:03 +0200 | [diff] [blame] | 46 | // Validate we are running via EFI. |
| 47 | if _, err := os.Stat("/sys/firmware/efi"); os.IsNotExist(err) { |
| Tim Windelschmidt | 1f51cf4 | 2024-10-01 17:04:28 +0200 | [diff] [blame] | 48 | // nolint:ST1005 |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 49 | return errors.New("Monogon OS can only be installed on EFI-booted machines, this one is not") |
| 50 | } |
| Tim Windelschmidt | fac4874 | 2023-04-24 19:04:55 +0200 | [diff] [blame] | 51 | |
| 52 | // Override the NodeParameters.NetworkConfig with the current NetworkConfig |
| 53 | // if it's missing. |
| 54 | if req.NodeParameters.NetworkConfig == nil { |
| 55 | req.NodeParameters.NetworkConfig = netConfig |
| 56 | } |
| 57 | |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 58 | if req.OsImage == nil { |
| 59 | return fmt.Errorf("missing OS image in OS installation request") |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 60 | } |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 61 | if req.OsImage.Digest == "" { |
| 62 | return fmt.Errorf("missing digest in OS installation request") |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 63 | } |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 64 | |
| 65 | client := ®istry.Client{ |
| 66 | GetBackOff: func() backoff.BackOff { |
| 67 | return backoff.NewExponentialBackOff() |
| 68 | }, |
| 69 | RetryNotify: func(err error, d time.Duration) { |
| 70 | l.Warningf("Error while fetching OS image, retrying in %v: %v", d, err) |
| 71 | }, |
| 72 | UserAgent: "Monogon-Cloud-Agent", |
| 73 | Scheme: req.OsImage.Scheme, |
| 74 | Host: req.OsImage.Host, |
| 75 | Repository: req.OsImage.Repository, |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 76 | } |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 77 | |
| 78 | image, err := client.Read(ctx, req.OsImage.Tag, req.OsImage.Digest) |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 79 | if err != nil { |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 80 | return fmt.Errorf("failed to fetch OS image: %w", err) |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 81 | } |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 82 | |
| Jan Schär | e19d279 | 2025-06-23 12:37:58 +0000 | [diff] [blame^] | 83 | osImage, err := osimage.Read(image) |
| Jan Schär | 4cc3d4d | 2025-04-14 11:46:47 +0000 | [diff] [blame] | 84 | if err != nil { |
| 85 | return fmt.Errorf("failed to fetch OS image: %w", err) |
| 86 | } |
| 87 | |
| 88 | efiPayload, err := osImage.Payload("kernel.efi") |
| 89 | if err != nil { |
| 90 | return fmt.Errorf("cannot open EFI payload in OS image: %w", err) |
| 91 | } |
| 92 | systemImage, err := osImage.Payload("system") |
| 93 | if err != nil { |
| 94 | return fmt.Errorf("cannot open system image in OS image: %w", err) |
| 95 | } |
| 96 | |
| 97 | l.Info("OS image config downloaded") |
| 98 | |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 99 | nodeParamsRaw, err := proto.Marshal(req.NodeParameters) |
| 100 | if err != nil { |
| 101 | return fmt.Errorf("failed marshaling: %w", err) |
| 102 | } |
| 103 | |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 104 | rootDev, err := blockdev.Open(filepath.Join("/dev", req.RootDevice)) |
| 105 | if err != nil { |
| 106 | return fmt.Errorf("failed to open root device: %w", err) |
| 107 | } |
| 108 | |
| Jan Schär | e19d279 | 2025-06-23 12:37:58 +0000 | [diff] [blame^] | 109 | installParams := metropolisInstall.Params{ |
| 110 | PartitionSize: metropolisInstall.PartitionSizeInfo{ |
| Lorenz Brun | 35fcf03 | 2023-06-29 04:15:58 +0200 | [diff] [blame] | 111 | ESP: 384, |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 112 | System: 4096, |
| 113 | Data: 128, |
| 114 | }, |
| Jan Schär | 4b88826 | 2025-05-13 09:12:03 +0000 | [diff] [blame] | 115 | Architecture: osImage.Config.ProductInfo.Architecture(), |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 116 | SystemImage: systemImage, |
| Jan Schär | c1b6df4 | 2025-03-20 08:52:18 +0000 | [diff] [blame] | 117 | EFIPayload: efiPayload, |
| 118 | ABLoader: structfs.Bytes(abloader), |
| 119 | NodeParameters: structfs.Bytes(nodeParamsRaw), |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 120 | Output: rootDev, |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 121 | } |
| 122 | |
| Jan Schär | e19d279 | 2025-06-23 12:37:58 +0000 | [diff] [blame^] | 123 | be, err := metropolisInstall.Write(&installParams) |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 124 | if err != nil { |
| 125 | return err |
| 126 | } |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 127 | bootEntryIdx, err := efivarfs.AddBootEntry(be) |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 128 | if err != nil { |
| 129 | return fmt.Errorf("error creating EFI boot entry: %w", err) |
| 130 | } |
| Lorenz Brun | 9933ef0 | 2023-07-06 18:28:29 +0200 | [diff] [blame] | 131 | if err := efivarfs.SetBootOrder(efivarfs.BootOrder{uint16(bootEntryIdx)}); err != nil { |
| Lorenz Brun | aadeb79 | 2023-03-27 15:53:56 +0200 | [diff] [blame] | 132 | return fmt.Errorf("error setting EFI boot order: %w", err) |
| 133 | } |
| 134 | l.Info("Metropolis installation completed") |
| 135 | return nil |
| 136 | } |