blob: 06935956958bb11c58c0fbac67aaf9066b2b8068 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Lorenz Brunaadeb792023-03-27 15:53:56 +02004package main
5
6import (
Jan Schär4cc3d4d2025-04-14 11:46:47 +00007 "context"
Lorenz Brun54a5a052023-10-02 16:40:11 +02008 _ "embed"
Lorenz Brunaadeb792023-03-27 15:53:56 +02009 "errors"
10 "fmt"
Tim Windelschmidt58321122024-09-10 02:26:03 +020011 "os"
Lorenz Brunaadeb792023-03-27 15:53:56 +020012 "path/filepath"
Jan Schär4cc3d4d2025-04-14 11:46:47 +000013 "time"
Lorenz Brunaadeb792023-03-27 15:53:56 +020014
15 "github.com/cenkalti/backoff/v4"
16 "google.golang.org/protobuf/proto"
17
18 bpb "source.monogon.dev/cloud/bmaas/server/api"
Jan Schär4cc3d4d2025-04-14 11:46:47 +000019 npb "source.monogon.dev/osbase/net/proto"
20
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020021 "source.monogon.dev/osbase/blockdev"
Tim Windelschmidtc2290c22024-08-15 19:56:00 +020022 "source.monogon.dev/osbase/build/mkimage/osimage"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020023 "source.monogon.dev/osbase/efivarfs"
Jan Schär4cc3d4d2025-04-14 11:46:47 +000024 ociosimage "source.monogon.dev/osbase/oci/osimage"
25 "source.monogon.dev/osbase/oci/registry"
Jan Schärc1b6df42025-03-20 08:52:18 +000026 "source.monogon.dev/osbase/structfs"
Jan Schär4cc3d4d2025-04-14 11:46:47 +000027 "source.monogon.dev/osbase/supervisor"
Lorenz Brunaadeb792023-03-27 15:53:56 +020028)
29
Tim Windelschmidt1f51cf42024-10-01 17:04:28 +020030//go:embed metropolis/node/core/abloader/abloader.efi
Lorenz Brun54a5a052023-10-02 16:40:11 +020031var abloader []byte
32
Lorenz Brunaadeb792023-03-27 15:53:56 +020033// install dispatches OSInstallationRequests to the appropriate installer
34// method
Jan Schär4cc3d4d2025-04-14 11:46:47 +000035func install(ctx context.Context, req *bpb.OSInstallationRequest, netConfig *npb.Net) error {
Lorenz Brunaadeb792023-03-27 15:53:56 +020036 switch reqT := req.Type.(type) {
37 case *bpb.OSInstallationRequest_Metropolis:
Jan Schär4cc3d4d2025-04-14 11:46:47 +000038 return installMetropolis(ctx, reqT.Metropolis, netConfig)
Lorenz Brunaadeb792023-03-27 15:53:56 +020039 default:
40 return errors.New("unknown installation request type")
41 }
42}
43
Jan Schär4cc3d4d2025-04-14 11:46:47 +000044func installMetropolis(ctx context.Context, req *bpb.MetropolisInstallationRequest, netConfig *npb.Net) error {
45 l := supervisor.Logger(ctx)
Tim Windelschmidt58321122024-09-10 02:26:03 +020046 // Validate we are running via EFI.
47 if _, err := os.Stat("/sys/firmware/efi"); os.IsNotExist(err) {
Tim Windelschmidt1f51cf42024-10-01 17:04:28 +020048 // nolint:ST1005
Lorenz Brunaadeb792023-03-27 15:53:56 +020049 return errors.New("Monogon OS can only be installed on EFI-booted machines, this one is not")
50 }
Tim Windelschmidtfac48742023-04-24 19:04:55 +020051
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är4cc3d4d2025-04-14 11:46:47 +000058 if req.OsImage == nil {
59 return fmt.Errorf("missing OS image in OS installation request")
Lorenz Brunaadeb792023-03-27 15:53:56 +020060 }
Jan Schär4cc3d4d2025-04-14 11:46:47 +000061 if req.OsImage.Digest == "" {
62 return fmt.Errorf("missing digest in OS installation request")
Lorenz Brunaadeb792023-03-27 15:53:56 +020063 }
Jan Schär4cc3d4d2025-04-14 11:46:47 +000064
65 client := &registry.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 Brunaadeb792023-03-27 15:53:56 +020076 }
Jan Schär4cc3d4d2025-04-14 11:46:47 +000077
78 image, err := client.Read(ctx, req.OsImage.Tag, req.OsImage.Digest)
Lorenz Brunaadeb792023-03-27 15:53:56 +020079 if err != nil {
Jan Schär4cc3d4d2025-04-14 11:46:47 +000080 return fmt.Errorf("failed to fetch OS image: %w", err)
Lorenz Brunaadeb792023-03-27 15:53:56 +020081 }
Lorenz Brunaadeb792023-03-27 15:53:56 +020082
Jan Schär4cc3d4d2025-04-14 11:46:47 +000083 osImage, err := ociosimage.Read(image)
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 Brunaadeb792023-03-27 15:53:56 +020099 nodeParamsRaw, err := proto.Marshal(req.NodeParameters)
100 if err != nil {
101 return fmt.Errorf("failed marshaling: %w", err)
102 }
103
Lorenz Brunad131882023-06-28 16:42:20 +0200104 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
Lorenz Brunaadeb792023-03-27 15:53:56 +0200109 installParams := osimage.Params{
110 PartitionSize: osimage.PartitionSizeInfo{
Lorenz Brun35fcf032023-06-29 04:15:58 +0200111 ESP: 384,
Lorenz Brunaadeb792023-03-27 15:53:56 +0200112 System: 4096,
113 Data: 128,
114 },
115 SystemImage: systemImage,
Jan Schärc1b6df42025-03-20 08:52:18 +0000116 EFIPayload: efiPayload,
117 ABLoader: structfs.Bytes(abloader),
118 NodeParameters: structfs.Bytes(nodeParamsRaw),
Lorenz Brunad131882023-06-28 16:42:20 +0200119 Output: rootDev,
Lorenz Brunaadeb792023-03-27 15:53:56 +0200120 }
121
Tim Windelschmidtcc27faa2024-08-01 02:18:35 +0200122 be, err := osimage.Write(&installParams)
Lorenz Brunaadeb792023-03-27 15:53:56 +0200123 if err != nil {
124 return err
125 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200126 bootEntryIdx, err := efivarfs.AddBootEntry(be)
Lorenz Brunaadeb792023-03-27 15:53:56 +0200127 if err != nil {
128 return fmt.Errorf("error creating EFI boot entry: %w", err)
129 }
Lorenz Brun9933ef02023-07-06 18:28:29 +0200130 if err := efivarfs.SetBootOrder(efivarfs.BootOrder{uint16(bootEntryIdx)}); err != nil {
Lorenz Brunaadeb792023-03-27 15:53:56 +0200131 return fmt.Errorf("error setting EFI boot order: %w", err)
132 }
133 l.Info("Metropolis installation completed")
134 return nil
135}