blob: 62be34f58093b5b586ed97b4d4f7048ee5a6daf7 [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"
Serge Bazanskibdc803b2023-03-27 10:55:09 +020011 "strings"
Lorenz Brune6573102021-11-02 14:15:37 +010012
Lorenz Brune6573102021-11-02 14:15:37 +010013 "github.com/spf13/cobra"
14
15 "source.monogon.dev/metropolis/cli/metroctl/core"
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020016 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
Serge Bazanski97783222021-12-14 16:04:26 +010017 "source.monogon.dev/metropolis/cli/pkg/datafile"
Lorenz Brune6573102021-11-02 14:15:37 +010018 "source.monogon.dev/metropolis/proto/api"
Serge Bazanskibdc803b2023-03-27 10:55:09 +020019 cpb "source.monogon.dev/metropolis/proto/common"
Lorenz Brune6573102021-11-02 14:15:37 +010020)
21
22var installCmd = &cobra.Command{
Lorenz Brun7a510192022-07-04 15:31:38 +000023 Short: "Contains subcommands to install Metropolis via different media.",
Lorenz Brune6573102021-11-02 14:15:37 +010024 Use: "install",
25}
26
Lorenz Brun7a510192022-07-04 15:31:38 +000027var bundlePath = installCmd.PersistentFlags().StringP("bundle", "b", "", "Path to the Metropolis bundle to be installed")
28
Lorenz Brune6573102021-11-02 14:15:37 +010029var genusbCmd = &cobra.Command{
Serge Bazanski97783222021-12-14 16:04:26 +010030 Use: "genusb target",
Lorenz Brune6573102021-11-02 14:15:37 +010031 Short: "Generates a Metropolis installer disk or image.",
Lorenz Brun7a510192022-07-04 15:31:38 +000032 Example: "metroctl install --bundle=metropolis-v0.1.zip genusb /dev/sdx",
Lorenz Brune6573102021-11-02 14:15:37 +010033 Args: cobra.ExactArgs(1), // One positional argument: the target
34 Run: doGenUSB,
35}
36
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020037// bootstrap is a flag controlling node parameters included in the installer
38// image. If set, the installed node will bootstrap a new cluster. Otherwise,
39// it will try to connect to the cluster which endpoints were provided with
40// the --endpoints flag.
41var bootstrap bool
42
Serge Bazanskibdc803b2023-03-27 10:55:09 +020043var bootstrapTPMMode string
44
Lorenz Brun7a510192022-07-04 15:31:38 +000045//go:embed metropolis/installer/kernel.efi
46var installer []byte
47
Lorenz Brune6573102021-11-02 14:15:37 +010048func doGenUSB(cmd *cobra.Command, args []string) {
Serge Bazanskibdc803b2023-03-27 10:55:09 +020049 var tpmMode cpb.ClusterConfiguration_TPMMode
50 switch strings.ToLower(bootstrapTPMMode) {
51 case "required", "require":
52 tpmMode = cpb.ClusterConfiguration_TPM_MODE_REQUIRED
53 case "best-effort", "besteffort":
54 tpmMode = cpb.ClusterConfiguration_TPM_MODE_BEST_EFFORT
55 case "disabled", "disable":
56 tpmMode = cpb.ClusterConfiguration_TPM_MODE_DISABLED
57 default:
58 log.Fatalf("Invalid --bootstrap-tpm-mode (must be one of: required, best-effort, disabled)")
59 }
60
Lorenz Brun7a510192022-07-04 15:31:38 +000061 var bundleReader io.Reader
62 var bundleSize uint64
63 if bundlePath == nil || *bundlePath == "" {
64 // Attempt Bazel runfile bundle if not explicitly set
65 bundle, err := datafile.Get("metropolis/node/bundle.zip")
66 if err != nil {
67 log.Fatalf("No bundle specified and fallback to runfiles failed: %v", err)
68 }
69 bundleReader = bytes.NewReader(bundle)
70 bundleSize = uint64(len(bundle))
71 } else {
72 // Load bundle from specified path
73 bundle, err := os.Open(*bundlePath)
74 if err != nil {
75 log.Fatalf("Failed to open specified bundle: %v", err)
76 }
77 bundleStat, err := bundle.Stat()
78 if err != nil {
79 log.Fatalf("Failed to stat specified bundle: %v", err)
80 }
81 bundleReader = bundle
82 bundleSize = uint64(bundleStat.Size())
83 }
Lorenz Brune6573102021-11-02 14:15:37 +010084
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020085 ctx := clicontext.WithInterrupt(context.Background())
86
Lorenz Brune6573102021-11-02 14:15:37 +010087 // TODO(lorenz): Have a key management story for this
Mateusz Zalega8234c162022-07-08 17:05:50 +020088 if err := os.MkdirAll(flags.configPath, 0700); err != nil && !os.IsExist(err) {
Lorenz Brune6573102021-11-02 14:15:37 +010089 log.Fatalf("Failed to create config directory: %v", err)
90 }
Lorenz Brune6573102021-11-02 14:15:37 +010091
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020092 var params *api.NodeParameters
93 if bootstrap {
Serge Bazanskicf23ebc2023-03-14 17:02:04 +010094 priv, err := core.GetOrMakeOwnerKey(flags.configPath)
95 if err != nil {
96 log.Fatalf("Failed to generate or get owner key: %v", err)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020097 }
Serge Bazanskicf23ebc2023-03-14 17:02:04 +010098 pub := priv.Public().(ed25519.PublicKey)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +020099 params = &api.NodeParameters{
100 Cluster: &api.NodeParameters_ClusterBootstrap_{
101 ClusterBootstrap: &api.NodeParameters_ClusterBootstrap{
Serge Bazanskicf23ebc2023-03-14 17:02:04 +0100102 OwnerPublicKey: pub,
Serge Bazanskibdc803b2023-03-27 10:55:09 +0200103 InitialClusterConfiguration: &cpb.ClusterConfiguration{
104 TpmMode: tpmMode,
105 },
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200106 },
Lorenz Brune6573102021-11-02 14:15:37 +0100107 },
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200108 }
109 } else {
Mateusz Zalegadb75e212022-08-04 17:31:34 +0200110 cc := dialAuthenticated(ctx)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200111 mgmt := api.NewManagementClient(cc)
112 resT, err := mgmt.GetRegisterTicket(ctx, &api.GetRegisterTicketRequest{})
113 if err != nil {
114 log.Fatalf("While receiving register ticket: %v", err)
115 }
116 resI, err := mgmt.GetClusterInfo(ctx, &api.GetClusterInfoRequest{})
117 if err != nil {
118 log.Fatalf("While receiving cluster directory: %v", err)
119 }
120
121 params = &api.NodeParameters{
122 Cluster: &api.NodeParameters_ClusterRegister_{
123 ClusterRegister: &api.NodeParameters_ClusterRegister{
124 RegisterTicket: resT.Ticket,
125 ClusterDirectory: resI.ClusterDirectory,
126 CaCertificate: resI.CaCertificate,
127 },
128 },
129 }
Lorenz Brune6573102021-11-02 14:15:37 +0100130 }
131
132 installerImageArgs := core.MakeInstallerImageArgs{
133 TargetPath: args[0],
Lorenz Brun7a510192022-07-04 15:31:38 +0000134 Installer: bytes.NewReader(installer),
Serge Bazanski97783222021-12-14 16:04:26 +0100135 InstallerSize: uint64(len(installer)),
Lorenz Brune6573102021-11-02 14:15:37 +0100136 NodeParams: params,
Lorenz Brun7a510192022-07-04 15:31:38 +0000137 Bundle: bundleReader,
138 BundleSize: bundleSize,
Lorenz Brune6573102021-11-02 14:15:37 +0100139 }
140
Serge Bazanski97783222021-12-14 16:04:26 +0100141 log.Printf("Generating installer image (this can take a while, see issues/92).")
Lorenz Brune6573102021-11-02 14:15:37 +0100142 if err := core.MakeInstallerImage(installerImageArgs); err != nil {
143 log.Fatalf("Failed to create installer: %v", err)
144 }
145}
146
147func init() {
148 rootCmd.AddCommand(installCmd)
Mateusz Zalegad5f2f7a2022-07-05 18:48:56 +0200149
150 genusbCmd.Flags().BoolVar(&bootstrap, "bootstrap", false, "Create a bootstrap installer image.")
Serge Bazanskibdc803b2023-03-27 10:55:09 +0200151 genusbCmd.Flags().StringVar(&bootstrapTPMMode, "bootstrap-tpm-mode", "required", "TPM mode to set on cluster (required, best-effort, disabled)")
Lorenz Brune6573102021-11-02 14:15:37 +0100152 installCmd.AddCommand(genusbCmd)
Lorenz Brune6573102021-11-02 14:15:37 +0100153}