package cmd import ( "bufio" "errors" "fmt" "os" "strings" "time" "github.com/cozy/cozy-stack/client" "github.com/cozy/cozy-stack/client/request" build "github.com/cozy/cozy-stack/pkg/config" "github.com/cozy/cozy-stack/pkg/config/config" "github.com/cozy/cozy-stack/pkg/consts" "github.com/cozy/cozy-stack/pkg/tlsclient" "github.com/spf13/cobra" "github.com/spf13/viper" "golang.org/x/term" ) // DefaultStorageDir is the default directory name in which data // is stored relatively to the cozy-stack binary. const DefaultStorageDir = "storage" const defaultDevDomain = "cozy.localhost:8080" var flagDomain string var cfgFile string var errMissingDomain = errors.New("Missing --domain flag, or COZY_DOMAIN env variable") // RootCmd represents the base command when called without any subcommands var RootCmd = &cobra.Command{ Use: "cozy-stack ", Short: "cozy-stack is the main command", Long: `Cozy is a platform that brings all your web services in the same private space. With it, your web apps and your devices can share data easily, providing you with a new experience. You can install Cozy on your own hardware where no one profiles you.`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return config.Setup(cfgFile) }, RunE: func(cmd *cobra.Command, args []string) error { // Display the usage/help by default return cmd.Usage() }, // Do not display usage on error SilenceUsage: true, // We have our own way to display error messages SilenceErrors: true, } func newClientSafe(domain string, scopes ...string) (*client.Client, error) { // For the CLI client, we rely on the admin APIs to generate a CLI token. // We may want in the future rely on OAuth to handle the permissions with // more granularity. ac := newAdminClient() return ac.NewInstanceClient(domain, scopes...) } func newClient(domain string, scopes ...string) *client.Client { client, err := newClientSafe(domain, scopes...) if err != nil { errPrintfln("Could not generate access to domain %s", domain) errPrintfln("%s", err) os.Exit(1) } return client } func newFilesClient(domain, driveID string) *client.FilesClient { return client.NewFilesClient(newClient(domain, consts.Files), driveID) } func newAdminClient() *client.AdminClient { pass := []byte(os.Getenv("COZY_ADMIN_PASSPHRASE")) if len(pass) == 0 { pass = []byte(os.Getenv("COZY_ADMIN_PASSWORD")) } if !build.IsDevRelease() { if len(pass) == 0 { var err error fmt.Fprintf(os.Stdout, "Password:") pass, err = term.ReadPassword(int(os.Stdin.Fd())) fmt.Fprintln(os.Stdout, "") if err != nil { errFatalf("Could not get password from standard input: %s\n", err) } } } httpClient, adminURL, err := tlsclient.NewHTTPClient(tlsclient.HTTPEndpoint{ Host: config.GetConfig().AdminHost, Port: config.GetConfig().AdminPort, Timeout: 30 * time.Minute, EnvPrefix: "COZY_ADMIN", }) checkNoErr(err) return &client.AdminClient{ Client: client.Client{ Scheme: adminURL.Scheme, Addr: adminURL.Host, Domain: adminURL.Host, Client: httpClient, Authorizer: &request.BasicAuthorizer{Password: string(pass)}, }, } } func init() { usageFunc := RootCmd.UsageFunc() RootCmd.SetUsageFunc(func(cmd *cobra.Command) error { _ = usageFunc(cmd) return nil }) flags := RootCmd.PersistentFlags() flags.StringVarP(&cfgFile, "config", "c", "", "configuration file (default \"$HOME/.cozy.yaml\")") flags.String("host", "localhost", "server host") checkNoErr(viper.BindPFlag("host", flags.Lookup("host"))) flags.IntP("port", "p", 8080, "server port") checkNoErr(viper.BindPFlag("port", flags.Lookup("port"))) flags.String("admin-host", "localhost", "administration server host") checkNoErr(viper.BindPFlag("admin.host", flags.Lookup("admin-host"))) flags.Int("admin-port", 6060, "administration server port") checkNoErr(viper.BindPFlag("admin.port", flags.Lookup("admin-port"))) } func checkNoErr(err error) { if err != nil { panic(err) } } func errPrintfln(format string, vals ...interface{}) { _, err := fmt.Fprintf(os.Stderr, format+"\n", vals...) if err != nil { panic(err) } } func errPrintf(format string, vals ...interface{}) { _, err := fmt.Fprintf(os.Stderr, format, vals...) if err != nil { panic(err) } } func errFatalf(format string, vals ...interface{}) { _, err := fmt.Fprintf(os.Stderr, format, vals...) if err != nil { panic(err) } os.Exit(1) } func deprecatedDomainArg() { errPrintfln("Please use --domain, the positional argument for domain has been deprecated") } func cozyDomain() string { domain := os.Getenv("COZY_DOMAIN") if domain == "" && build.IsDevRelease() { domain = defaultDevDomain } return domain } func prompt(text string) string { fmt.Fprintf(os.Stderr, "%s ", text) r := bufio.NewReader(os.Stdin) s, err := r.ReadString('\n') if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } return strings.TrimSuffix(s, "\n") }