blob: 005b590b741fc7ba80f59d04b751781e85c436a2 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +01004package main
5
6import (
7 "archive/zip"
8 "bytes"
9 _ "embed"
10 "fmt"
11 "io/fs"
12 "os"
13 "path/filepath"
14
15 "source.monogon.dev/go/logging"
16 "source.monogon.dev/osbase/blockdev"
17 "source.monogon.dev/osbase/build/mkimage/osimage"
18 "source.monogon.dev/osbase/efivarfs"
19)
20
Tim Windelschmidt1f51cf42024-10-01 17:04:28 +020021//go:embed metropolis/node/core/abloader/abloader.efi
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +010022var abloader []byte
23
24// FileSizedReader is a small adapter from fs.File to fs.SizedReader
25// Panics on Stat() failure, so should only be used with sources where Stat()
26// cannot fail.
27type FileSizedReader struct {
28 fs.File
29}
30
31func (f FileSizedReader) Size() int64 {
32 stat, err := f.Stat()
33 if err != nil {
34 panic(err)
35 }
36 return stat.Size()
37}
38
39// EnvInstallTarget environment variable which tells the takeover binary where
40// to install to
41const EnvInstallTarget = "TAKEOVER_INSTALL_TARGET"
42
43func installMetropolis(l logging.Leveled) error {
44 // Validate we are running via EFI.
45 if _, err := os.Stat("/sys/firmware/efi"); os.IsNotExist(err) {
Tim Windelschmidt1f51cf42024-10-01 17:04:28 +020046 // nolint:ST1005
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +010047 return fmt.Errorf("Monogon OS can only be installed on EFI-booted machines, this one is not")
48 }
49
50 metropolisSpecRaw, err := os.ReadFile("/params.pb")
51 if err != nil {
52 return err
53 }
54
55 bundleRaw, err := os.Open("/bundle.zip")
56 if err != nil {
57 return err
58 }
59
60 bundleStat, err := bundleRaw.Stat()
61 if err != nil {
62 return err
63 }
64
65 bundle, err := zip.NewReader(bundleRaw, bundleStat.Size())
66 if err != nil {
67 return fmt.Errorf("failed to open node bundle: %w", err)
68 }
69
70 installParams, err := setupOSImageParams(bundle, metropolisSpecRaw, os.Getenv(EnvInstallTarget))
71 if err != nil {
72 return err
73 }
74
75 be, err := osimage.Write(installParams)
76 if err != nil {
77 return fmt.Errorf("failed to apply installation: %w", err)
78 }
79 bootEntryIdx, err := efivarfs.AddBootEntry(be)
80 if err != nil {
81 return fmt.Errorf("error creating EFI boot entry: %w", err)
82 }
83 if err := efivarfs.SetBootOrder(efivarfs.BootOrder{uint16(bootEntryIdx)}); err != nil {
84 return fmt.Errorf("error setting EFI boot order: %w", err)
85 }
86 l.Info("Metropolis installation completed")
87 return nil
88}
89
90func setupOSImageParams(bundle *zip.Reader, metropolisSpecRaw []byte, installTarget string) (*osimage.Params, error) {
91 rootDev, err := blockdev.Open(filepath.Join("/dev", installTarget))
92 if err != nil {
93 return nil, fmt.Errorf("failed to open root device: %w", err)
94 }
95
96 efiPayload, err := bundle.Open("kernel_efi.efi")
97 if err != nil {
98 return nil, fmt.Errorf("invalid bundle: %w", err)
99 }
100
101 systemImage, err := bundle.Open("verity_rootfs.img")
102 if err != nil {
103 return nil, fmt.Errorf("invalid bundle: %w", err)
104 }
105
106 return &osimage.Params{
107 PartitionSize: osimage.PartitionSizeInfo{
108 ESP: 384,
109 System: 4096,
110 Data: 128,
111 },
112 SystemImage: systemImage,
113 EFIPayload: FileSizedReader{efiPayload},
114 ABLoader: bytes.NewReader(abloader),
115 NodeParameters: bytes.NewReader(metropolisSpecRaw),
116 Output: rootDev,
117 }, nil
118}