blob: e43e07c5fde124be662cc7bdeb46d92cf4cf851e [file] [log] [blame]
Lorenz Brune6573102021-11-02 14:15:37 +01001package main
2
3import (
Serge Bazanski97783222021-12-14 16:04:26 +01004 "bytes"
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +02005 "context"
Lorenz Brune6573102021-11-02 14:15:37 +01006 "crypto/ed25519"
Lorenz Brun7a510192022-07-04 15:31:38 +00007 _ "embed"
Lorenz Brun7a510192022-07-04 15:31:38 +00008 "io"
Lorenz Brune6573102021-11-02 14:15:37 +01009 "log"
10 "os"
Lorenz Brune6573102021-11-02 14:15:37 +010011
Lorenz Brune6573102021-11-02 14:15:37 +010012 "github.com/spf13/cobra"
13
14 "source.monogon.dev/metropolis/cli/metroctl/core"
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020015 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
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{
Lorenz Brun7a510192022-07-04 15:31:38 +000021 Short: "Contains subcommands to install Metropolis via different media.",
Lorenz Brune6573102021-11-02 14:15:37 +010022 Use: "install",
23}
24
Lorenz Brun7a510192022-07-04 15:31:38 +000025var bundlePath = installCmd.PersistentFlags().StringP("bundle", "b", "", "Path to the Metropolis bundle to be installed")
26
Lorenz Brune6573102021-11-02 14:15:37 +010027var genusbCmd = &cobra.Command{
Serge Bazanski97783222021-12-14 16:04:26 +010028 Use: "genusb target",
Lorenz Brune6573102021-11-02 14:15:37 +010029 Short: "Generates a Metropolis installer disk or image.",
Lorenz Brun7a510192022-07-04 15:31:38 +000030 Example: "metroctl install --bundle=metropolis-v0.1.zip genusb /dev/sdx",
Lorenz Brune6573102021-11-02 14:15:37 +010031 Args: cobra.ExactArgs(1), // One positional argument: the target
32 Run: doGenUSB,
33}
34
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020035// bootstrap is a flag controlling node parameters included in the installer
36// image. If set, the installed node will bootstrap a new cluster. Otherwise,
37// it will try to connect to the cluster which endpoints were provided with
38// the --endpoints flag.
39var bootstrap bool
40
Lorenz Brun7a510192022-07-04 15:31:38 +000041//go:embed metropolis/installer/kernel.efi
42var installer []byte
43
Lorenz Brune6573102021-11-02 14:15:37 +010044func doGenUSB(cmd *cobra.Command, args []string) {
Lorenz Brun7a510192022-07-04 15:31:38 +000045 var bundleReader io.Reader
46 var bundleSize uint64
47 if bundlePath == nil || *bundlePath == "" {
48 // Attempt Bazel runfile bundle if not explicitly set
49 bundle, err := datafile.Get("metropolis/node/bundle.zip")
50 if err != nil {
51 log.Fatalf("No bundle specified and fallback to runfiles failed: %v", err)
52 }
53 bundleReader = bytes.NewReader(bundle)
54 bundleSize = uint64(len(bundle))
55 } else {
56 // Load bundle from specified path
57 bundle, err := os.Open(*bundlePath)
58 if err != nil {
59 log.Fatalf("Failed to open specified bundle: %v", err)
60 }
61 bundleStat, err := bundle.Stat()
62 if err != nil {
63 log.Fatalf("Failed to stat specified bundle: %v", err)
64 }
65 bundleReader = bundle
66 bundleSize = uint64(bundleStat.Size())
67 }
Lorenz Brune6573102021-11-02 14:15:37 +010068
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020069 ctx := clicontext.WithInterrupt(context.Background())
70
Lorenz Brune6573102021-11-02 14:15:37 +010071 // TODO(lorenz): Have a key management story for this
Mateusz Zalega8234c162022-07-08 17:05:50 +020072 if err := os.MkdirAll(flags.configPath, 0700); err != nil && !os.IsExist(err) {
Lorenz Brune6573102021-11-02 14:15:37 +010073 log.Fatalf("Failed to create config directory: %v", err)
74 }
Lorenz Brune6573102021-11-02 14:15:37 +010075
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020076 var params *api.NodeParameters
77 if bootstrap {
Serge Bazanskicf23ebc2023-03-14 17:02:04 +010078 priv, err := core.GetOrMakeOwnerKey(flags.configPath)
79 if err != nil {
80 log.Fatalf("Failed to generate or get owner key: %v", err)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020081 }
Serge Bazanskicf23ebc2023-03-14 17:02:04 +010082 pub := priv.Public().(ed25519.PublicKey)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020083 params = &api.NodeParameters{
84 Cluster: &api.NodeParameters_ClusterBootstrap_{
85 ClusterBootstrap: &api.NodeParameters_ClusterBootstrap{
Serge Bazanskicf23ebc2023-03-14 17:02:04 +010086 OwnerPublicKey: pub,
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020087 },
Lorenz Brune6573102021-11-02 14:15:37 +010088 },
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020089 }
90 } else {
Mateusz Zalegadb75e212022-08-04 17:31:34 +020091 cc := dialAuthenticated(ctx)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020092 mgmt := api.NewManagementClient(cc)
93 resT, err := mgmt.GetRegisterTicket(ctx, &api.GetRegisterTicketRequest{})
94 if err != nil {
95 log.Fatalf("While receiving register ticket: %v", err)
96 }
97 resI, err := mgmt.GetClusterInfo(ctx, &api.GetClusterInfoRequest{})
98 if err != nil {
99 log.Fatalf("While receiving cluster directory: %v", err)
100 }
101
102 params = &api.NodeParameters{
103 Cluster: &api.NodeParameters_ClusterRegister_{
104 ClusterRegister: &api.NodeParameters_ClusterRegister{
105 RegisterTicket: resT.Ticket,
106 ClusterDirectory: resI.ClusterDirectory,
107 CaCertificate: resI.CaCertificate,
108 },
109 },
110 }
Lorenz Brune6573102021-11-02 14:15:37 +0100111 }
112
113 installerImageArgs := core.MakeInstallerImageArgs{
114 TargetPath: args[0],
Lorenz Brun7a510192022-07-04 15:31:38 +0000115 Installer: bytes.NewReader(installer),
Serge Bazanski97783222021-12-14 16:04:26 +0100116 InstallerSize: uint64(len(installer)),
Lorenz Brune6573102021-11-02 14:15:37 +0100117 NodeParams: params,
Lorenz Brun7a510192022-07-04 15:31:38 +0000118 Bundle: bundleReader,
119 BundleSize: bundleSize,
Lorenz Brune6573102021-11-02 14:15:37 +0100120 }
121
Serge Bazanski97783222021-12-14 16:04:26 +0100122 log.Printf("Generating installer image (this can take a while, see issues/92).")
Lorenz Brune6573102021-11-02 14:15:37 +0100123 if err := core.MakeInstallerImage(installerImageArgs); err != nil {
124 log.Fatalf("Failed to create installer: %v", err)
125 }
126}
127
128func init() {
129 rootCmd.AddCommand(installCmd)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200130
131 genusbCmd.Flags().BoolVar(&bootstrap, "bootstrap", false, "Create a bootstrap installer image.")
Lorenz Brune6573102021-11-02 14:15:37 +0100132 installCmd.AddCommand(genusbCmd)
Lorenz Brune6573102021-11-02 14:15:37 +0100133}