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  }