blob: 949a9a7d93b634edcb96c505c3a76482af68fc82 [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
Tim Windelschmidtb21bdf92025-05-28 18:37:35 +020018 apb "source.monogon.dev/cloud/agent/api"
Jan Schär4cc3d4d2025-04-14 11:46:47 +000019 npb "source.monogon.dev/osbase/net/proto"
20
Jan Schäre19d2792025-06-23 12:37:58 +000021 metropolisInstall "source.monogon.dev/metropolis/installer/install"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020022 "source.monogon.dev/osbase/blockdev"
23 "source.monogon.dev/osbase/efivarfs"
Jan Schäre19d2792025-06-23 12:37:58 +000024 "source.monogon.dev/osbase/oci/osimage"
Jan Schär4cc3d4d2025-04-14 11:46:47 +000025 "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
Jan Schär69b76872025-05-14 16:39:47 +000030//go:embed metropolis/node/abloader/abloader_bin.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
Tim Windelschmidtb21bdf92025-05-28 18:37:35 +020035func install(ctx context.Context, req *apb.OSInstallationRequest, netConfig *npb.Net) error {
Lorenz Brunaadeb792023-03-27 15:53:56 +020036 switch reqT := req.Type.(type) {
Tim Windelschmidtb21bdf92025-05-28 18:37:35 +020037 case *apb.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
Tim Windelschmidtb21bdf92025-05-28 18:37:35 +020044func installMetropolis(ctx context.Context, req *apb.MetropolisInstallationRequest, netConfig *npb.Net) error {
Jan Schär4cc3d4d2025-04-14 11:46:47 +000045 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äre19d2792025-06-23 12:37:58 +000083 osImage, err := osimage.Read(image)
Jan Schär4cc3d4d2025-04-14 11:46:47 +000084 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
Jan Schäre19d2792025-06-23 12:37:58 +0000109 installParams := metropolisInstall.Params{
110 PartitionSize: metropolisInstall.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 },
Jan Schär4b888262025-05-13 09:12:03 +0000115 Architecture: osImage.Config.ProductInfo.Architecture(),
Lorenz Brunaadeb792023-03-27 15:53:56 +0200116 SystemImage: systemImage,
Jan Schärc1b6df42025-03-20 08:52:18 +0000117 EFIPayload: efiPayload,
118 ABLoader: structfs.Bytes(abloader),
119 NodeParameters: structfs.Bytes(nodeParamsRaw),
Lorenz Brunad131882023-06-28 16:42:20 +0200120 Output: rootDev,
Lorenz Brunaadeb792023-03-27 15:53:56 +0200121 }
122
Jan Schäre19d2792025-06-23 12:37:58 +0000123 be, err := metropolisInstall.Write(&installParams)
Lorenz Brunaadeb792023-03-27 15:53:56 +0200124 if err != nil {
125 return err
126 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200127 bootEntryIdx, err := efivarfs.AddBootEntry(be)
Lorenz Brunaadeb792023-03-27 15:53:56 +0200128 if err != nil {
129 return fmt.Errorf("error creating EFI boot entry: %w", err)
130 }
Lorenz Brun9933ef02023-07-06 18:28:29 +0200131 if err := efivarfs.SetBootOrder(efivarfs.BootOrder{uint16(bootEntryIdx)}); err != nil {
Lorenz Brunaadeb792023-03-27 15:53:56 +0200132 return fmt.Errorf("error setting EFI boot order: %w", err)
133 }
134 l.Info("Metropolis installation completed")
135 return nil
136}