blob: c314fadcec89c00d51f936efc7c0289877fa0f7f [file] [log] [blame]
Lorenz Brune6573102021-11-02 14:15:37 +01001package main
2
3import (
Serge Bazanski97783222021-12-14 16:04:26 +01004 "bytes"
Lorenz Brune6573102021-11-02 14:15:37 +01005 "crypto/ed25519"
6 "crypto/rand"
7 "encoding/pem"
Lorenz Brune6573102021-11-02 14:15:37 +01008 "log"
9 "os"
10 "path/filepath"
11
12 "github.com/adrg/xdg"
13 "github.com/spf13/cobra"
14
15 "source.monogon.dev/metropolis/cli/metroctl/core"
Serge Bazanski97783222021-12-14 16:04:26 +010016 "source.monogon.dev/metropolis/cli/pkg/datafile"
Lorenz Brune6573102021-11-02 14:15:37 +010017 "source.monogon.dev/metropolis/proto/api"
18)
19
20var installCmd = &cobra.Command{
21 Short: "Contains subcommands to install Metropolis over different mediums.",
22 Use: "install",
23}
24
Lorenz Brune6573102021-11-02 14:15:37 +010025var genusbCmd = &cobra.Command{
Serge Bazanski97783222021-12-14 16:04:26 +010026 Use: "genusb target",
Lorenz Brune6573102021-11-02 14:15:37 +010027 Short: "Generates a Metropolis installer disk or image.",
Serge Bazanski97783222021-12-14 16:04:26 +010028 Example: "metroctl install genusb /dev/sdx",
Lorenz Brune6573102021-11-02 14:15:37 +010029 Args: cobra.ExactArgs(1), // One positional argument: the target
30 Run: doGenUSB,
31}
32
33// A PEM block type for a Metropolis initial owner private key
34const ownerKeyType = "METROPOLIS INITIAL OWNER PRIVATE KEY"
35
36func doGenUSB(cmd *cobra.Command, args []string) {
Mateusz Zalegaedffbb52022-01-11 15:27:22 +010037 installer := datafile.MustGet("metropolis/installer/kernel.efi")
Serge Bazanski97783222021-12-14 16:04:26 +010038 bundle := datafile.MustGet("metropolis/node/node.zip")
Lorenz Brune6573102021-11-02 14:15:37 +010039
40 // TODO(lorenz): Have a key management story for this
41 if err := os.MkdirAll(filepath.Join(xdg.ConfigHome, "metroctl"), 0700); err != nil {
42 log.Fatalf("Failed to create config directory: %v", err)
43 }
44 var ownerPublicKey ed25519.PublicKey
Lorenz Brun764a2de2021-11-22 16:26:36 +010045 ownerPrivateKeyPEM, err := os.ReadFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"))
Lorenz Brune6573102021-11-02 14:15:37 +010046 if os.IsNotExist(err) {
47 pub, priv, err := ed25519.GenerateKey(rand.Reader)
48 if err != nil {
49 log.Fatalf("Failed to generate owner private key: %v", err)
50 }
51 pemPriv := pem.EncodeToMemory(&pem.Block{Type: ownerKeyType, Bytes: priv})
Lorenz Brun764a2de2021-11-22 16:26:36 +010052 if err := os.WriteFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"), pemPriv, 0600); err != nil {
Lorenz Brune6573102021-11-02 14:15:37 +010053 log.Fatalf("Failed to store owner private key: %v", err)
54 }
55 ownerPublicKey = pub
56 } else if err != nil {
57 log.Fatalf("Failed to load owner private key: %v", err)
58 } else {
59 block, _ := pem.Decode(ownerPrivateKeyPEM)
60 if block == nil {
61 log.Fatalf("owner-key.pem contains invalid PEM")
62 }
63 if block.Type != ownerKeyType {
64 log.Fatalf("owner-key.pem contains a PEM block that's not a %v", ownerKeyType)
65 }
66 if len(block.Bytes) != ed25519.PrivateKeySize {
67 log.Fatal("owner-key.pem contains non-Ed25519 key")
68 }
69 ownerPrivateKey := ed25519.PrivateKey(block.Bytes)
70 ownerPublicKey = ownerPrivateKey.Public().(ed25519.PublicKey)
71 }
72
73 // TODO(lorenz): This can only bootstrap right now. As soon as @serge's role
74 // management has stabilized we can replace this with a proper
75 // implementation.
76 params := &api.NodeParameters{
77 Cluster: &api.NodeParameters_ClusterBootstrap_{
78 ClusterBootstrap: &api.NodeParameters_ClusterBootstrap{
79 OwnerPublicKey: ownerPublicKey,
80 },
81 },
82 }
83
84 installerImageArgs := core.MakeInstallerImageArgs{
85 TargetPath: args[0],
Serge Bazanski97783222021-12-14 16:04:26 +010086 Installer: bytes.NewBuffer(installer),
87 InstallerSize: uint64(len(installer)),
Lorenz Brune6573102021-11-02 14:15:37 +010088 NodeParams: params,
Serge Bazanski97783222021-12-14 16:04:26 +010089 Bundle: bytes.NewBuffer(bundle),
90 BundleSize: uint64(len(bundle)),
Lorenz Brune6573102021-11-02 14:15:37 +010091 }
92
Serge Bazanski97783222021-12-14 16:04:26 +010093 log.Printf("Generating installer image (this can take a while, see issues/92).")
Lorenz Brune6573102021-11-02 14:15:37 +010094 if err := core.MakeInstallerImage(installerImageArgs); err != nil {
95 log.Fatalf("Failed to create installer: %v", err)
96 }
97}
98
99func init() {
100 rootCmd.AddCommand(installCmd)
101 installCmd.AddCommand(genusbCmd)
Lorenz Brune6573102021-11-02 14:15:37 +0100102}