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  }