github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/cmd/serve.go (about) 1 // Package cmd is where the CLI commands and options are defined. 2 package cmd 3 4 import ( 5 "context" 6 "errors" 7 "fmt" 8 "net/url" 9 "os" 10 "os/signal" 11 "path" 12 "path/filepath" 13 "strings" 14 "time" 15 16 "github.com/cozy/cozy-stack/model/stack" 17 build "github.com/cozy/cozy-stack/pkg/config" 18 "github.com/cozy/cozy-stack/pkg/config/config" 19 "github.com/cozy/cozy-stack/pkg/utils" 20 "github.com/cozy/cozy-stack/web" 21 "github.com/spf13/cobra" 22 "github.com/spf13/viper" 23 ) 24 25 var flagAllowRoot bool 26 var flagAppdirs []string 27 var flagDevMode bool 28 var flagMailhog bool 29 30 // serveCmd represents the serve command 31 var serveCmd = &cobra.Command{ 32 Use: "serve", 33 Short: "Starts the stack and listens for HTTP calls", 34 Long: `Starts the stack and listens for HTTP calls 35 It will accept HTTP requests on localhost:8080 by default. 36 Use the --port and --host flags to change the listening option. 37 38 The SIGINT signal will trigger a graceful stop of cozy-stack: it will wait that 39 current HTTP requests and jobs are finished (in a limit of 2 minutes) before 40 exiting. 41 42 If you are the developer of a client-side app, you can use --appdir 43 to mount a directory as the application with the 'app' slug. 44 `, 45 Example: `The most often, this command is used in its simple form: 46 47 $ cozy-stack serve 48 49 But if you want to develop two apps in local (to test their interactions for 50 example), you can use the --appdir flag like this: 51 52 $ cozy-stack serve --appdir appone:/path/to/app_one,apptwo:/path/to/app_two 53 `, 54 RunE: func(cmd *cobra.Command, args []string) error { 55 if !flagAllowRoot && os.Getuid() == 0 { 56 errPrintfln("Use --allow-root if you really want to start with the root user") 57 return errors.New("Starting cozy-stack serve as root not allowed") 58 } 59 60 if flagDevMode { 61 build.BuildMode = build.ModeDev 62 } 63 64 var apps map[string]string 65 if len(flagAppdirs) > 0 { 66 apps = make(map[string]string) 67 for _, app := range flagAppdirs { 68 parts := strings.Split(app, ":") 69 switch len(parts) { 70 case 1: 71 apps["app"] = parts[0] 72 case 2: 73 apps[parts[0]] = parts[1] 74 default: 75 return errors.New("Invalid appdir value") 76 } 77 } 78 } 79 80 if !build.IsDevRelease() { 81 adminSecretFile := config.GetConfig().AdminSecretFileName 82 if _, err := config.FindConfigFile(adminSecretFile); err != nil { 83 return err 84 } 85 } 86 87 if flagMailhog { 88 cfg := config.GetConfig() 89 cfg.Mail.NativeTLS = false 90 cfg.Mail.DisableTLS = true 91 cfg.Mail.Port = 1025 92 cfg.CampaignMail.NativeTLS = false 93 cfg.CampaignMail.DisableTLS = true 94 cfg.CampaignMail.Port = 1025 95 } 96 97 processes, services, err := stack.Start() 98 if err != nil { 99 return err 100 } 101 102 var servers *web.Servers 103 if apps != nil { 104 servers, err = web.ListenAndServeWithAppDir(apps, services) 105 } else { 106 servers, err = web.ListenAndServe(services) 107 } 108 if err != nil { 109 return err 110 } 111 112 group := utils.NewGroupShutdown(servers, processes) 113 114 sigs := make(chan os.Signal, 1) 115 signal.Notify(sigs, os.Interrupt) 116 117 select { 118 case err := <-servers.Wait(): 119 return err 120 case <-sigs: 121 fmt.Println("\nReceived interrupt signal:") 122 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) 123 defer cancel() // make gometalinter happy 124 if err := group.Shutdown(ctx); err != nil { 125 return err 126 } 127 fmt.Println("All settled, bye bye !") 128 return nil 129 } 130 }, 131 } 132 133 func init() { 134 binDir, err := filepath.Abs(filepath.Dir(os.Args[0])) 135 if err != nil { 136 panic(err) 137 } 138 139 flags := serveCmd.PersistentFlags() 140 flags.String("subdomains", "nested", "how to structure the subdomains for apps (can be nested or flat)") 141 checkNoErr(viper.BindPFlag("subdomains", flags.Lookup("subdomains"))) 142 143 flags.String("assets", "", "path to the directory with the assets (use the packed assets by default)") 144 checkNoErr(viper.BindPFlag("assets", flags.Lookup("assets"))) 145 146 flags.String("doctypes", "", "path to the directory with the doctypes (for developing/testing a remote doctype)") 147 checkNoErr(viper.BindPFlag("doctypes", flags.Lookup("doctypes"))) 148 149 defaultFsURL := &url.URL{ 150 Scheme: "file", 151 Path: path.Join(filepath.ToSlash(binDir), DefaultStorageDir), 152 } 153 flags.String("fs-url", defaultFsURL.String(), "filesystem url") 154 checkNoErr(viper.BindPFlag("fs.url", flags.Lookup("fs-url"))) 155 156 flags.Int("fs-default-layout", -1, "Default layout for Swift (2 for layout v3)") 157 checkNoErr(viper.BindPFlag("fs.default_layout", flags.Lookup("fs-default-layout"))) 158 159 flags.String("couchdb-url", "http://localhost:5984/", "CouchDB URL") 160 checkNoErr(viper.BindPFlag("couchdb.url", flags.Lookup("couchdb-url"))) 161 162 flags.String("lock-url", "", "URL for the locks, redis or in-memory") 163 checkNoErr(viper.BindPFlag("lock.url", flags.Lookup("lock-url"))) 164 165 flags.String("sessions-url", "", "URL for the sessions storage, redis or in-memory") 166 checkNoErr(viper.BindPFlag("sessions.url", flags.Lookup("sessions-url"))) 167 168 flags.String("downloads-url", "", "URL for the download secret storage, redis or in-memory") 169 checkNoErr(viper.BindPFlag("downloads.url", flags.Lookup("downloads-url"))) 170 171 flags.String("jobs-url", "", "URL for the jobs system synchronization, redis or in-memory") 172 checkNoErr(viper.BindPFlag("jobs.url", flags.Lookup("jobs-url"))) 173 174 flags.String("konnectors-cmd", "", "konnectors command to be executed") 175 checkNoErr(viper.BindPFlag("konnectors.cmd", flags.Lookup("konnectors-cmd"))) 176 177 flags.String("konnectors-oauthstate", "", "URL for the storage of OAuth state for konnectors, redis or in-memory") 178 checkNoErr(viper.BindPFlag("konnectors.oauthstate", flags.Lookup("konnectors-oauthstate"))) 179 180 flags.String("realtime-url", "", "URL for realtime in the browser via webocket, redis or in-memory") 181 checkNoErr(viper.BindPFlag("realtime.url", flags.Lookup("realtime-url"))) 182 183 flags.String("rate-limiting-url", "", "URL for rate-limiting counters, redis or in-memory") 184 checkNoErr(viper.BindPFlag("rate_limiting.url", flags.Lookup("rate-limiting-url"))) 185 186 flags.String("log-level", "info", "define the log level") 187 checkNoErr(viper.BindPFlag("log.level", flags.Lookup("log-level"))) 188 189 flags.Bool("log-syslog", false, "use the local syslog for logging") 190 checkNoErr(viper.BindPFlag("log.syslog", flags.Lookup("log-syslog"))) 191 192 flags.StringSlice("flagship-apk-package-names", []string{"io.cozy.drive.mobile", "io.cozy.flagship.mobile"}, "Package name for the flagship app on android") 193 checkNoErr(viper.BindPFlag("flagship.apk_package_names", flags.Lookup("flagship-apk-package-names"))) 194 195 flags.StringSlice("flagship-apk-certificate-digests", []string{"u2eUUnfB4Y7k7eqQL7u2jiYDJeVBwZoSV3PZSs8pttc="}, "SHA-256 hash (base64 encoded) of the flagship app's signing certificate on android") 196 checkNoErr(viper.BindPFlag("flagship.apk_certificate_digests", flags.Lookup("flagship-apk-certificate-digests"))) 197 198 flags.StringSlice("flagship-play-integrity-decryption-keys", []string{"bVcBAv0eO64NKIvDoRHpnTOZVxAkhMuFwRHrTEMr23U="}, "Decryption key for the Google Play Integrity API") 199 checkNoErr(viper.BindPFlag("flagship.play_integrity_decryption_keys", flags.Lookup("flagship-play-integrity-decryption-keys"))) 200 201 flags.StringSlice("flagship-play-integrity-verification-keys", []string{"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElTF2uARN7oxfoDWyERYMe6QutI2NqS+CAtVmsPDIRjBBxF96fYojFVXRRsMb86PjkE21Ol+sO1YuspY+YuDRMw=="}, "Verification key for the Google Play Integrity API") 202 checkNoErr(viper.BindPFlag("flagship.play_integrity_verification_keys", flags.Lookup("flagship-play-integrity-verification-keys"))) 203 204 flags.StringSlice("flagship-apple-app-ids", []string{"3AKXFMV43J.io.cozy.drive.mobile", "3AKXFMV43J.io.cozy.flagship.mobile"}, "App ID of the flagship app on iOS") 205 checkNoErr(viper.BindPFlag("flagship.apple_app_ids", flags.Lookup("flagship-apple-app-ids"))) 206 207 flags.String("geodb", ".", "define the location of the database for IP -> City lookups") 208 checkNoErr(viper.BindPFlag("geodb", flags.Lookup("geodb"))) 209 210 flags.String("mail-alert-address", "", "mail address used for alerts (instance deletion failure for example)") 211 checkNoErr(viper.BindPFlag("mail.alert_address", flags.Lookup("mail-alert-address"))) 212 213 flags.String("mail-noreply-address", "", "mail address used for sending mail as a noreply (forgot passwords for example)") 214 checkNoErr(viper.BindPFlag("mail.noreply_address", flags.Lookup("mail-noreply-address"))) 215 216 flags.String("mail-noreply-name", "My Cozy", "mail name used for sending mail as a noreply (forgot passwords for example)") 217 checkNoErr(viper.BindPFlag("mail.noreply_name", flags.Lookup("mail-noreply-name"))) 218 219 flags.String("mail-reply-to", "", "mail address used to the reply-to (support for example)") 220 checkNoErr(viper.BindPFlag("mail.reply_to", flags.Lookup("mail-reply-to"))) 221 222 flags.String("mail-host", "localhost", "mail smtp host") 223 checkNoErr(viper.BindPFlag("mail.host", flags.Lookup("mail-host"))) 224 225 flags.Int("mail-port", 25, "mail smtp port") 226 checkNoErr(viper.BindPFlag("mail.port", flags.Lookup("mail-port"))) 227 228 flags.String("mail-username", "", "mail smtp username") 229 checkNoErr(viper.BindPFlag("mail.username", flags.Lookup("mail-username"))) 230 231 flags.String("mail-password", "", "mail smtp password") 232 checkNoErr(viper.BindPFlag("mail.password", flags.Lookup("mail-password"))) 233 234 flags.Bool("mail-use-ssl", false, "use ssl for mail sending (smtps)") 235 checkNoErr(viper.BindPFlag("mail.use_ssl", flags.Lookup("mail-use-ssl"))) 236 237 flags.Bool("mail-disable-tls", true, "disable starttls on smtp") 238 checkNoErr(viper.BindPFlag("mail.disable_tls", flags.Lookup("mail-disable-tls"))) 239 240 flags.String("mail-local-name", "localhost", "hostname sent to the smtp server with the HELO command") 241 checkNoErr(viper.BindPFlag("mail.local_name", flags.Lookup("mail-local-name"))) 242 243 flags.String("move-url", "https://move.cozycloud.cc/", "URL for the move wizard") 244 checkNoErr(viper.BindPFlag("move.url", flags.Lookup("move-url"))) 245 246 flags.String("onlyoffice-url", "", "URL for the OnlyOffice server") 247 checkNoErr(viper.BindPFlag("office.default.onlyoffice_url", flags.Lookup("onlyoffice-url"))) 248 249 flags.String("onlyoffice-outbox-secret", "", "Secret used for verifying requests from the OnlyOffice server") 250 checkNoErr(viper.BindPFlag("office.default.onlyoffice_outbox_secret", flags.Lookup("onlyoffice-outbox-secret"))) 251 252 flags.String("onlyoffice-inbox-secret", "", "Secret used for signing requests to the OnlyOffice server") 253 checkNoErr(viper.BindPFlag("office.default.onlyoffice_inbox_secret", flags.Lookup("onlyoffice-inbox-secret"))) 254 255 flags.String("password-reset-interval", "15m", "minimal duration between two password reset") 256 checkNoErr(viper.BindPFlag("password_reset_interval", flags.Lookup("password-reset-interval"))) 257 258 flags.BoolVar(&flagMailhog, "mailhog", false, "Alias of --mail-disable-tls --mail-port 1025, useful for MailHog") 259 flags.BoolVar(&flagDevMode, "dev", false, "Allow to run in dev mode for a prod release (disabled by default)") 260 flags.BoolVar(&flagAllowRoot, "allow-root", false, "Allow to start as root (disabled by default)") 261 flags.StringSliceVar(&flagAppdirs, "appdir", nil, "Mount a directory as the 'app' application") 262 263 flags.Bool("remote-allow-custom-port", false, "Allow to specify a port in request files for remote doctypes") 264 checkNoErr(viper.BindPFlag("remote_allow_custom_port", flags.Lookup("remote-allow-custom-port"))) 265 266 flags.Bool("disable-csp", false, "Disable the Content Security Policy (only available for development)") 267 checkNoErr(viper.BindPFlag("disable_csp", flags.Lookup("disable-csp"))) 268 269 flags.String("csp-allowlist", "", "Add domains for the default allowed origins of the Content Secury Policy") 270 checkNoErr(viper.BindPFlag("csp_allowlist", flags.Lookup("csp-allowlist"))) 271 272 flags.String("vault-decryptor-key", "", "the path to the key used to decrypt credentials") 273 checkNoErr(viper.BindPFlag("vault.credentials_decryptor_key", flags.Lookup("vault-decryptor-key"))) 274 275 flags.String("vault-encryptor-key", "", "the path to the key used to encrypt credentials") 276 checkNoErr(viper.BindPFlag("vault.credentials_encryptor_key", flags.Lookup("vault-encryptor-key"))) 277 278 RootCmd.AddCommand(serveCmd) 279 }