blob: cd537a90e9c293dec32ecf6ae2c538f813a2d75d [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 (
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +01007 _ "embed"
8 "fmt"
9 "io"
10 "os"
11 "path/filepath"
12
13 "github.com/cavaliergopher/cpio"
14 "github.com/klauspost/compress/zstd"
15 "golang.org/x/sys/unix"
16 "google.golang.org/protobuf/proto"
17
18 apb "source.monogon.dev/metropolis/proto/api"
19 netapi "source.monogon.dev/osbase/net/proto"
20
21 "source.monogon.dev/osbase/bootparam"
22 "source.monogon.dev/osbase/build/mkimage/osimage"
23 "source.monogon.dev/osbase/kexec"
24 netdump "source.monogon.dev/osbase/net/dump"
Jan Schär5fdca562025-04-14 11:33:29 +000025 "source.monogon.dev/osbase/oci"
Jan Schär997faa42025-03-26 14:24:08 +000026 "source.monogon.dev/osbase/structfs"
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +010027)
28
Lorenz Brun6df40aa2025-05-22 15:35:44 +020029//go:embed third_party/linux/Image
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +010030var kernel []byte
31
32//go:embed third_party/ucode.cpio
33var ucode []byte
34
35//go:embed initramfs.cpio.zst
36var initramfs []byte
37
38// newMemfile creates a new file which is not located on a specific filesystem,
39// but is instead backed by anonymous memory.
40func newMemfile(name string, flags int) (*os.File, error) {
41 fd, err := unix.MemfdCreate(name, flags)
42 if err != nil {
43 return nil, fmt.Errorf("memfd_create failed: %w", err)
44 }
45 return os.NewFile(uintptr(fd), name), nil
46}
47
Jan Schär997faa42025-03-26 14:24:08 +000048func writeCPIO(w io.Writer, root structfs.Tree) error {
49 cpioW := cpio.NewWriter(w)
50 for path, node := range root.Walk() {
51 switch {
52 case node.Mode.IsDir():
53 err := cpioW.WriteHeader(&cpio.Header{
54 Name: "/" + path,
55 Mode: cpio.TypeDir | (cpio.FileMode(node.Mode) & cpio.ModePerm),
56 })
57 if err != nil {
58 return err
59 }
60 case node.Mode.IsRegular():
61 err := cpioW.WriteHeader(&cpio.Header{
62 Name: "/" + path,
63 Size: node.Content.Size(),
64 Mode: cpio.TypeReg | (cpio.FileMode(node.Mode) & cpio.ModePerm),
65 })
66 if err != nil {
67 return err
68 }
69 content, err := node.Content.Open()
70 if err != nil {
71 return fmt.Errorf("cpio write %q: %w", path, err)
72 }
73 _, err = io.Copy(cpioW, content)
74 content.Close()
75 if err != nil {
76 return fmt.Errorf("cpio write %q: %w", path, err)
77 }
78 default:
79 return fmt.Errorf("cpio write %q: unsupported file type %s", path, node.Mode.Type().String())
80 }
81 }
82 return cpioW.Close()
83}
84
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +010085func setupTakeover(nodeParamsRaw []byte, target string) ([]string, error) {
86 // Validate we are running via EFI.
87 if _, err := os.Stat("/sys/firmware/efi"); os.IsNotExist(err) {
88 //nolint:ST1005
89 return nil, fmt.Errorf("Monogon OS can only be installed on EFI-booted machines, this one is not")
90 }
91
92 currPath, err := os.Executable()
93 if err != nil {
94 return nil, err
95 }
96
Jan Schär5fdca562025-04-14 11:33:29 +000097 image, err := oci.ReadLayout(filepath.Join(filepath.Dir(currPath), "osimage"))
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +010098 if err != nil {
Jan Schär5fdca562025-04-14 11:33:29 +000099 return nil, fmt.Errorf("failed to read OS image: %w", err)
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100100 }
101
102 // Dump the current network configuration
103 netconf, warnings, err := netdump.Dump()
104 if err != nil {
105 return nil, fmt.Errorf("failed to dump network configuration: %w", err)
106 }
107
108 if len(netconf.Nameserver) == 0 {
109 netconf.Nameserver = []*netapi.Nameserver{{
110 Ip: "8.8.8.8",
111 }, {
112 Ip: "1.1.1.1",
113 }}
114 }
115
116 var params apb.NodeParameters
117 if err := proto.Unmarshal(nodeParamsRaw, &params); err != nil {
118 return nil, fmt.Errorf("failed to unmarshal node parameters: %w", err)
119 }
120
121 // Override the NodeParameters.NetworkConfig with the current NetworkConfig
122 // if it's missing.
123 if params.NetworkConfig == nil {
124 params.NetworkConfig = netconf
125 }
126
127 // Marshal NodeParameters again.
128 nodeParamsRaw, err = proto.Marshal(&params)
129 if err != nil {
130 return nil, fmt.Errorf("failed marshaling: %w", err)
131 }
132
Jan Schär5fdca562025-04-14 11:33:29 +0000133 oParams, err := setupOSImageParams(image, nodeParamsRaw, target)
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100134 if err != nil {
135 return nil, err
136 }
137
138 // Validate that this installation will not fail because of disk issues
139 if _, err := osimage.Plan(oParams); err != nil {
140 return nil, fmt.Errorf("failed to plan installation: %w", err)
141 }
142
143 // Load data from embedded files into memfiles as the kexec load syscall
144 // requires file descriptors.
145 kernelFile, err := newMemfile("kernel", 0)
146 if err != nil {
147 return nil, fmt.Errorf("failed to create kernel memfile: %w", err)
148 }
149 initramfsFile, err := newMemfile("initramfs", 0)
150 if err != nil {
151 return nil, fmt.Errorf("failed to create initramfs memfile: %w", err)
152 }
Jan Schär997faa42025-03-26 14:24:08 +0000153 if _, err := kernelFile.Write(kernel); err != nil {
154 return nil, fmt.Errorf("failed to write kernel into memory-backed file: %w", err)
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100155 }
Jan Schär997faa42025-03-26 14:24:08 +0000156 if _, err := initramfsFile.Write(ucode); err != nil {
157 return nil, fmt.Errorf("failed to write ucode into memory-backed file: %w", err)
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100158 }
Jan Schär997faa42025-03-26 14:24:08 +0000159 if _, err := initramfsFile.Write(initramfs); err != nil {
160 return nil, fmt.Errorf("failed to write initramfs into memory-backed file: %w", err)
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100161 }
162
Jan Schär5fdca562025-04-14 11:33:29 +0000163 // Append this executable, node params and OS image to initramfs.
Jan Schär997faa42025-03-26 14:24:08 +0000164 self, err := structfs.OSPathBlob("/proc/self/exe")
165 if err != nil {
166 return nil, err
167 }
Jan Schär5fdca562025-04-14 11:33:29 +0000168 imageLayout, err := oci.CreateLayout(image)
169 if err != nil {
170 return nil, err
171 }
Jan Schär997faa42025-03-26 14:24:08 +0000172 root := structfs.Tree{
173 structfs.File("init", self, structfs.WithPerm(0o755)),
174 structfs.File("params.pb", structfs.Bytes(nodeParamsRaw)),
Jan Schär5fdca562025-04-14 11:33:29 +0000175 structfs.Dir("osimage", imageLayout),
Jan Schär997faa42025-03-26 14:24:08 +0000176 }
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100177 compressedW, err := zstd.NewWriter(initramfsFile, zstd.WithEncoderLevel(1))
178 if err != nil {
179 return nil, fmt.Errorf("while creating zstd writer: %w", err)
180 }
Jan Schär997faa42025-03-26 14:24:08 +0000181 err = writeCPIO(compressedW, root)
182 if err != nil {
183 return nil, err
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100184 }
Jan Schär997faa42025-03-26 14:24:08 +0000185 err = compressedW.Close()
186 if err != nil {
187 return nil, err
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100188 }
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100189
190 initParams := bootparam.Params{
191 bootparam.Param{Param: "quiet"},
192 bootparam.Param{Param: launchModeEnv, Value: launchModeInit},
193 bootparam.Param{Param: EnvInstallTarget, Value: target},
194 bootparam.Param{Param: "init", Value: "/init"},
195 }
196
197 var customConsoles bool
198 cmdline, err := os.ReadFile("/proc/cmdline")
199 if err != nil {
200 warnings = append(warnings, fmt.Errorf("unable to read current kernel command line: %w", err))
201 } else {
202 params, _, err := bootparam.Unmarshal(string(cmdline))
203 // If the existing command line is well-formed, add all existing console
204 // parameters to the console for the agent
205 if err == nil {
206 for _, p := range params {
207 if p.Param == "console" {
208 initParams = append(initParams, p)
209 customConsoles = true
210 }
211 }
212 }
213 }
214 if !customConsoles {
215 // Add the "default" console on x86
216 initParams = append(initParams, bootparam.Param{Param: "console", Value: "ttyS0,115200"})
217 }
218 agentCmdline, err := bootparam.Marshal(initParams, "")
219 if err != nil {
220 return nil, fmt.Errorf("failed to marshal bootparams: %w", err)
221 }
222 // Stage agent payload into kernel memory
223 if err := kexec.FileLoad(kernelFile, initramfsFile, agentCmdline); err != nil {
224 return nil, fmt.Errorf("failed to load kexec payload: %w", err)
225 }
226 var warningsStrs []string
227 for _, w := range warnings {
228 warningsStrs = append(warningsStrs, w.Error())
229 }
230 return warningsStrs, nil
231}