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 }