| package main | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"crypto/ed25519" | 
 | 	"crypto/rand" | 
 | 	"encoding/pem" | 
 | 	"log" | 
 | 	"os" | 
 | 	"path/filepath" | 
 |  | 
 | 	"github.com/adrg/xdg" | 
 | 	"github.com/spf13/cobra" | 
 |  | 
 | 	"source.monogon.dev/metropolis/cli/metroctl/core" | 
 | 	"source.monogon.dev/metropolis/cli/pkg/datafile" | 
 | 	"source.monogon.dev/metropolis/proto/api" | 
 | ) | 
 |  | 
 | var installCmd = &cobra.Command{ | 
 | 	Short: "Contains subcommands to install Metropolis over different mediums.", | 
 | 	Use:   "install", | 
 | } | 
 |  | 
 | var genusbCmd = &cobra.Command{ | 
 | 	Use:     "genusb target", | 
 | 	Short:   "Generates a Metropolis installer disk or image.", | 
 | 	Example: "metroctl install genusb /dev/sdx", | 
 | 	Args:    cobra.ExactArgs(1), // One positional argument: the target | 
 | 	Run:     doGenUSB, | 
 | } | 
 |  | 
 | // A PEM block type for a Metropolis initial owner private key | 
 | const ownerKeyType = "METROPOLIS INITIAL OWNER PRIVATE KEY" | 
 |  | 
 | func doGenUSB(cmd *cobra.Command, args []string) { | 
 | 	installer := datafile.MustGet("metropolis/installer/kernel.efi") | 
 | 	bundle := datafile.MustGet("metropolis/node/node.zip") | 
 |  | 
 | 	// TODO(lorenz): Have a key management story for this | 
 | 	if err := os.MkdirAll(filepath.Join(xdg.ConfigHome, "metroctl"), 0700); err != nil { | 
 | 		log.Fatalf("Failed to create config directory: %v", err) | 
 | 	} | 
 | 	var ownerPublicKey ed25519.PublicKey | 
 | 	ownerPrivateKeyPEM, err := os.ReadFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem")) | 
 | 	if os.IsNotExist(err) { | 
 | 		pub, priv, err := ed25519.GenerateKey(rand.Reader) | 
 | 		if err != nil { | 
 | 			log.Fatalf("Failed to generate owner private key: %v", err) | 
 | 		} | 
 | 		pemPriv := pem.EncodeToMemory(&pem.Block{Type: ownerKeyType, Bytes: priv}) | 
 | 		if err := os.WriteFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"), pemPriv, 0600); err != nil { | 
 | 			log.Fatalf("Failed to store owner private key: %v", err) | 
 | 		} | 
 | 		ownerPublicKey = pub | 
 | 	} else if err != nil { | 
 | 		log.Fatalf("Failed to load owner private key: %v", err) | 
 | 	} else { | 
 | 		block, _ := pem.Decode(ownerPrivateKeyPEM) | 
 | 		if block == nil { | 
 | 			log.Fatalf("owner-key.pem contains invalid PEM") | 
 | 		} | 
 | 		if block.Type != ownerKeyType { | 
 | 			log.Fatalf("owner-key.pem contains a PEM block that's not a %v", ownerKeyType) | 
 | 		} | 
 | 		if len(block.Bytes) != ed25519.PrivateKeySize { | 
 | 			log.Fatal("owner-key.pem contains non-Ed25519 key") | 
 | 		} | 
 | 		ownerPrivateKey := ed25519.PrivateKey(block.Bytes) | 
 | 		ownerPublicKey = ownerPrivateKey.Public().(ed25519.PublicKey) | 
 | 	} | 
 |  | 
 | 	// TODO(lorenz): This can only bootstrap right now. As soon as @serge's role | 
 | 	// management has stabilized we can replace this with a proper | 
 | 	// implementation. | 
 | 	params := &api.NodeParameters{ | 
 | 		Cluster: &api.NodeParameters_ClusterBootstrap_{ | 
 | 			ClusterBootstrap: &api.NodeParameters_ClusterBootstrap{ | 
 | 				OwnerPublicKey: ownerPublicKey, | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	installerImageArgs := core.MakeInstallerImageArgs{ | 
 | 		TargetPath:    args[0], | 
 | 		Installer:     bytes.NewBuffer(installer), | 
 | 		InstallerSize: uint64(len(installer)), | 
 | 		NodeParams:    params, | 
 | 		Bundle:        bytes.NewBuffer(bundle), | 
 | 		BundleSize:    uint64(len(bundle)), | 
 | 	} | 
 |  | 
 | 	log.Printf("Generating installer image (this can take a while, see issues/92).") | 
 | 	if err := core.MakeInstallerImage(installerImageArgs); err != nil { | 
 | 		log.Fatalf("Failed to create installer: %v", err) | 
 | 	} | 
 | } | 
 |  | 
 | func init() { | 
 | 	rootCmd.AddCommand(installCmd) | 
 | 	installCmd.AddCommand(genusbCmd) | 
 | } |