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  }