github.com/anycable/anycable-go@v1.5.1/cli/options.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"github.com/anycable/anycable-go/config"
     9  	"github.com/anycable/anycable-go/node"
    10  	"github.com/anycable/anycable-go/version"
    11  	"github.com/nats-io/nats.go"
    12  	"github.com/urfave/cli/v2"
    13  )
    14  
    15  type cliOption func(*cli.App) error
    16  
    17  type customOptionsFactory = func() ([]cli.Flag, error)
    18  
    19  func WithCLIName(name string) cliOption {
    20  	return func(app *cli.App) error {
    21  		app.Name = name
    22  		return nil
    23  	}
    24  }
    25  
    26  func WithCLIVersion(str string) cliOption {
    27  	return func(app *cli.App) error {
    28  		app.Version = str
    29  		return nil
    30  	}
    31  }
    32  
    33  func WithCLIUsageHeader(desc string) cliOption {
    34  	return func(app *cli.App) error {
    35  		app.Usage = desc
    36  		return nil
    37  	}
    38  }
    39  
    40  func WithCLICustomOptions(factory customOptionsFactory) cliOption {
    41  	return func(app *cli.App) error {
    42  		custom, err := factory()
    43  		if err != nil {
    44  			return err
    45  		}
    46  
    47  		app.Flags = append(app.Flags, custom...)
    48  		return nil
    49  	}
    50  }
    51  
    52  // NewConfigFromCLI reads config from os.Args. It returns config, error (if any) and a bool value
    53  // indicating that the usage message or version was shown, no further action required.
    54  func NewConfigFromCLI(args []string, opts ...cliOption) (*config.Config, error, bool) {
    55  	c := config.NewConfig()
    56  
    57  	var path, headers, cookieFilter, mtags string
    58  	var helpOrVersionWereShown = true
    59  	var metricsFilter string
    60  	var enatsRoutes, enatsGateways string
    61  	var presets string
    62  	var turboRailsKey, cableReadyKey string
    63  	var turboRailsClearText, cableReadyClearText bool
    64  	var jwtIdKey, jwtIdParam string
    65  	var jwtIdEnforce bool
    66  	var noRPC bool
    67  	var isPublic bool
    68  
    69  	// Print raw version without prefix
    70  	cli.VersionPrinter = func(cCtx *cli.Context) {
    71  		_, _ = fmt.Fprintf(cCtx.App.Writer, "%v\n", cCtx.App.Version)
    72  	}
    73  
    74  	flags := []cli.Flag{}
    75  	flags = append(flags, serverCLIFlags(&c, &path, &isPublic)...)
    76  	flags = append(flags, sslCLIFlags(&c)...)
    77  	flags = append(flags, broadcastCLIFlags(&c)...)
    78  	flags = append(flags, brokerCLIFlags(&c)...)
    79  	flags = append(flags, redisCLIFlags(&c)...)
    80  	flags = append(flags, httpBroadcastCLIFlags(&c)...)
    81  	flags = append(flags, natsCLIFlags(&c)...)
    82  	flags = append(flags, rpcCLIFlags(&c, &headers, &cookieFilter, &noRPC)...)
    83  	flags = append(flags, disconnectorCLIFlags(&c)...)
    84  	flags = append(flags, logCLIFlags(&c)...)
    85  	flags = append(flags, metricsCLIFlags(&c, &metricsFilter, &mtags)...)
    86  	flags = append(flags, wsCLIFlags(&c)...)
    87  	flags = append(flags, pingCLIFlags(&c)...)
    88  	flags = append(flags, jwtCLIFlags(&c, &jwtIdKey, &jwtIdParam, &jwtIdEnforce)...)
    89  	flags = append(flags, signedStreamsCLIFlags(&c, &turboRailsKey, &cableReadyKey, &turboRailsClearText, &cableReadyClearText)...)
    90  	flags = append(flags, statsdCLIFlags(&c)...)
    91  	flags = append(flags, embeddedNatsCLIFlags(&c, &enatsRoutes, &enatsGateways)...)
    92  	flags = append(flags, sseCLIFlags(&c)...)
    93  	flags = append(flags, miscCLIFlags(&c, &presets)...)
    94  
    95  	app := &cli.App{
    96  		Name:            "anycable-go",
    97  		Version:         version.Version(),
    98  		Usage:           "AnyCable-Go, The WebSocket server for https://anycable.io",
    99  		HideHelpCommand: true,
   100  		Flags:           flags,
   101  		Action: func(nc *cli.Context) error {
   102  			helpOrVersionWereShown = false
   103  			return nil
   104  		},
   105  	}
   106  
   107  	for _, o := range opts {
   108  		err := o(app)
   109  		if err != nil {
   110  			return &config.Config{}, err, false
   111  		}
   112  	}
   113  
   114  	err := app.Run(args)
   115  	if err != nil {
   116  		return &config.Config{}, err, false
   117  	}
   118  
   119  	// helpOrVersionWereShown = false indicates that the default action has been run.
   120  	// true means that help/version message was displayed.
   121  	//
   122  	// Unfortunately, cli module does not support another way of detecting if or which
   123  	// command was run.
   124  	if helpOrVersionWereShown {
   125  		return &config.Config{}, nil, true
   126  	}
   127  
   128  	if path != "" {
   129  		c.Path = strings.Split(path, ",")
   130  	}
   131  
   132  	c.Headers = strings.Split(strings.ToLower(headers), ",")
   133  
   134  	if len(cookieFilter) > 0 {
   135  		c.Cookies = strings.Split(cookieFilter, ",")
   136  	}
   137  
   138  	if c.Debug {
   139  		c.LogLevel = "debug"
   140  	}
   141  
   142  	if c.Metrics.Port == 0 {
   143  		c.Metrics.Port = c.Port
   144  	}
   145  
   146  	if mtags != "" {
   147  		c.Metrics.Tags = parseTags(mtags)
   148  	}
   149  
   150  	if c.Metrics.LogInterval > 0 {
   151  		fmt.Println(`DEPRECATION WARNING: metrics_log_interval option is deprecated
   152  and will be deleted in the next major release of anycable-go.
   153  Use metrics_rotate_interval instead.`)
   154  
   155  		if c.Metrics.RotateInterval == 0 {
   156  			c.Metrics.RotateInterval = c.Metrics.LogInterval
   157  		}
   158  	}
   159  
   160  	if metricsFilter != "" {
   161  		c.Metrics.LogFilter = strings.Split(metricsFilter, ",")
   162  	}
   163  
   164  	if enatsRoutes != "" {
   165  		c.EmbeddedNats.Routes = strings.Split(enatsRoutes, ",")
   166  	}
   167  
   168  	if enatsGateways != "" {
   169  		c.EmbeddedNats.Gateways = strings.Split(enatsGateways, ";")
   170  	}
   171  
   172  	if presets != "" {
   173  		c.UserPresets = strings.Split(presets, ",")
   174  	}
   175  
   176  	// Automatically set the URL of the embedded NATS as the pub/sub server URL
   177  	if c.EmbedNats && c.NATS.Servers == nats.DefaultURL {
   178  		c.NATS.Servers = c.EmbeddedNats.ServiceAddr
   179  	}
   180  
   181  	if c.DisconnectorDisabled {
   182  		fmt.Println(`DEPRECATION WARNING: disable_disconnect option is deprecated
   183  and will be removed in the next major release of anycable-go.
   184  Use disconnect_mode=never instead.`)
   185  
   186  		c.App.DisconnectMode = node.DISCONNECT_MODE_NEVER
   187  	}
   188  
   189  	if c.DisconnectQueue.ShutdownTimeout > 0 {
   190  		fmt.Println(`DEPRECATION WARNING: disconnect_timeout option is deprecated
   191  and will be removed in the next major release of anycable-go.
   192  Use shutdown_timeout instead.`)
   193  	}
   194  
   195  	c.SSE.AllowedOrigins = c.WS.AllowedOrigins
   196  	c.HTTPBroadcast.CORSHosts = c.WS.AllowedOrigins
   197  
   198  	if turboRailsKey != "" {
   199  		fmt.Println(`DEPRECATION WARNING: turbo_rails_key option is deprecated
   200  and will be removed in the next major release of anycable-go.
   201  Use turbo_streams_secret instead.`)
   202  
   203  		c.Streams.TurboSecret = turboRailsKey
   204  
   205  		c.Streams.Turbo = true
   206  	}
   207  
   208  	if turboRailsClearText {
   209  		fmt.Println(`DEPRECATION WARNING: turbo_rails_cleartext option is deprecated
   210  and will be removed in the next major release of anycable-go.
   211  It has no effect anymore, use public streams instead.`)
   212  	}
   213  
   214  	if cableReadyKey != "" {
   215  		fmt.Println(`DEPRECATION WARNING: cable_ready_key option is deprecated
   216  and will be removed in the next major release of anycable-go.
   217  Use cable_ready_secret instead.`)
   218  
   219  		c.Streams.CableReadySecret = cableReadyKey
   220  
   221  		c.Streams.CableReady = true
   222  	}
   223  
   224  	if cableReadyClearText {
   225  		fmt.Println(`DEPRECATION WARNING: cable_ready_cleartext option is deprecated
   226  and will be removed in the next major release of anycable-go.
   227  It has no effect anymore, use public streams instead.`)
   228  	}
   229  
   230  	if jwtIdKey != "" {
   231  		fmt.Println(`DEPRECATION WARNING: jwt_id_key option is deprecated
   232  and will be removed in the next major release of anycable-go.
   233  Use jwt_secret instead.`)
   234  
   235  		if c.JWT.Secret == "" {
   236  			c.JWT.Secret = jwtIdKey
   237  		}
   238  	}
   239  
   240  	if jwtIdParam != "" {
   241  		fmt.Println(`DEPRECATION WARNING: jwt_id_param option is deprecated
   242  and will be removed in the next major release of anycable-go.
   243  Use jwt_param instead.`)
   244  
   245  		if c.JWT.Param == "" {
   246  			c.JWT.Param = jwtIdParam
   247  		}
   248  	}
   249  
   250  	if jwtIdEnforce {
   251  		fmt.Println(`DEPRECATION WARNING: jwt_id_enforce option is deprecated
   252  and will be removed in the next major release of anycable-go.
   253  Use enfore_jwt instead.`)
   254  
   255  		c.JWT.Force = true
   256  	}
   257  
   258  	// Configure RPC
   259  	if noRPC {
   260  		c.RPC.Implementation = "none"
   261  	}
   262  
   263  	// Legacy HTTP authentication stuff
   264  	if c.HTTPBroadcast.Secret != "" {
   265  		fmt.Println(`DEPRECATION WARNING: http_broadcast_secret option is deprecated
   266  and will be removed in the next major release of anycable-go.
   267  Use broadcast_key instead.`)
   268  	}
   269  
   270  	if c.HTTPBroadcast.Secret == "" {
   271  		c.HTTPBroadcast.Secret = c.BroadcastKey
   272  	}
   273  
   274  	// Fallback secrets
   275  	if c.Secret != "" {
   276  		if c.Streams.Secret == "" {
   277  			c.Streams.Secret = c.Secret
   278  		}
   279  
   280  		if c.JWT.Secret == "" {
   281  			c.JWT.Secret = c.Secret
   282  		}
   283  
   284  		if c.HTTPBroadcast.Secret == "" {
   285  			c.HTTPBroadcast.SecretBase = c.Secret
   286  		}
   287  
   288  		if c.RPC.Secret == "" {
   289  			c.RPC.SecretBase = c.Secret
   290  		}
   291  	}
   292  
   293  	// Nullify none secrets
   294  	if c.Streams.Secret == "none" {
   295  		c.Streams.Secret = ""
   296  	}
   297  
   298  	if c.JWT.Secret == "none" {
   299  		c.JWT.Secret = ""
   300  	}
   301  
   302  	if c.RPC.Secret == "none" {
   303  		c.RPC.Secret = ""
   304  	}
   305  
   306  	if c.HTTPBroadcast.Secret == "none" {
   307  		c.HTTPBroadcast.Secret = ""
   308  	}
   309  
   310  	// Configure default HTTP port
   311  	if c.HTTPBroadcast.Port == 0 {
   312  		if c.HTTPBroadcast.IsSecured() {
   313  			c.HTTPBroadcast.Port = c.Port
   314  		} else {
   315  			c.HTTPBroadcast.Port = 8090
   316  		}
   317  	}
   318  
   319  	// Configure public mode and other insecure features
   320  	if isPublic {
   321  		c.SkipAuth = true
   322  		c.Streams.Public = true
   323  		// Ensure broadcasting is also public
   324  		c.HTTPBroadcast.Secret = ""
   325  		c.HTTPBroadcast.SecretBase = ""
   326  	}
   327  
   328  	return &c, nil, false
   329  }
   330  
   331  // NewConfig returns a new AnyCable configuration combining default values and values from the environment.
   332  func NewConfig() *config.Config {
   333  	c, err, _ := NewConfigFromCLI([]string{})
   334  
   335  	if err != nil {
   336  		panic(err)
   337  	}
   338  
   339  	return c
   340  }
   341  
   342  // Flags ordering issue: https://github.com/urfave/cli/pull/1430
   343  
   344  const (
   345  	serverCategoryDescription        = "ANYCABLE-GO SERVER:"
   346  	sslCategoryDescription           = "SSL:"
   347  	broadcastCategoryDescription     = "BROADCASTING:"
   348  	redisCategoryDescription         = "REDIS:"
   349  	httpBroadcastCategoryDescription = "HTTP BROADCAST:"
   350  	natsCategoryDescription          = "NATS:"
   351  	rpcCategoryDescription           = "RPC:"
   352  	disconnectorCategoryDescription  = "DISCONNECTOR:"
   353  	logCategoryDescription           = "LOG:"
   354  	metricsCategoryDescription       = "METRICS:"
   355  	wsCategoryDescription            = "WEBSOCKETS:"
   356  	pingCategoryDescription          = "PING:"
   357  	jwtCategoryDescription           = "JWT:"
   358  	signedStreamsCategoryDescription = "SIGNED STREAMS:"
   359  	statsdCategoryDescription        = "STATSD:"
   360  	embeddedNatsCategoryDescription  = "EMBEDDED NATS:"
   361  	miscCategoryDescription          = "MISC:"
   362  	brokerCategoryDescription        = "BROKER:"
   363  	sseCategoryDescription           = "SERVER-SENT EVENTS:"
   364  
   365  	envPrefix = "ANYCABLE_"
   366  )
   367  
   368  var (
   369  	splitFlagName = regexp.MustCompile("[_-]")
   370  )
   371  
   372  // serverCLIFlags returns base server flags
   373  func serverCLIFlags(c *config.Config, path *string, isPublic *bool) []cli.Flag {
   374  	return withDefaults(serverCategoryDescription, []cli.Flag{
   375  		&cli.StringFlag{
   376  			Name:        "host",
   377  			Value:       c.Host,
   378  			Usage:       "Server host",
   379  			Destination: &c.Host,
   380  		},
   381  
   382  		&cli.IntFlag{
   383  			Name:        "port",
   384  			Value:       c.Port,
   385  			Usage:       "Server port",
   386  			EnvVars:     []string{envPrefix + "PORT", "PORT"},
   387  			Destination: &c.Port,
   388  		},
   389  
   390  		&cli.StringFlag{
   391  			Name:        "secret",
   392  			Usage:       "A common secret key used by all features by default",
   393  			Value:       c.Secret,
   394  			Destination: &c.Secret,
   395  		},
   396  
   397  		&cli.StringFlag{
   398  			Name:        "broadcast_key",
   399  			Usage:       "An authentication key for broadcast requests",
   400  			Value:       c.BroadcastKey,
   401  			Destination: &c.BroadcastKey,
   402  		},
   403  
   404  		&cli.BoolFlag{
   405  			Name:        "public",
   406  			Usage:       "[DANGER ZONE] Run server in the public mode allowing all connections and stream subscriptions",
   407  			Destination: isPublic,
   408  		},
   409  
   410  		&cli.BoolFlag{
   411  			Name:        "noauth",
   412  			Usage:       "[DANGER ZONE] Disable client authentication over RPC",
   413  			Value:       c.SkipAuth,
   414  			Destination: &c.SkipAuth,
   415  		},
   416  
   417  		&cli.IntFlag{
   418  			Name:        "max-conn",
   419  			Usage:       "Limit simultaneous server connections (0 – without limit)",
   420  			Destination: &c.MaxConn,
   421  		},
   422  
   423  		&cli.StringFlag{
   424  			Name:        "path",
   425  			Value:       strings.Join(c.Path, ","),
   426  			Usage:       "WebSocket endpoint path (you can specify multiple paths using comma as separator)",
   427  			Destination: path,
   428  		},
   429  
   430  		&cli.StringFlag{
   431  			Name:        "health-path",
   432  			Value:       c.HealthPath,
   433  			Usage:       "HTTP health endpoint path",
   434  			Destination: &c.HealthPath,
   435  		},
   436  
   437  		&cli.IntFlag{
   438  			Name:        "shutdown_timeout",
   439  			Usage:       "Graceful shutdown timeout (in seconds)",
   440  			Value:       c.App.ShutdownTimeout,
   441  			Destination: &c.App.ShutdownTimeout,
   442  		},
   443  
   444  		&cli.IntFlag{
   445  			Name:        "shutdown_pool_size",
   446  			Usage:       "The number of goroutines to use for disconnect calls on shutdown",
   447  			Value:       c.App.ShutdownDisconnectPoolSize,
   448  			Destination: &c.App.ShutdownDisconnectPoolSize,
   449  			Hidden:      true,
   450  		},
   451  	})
   452  }
   453  
   454  // sslCLIFlags returns SSL flags
   455  func sslCLIFlags(c *config.Config) []cli.Flag {
   456  	return withDefaults(sslCategoryDescription, []cli.Flag{
   457  		&cli.PathFlag{
   458  			Name:        "ssl_cert",
   459  			Usage:       "SSL certificate path",
   460  			Destination: &c.SSL.CertPath,
   461  		},
   462  
   463  		&cli.PathFlag{
   464  			Name:        "ssl_key",
   465  			Usage:       "SSL private key path",
   466  			Destination: &c.SSL.KeyPath,
   467  		},
   468  	})
   469  }
   470  
   471  // broadcastCLIFlags returns broadcast_adapter flag
   472  func broadcastCLIFlags(c *config.Config) []cli.Flag {
   473  	return withDefaults(broadcastCategoryDescription, []cli.Flag{
   474  		&cli.StringFlag{
   475  			Name:        "broadcast_adapter",
   476  			Usage:       "Broadcasting adapter to use (http, redisx, redis or nats). You can specify multiple at once via a comma-separated list",
   477  			Value:       c.BroadcastAdapter,
   478  			Destination: &c.BroadcastAdapter,
   479  		},
   480  		&cli.StringFlag{
   481  			Name:        "broker",
   482  			Usage:       "Broker engine to use (memory)",
   483  			Value:       c.BrokerAdapter,
   484  			Destination: &c.BrokerAdapter,
   485  		},
   486  		&cli.StringFlag{
   487  			Name:        "pubsub",
   488  			Usage:       "Pub/Sub adapter to use (redis or nats)",
   489  			Value:       c.PubSubAdapter,
   490  			Destination: &c.PubSubAdapter,
   491  		},
   492  		&cli.IntFlag{
   493  			Name:        "hub_gopool_size",
   494  			Usage:       "The size of the goroutines pool to broadcast messages",
   495  			Value:       c.App.HubGopoolSize,
   496  			Destination: &c.App.HubGopoolSize,
   497  			Hidden:      true,
   498  		},
   499  	})
   500  }
   501  
   502  // brokerCLIFlags returns broker related flags
   503  func brokerCLIFlags(c *config.Config) []cli.Flag {
   504  	return withDefaults(brokerCategoryDescription, []cli.Flag{
   505  		&cli.IntFlag{
   506  			Name:        "history_limit",
   507  			Usage:       "Max number of messages to keep in the stream's history",
   508  			Value:       c.Broker.HistoryLimit,
   509  			Destination: &c.Broker.HistoryLimit,
   510  		},
   511  		&cli.Int64Flag{
   512  			Name:        "history_ttl",
   513  			Usage:       "TTL for messages in streams history (seconds)",
   514  			Value:       c.Broker.HistoryTTL,
   515  			Destination: &c.Broker.HistoryTTL,
   516  		},
   517  		&cli.Int64Flag{
   518  			Name:        "sessions_ttl",
   519  			Usage:       "TTL for expired/disconnected sessions (seconds)",
   520  			Value:       c.Broker.SessionsTTL,
   521  			Destination: &c.Broker.SessionsTTL,
   522  		},
   523  	})
   524  }
   525  
   526  func redisCLIFlags(c *config.Config) []cli.Flag {
   527  	return withDefaults(redisCategoryDescription, []cli.Flag{
   528  		&cli.StringFlag{
   529  			Name:        "redis_url",
   530  			Usage:       "Redis url",
   531  			Value:       c.Redis.URL,
   532  			EnvVars:     []string{envPrefix + "REDIS_URL", "REDIS_URL"},
   533  			Destination: &c.Redis.URL,
   534  		},
   535  
   536  		&cli.StringFlag{
   537  			Name:        "redis_channel",
   538  			Usage:       "Redis channel for broadcasts",
   539  			Value:       c.Redis.Channel,
   540  			Destination: &c.Redis.Channel,
   541  		},
   542  
   543  		&cli.StringFlag{
   544  			Name:        "redis_sentinels",
   545  			Usage:       "Comma separated list of sentinel hosts, format: 'hostname:port,..'",
   546  			Destination: &c.Redis.Sentinels,
   547  		},
   548  
   549  		&cli.IntFlag{
   550  			Name:        "redis_sentinel_discovery_interval",
   551  			Usage:       "Interval to rediscover sentinels in seconds",
   552  			Value:       c.Redis.SentinelDiscoveryInterval,
   553  			Destination: &c.Redis.SentinelDiscoveryInterval,
   554  			Hidden:      true,
   555  		},
   556  
   557  		&cli.IntFlag{
   558  			Name:        "redis_keepalive_interval",
   559  			Usage:       "Interval to periodically ping Redis to make sure it's alive",
   560  			Value:       c.Redis.KeepalivePingInterval,
   561  			Destination: &c.Redis.KeepalivePingInterval,
   562  			Hidden:      true,
   563  		},
   564  
   565  		&cli.BoolFlag{
   566  			Name:        "redis_tls_verify",
   567  			Usage:       "Verify Redis server TLS certificate (only if URL protocol is rediss://)",
   568  			Value:       c.Redis.TLSVerify,
   569  			Destination: &c.Redis.TLSVerify,
   570  			Hidden:      true,
   571  		},
   572  
   573  		&cli.BoolFlag{
   574  			Name:        "redis_disable_cache",
   575  			Usage:       "Disable client-side caching",
   576  			Value:       c.Redis.DisableCache,
   577  			Destination: &c.Redis.DisableCache,
   578  			Hidden:      true,
   579  		},
   580  	})
   581  }
   582  
   583  // httpBroadcastCLIFlags returns HTTP CLI flags
   584  func httpBroadcastCLIFlags(c *config.Config) []cli.Flag {
   585  	return withDefaults(httpBroadcastCategoryDescription, []cli.Flag{
   586  		&cli.IntFlag{
   587  			Name:        "http_broadcast_port",
   588  			Usage:       "HTTP pub/sub server port",
   589  			Value:       c.HTTPBroadcast.Port,
   590  			Destination: &c.HTTPBroadcast.Port,
   591  		},
   592  
   593  		&cli.StringFlag{
   594  			Name:        "http_broadcast_path",
   595  			Usage:       "HTTP pub/sub endpoint path",
   596  			Value:       c.HTTPBroadcast.Path,
   597  			Destination: &c.HTTPBroadcast.Path,
   598  		},
   599  
   600  		&cli.StringFlag{
   601  			Name:        "http_broadcast_secret",
   602  			Usage:       "[Deprecated] HTTP pub/sub authorization secret",
   603  			Destination: &c.HTTPBroadcast.Secret,
   604  			Hidden:      true,
   605  		},
   606  
   607  		&cli.BoolFlag{
   608  			Name:        "http_broadcast_cors",
   609  			Destination: &c.HTTPBroadcast.AddCORSHeaders,
   610  			Hidden:      true,
   611  		},
   612  	})
   613  }
   614  
   615  // natsCLIFlags returns NATS cli flags
   616  func natsCLIFlags(c *config.Config) []cli.Flag {
   617  	return withDefaults(natsCategoryDescription, []cli.Flag{
   618  		&cli.StringFlag{
   619  			Name:        "nats_servers",
   620  			Usage:       "Comma separated list of NATS cluster servers",
   621  			Value:       c.NATS.Servers,
   622  			Destination: &c.NATS.Servers,
   623  		},
   624  
   625  		&cli.StringFlag{
   626  			Name:        "nats_channel",
   627  			Usage:       "NATS channel for broadcasts",
   628  			Value:       c.NATS.Channel,
   629  			Destination: &c.NATS.Channel,
   630  		},
   631  
   632  		&cli.BoolFlag{
   633  			Name:        "nats_dont_randomize_servers",
   634  			Usage:       "Pass this option to disable NATS servers randomization during (re-)connect",
   635  			Destination: &c.NATS.DontRandomizeServers,
   636  			Hidden:      true,
   637  		},
   638  	})
   639  }
   640  
   641  // embeddedNatsCLIFlags returns NATS cli flags
   642  func embeddedNatsCLIFlags(c *config.Config, routes *string, gateways *string) []cli.Flag {
   643  	return withDefaults(embeddedNatsCategoryDescription, []cli.Flag{
   644  		&cli.BoolFlag{
   645  			Name:        "embed_nats",
   646  			Usage:       "Enable embedded NATS server and use it for pub/sub",
   647  			Value:       c.EmbedNats,
   648  			Destination: &c.EmbedNats,
   649  		},
   650  
   651  		&cli.StringFlag{
   652  			Name:        "enats_addr",
   653  			Usage:       "NATS server bind address",
   654  			Value:       c.EmbeddedNats.ServiceAddr,
   655  			Destination: &c.EmbeddedNats.ServiceAddr,
   656  			Hidden:      true,
   657  		},
   658  
   659  		&cli.StringFlag{
   660  			Name:        "enats_cluster",
   661  			Usage:       "NATS cluster service bind address",
   662  			Value:       c.EmbeddedNats.ClusterAddr,
   663  			Destination: &c.EmbeddedNats.ClusterAddr,
   664  			Hidden:      true,
   665  		},
   666  
   667  		&cli.StringFlag{
   668  			Name:        "enats_cluster_name",
   669  			Usage:       "NATS cluster name",
   670  			Value:       c.EmbeddedNats.ClusterName,
   671  			Destination: &c.EmbeddedNats.ClusterName,
   672  		},
   673  
   674  		&cli.StringFlag{
   675  			Name:        "enats_cluster_routes",
   676  			Usage:       "Comma-separated list of known cluster addresses",
   677  			Destination: routes,
   678  		},
   679  
   680  		&cli.StringFlag{
   681  			Name:        "enats_gateway",
   682  			Usage:       "NATS gateway bind address",
   683  			Value:       c.EmbeddedNats.GatewayAddr,
   684  			Destination: &c.EmbeddedNats.GatewayAddr,
   685  			Hidden:      true,
   686  		},
   687  
   688  		&cli.StringFlag{
   689  			Name:        "enats_gateways",
   690  			Usage:       "Semicolon-separated list of known gateway configurations: name_a:gateway_1,gateway_2;name_b:gateway_4",
   691  			Destination: gateways,
   692  		},
   693  
   694  		&cli.StringFlag{
   695  			Name:        "enats_gateway_advertise",
   696  			Usage:       "NATS gateway advertise address",
   697  			Value:       c.EmbeddedNats.GatewayAdvertise,
   698  			Destination: &c.EmbeddedNats.GatewayAdvertise,
   699  		},
   700  
   701  		&cli.StringFlag{
   702  			Name:        "enats_store_dir",
   703  			Usage:       "Embedded NATS store directory (for JetStream)",
   704  			Value:       c.EmbeddedNats.StoreDir,
   705  			Destination: &c.EmbeddedNats.StoreDir,
   706  			Hidden:      true,
   707  		},
   708  
   709  		&cli.StringFlag{
   710  			Name:        "enats_server_name",
   711  			Usage:       "Embedded NATS unique server name (required for JetStream), auto-generated by default",
   712  			Value:       c.EmbeddedNats.Name,
   713  			Destination: &c.EmbeddedNats.Name,
   714  		},
   715  
   716  		&cli.BoolFlag{
   717  			Name:        "enats_debug",
   718  			Usage:       "Enable NATS server logs",
   719  			Destination: &c.EmbeddedNats.Debug,
   720  			Hidden:      true,
   721  		},
   722  
   723  		&cli.BoolFlag{
   724  			Name:        "enats_trace",
   725  			Usage:       "Enable NATS server protocol trace logs",
   726  			Destination: &c.EmbeddedNats.Trace,
   727  			Hidden:      true,
   728  		},
   729  	})
   730  }
   731  
   732  // rpcCLIFlags returns CLI flags for RPC
   733  func rpcCLIFlags(c *config.Config, headers, cookieFilter *string, isNone *bool) []cli.Flag {
   734  	return withDefaults(rpcCategoryDescription, []cli.Flag{
   735  		&cli.StringFlag{
   736  			Name:        "rpc_host",
   737  			Usage:       "RPC service address (full URL in case of HTTP RPC)",
   738  			Value:       c.RPC.Host,
   739  			Destination: &c.RPC.Host,
   740  		},
   741  
   742  		&cli.BoolFlag{
   743  			Name:        "norpc",
   744  			Usage:       "Disable RPC component and run server in the standalone mode",
   745  			Destination: isNone,
   746  		},
   747  
   748  		&cli.IntFlag{
   749  			Name:        "rpc_concurrency",
   750  			Usage:       "Max number of concurrent RPC request; should be slightly less than the RPC server concurrency",
   751  			Value:       c.RPC.Concurrency,
   752  			Destination: &c.RPC.Concurrency,
   753  		},
   754  
   755  		&cli.BoolFlag{
   756  			Name:        "rpc_enable_tls",
   757  			Usage:       "Enable client-side TLS with the RPC server",
   758  			Destination: &c.RPC.EnableTLS,
   759  			Hidden:      true,
   760  		},
   761  
   762  		&cli.BoolFlag{
   763  			Name:        "rpc_tls_verify",
   764  			Usage:       "Whether to verify the RPC server certificate",
   765  			Destination: &c.RPC.TLSVerify,
   766  			Value:       true,
   767  			Hidden:      true,
   768  		},
   769  
   770  		&cli.StringFlag{
   771  			Name:        "rpc_tls_root_ca",
   772  			Usage:       "CA root certificate file path or contents in PEM format (if not set, system CAs will be used)",
   773  			Destination: &c.RPC.TLSRootCA,
   774  			Hidden:      true,
   775  		},
   776  
   777  		&cli.IntFlag{
   778  			Name:        "rpc_max_call_recv_size",
   779  			Usage:       "Override default MaxCallRecvMsgSize for RPC client (bytes)",
   780  			Value:       c.RPC.MaxRecvSize,
   781  			Destination: &c.RPC.MaxRecvSize,
   782  			Hidden:      true,
   783  		},
   784  
   785  		&cli.IntFlag{
   786  			Name:        "rpc_max_call_send_size",
   787  			Usage:       "Override default MaxCallSendMsgSize for RPC client (bytes)",
   788  			Value:       c.RPC.MaxSendSize,
   789  			Destination: &c.RPC.MaxSendSize,
   790  			Hidden:      true,
   791  		},
   792  
   793  		&cli.StringFlag{
   794  			Name:        "headers",
   795  			Usage:       "List of headers to proxy to RPC",
   796  			Value:       strings.Join(c.Headers, ","),
   797  			Destination: headers,
   798  		},
   799  
   800  		&cli.StringFlag{
   801  			Name:        "proxy-cookies",
   802  			Usage:       "Cookie keys to send to RPC, default is all",
   803  			Destination: cookieFilter,
   804  		},
   805  
   806  		&cli.StringFlag{
   807  			Name:        "rpc_impl",
   808  			Usage:       "RPC implementation (grpc, http)",
   809  			Value:       c.RPC.Implementation,
   810  			Destination: &c.RPC.Implementation,
   811  			Hidden:      true,
   812  		},
   813  
   814  		&cli.StringFlag{
   815  			Name:        "http_rpc_secret",
   816  			Usage:       "Authentication secret for RPC over HTTP",
   817  			Value:       c.RPC.Secret,
   818  			Destination: &c.RPC.Secret,
   819  		},
   820  
   821  		&cli.IntFlag{
   822  			Name:        "http_rpc_timeout",
   823  			Usage:       "HTTP RPC timeout (in ms)",
   824  			Value:       c.RPC.RequestTimeout,
   825  			Destination: &c.RPC.RequestTimeout,
   826  			Hidden:      true,
   827  		},
   828  	})
   829  }
   830  
   831  // rpcCLIFlags returns CLI flags for disconnect options
   832  func disconnectorCLIFlags(c *config.Config) []cli.Flag {
   833  	return withDefaults(disconnectorCategoryDescription, []cli.Flag{
   834  		&cli.StringFlag{
   835  			Name:        "disconnect_mode",
   836  			Usage:       "Define when to call Disconnect callback (always, never, auto)",
   837  			Destination: &c.App.DisconnectMode,
   838  			Value:       c.App.DisconnectMode,
   839  		},
   840  
   841  		&cli.IntFlag{
   842  			Name:        "disconnect_rate",
   843  			Usage:       "Max number of Disconnect calls per second",
   844  			Value:       c.DisconnectQueue.Rate,
   845  			Destination: &c.DisconnectQueue.Rate,
   846  			Hidden:      true,
   847  		},
   848  
   849  		&cli.IntFlag{
   850  			Name:        "disconnect_backlog_size",
   851  			Usage:       "The size of the channel's buffer for disconnect requests",
   852  			Value:       c.DisconnectQueue.Backlog,
   853  			Destination: &c.DisconnectQueue.Backlog,
   854  			Hidden:      true,
   855  		},
   856  
   857  		&cli.IntFlag{
   858  			Name:        "disconnect_timeout",
   859  			Usage:       "[DEPRECATED] Graceful shutdown timeout (in seconds)",
   860  			Value:       c.DisconnectQueue.ShutdownTimeout,
   861  			Destination: &c.DisconnectQueue.ShutdownTimeout,
   862  			Hidden:      true,
   863  		},
   864  
   865  		&cli.BoolFlag{
   866  			Name:        "disable_disconnect",
   867  			Usage:       "Disable calling Disconnect callback",
   868  			Destination: &c.DisconnectorDisabled,
   869  			Hidden:      true,
   870  		},
   871  	})
   872  }
   873  
   874  // rpcCLIFlags returns CLI flags for logging
   875  func logCLIFlags(c *config.Config) []cli.Flag {
   876  	return withDefaults(logCategoryDescription, []cli.Flag{
   877  		&cli.StringFlag{
   878  			Name:        "log_level",
   879  			Usage:       "Set logging level (debug/info/warn/error)",
   880  			Value:       c.LogLevel,
   881  			Destination: &c.LogLevel,
   882  		},
   883  
   884  		&cli.StringFlag{
   885  			Name:        "log_format",
   886  			Usage:       "Set logging format (text/json)",
   887  			Value:       c.LogFormat,
   888  			Destination: &c.LogFormat,
   889  		},
   890  
   891  		&cli.BoolFlag{
   892  			Name:        "debug",
   893  			Usage:       "Enable debug mode (more verbose logging)",
   894  			Destination: &c.Debug,
   895  		},
   896  	})
   897  }
   898  
   899  // metricsCLIFlags returns CLI flags for metrics
   900  func metricsCLIFlags(c *config.Config, filter *string, mtags *string) []cli.Flag {
   901  	return withDefaults(metricsCategoryDescription, []cli.Flag{
   902  		// Metrics
   903  		&cli.BoolFlag{
   904  			Name:        "metrics_log",
   905  			Usage:       "Enable metrics logging (with info level)",
   906  			Destination: &c.Metrics.Log,
   907  		},
   908  
   909  		&cli.IntFlag{
   910  			Name:        "metrics_rotate_interval",
   911  			Usage:       "Specify how often flush metrics to writers (logs, statsd) (in seconds)",
   912  			Value:       c.Metrics.RotateInterval,
   913  			Destination: &c.Metrics.RotateInterval,
   914  		},
   915  
   916  		&cli.IntFlag{
   917  			Name:        "metrics_log_interval",
   918  			Usage:       "DEPRECATED. Specify how often flush metrics logs (in seconds)",
   919  			Value:       c.Metrics.LogInterval,
   920  			Destination: &c.Metrics.LogInterval,
   921  			Hidden:      true,
   922  		},
   923  
   924  		&cli.StringFlag{
   925  			Name:        "metrics_log_filter",
   926  			Usage:       "Specify list of metrics to print to log (to reduce the output)",
   927  			Destination: filter,
   928  		},
   929  
   930  		&cli.StringFlag{
   931  			Name:        "metrics_log_formatter",
   932  			Usage:       "Specify the path to custom Ruby formatter script (only supported on MacOS and Linux)",
   933  			Destination: &c.Metrics.LogFormatter,
   934  			Hidden:      true,
   935  		},
   936  
   937  		&cli.StringFlag{
   938  			Name:        "metrics_http",
   939  			Usage:       "Enable HTTP metrics endpoint at the specified path",
   940  			Destination: &c.Metrics.HTTP,
   941  		},
   942  
   943  		&cli.StringFlag{
   944  			Name:        "metrics_host",
   945  			Usage:       "Server host for metrics endpoint",
   946  			Destination: &c.Metrics.Host,
   947  		},
   948  
   949  		&cli.IntFlag{
   950  			Name:        "metrics_port",
   951  			Usage:       "Server port for metrics endpoint, the same as for main server by default",
   952  			Destination: &c.Metrics.Port,
   953  		},
   954  
   955  		&cli.StringFlag{
   956  			Name:        "metrics_tags",
   957  			Usage:       "Comma-separated list of default (global) tags to add to every metric",
   958  			Destination: mtags,
   959  		},
   960  
   961  		&cli.IntFlag{
   962  			Name:        "stats_refresh_interval",
   963  			Usage:       "How often to refresh the server stats (in seconds)",
   964  			Value:       c.App.StatsRefreshInterval,
   965  			Destination: &c.App.StatsRefreshInterval,
   966  		},
   967  	})
   968  }
   969  
   970  // wsCLIFlags returns CLI flags for WebSocket
   971  func wsCLIFlags(c *config.Config) []cli.Flag {
   972  	return withDefaults(wsCategoryDescription, []cli.Flag{
   973  		&cli.IntFlag{
   974  			Name:        "read_buffer_size",
   975  			Usage:       "WebSocket connection read buffer size",
   976  			Value:       c.WS.ReadBufferSize,
   977  			Destination: &c.WS.ReadBufferSize,
   978  			Hidden:      true,
   979  		},
   980  
   981  		&cli.IntFlag{
   982  			Name:        "write_buffer_size",
   983  			Usage:       "WebSocket connection write buffer size",
   984  			Value:       c.WS.WriteBufferSize,
   985  			Destination: &c.WS.WriteBufferSize,
   986  			Hidden:      true,
   987  		},
   988  
   989  		&cli.Int64Flag{
   990  			Name:        "max_message_size",
   991  			Usage:       "Maximum size of a message in bytes",
   992  			Value:       c.WS.MaxMessageSize,
   993  			Destination: &c.WS.MaxMessageSize,
   994  			Hidden:      true,
   995  		},
   996  
   997  		&cli.BoolFlag{
   998  			Name:        "enable_ws_compression",
   999  			Usage:       "Enable experimental WebSocket per message compression",
  1000  			Destination: &c.WS.EnableCompression,
  1001  			Hidden:      true,
  1002  		},
  1003  
  1004  		&cli.StringFlag{
  1005  			Name:        "allowed_origins",
  1006  			Usage:       `Accept requests only from specified origins, e.g., "www.example.com,*example.io". No check is performed if empty`,
  1007  			Destination: &c.WS.AllowedOrigins,
  1008  		},
  1009  	})
  1010  }
  1011  
  1012  // pingCLIFlags returns CLI flag for ping settings
  1013  func pingCLIFlags(c *config.Config) []cli.Flag {
  1014  	return withDefaults(pingCategoryDescription, []cli.Flag{
  1015  		&cli.IntFlag{
  1016  			Name:        "ping_interval",
  1017  			Usage:       "Action Cable ping interval (in seconds)",
  1018  			Value:       c.App.PingInterval,
  1019  			Destination: &c.App.PingInterval,
  1020  		},
  1021  
  1022  		&cli.StringFlag{
  1023  			Name:        "ping_timestamp_precision",
  1024  			Usage:       "Precision for timestamps in ping messages (s, ms, ns)",
  1025  			Value:       c.App.PingTimestampPrecision,
  1026  			Destination: &c.App.PingTimestampPrecision,
  1027  			Hidden:      true,
  1028  		},
  1029  
  1030  		&cli.IntFlag{
  1031  			Name:        "pong_timeout",
  1032  			Usage:       `How long to wait for a pong response before disconnecting the client (in seconds). Zero means no pongs required`,
  1033  			Value:       c.App.PongTimeout,
  1034  			Destination: &c.App.PongTimeout,
  1035  		},
  1036  	})
  1037  }
  1038  
  1039  // jwtCLIFlags returns CLI flags for JWT
  1040  func jwtCLIFlags(c *config.Config, jwtIdKey *string, jwtIdParam *string, jwtIdEnforce *bool) []cli.Flag {
  1041  	return withDefaults(jwtCategoryDescription, []cli.Flag{
  1042  		&cli.StringFlag{
  1043  			Name:        "jwt_id_key",
  1044  			Destination: jwtIdKey,
  1045  			Usage:       "[Depracated]",
  1046  			Hidden:      true,
  1047  		},
  1048  
  1049  		&cli.StringFlag{
  1050  			Name:        "jwt_secret",
  1051  			Usage:       "The encryption key used to verify JWT tokens",
  1052  			Destination: &c.JWT.Secret,
  1053  		},
  1054  
  1055  		&cli.StringFlag{
  1056  			Name:        "jwt_id_param",
  1057  			Destination: jwtIdParam,
  1058  			Usage:       "[Deprecated]",
  1059  			Hidden:      true,
  1060  		},
  1061  
  1062  		&cli.StringFlag{
  1063  			Name:        "jwt_param",
  1064  			Usage:       "The name of a query string param or an HTTP header carrying a token",
  1065  			Value:       c.JWT.Param,
  1066  			Destination: &c.JWT.Param,
  1067  		},
  1068  
  1069  		&cli.BoolFlag{
  1070  			Name:        "jwt_id_enforce",
  1071  			Usage:       "[Deprecated]",
  1072  			Destination: jwtIdEnforce,
  1073  			Hidden:      true,
  1074  		},
  1075  
  1076  		&cli.BoolFlag{
  1077  			Name:        "enforce_jwt",
  1078  			Usage:       "Whether to enforce token presence for all connections",
  1079  			Destination: &c.JWT.Force,
  1080  		},
  1081  	})
  1082  }
  1083  
  1084  // signedStreamsCLIFlags returns misc CLI flags
  1085  func signedStreamsCLIFlags(c *config.Config, turboRailsKey *string, cableReadyKey *string, turboRailsClearText *bool, cableReadyCleartext *bool) []cli.Flag {
  1086  	return withDefaults(signedStreamsCategoryDescription, []cli.Flag{
  1087  		&cli.StringFlag{
  1088  			Name:        "streams_secret",
  1089  			Usage:       "Secret you use to sign stream names",
  1090  			Destination: &c.Streams.Secret,
  1091  		},
  1092  
  1093  		&cli.BoolFlag{
  1094  			Name:        "public_streams",
  1095  			Usage:       "Enable public (unsigned) streams",
  1096  			Destination: &c.Streams.Public,
  1097  		},
  1098  
  1099  		&cli.BoolFlag{
  1100  			Name:        "streams_whisper",
  1101  			Usage:       "Enable whispering for signed pub/sub streams",
  1102  			Destination: &c.Streams.Whisper,
  1103  		},
  1104  
  1105  		&cli.BoolFlag{
  1106  			Name:        "turbo_streams",
  1107  			Usage:       "Enable Turbo Streams support",
  1108  			Destination: &c.Streams.Turbo,
  1109  		},
  1110  
  1111  		&cli.BoolFlag{
  1112  			Name:        "cable_ready",
  1113  			Usage:       "Enable Cable Ready support",
  1114  			Destination: &c.Streams.CableReady,
  1115  		},
  1116  
  1117  		&cli.StringFlag{
  1118  			Name:        "turbo_rails_key",
  1119  			Usage:       "[Deprecated]",
  1120  			Destination: turboRailsKey,
  1121  			Hidden:      true,
  1122  		},
  1123  
  1124  		&cli.StringFlag{
  1125  			Name:        "turbo_streams_secret",
  1126  			Usage:       "A custom secret to verify Turbo Streams",
  1127  			Destination: &c.Streams.TurboSecret,
  1128  		},
  1129  
  1130  		&cli.BoolFlag{
  1131  			Name:        "turbo_rails_cleartext",
  1132  			Usage:       "[DEPRECATED] Enable Turbo Streams fastlane without stream names signing",
  1133  			Destination: turboRailsClearText,
  1134  			Hidden:      true,
  1135  		},
  1136  
  1137  		&cli.StringFlag{
  1138  			Name:        "cable_ready_key",
  1139  			Usage:       "[Deprecated]",
  1140  			Destination: cableReadyKey,
  1141  			Hidden:      true,
  1142  		},
  1143  
  1144  		&cli.StringFlag{
  1145  			Name:        "cable_ready_secret",
  1146  			Usage:       "A custom secret to verify CableReady streams",
  1147  			Destination: &c.Streams.CableReadySecret,
  1148  		},
  1149  
  1150  		&cli.BoolFlag{
  1151  			Name:        "cable_ready_cleartext",
  1152  			Usage:       "[DEPRECATED] Enable Cable Ready fastlane without stream names signing",
  1153  			Destination: cableReadyCleartext,
  1154  			Hidden:      true,
  1155  		},
  1156  	})
  1157  }
  1158  
  1159  // StatsD related flags
  1160  func statsdCLIFlags(c *config.Config) []cli.Flag {
  1161  	return withDefaults(statsdCategoryDescription, []cli.Flag{
  1162  		&cli.StringFlag{
  1163  			Name:        "statsd_host",
  1164  			Usage:       "Server host for metrics sent to statsd server in the format <host>:<port>",
  1165  			Destination: &c.Metrics.Statsd.Host,
  1166  		},
  1167  		&cli.StringFlag{
  1168  			Name:        "statsd_prefix",
  1169  			Usage:       "Statsd metrics prefix",
  1170  			Value:       c.Metrics.Statsd.Prefix,
  1171  			Destination: &c.Metrics.Statsd.Prefix,
  1172  		},
  1173  		&cli.IntFlag{
  1174  			Name:        "statsd_max_packet_size",
  1175  			Usage:       "Statsd client maximum UDP packet size",
  1176  			Value:       c.Metrics.Statsd.MaxPacketSize,
  1177  			Destination: &c.Metrics.Statsd.MaxPacketSize,
  1178  			Hidden:      true,
  1179  		},
  1180  		&cli.StringFlag{
  1181  			Name:        "statsd_tags_format",
  1182  			Usage:       `One of "datadog", "influxdb", or "graphite"`,
  1183  			Value:       c.Metrics.Statsd.TagFormat,
  1184  			Destination: &c.Metrics.Statsd.TagFormat,
  1185  		},
  1186  	})
  1187  }
  1188  
  1189  // sseCLIFlags returns CLI flags for SSE
  1190  func sseCLIFlags(c *config.Config) []cli.Flag {
  1191  	return withDefaults(sseCategoryDescription, []cli.Flag{
  1192  		&cli.BoolFlag{
  1193  			Name:        "sse",
  1194  			Usage:       "Enable SSE endpoint",
  1195  			Value:       c.SSE.Enabled,
  1196  			Destination: &c.SSE.Enabled,
  1197  		},
  1198  		&cli.StringFlag{
  1199  			Name:        "sse_path",
  1200  			Usage:       "SSE endpoint path",
  1201  			Value:       c.SSE.Path,
  1202  			Destination: &c.SSE.Path,
  1203  		},
  1204  	})
  1205  }
  1206  
  1207  // miscCLIFlags returns uncategorized flags
  1208  func miscCLIFlags(c *config.Config, presets *string) []cli.Flag {
  1209  	return withDefaults(miscCategoryDescription, []cli.Flag{
  1210  		&cli.StringFlag{
  1211  			Name:        "presets",
  1212  			Usage:       "Configuration presets, comma-separated (none, fly, heroku, broker). Inferred automatically",
  1213  			Destination: presets,
  1214  		},
  1215  	})
  1216  }
  1217  
  1218  // withDefaults sets category and env var name a flags passed as the arument
  1219  func withDefaults(category string, flags []cli.Flag) []cli.Flag {
  1220  	for _, f := range flags {
  1221  		switch v := f.(type) {
  1222  		case *cli.IntFlag:
  1223  			v.Category = category
  1224  			if len(v.EnvVars) == 0 {
  1225  				v.EnvVars = []string{nameToEnvVarName(v.Name)}
  1226  			}
  1227  		case *cli.Int64Flag:
  1228  			v.Category = category
  1229  			if len(v.EnvVars) == 0 {
  1230  				v.EnvVars = []string{nameToEnvVarName(v.Name)}
  1231  			}
  1232  		case *cli.Float64Flag:
  1233  			v.Category = category
  1234  			if len(v.EnvVars) == 0 {
  1235  				v.EnvVars = []string{nameToEnvVarName(v.Name)}
  1236  			}
  1237  		case *cli.DurationFlag:
  1238  			v.Category = category
  1239  			if len(v.EnvVars) == 0 {
  1240  				v.EnvVars = []string{nameToEnvVarName(v.Name)}
  1241  			}
  1242  		case *cli.BoolFlag:
  1243  			v.Category = category
  1244  			if len(v.EnvVars) == 0 {
  1245  				v.EnvVars = []string{nameToEnvVarName(v.Name)}
  1246  			}
  1247  		case *cli.StringFlag:
  1248  			v.Category = category
  1249  			if len(v.EnvVars) == 0 {
  1250  				v.EnvVars = []string{nameToEnvVarName(v.Name)}
  1251  			}
  1252  		case *cli.PathFlag:
  1253  			v.Category = category
  1254  			if len(v.EnvVars) == 0 {
  1255  				v.EnvVars = []string{nameToEnvVarName(v.Name)}
  1256  			}
  1257  		case *cli.TimestampFlag:
  1258  			v.Category = category
  1259  			if len(v.EnvVars) == 0 {
  1260  				v.EnvVars = []string{nameToEnvVarName(v.Name)}
  1261  			}
  1262  		}
  1263  	}
  1264  	return flags
  1265  }
  1266  
  1267  // nameToEnvVarName converts flag name to env variable
  1268  func nameToEnvVarName(name string) string {
  1269  	split := splitFlagName.Split(name, -1)
  1270  	set := []string{}
  1271  
  1272  	for i := range split {
  1273  		set = append(set, strings.ToUpper(split[i]))
  1274  	}
  1275  
  1276  	return envPrefix + strings.Join(set, "_")
  1277  }
  1278  
  1279  func parseTags(str string) map[string]string {
  1280  	tags := strings.Split(str, ",")
  1281  
  1282  	res := make(map[string]string, len(tags))
  1283  
  1284  	for _, v := range tags {
  1285  		parts := strings.Split(v, ":")
  1286  		res[parts[0]] = parts[1]
  1287  	}
  1288  
  1289  	return res
  1290  }