github.com/sqlitebrowser/dio@v0.0.0-20240125125356-b587368e5c6b/cmd/root.go (about)

     1  package cmd
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  
    13  	"github.com/mitchellh/go-homedir"
    14  	"github.com/spf13/cobra"
    15  	"github.com/spf13/viper"
    16  	"golang.org/x/text/message"
    17  )
    18  
    19  const (
    20  	DIO_VERSION = "0.3.1"
    21  )
    22  
    23  var (
    24  	certUser       string
    25  	cfgFile, cloud string
    26  	fOut           = io.Writer(os.Stdout)
    27  	numFormat      *message.Printer
    28  	TLSConfig      tls.Config
    29  )
    30  
    31  // RootCmd represents the base command when called without any subcommands
    32  var RootCmd = &cobra.Command{
    33  	Use:   "dio",
    34  	Short: "Command line interface to DBHub.io",
    35  	Long: `dio is a command line interface (CLI) for DBHub.io.
    36  
    37  With dio you can send and receive database files to a DBHub.io cloud,
    38  and manipulate its tags and branches.`,
    39  	SilenceErrors: true,
    40  	SilenceUsage:  true,
    41  }
    42  
    43  // Execute adds all child commands to the root command & sets flags appropriately.
    44  // This is called by main.main(). It only needs to happen once to the rootCmd.
    45  func Execute() {
    46  	if err := RootCmd.Execute(); err != nil {
    47  		fmt.Println(err)
    48  		os.Exit(1)
    49  	}
    50  }
    51  
    52  func init() {
    53  	// Add support for pretty printing numbers
    54  	numFormat = message.NewPrinter(message.MatchLanguage("en"))
    55  
    56  	// When run from go test we skip this, as we generate a temporary config file in the test suite setup
    57  	if os.Getenv("IS_TESTING") == "yes" {
    58  		return
    59  	}
    60  
    61  	// Add the global environment variables
    62  	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
    63  		fmt.Sprintf("config file (default is %s)", filepath.Join("$HOME", ".dio", "config.toml")))
    64  	RootCmd.PersistentFlags().StringVar(&cloud, "cloud", "https://db4s.dbhub.io",
    65  		"Address of the DBHub.io cloud")
    66  
    67  	// Read all of our configuration data now
    68  	if cfgFile != "" {
    69  		// Use config file from the flag
    70  		viper.SetConfigFile(cfgFile)
    71  	} else {
    72  		// Find home directory
    73  		home, err := homedir.Dir()
    74  		if err != nil {
    75  			fmt.Println(err)
    76  			os.Exit(1)
    77  		}
    78  
    79  		// Search for config in ".dio" subdirectory under the users home directory
    80  		p := filepath.Join(home, ".dio")
    81  		viper.AddConfigPath(p)
    82  		viper.SetConfigName("config")
    83  		cfgFile = filepath.Join(p, "config.toml")
    84  	}
    85  
    86  	// If a config file is found, read it in.
    87  	if err := viper.ReadInConfig(); err != nil {
    88  		// No configuration file was found, so generate a default one and let the user know they need to supply the
    89  		// missing info
    90  		errInner := generateConfig(cfgFile)
    91  		if errInner != nil {
    92  			log.Fatalln(errInner)
    93  			return
    94  		}
    95  		log.Fatalf("No usable configuration file was found, so a default one has been generated in: %s\n"+
    96  			"Please update it with your name, and the path to your DBHub.io user certificate file.\n", cfgFile)
    97  		return
    98  	}
    99  
   100  	// Make sure the paths to our CA Chain and user certificate have been set
   101  	if found := viper.IsSet("certs.cachain"); found == false {
   102  		log.Fatal("Path to Certificate Authority chain file not set in the config file")
   103  		return
   104  	}
   105  	if found := viper.IsSet("certs.cert"); found == false {
   106  		log.Fatal("Path to user certificate file not set in the config file")
   107  		return
   108  	}
   109  
   110  	// If an alternative DBHub.io cloud address is set in the config file, use that
   111  	if found := viper.IsSet("general.cloud"); found == true {
   112  		// If the user provided an override on the command line, that will override this anyway
   113  		cloud = viper.GetString("general.cloud")
   114  	}
   115  
   116  	// Read our certificate info, if present
   117  	ourCAPool := x509.NewCertPool()
   118  	chainFile, err := ioutil.ReadFile(viper.GetString("certs.cachain"))
   119  	if err != nil {
   120  		log.Fatal(err)
   121  	}
   122  	ok := ourCAPool.AppendCertsFromPEM(chainFile)
   123  	if !ok {
   124  		log.Fatal("Error when loading certificate chain file")
   125  	}
   126  
   127  	// TODO: Check if the client certificate file is present
   128  	certFile := viper.GetString("certs.cert")
   129  	if _, err = os.Stat(certFile); err != nil {
   130  		log.Fatalf("Please download your client certificate from DBHub.io, then update the configuration "+
   131  			"file '%s' with its path", cfgFile)
   132  	}
   133  
   134  	// Load a client certificate file
   135  	cert, err := tls.LoadX509KeyPair(certFile, certFile)
   136  	if err != nil {
   137  		log.Fatal(err)
   138  	}
   139  
   140  	// Load our self signed CA Cert chain, and set TLS1.2 as minimum
   141  	TLSConfig = tls.Config{
   142  		Certificates:             []tls.Certificate{cert},
   143  		ClientCAs:                ourCAPool,
   144  		InsecureSkipVerify:       true,
   145  		MinVersion:               tls.VersionTLS12,
   146  		PreferServerCipherSuites: true,
   147  		RootCAs:                  ourCAPool,
   148  	}
   149  
   150  	// Extract the username and email from the TLS certificate
   151  	var email string
   152  	certUser, email, _, err = getUserAndServer()
   153  	if err != nil {
   154  		log.Fatal(err)
   155  	}
   156  	viper.Set("user.email", email)
   157  }