github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/cmd/root.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/cozy/cozy-stack/client"
    12  	"github.com/cozy/cozy-stack/client/request"
    13  	build "github.com/cozy/cozy-stack/pkg/config"
    14  	"github.com/cozy/cozy-stack/pkg/config/config"
    15  	"github.com/cozy/cozy-stack/pkg/tlsclient"
    16  	"github.com/spf13/cobra"
    17  	"github.com/spf13/viper"
    18  	"golang.org/x/term"
    19  )
    20  
    21  // DefaultStorageDir is the default directory name in which data
    22  // is stored relatively to the cozy-stack binary.
    23  const DefaultStorageDir = "storage"
    24  
    25  const defaultDevDomain = "cozy.localhost:8080"
    26  
    27  var flagDomain string
    28  
    29  var cfgFile string
    30  
    31  var errMissingDomain = errors.New("Missing --domain flag, or COZY_DOMAIN env variable")
    32  
    33  // RootCmd represents the base command when called without any subcommands
    34  var RootCmd = &cobra.Command{
    35  	Use:   "cozy-stack <command>",
    36  	Short: "cozy-stack is the main command",
    37  	Long: `Cozy is a platform that brings all your web services in the same private space.
    38  With it, your web apps and your devices can share data easily, providing you
    39  with a new experience. You can install Cozy on your own hardware where no one
    40  profiles you.`,
    41  	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
    42  		return config.Setup(cfgFile)
    43  	},
    44  	RunE: func(cmd *cobra.Command, args []string) error {
    45  		// Display the usage/help by default
    46  		return cmd.Usage()
    47  	},
    48  	// Do not display usage on error
    49  	SilenceUsage: true,
    50  	// We have our own way to display error messages
    51  	SilenceErrors: true,
    52  }
    53  
    54  func newClientSafe(domain string, scopes ...string) (*client.Client, error) {
    55  	// For the CLI client, we rely on the admin APIs to generate a CLI token.
    56  	// We may want in the future rely on OAuth to handle the permissions with
    57  	// more granularity.
    58  	ac := newAdminClient()
    59  	return ac.NewInstanceClient(domain, scopes...)
    60  }
    61  
    62  func newClient(domain string, scopes ...string) *client.Client {
    63  	client, err := newClientSafe(domain, scopes...)
    64  	if err != nil {
    65  		errPrintfln("Could not generate access to domain %s", domain)
    66  		errPrintfln("%s", err)
    67  		os.Exit(1)
    68  	}
    69  	return client
    70  }
    71  
    72  func newAdminClient() *client.AdminClient {
    73  	pass := []byte(os.Getenv("COZY_ADMIN_PASSPHRASE"))
    74  	if len(pass) == 0 {
    75  		pass = []byte(os.Getenv("COZY_ADMIN_PASSWORD"))
    76  	}
    77  	if !build.IsDevRelease() {
    78  		if len(pass) == 0 {
    79  			var err error
    80  			fmt.Fprintf(os.Stdout, "Password:")
    81  			pass, err = term.ReadPassword(int(os.Stdin.Fd()))
    82  			fmt.Fprintln(os.Stdout, "")
    83  			if err != nil {
    84  				errFatalf("Could not get password from standard input: %s\n", err)
    85  			}
    86  		}
    87  	}
    88  
    89  	httpClient, adminURL, err := tlsclient.NewHTTPClient(tlsclient.HTTPEndpoint{
    90  		Host:      config.GetConfig().AdminHost,
    91  		Port:      config.GetConfig().AdminPort,
    92  		Timeout:   30 * time.Minute,
    93  		EnvPrefix: "COZY_ADMIN",
    94  	})
    95  	checkNoErr(err)
    96  
    97  	return &client.AdminClient{
    98  		Client: client.Client{
    99  			Scheme:     adminURL.Scheme,
   100  			Addr:       adminURL.Host,
   101  			Domain:     adminURL.Host,
   102  			Client:     httpClient,
   103  			Authorizer: &request.BasicAuthorizer{Password: string(pass)},
   104  		},
   105  	}
   106  }
   107  
   108  func init() {
   109  	usageFunc := RootCmd.UsageFunc()
   110  	RootCmd.SetUsageFunc(func(cmd *cobra.Command) error {
   111  		_ = usageFunc(cmd)
   112  		return nil
   113  	})
   114  
   115  	flags := RootCmd.PersistentFlags()
   116  	flags.StringVarP(&cfgFile, "config", "c", "", "configuration file (default \"$HOME/.cozy.yaml\")")
   117  
   118  	flags.String("host", "localhost", "server host")
   119  	checkNoErr(viper.BindPFlag("host", flags.Lookup("host")))
   120  
   121  	flags.IntP("port", "p", 8080, "server port")
   122  	checkNoErr(viper.BindPFlag("port", flags.Lookup("port")))
   123  
   124  	flags.String("admin-host", "localhost", "administration server host")
   125  	checkNoErr(viper.BindPFlag("admin.host", flags.Lookup("admin-host")))
   126  
   127  	flags.Int("admin-port", 6060, "administration server port")
   128  	checkNoErr(viper.BindPFlag("admin.port", flags.Lookup("admin-port")))
   129  }
   130  
   131  func checkNoErr(err error) {
   132  	if err != nil {
   133  		panic(err)
   134  	}
   135  }
   136  
   137  func errPrintfln(format string, vals ...interface{}) {
   138  	_, err := fmt.Fprintf(os.Stderr, format+"\n", vals...)
   139  	if err != nil {
   140  		panic(err)
   141  	}
   142  }
   143  
   144  func errPrintf(format string, vals ...interface{}) {
   145  	_, err := fmt.Fprintf(os.Stderr, format, vals...)
   146  	if err != nil {
   147  		panic(err)
   148  	}
   149  }
   150  
   151  func errFatalf(format string, vals ...interface{}) {
   152  	_, err := fmt.Fprintf(os.Stderr, format, vals...)
   153  	if err != nil {
   154  		panic(err)
   155  	}
   156  	os.Exit(1)
   157  }
   158  
   159  func deprecatedDomainArg() {
   160  	errPrintfln("Please use --domain, the positional argument for domain has been deprecated")
   161  }
   162  
   163  func cozyDomain() string {
   164  	domain := os.Getenv("COZY_DOMAIN")
   165  	if domain == "" && build.IsDevRelease() {
   166  		domain = defaultDevDomain
   167  	}
   168  	return domain
   169  }
   170  
   171  func prompt(text string) string {
   172  	fmt.Fprintf(os.Stderr, "%s ", text)
   173  	r := bufio.NewReader(os.Stdin)
   174  	s, err := r.ReadString('\n')
   175  	if err != nil {
   176  		fmt.Fprintf(os.Stderr, "%s\n", err)
   177  		os.Exit(1)
   178  	}
   179  	return strings.TrimSuffix(s, "\n")
   180  }