github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/exp/netbootxyz/netbootxyz.go (about) 1 // Copyright 2021 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "crypto/tls" 9 "flag" 10 "fmt" 11 "io" 12 "net/http" 13 "os" 14 "strings" 15 "time" 16 17 "github.com/mvdan/u-root-coreutils/pkg/boot/kexec" 18 "github.com/mvdan/u-root-coreutils/pkg/boot/menu" 19 "gopkg.in/yaml.v2" 20 ) 21 22 var ( 23 githubBaseURL = "https://github.com/netbootxyz" 24 netbootxyzURL = "https://raw.githubusercontent.com/netbootxyz/netboot.xyz/development/endpoints.yml" 25 26 verbose = flag.Bool("v", false, "Verbose output") 27 noLoad = flag.Bool("no-load", false, "Get DHCP response, print chosen boot configuration, but do not download + exec it") 28 noExec = flag.Bool("no-exec", false, "Download boot configuration, but do not exec it") 29 ifName = flag.String("i", "eth0", "Interface to send packets through") 30 31 bootMenu []menu.Entry 32 subMenu []menu.Entry 33 34 kernelList []Endpoint 35 filesystemList []Endpoint 36 37 OSEndpoints []OSEndpoint 38 39 majorOS = []string{ 40 "Ubuntu", 41 "Pop", 42 "Mint", 43 "Debian", 44 "Fedora", 45 "Gentoo", 46 } 47 48 OSCommandline = map[string]string{ 49 "Ubuntu": "boot=casper netboot=url url=%s", 50 "Mint": "boot=casper netboot=url url=%s", 51 "Pop": "boot=casper netboot=url url=%s", 52 "Debian": "boot=live fetch=%s", 53 "Fedora": "root=live:%s ro rd.live.image rd.lvm=0 rd.luks=0 rd.md=0 rd.dm=0", 54 "Gentoo": "root=/dev/ram0 init=/linuxrc loop=/image.squashfs looptype=squashfs cdroot=1 real_root=/ fetch=%s", 55 } 56 57 banner = ` 58 59 __________________________________ 60 < Netbootxyz is even hotter nowadays > 61 ---------------------------------- 62 \ ^__^ 63 \ (oo)\_______ 64 (__)\ )\/\ 65 ||----w | 66 || || 67 68 ` 69 ) 70 71 const ( 72 dhcpTimeout = 5 * time.Second 73 dhcpTries = 3 74 ) 75 76 // Endpoint - YAML Endpoint 77 type Endpoint struct { 78 Name string 79 Path string `yaml:"path"` 80 Os string `yaml:"os"` 81 Version string `yaml:"version"` 82 Files []string `yaml:"files"` 83 Flavor string `yaml:"flavor"` 84 Kernel string `yaml:"kernel"` 85 } 86 87 // Endpoints - Map for OS Endpoints 88 type Endpoints struct { 89 Endpoints map[string]Endpoint `yaml:"endpoints"` 90 } 91 92 // OSEndpoint - Parsed version of Endpoint 93 type OSEndpoint struct { 94 Name string 95 RawName string 96 Vmlinuz string 97 Initrd string 98 Filesystem string 99 Version string 100 Commandline string 101 OS string 102 onlyLabel bool 103 } 104 105 // Label - Menu Function Label 106 func (o OSEndpoint) Label() string { 107 return o.Name 108 } 109 110 // Load - Load data into kexec 111 func (o OSEndpoint) Load() error { 112 if o.onlyLabel == true { 113 subMenu = nil 114 if o.Name == "Other" { 115 // Load all other OS's 116 for _, value := range OSEndpoints { 117 _, found := OSCommandline[value.OS] 118 if !found { 119 subMenu = append(subMenu, value) 120 } 121 } 122 } else { 123 // Now we load everything with this label 124 for _, value := range OSEndpoints { 125 if value.OS == o.Name { 126 subMenu = append(subMenu, value) 127 } 128 } 129 } 130 menu.ShowMenuAndLoad(true, subMenu...) 131 132 return nil 133 } 134 135 if *noLoad { 136 fmt.Printf("Selected %s\n", o.Name) 137 fmt.Printf("Commandline: %s\n", o.Commandline) 138 } else { 139 tmpPath := "/tmp/" + strings.ReplaceAll(o.Name, " ", "") + "/" 140 err := os.Mkdir(tmpPath, 0o666) 141 if err != nil { 142 return err 143 } 144 fmt.Printf("Download to %s\n", tmpPath) 145 146 err = downloadFile(tmpPath+"vmlinuz", o.Vmlinuz) 147 if err != nil { 148 return err 149 } 150 151 err = downloadFile(tmpPath+"initrd", o.Initrd) 152 if err != nil { 153 return err 154 } 155 156 vmlinuz, err := os.Open(tmpPath + "vmlinuz") 157 if err != nil { 158 return err 159 } 160 161 initrd, err := os.Open(tmpPath + "initrd") 162 if err != nil { 163 return err 164 } 165 166 if *noExec { 167 fmt.Println("Loaded Kernel and Initrd") 168 fmt.Printf("With Kernel at %s\n", tmpPath+"vmlinuz") 169 fmt.Printf("With Initrd at %s\n", tmpPath+"initrd") 170 fmt.Printf("Commandline: %s\n", o.Commandline) 171 } else { 172 fmt.Println("Loading Kernel and Initrd into kexec") 173 fmt.Printf("With Kernel at %s\n", tmpPath+"vmlinuz") 174 fmt.Printf("With Initrd at %s\n", tmpPath+"initrd") 175 fmt.Printf("Commandline: %s\n", o.Commandline) 176 177 // Load Kernel and initrd 178 if err = kexec.FileLoad(vmlinuz, initrd, o.Commandline); err != nil { 179 return err 180 } 181 182 // Load KExec kernel and initrd - init cmdline 183 return kexec.Reboot() 184 } 185 return err 186 } 187 return nil 188 } 189 190 // Exec - Execute new kernel 191 func (o OSEndpoint) Exec() error { 192 return nil 193 } 194 195 // IsDefault - Default Configuration 196 func (o OSEndpoint) IsDefault() bool { 197 return false 198 } 199 200 // Edit - Edit something 201 func (o OSEndpoint) Edit(func(cmdline string) string) { 202 return 203 } 204 205 // indexOf - Returns index of an element in an array 206 func indexOf(element string, data []string) int { 207 for k, v := range data { 208 if element == v { 209 return k 210 } 211 } 212 return -1 // not found. 213 } 214 215 // remove element from array 216 func remove(slice []string, s int) []string { 217 return append(slice[:s], slice[s+1:]...) 218 } 219 220 func main() { 221 // Print Banner and parse arguments 222 fmt.Print(banner) 223 time.Sleep(2 * time.Second) 224 flag.Parse() 225 226 // Get an IP address via DHCP 227 err := configureDHCPNetwork() 228 if err != nil { 229 fmt.Printf("Error while getting IP : %v\n", err) 230 } 231 232 // Set up HTTP client 233 config := &tls.Config{InsecureSkipVerify: false} 234 tr := &http.Transport{TLSClientConfig: config} 235 client := &http.Client{Transport: tr} 236 237 // Fetch NetBoot(dot)xyz endpoints 238 req, err := http.NewRequest(http.MethodGet, netbootxyzURL, nil) 239 if err != nil { 240 fmt.Printf("New Request Error : %v\n", err) 241 } 242 response, err := client.Do(req) 243 if err != nil { 244 fmt.Printf("Error : %v\n", err) 245 } 246 content, err := io.ReadAll(response.Body) 247 if err != nil { 248 fmt.Println(err) 249 return 250 } 251 252 // Parse YAML 253 var e Endpoints 254 err = yaml.Unmarshal(content, &e) 255 if err != nil { 256 fmt.Println(err.Error()) 257 return 258 } 259 260 // Sort entries into either Kernel or Distros 261 // File Systems could also contain a Kernel directly! 262 tmp := make(map[string]struct{}) 263 for key, value := range e.Endpoints { 264 value.Name = key 265 if value.Os != "" { 266 tmp[value.Os] = struct{}{} 267 } 268 if strings.Contains(key, "kernel") { 269 // Endpoint contains kernel and initrd 270 kernelList = append(kernelList, value) 271 } else { 272 // Endpoint contains filesystem 273 filesystemList = append(filesystemList, value) 274 } 275 } 276 277 // Store keys from tmp in OSEntriesInMenu 278 OSEntriesInMenu := make([]string, 0, len(tmp)) 279 for k := range tmp { 280 OSEntriesInMenu = append(OSEntriesInMenu, strings.Title(k)) 281 } 282 283 for _, entry := range filesystemList { 284 // Define menu entry and fill with data 285 var OSEntry OSEndpoint 286 OSEntry.RawName = entry.Name 287 OSEntry.Name = strings.Title(entry.Os) + " " + strings.Title(entry.Version) + " " + strings.Title(entry.Flavor) 288 OSEntry.OS = strings.Title(entry.Os) 289 290 files := entry.Files 291 // Set kernel and initrd if found in endpoint 292 if entry.Name == entry.Kernel || entry.Kernel == "" { 293 OSEntry.Vmlinuz = githubBaseURL + entry.Path + "vmlinuz" 294 OSEntry.Initrd = githubBaseURL + entry.Path + "initrd" 295 } else if entry.Kernel != "" { 296 // Search for corresponding kernel in the kernel list 297 for _, value := range kernelList { 298 if value.Name == entry.Kernel { 299 OSEntry.Vmlinuz = githubBaseURL + value.Path + "vmlinuz" 300 OSEntry.Initrd = githubBaseURL + value.Path + "initrd" 301 break 302 } 303 } 304 } 305 // Remove already saved kernel and initrd from "files" 306 if indexOf("vmlinuz", files) != -1 { 307 files = remove(files, indexOf("vmlinuz", files)) 308 } 309 if indexOf("initrd", files) != -1 { 310 files = remove(files, indexOf("initrd", files)) 311 } 312 // Add filesystem entry 313 if len(files) != 0 { 314 OSEntry.Filesystem = githubBaseURL + entry.Path + files[0] 315 } 316 // Set specific cmdline if defined or resort to generic cmdline 317 _, found := OSCommandline[OSEntry.OS] 318 if found { 319 OSEntry.Commandline = "earlyprintk=ttyS0,115200 console=ttyS0,115200 ip=dhcp initrd=initrd " + 320 fmt.Sprintf(OSCommandline[OSEntry.OS], OSEntry.Filesystem) 321 } else { 322 OSEntry.Commandline = "earlyprintk=ttyS0,115200 console=ttyS0,115200 ip=dhcp initrd=initrd " + 323 fmt.Sprintf("boot=casper netboot=url url=%s", OSEntry.Filesystem) 324 } 325 // Store each fully configured endpoint 326 OSEndpoints = append(OSEndpoints, OSEntry) 327 } 328 // Only add major OS first 329 for _, value := range OSEntriesInMenu { 330 entry := OSEndpoint{ 331 Name: value, 332 onlyLabel: true, 333 } 334 for _, val := range majorOS { 335 if val == value { 336 bootMenu = append(bootMenu, entry) 337 } 338 } 339 } 340 // Group non-major distributions here 341 entry := OSEndpoint{ 342 Name: "Other", 343 onlyLabel: true, 344 } 345 // Fill menu with remaining options 346 bootMenu = append(bootMenu, entry) 347 bootMenu = append(bootMenu, menu.Reboot{}) 348 bootMenu = append(bootMenu, menu.StartShell{}) 349 menu.SetInitialTimeout(90 * time.Second) 350 menu.ShowMenuAndLoad(true, bootMenu...) 351 }