blob: 07c44de1742cbae946fd0d4791f9ca37def6cead [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"
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +01008 _ "embed"
9 "fmt"
10 "io"
11 "os"
12 "path/filepath"
13
14 "github.com/cavaliergopher/cpio"
15 "github.com/klauspost/compress/zstd"
16 "golang.org/x/sys/unix"
17 "google.golang.org/protobuf/proto"
18
19 apb "source.monogon.dev/metropolis/proto/api"
20 netapi "source.monogon.dev/osbase/net/proto"
21
22 "source.monogon.dev/osbase/bootparam"
23 "source.monogon.dev/osbase/build/mkimage/osimage"
24 "source.monogon.dev/osbase/kexec"
25 netdump "source.monogon.dev/osbase/net/dump"
Jan Schär997faa42025-03-26 14:24:08 +000026 "source.monogon.dev/osbase/structfs"
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +010027)
28
29//go:embed third_party/linux/bzImage
30var 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är997faa42025-03-26 14:24:08 +000097 bundleBlob, err := structfs.OSPathBlob(filepath.Join(filepath.Dir(currPath), "bundle.zip"))
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +010098 if err != nil {
99 return nil, err
100 }
101
Jan Schär997faa42025-03-26 14:24:08 +0000102 bundleRaw, err := bundleBlob.Open()
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100103 if err != nil {
104 return nil, err
105 }
Jan Schär997faa42025-03-26 14:24:08 +0000106 defer bundleRaw.Close()
107 bundle, err := zip.NewReader(bundleRaw.(io.ReaderAt), bundleBlob.Size())
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100108 if err != nil {
109 return nil, fmt.Errorf("failed to open node bundle: %w", err)
110 }
111
112 // Dump the current network configuration
113 netconf, warnings, err := netdump.Dump()
114 if err != nil {
115 return nil, fmt.Errorf("failed to dump network configuration: %w", err)
116 }
117
118 if len(netconf.Nameserver) == 0 {
119 netconf.Nameserver = []*netapi.Nameserver{{
120 Ip: "8.8.8.8",
121 }, {
122 Ip: "1.1.1.1",
123 }}
124 }
125
126 var params apb.NodeParameters
127 if err := proto.Unmarshal(nodeParamsRaw, &params); err != nil {
128 return nil, fmt.Errorf("failed to unmarshal node parameters: %w", err)
129 }
130
131 // Override the NodeParameters.NetworkConfig with the current NetworkConfig
132 // if it's missing.
133 if params.NetworkConfig == nil {
134 params.NetworkConfig = netconf
135 }
136
137 // Marshal NodeParameters again.
138 nodeParamsRaw, err = proto.Marshal(&params)
139 if err != nil {
140 return nil, fmt.Errorf("failed marshaling: %w", err)
141 }
142
143 oParams, err := setupOSImageParams(bundle, nodeParamsRaw, target)
144 if err != nil {
145 return nil, err
146 }
147
148 // Validate that this installation will not fail because of disk issues
149 if _, err := osimage.Plan(oParams); err != nil {
150 return nil, fmt.Errorf("failed to plan installation: %w", err)
151 }
152
153 // Load data from embedded files into memfiles as the kexec load syscall
154 // requires file descriptors.
155 kernelFile, err := newMemfile("kernel", 0)
156 if err != nil {
157 return nil, fmt.Errorf("failed to create kernel memfile: %w", err)
158 }
159 initramfsFile, err := newMemfile("initramfs", 0)
160 if err != nil {
161 return nil, fmt.Errorf("failed to create initramfs memfile: %w", err)
162 }
Jan Schär997faa42025-03-26 14:24:08 +0000163 if _, err := kernelFile.Write(kernel); err != nil {
164 return nil, fmt.Errorf("failed to write kernel into memory-backed file: %w", err)
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100165 }
Jan Schär997faa42025-03-26 14:24:08 +0000166 if _, err := initramfsFile.Write(ucode); err != nil {
167 return nil, fmt.Errorf("failed to write ucode into memory-backed file: %w", err)
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100168 }
Jan Schär997faa42025-03-26 14:24:08 +0000169 if _, err := initramfsFile.Write(initramfs); err != nil {
170 return nil, fmt.Errorf("failed to write initramfs into memory-backed file: %w", err)
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100171 }
172
173 // Append this executable, the bundle and node params to initramfs
Jan Schär997faa42025-03-26 14:24:08 +0000174 self, err := structfs.OSPathBlob("/proc/self/exe")
175 if err != nil {
176 return nil, err
177 }
178 root := structfs.Tree{
179 structfs.File("init", self, structfs.WithPerm(0o755)),
180 structfs.File("params.pb", structfs.Bytes(nodeParamsRaw)),
181 structfs.File("bundle.zip", bundleBlob),
182 }
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100183 compressedW, err := zstd.NewWriter(initramfsFile, zstd.WithEncoderLevel(1))
184 if err != nil {
185 return nil, fmt.Errorf("while creating zstd writer: %w", err)
186 }
Jan Schär997faa42025-03-26 14:24:08 +0000187 err = writeCPIO(compressedW, root)
188 if err != nil {
189 return nil, err
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100190 }
Jan Schär997faa42025-03-26 14:24:08 +0000191 err = compressedW.Close()
192 if err != nil {
193 return nil, err
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100194 }
Tim Windelschmidt7a1b27d2024-02-22 23:54:58 +0100195
196 initParams := bootparam.Params{
197 bootparam.Param{Param: "quiet"},
198 bootparam.Param{Param: launchModeEnv, Value: launchModeInit},
199 bootparam.Param{Param: EnvInstallTarget, Value: target},
200 bootparam.Param{Param: "init", Value: "/init"},
201 }
202
203 var customConsoles bool
204 cmdline, err := os.ReadFile("/proc/cmdline")
205 if err != nil {
206 warnings = append(warnings, fmt.Errorf("unable to read current kernel command line: %w", err))
207 } else {
208 params, _, err := bootparam.Unmarshal(string(cmdline))
209 // If the existing command line is well-formed, add all existing console
210 // parameters to the console for the agent
211 if err == nil {
212 for _, p := range params {
213 if p.Param == "console" {
214 initParams = append(initParams, p)
215 customConsoles = true
216 }
217 }
218 }
219 }
220 if !customConsoles {
221 // Add the "default" console on x86
222 initParams = append(initParams, bootparam.Param{Param: "console", Value: "ttyS0,115200"})
223 }
224 agentCmdline, err := bootparam.Marshal(initParams, "")
225 if err != nil {
226 return nil, fmt.Errorf("failed to marshal bootparams: %w", err)
227 }
228 // Stage agent payload into kernel memory
229 if err := kexec.FileLoad(kernelFile, initramfsFile, agentCmdline); err != nil {
230 return nil, fmt.Errorf("failed to load kexec payload: %w", err)
231 }
232 var warningsStrs []string
233 for _, w := range warnings {
234 warningsStrs = append(warningsStrs, w.Error())
235 }
236 return warningsStrs, nil
237}