mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-05 04:59:06 +02:00
[management,signal,proxy,relay,misc] Unify service configuration loading
Service entry points currently resolve defaults, files, environment variables, and flags differently, which makes precedence inconsistent and prevents some services from using config files. Introduce one Viper-backed loader and migrate Combined, Management, Relay, Signal, and Proxy while preserving compatibility aliases and Management template expansion.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
configloader "github.com/netbirdio/netbird/util/config"
|
||||
)
|
||||
|
||||
// Config contains Signal service startup configuration.
|
||||
type Config struct {
|
||||
Port int `yaml:"port" env:"NB_PORT" flag:"port"`
|
||||
MetricsPort int `yaml:"metricsPort" env:"NB_METRICS_PORT" flag:"metrics-port"`
|
||||
LetsencryptDomain string `yaml:"letsencryptDomain" env:"NB_LETSENCRYPT_DOMAIN" flag:"letsencrypt-domain"`
|
||||
LetsencryptEmail string `yaml:"letsencryptEmail" env:"NB_LETSENCRYPT_EMAIL" flag:"letsencrypt-email"`
|
||||
LetsencryptDataDir string `yaml:"letsencryptDataDir" env:"NB_LETSENCRYPT_DATA_DIR,NB_SSL_DIR" flag:"letsencrypt-data-dir,ssl-dir"`
|
||||
CertFile string `yaml:"certFile" env:"NB_CERT_FILE" flag:"cert-file"`
|
||||
CertKey string `yaml:"certKey" env:"NB_CERT_KEY" flag:"cert-key"`
|
||||
LogLevel string `yaml:"logLevel" env:"NB_LOG_LEVEL" flag:"log-level"`
|
||||
LogFile string `yaml:"logFile" env:"NB_LOG_FILE" flag:"log-file"`
|
||||
PprofAddress string `yaml:"pprofAddress" env:"NB_PPROF_ADDR"`
|
||||
}
|
||||
|
||||
func defaultConfig() *Config {
|
||||
logFile := "/var/log/netbird/signal.log"
|
||||
if runtime.GOOS == "windows" {
|
||||
logFile = os.Getenv("PROGRAMDATA") + "\\Netbird\\signal.log"
|
||||
}
|
||||
return &Config{
|
||||
MetricsPort: 9090,
|
||||
LogLevel: "info",
|
||||
LogFile: logFile,
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfig(cmd *cobra.Command, configPath string) (*Config, error) {
|
||||
return configloader.Load(configPath, defaultConfig(), configloader.Options{
|
||||
TagName: "yaml",
|
||||
AllowMissing: configPath == "",
|
||||
FlagSet: cmd.Flags(),
|
||||
Strict: true,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadConfigPrecedence(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "signal.yaml")
|
||||
require.NoError(t, os.WriteFile(configPath, []byte(`
|
||||
port: 10001
|
||||
metricsPort: 9091
|
||||
logLevel: warn
|
||||
pprofAddress: localhost:6060
|
||||
`), 0o600))
|
||||
t.Setenv("NB_METRICS_PORT", "9191")
|
||||
t.Setenv("NB_SSL_DIR", "/legacy-certs")
|
||||
require.NoError(t, runCmd.ParseFlags(nil))
|
||||
|
||||
portFlag := runCmd.Flags().Lookup("port")
|
||||
oldPort := portFlag.Value.String()
|
||||
oldChanged := portFlag.Changed
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, portFlag.Value.Set(oldPort))
|
||||
portFlag.Changed = oldChanged
|
||||
})
|
||||
require.NoError(t, portFlag.Value.Set("10002"))
|
||||
portFlag.Changed = true
|
||||
|
||||
cfg, err := loadConfig(runCmd, configPath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 10002, cfg.Port, "Flags should override the configuration file")
|
||||
assert.Equal(t, 9191, cfg.MetricsPort, "Environment should override the configuration file")
|
||||
assert.Equal(t, "warn", cfg.LogLevel, "File values should override defaults")
|
||||
assert.Equal(t, "/legacy-certs", cfg.LetsencryptDataDir, "Legacy environment aliases should remain supported")
|
||||
assert.Equal(t, "localhost:6060", cfg.PprofAddress, "Environment-only settings should load from the file")
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// setFlagsFromEnvVars reads and updates flag values from environment variables with prefix NB_
|
||||
func setFlagsFromEnvVars(cmd *cobra.Command) {
|
||||
flags := cmd.PersistentFlags()
|
||||
flags.VisitAll(func(f *pflag.Flag) {
|
||||
newEnvVar := flagNameToEnvVar(f.Name, "NB_")
|
||||
value, present := os.LookupEnv(newEnvVar)
|
||||
if !present {
|
||||
return
|
||||
}
|
||||
|
||||
err := flags.Set(f.Name, value)
|
||||
if err != nil {
|
||||
log.Infof("unable to configure flag %s using variable %s, err: %v", f.Name, newEnvVar, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// flagNameToEnvVar converts flag name to environment var name adding a prefix,
|
||||
// replacing dashes and making all uppercase (e.g. setup-keys is converted to NB_SETUP_KEYS according to the input prefix)
|
||||
func flagNameToEnvVar(cmdFlag string, prefix string) string {
|
||||
parsed := strings.ReplaceAll(cmdFlag, "-", "_")
|
||||
upper := strings.ToUpper(parsed)
|
||||
return prefix + upper
|
||||
}
|
||||
+5
-12
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -17,9 +16,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
logLevel string
|
||||
defaultLogFile string
|
||||
logFile string
|
||||
logLevel string
|
||||
logFile string
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "netbird-signal",
|
||||
@@ -39,14 +37,9 @@ func Execute() error {
|
||||
|
||||
func init() {
|
||||
stopCh = make(chan int)
|
||||
defaultLogFile = "/var/log/netbird/signal.log"
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Netbird\\" + "signal.log"
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
||||
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout")
|
||||
defaults := defaultConfig()
|
||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", defaults.LogLevel, "")
|
||||
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaults.LogFile, "sets Netbird log path. If console is specified the log will be output to stdout")
|
||||
rootCmd.AddCommand(runCmd)
|
||||
}
|
||||
|
||||
|
||||
+32
-22
@@ -10,7 +10,6 @@ import (
|
||||
"net/http"
|
||||
// nolint:gosec
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
@@ -46,6 +45,8 @@ var (
|
||||
signalLetsencryptDataDir string
|
||||
signalCertFile string
|
||||
signalCertKey string
|
||||
signalConfigPath string
|
||||
signalPprofAddress string
|
||||
|
||||
signalKaep = grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
||||
MinTime: 5 * time.Second,
|
||||
@@ -64,30 +65,25 @@ var (
|
||||
Short: "start NetBird Signal Server daemon",
|
||||
SilenceUsage: true,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
cfg, err := loadConfig(cmd, signalConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed initializing log: %w", err)
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
applyConfig(cfg)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// detect whether user specified a port
|
||||
userPort := cmd.Flag("port").Changed
|
||||
|
||||
var tlsEnabled bool
|
||||
if signalLetsencryptDomain != "" || (signalCertFile != "" && signalCertKey != "") {
|
||||
tlsEnabled = true
|
||||
}
|
||||
|
||||
if !userPort {
|
||||
// different defaults for signalPort
|
||||
if tlsEnabled {
|
||||
if signalPort == 0 {
|
||||
if signalLetsencryptDomain != "" || (signalCertFile != "" && signalCertKey != "") {
|
||||
signalPort = 443
|
||||
} else {
|
||||
signalPort = 80
|
||||
}
|
||||
}
|
||||
|
||||
if err := util.InitLog(logLevel, logFile); err != nil {
|
||||
return fmt.Errorf("initialize log: %w", err)
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -196,10 +192,10 @@ var (
|
||||
)
|
||||
|
||||
func startPprof() {
|
||||
if pprofAddr := os.Getenv("NB_PPROF_ADDR"); pprofAddr != "" {
|
||||
log.Infof("pprof enabled, listening on: %s", pprofAddr)
|
||||
if signalPprofAddress != "" {
|
||||
log.Infof("pprof enabled, listening on: %s", signalPprofAddress)
|
||||
go func() {
|
||||
if err := http.ListenAndServe(pprofAddr, nil); err != nil {
|
||||
if err := http.ListenAndServe(signalPprofAddress, nil); err != nil {
|
||||
log.Fatalf("pprof server failed: %v", err)
|
||||
}
|
||||
}()
|
||||
@@ -328,13 +324,27 @@ func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
runCmd.PersistentFlags().IntVar(&signalPort, "port", 80, "Server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
|
||||
runCmd.Flags().IntVar(&metricsPort, "metrics-port", 9090, "metrics endpoint http port. Metrics are accessible under host:metrics-port/metrics")
|
||||
defaults := defaultConfig()
|
||||
runCmd.PersistentFlags().StringVar(&signalConfigPath, "config", "", "path to configuration file")
|
||||
runCmd.PersistentFlags().IntVar(&signalPort, "port", defaults.Port, "Server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise)")
|
||||
runCmd.Flags().IntVar(&metricsPort, "metrics-port", defaults.MetricsPort, "metrics endpoint http port. Metrics are accessible under host:metrics-port/metrics")
|
||||
runCmd.PersistentFlags().StringVar(&signalLetsencryptDataDir, "letsencrypt-data-dir", "", "a directory to store Let's Encrypt data. Required if Let's Encrypt is enabled.")
|
||||
runCmd.PersistentFlags().StringVar(&signalLetsencryptDataDir, "ssl-dir", "", "server ssl directory location. *Required only for Let's Encrypt certificates. Deprecated: use --letsencrypt-data-dir")
|
||||
runCmd.PersistentFlags().StringVar(&signalLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
|
||||
runCmd.PersistentFlags().StringVar(&signalLetsencryptEmail, "letsencrypt-email", "", "email address to use for Let's Encrypt certificate registration")
|
||||
runCmd.PersistentFlags().StringVar(&signalCertFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
||||
runCmd.PersistentFlags().StringVar(&signalCertKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
||||
setFlagsFromEnvVars(runCmd)
|
||||
}
|
||||
|
||||
func applyConfig(cfg *Config) {
|
||||
signalPort = cfg.Port
|
||||
metricsPort = cfg.MetricsPort
|
||||
signalLetsencryptDomain = cfg.LetsencryptDomain
|
||||
signalLetsencryptEmail = cfg.LetsencryptEmail
|
||||
signalLetsencryptDataDir = cfg.LetsencryptDataDir
|
||||
signalCertFile = cfg.CertFile
|
||||
signalCertKey = cfg.CertKey
|
||||
logLevel = cfg.LogLevel
|
||||
logFile = cfg.LogFile
|
||||
signalPprofAddress = cfg.PprofAddress
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user