github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/setup.go (about) 1 package cmd 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 10 "github.com/qri-io/ioes" 11 "github.com/qri-io/qfs/qipfs" 12 "github.com/qri-io/qri/auth/key" 13 "github.com/qri-io/qri/config" 14 "github.com/qri-io/qri/dsref" 15 "github.com/qri-io/qri/lib" 16 "github.com/qri-io/qri/profile" 17 "github.com/spf13/cobra" 18 ) 19 20 // NewSetupCommand creates a setup command 21 func NewSetupCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command { 22 o := &SetupOptions{IOStreams: ioStreams} 23 cmd := &cobra.Command{ 24 Use: "setup", 25 Short: "initialize qri and IPFS repositories, provision a new qri ID", 26 Long: `Setup is the first command you run to get a fresh install of Qri. If you’ve 27 never run qri before, you’ll need to run setup before you can do anything. 28 29 Setup does a few things: 30 - create a qri repository to keep all of your data 31 - provisions a new qri ID 32 - create an IPFS repository if one doesn’t exist 33 34 This command is automatically run if you invoke any Qri command without first 35 running setup. If setup has already been run, by default Qri won’t let you 36 overwrite this info. 37 38 Use the ` + "`--remove`" + ` to remove your Qri repo. This deletes your entire repo, 39 including all your datasets, and de-registers your username from the registry.`, 40 Example: ` # Run setup with a username of your choosing: 41 $ qri setup --username=your_great_username`, 42 Annotations: map[string]string{ 43 "group": "other", 44 }, 45 Args: cobra.NoArgs, 46 RunE: func(cmd *cobra.Command, args []string) error { 47 if err := o.Complete(f, args); err != nil { 48 return err 49 } 50 return o.Run(f) 51 }, 52 } 53 54 cmd.Flags().BoolVarP(&o.Anonymous, "anonymous", "a", false, "use an auto-generated username") 55 cmd.Flags().BoolVarP(&o.Overwrite, "overwrite", "", false, "overwrite repo if one exists") 56 cmd.Flags().BoolVarP(&o.IPFS, "init-ipfs", "", true, "initialize an IPFS repo if one isn't present") 57 cmd.Flags().BoolVarP(&o.Remove, "remove", "", false, "permanently remove qri, overrides all setup options") 58 cmd.Flags().BoolVarP(&o.RSAKey, "rsa", "", false, "setup qri with an RSA key") 59 cmd.Flags().StringVarP(&o.Registry, "registry", "", "", "override default registry URL, set to 'none' to remove registry") 60 cmd.Flags().StringVarP(&o.Username, "username", "", "", "choose your desired username") 61 cmd.Flags().StringVarP(&o.IPFSConfigData, "ipfs-config", "", "", "json-encoded configuration data, specify a filepath with '@' prefix") 62 cmd.Flags().StringVarP(&o.ConfigData, "config-data", "", "", "json-encoded configuration data, specify a filepath with '@' prefix") 63 cmd.Flags().BoolVar(&o.GimmeDoggo, "gimme-doggo", false, "create and display a doggo name only") 64 65 return cmd 66 } 67 68 // SetupOptions encapsulates state for the setup command 69 type SetupOptions struct { 70 ioes.IOStreams 71 repoPath string 72 ctors Constructors 73 74 Anonymous bool 75 Overwrite bool 76 IPFS bool 77 Remove bool 78 Username string 79 Registry string 80 IPFSConfigData string 81 ConfigData string 82 GimmeDoggo bool 83 RSAKey bool 84 } 85 86 // Complete adds any missing configuration that can only be added just before calling Run 87 func (o *SetupOptions) Complete(f Factory, args []string) (err error) { 88 o.repoPath = f.RepoPath() 89 o.ctors = f.Constructors() 90 return 91 } 92 93 // Run executes the setup command 94 func (o *SetupOptions) Run(f Factory) error { 95 if o.GimmeDoggo { 96 return o.CreateAndDisplayDoggo() 97 } 98 99 if o.Remove { 100 cfg, err := f.Config() 101 if err != nil { 102 return err 103 } 104 // TODO - add a big warning here that requires user input 105 err = lib.Teardown(lib.TeardownParams{ 106 Config: cfg, 107 RepoPath: o.repoPath, 108 }) 109 if err != nil { 110 return err 111 } 112 printSuccess(o.Out, "repo removed") 113 return nil 114 } 115 116 if err := o.DoSetup(f); err != nil { 117 return err 118 } 119 120 printSuccess(o.Out, "set up qri repo at: %s\n", o.repoPath) 121 return nil 122 } 123 124 // DoSetup executes the setup-ie bit from the setup command 125 func (o *SetupOptions) DoSetup(f Factory) (err error) { 126 cfg := config.DefaultConfig() 127 128 envVars := map[string]*string{ 129 "QRI_SETUP_CONFIG_DATA": &o.ConfigData, 130 "QRI_SETUP_IPFS_CONFIG_DATA": &o.IPFSConfigData, 131 } 132 mapEnvVars(envVars) 133 134 if o.ConfigData != "" { 135 if err = readAtFile(&o.ConfigData); err != nil { 136 return err 137 } 138 139 err = json.Unmarshal([]byte(o.ConfigData), cfg) 140 if cfg.Profile != nil { 141 o.Username = cfg.Profile.Peername 142 } 143 if err != nil { 144 return err 145 } 146 } 147 148 // Handle the --username flag, or prompt the user for a username 149 if o.Username != "" { 150 cfg.Profile.Peername = o.Username 151 } else if !o.Anonymous { 152 cfg.Profile.Peername = prompt(o.Out, o.In, "choose username (leave empty to generate a default name):") 153 } 154 155 // If a username was passed with the --username flag or entered by prompt, make sure its valid 156 if cfg.Profile.Peername != "" { 157 if err := dsref.EnsureValidUsername(cfg.Profile.Peername); err != nil { 158 return err 159 } 160 } 161 162 if o.Registry == "none" { 163 cfg.Registry = nil 164 } else if o.Registry != "" { 165 cfg.Registry.Location = o.Registry 166 } 167 168 gen := o.ctors.CryptoGenerator 169 if o.RSAKey { 170 gen = key.NewRSACryptoGenerator() 171 } 172 173 p := lib.SetupParams{ 174 Config: cfg, 175 RepoPath: o.repoPath, 176 SetupIPFS: o.IPFS, 177 InitIPFSFunc: o.ctors.InitIPFS, 178 Generator: gen, 179 Register: o.Registry == "none", 180 } 181 182 if o.IPFSConfigData != "" { 183 if err = readAtFile(&o.IPFSConfigData); err != nil { 184 return err 185 } 186 p.SetupIPFSConfigData = []byte(o.IPFSConfigData) 187 } 188 189 return lib.Setup(p) 190 } 191 192 // CreateAndDisplayDoggo creates and display a doggo name 193 func (o *SetupOptions) CreateAndDisplayDoggo() error { 194 _, peerID := o.ctors.CryptoGenerator.GeneratePrivateKeyAndPeerID() 195 dognick := profile.AnonUsername(peerID) 196 printSuccess(o.Out, dognick) 197 return nil 198 } 199 200 func mapEnvVars(vars map[string]*string) { 201 for envVar, value := range vars { 202 envVal := os.Getenv(envVar) 203 if envVal != "" { 204 fmt.Printf("reading %s from env\n", envVar) 205 *value = envVal 206 } 207 } 208 } 209 210 func setupRepoIfEmpty(repoPath, configPath string, g key.CryptoGenerator) error { 211 if repoPath != "" { 212 if _, err := os.Stat(filepath.Join(repoPath, "config")); os.IsNotExist(err) { 213 if err := os.MkdirAll(repoPath, os.ModePerm); err != nil { 214 return err 215 } 216 if err := qipfs.InitRepo(repoPath, configPath); err != nil { 217 return err 218 } 219 } 220 } 221 return nil 222 } 223 224 // readAtFile is a unix curl inspired method. any data input that begins with "@" 225 // is assumed to instead be a filepath that should be read & replaced with the contents 226 // of the specified path 227 func readAtFile(data *string) error { 228 d := *data 229 if len(d) > 0 && d[0] == '@' { 230 fileData, err := ioutil.ReadFile(d[1:]) 231 if err != nil { 232 return err 233 } 234 *data = string(fileData) 235 } 236 return nil 237 }