diff --git a/cmd/pixad/main.go b/cmd/pixad/main.go index 9ff12b7..2be3a51 100644 --- a/cmd/pixad/main.go +++ b/cmd/pixad/main.go @@ -2,6 +2,10 @@ package main import ( + "fmt" + "os" + + "github.com/spf13/cobra" "go.uber.org/fx" "sneak.berlin/go/pixa/internal/config" "sneak.berlin/go/pixa/internal/database" @@ -19,11 +23,33 @@ var ( Buildarch string //nolint:gochecknoglobals // set by ldflags ) +var configPath string //nolint:gochecknoglobals // cobra flag + func main() { + rootCmd := &cobra.Command{ + Use: "pixad", + Short: "Pixa image caching proxy server", + Run: run, + } + + rootCmd.Flags().StringVarP(&configPath, "config", "c", "", "path to config file") + + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func run(_ *cobra.Command, _ []string) { globals.Appname = Appname globals.Version = Version globals.Buildarch = Buildarch + // Set config path in environment if specified via flag + if configPath != "" { + _ = os.Setenv("PIXA_CONFIG_PATH", configPath) + } + fx.New( fx.Provide( config.New, diff --git a/go.mod b/go.mod index 7a8f529..6c7a1f1 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/go-chi/cors v1.2.2 github.com/prometheus/client_golang v1.23.2 github.com/slok/go-http-metrics v0.13.0 + github.com/spf13/cobra v1.10.2 go.uber.org/fx v1.24.0 golang.org/x/image v0.34.0 modernc.org/sqlite v1.42.2 @@ -85,6 +86,7 @@ require ( github.com/hashicorp/hcl v1.0.1-vault-7 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/vault/api v1.20.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect diff --git a/go.sum b/go.sum index be40c98..a65d6fb 100644 --- a/go.sum +++ b/go.sum @@ -85,6 +85,7 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -228,6 +229,8 @@ github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4= github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -340,6 +343,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -350,6 +354,9 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/slok/go-http-metrics v0.13.0 h1:lQDyJJx9wKhmbliyUsZ2l6peGnXRHjsjoqPt5VYzcP8= github.com/slok/go-http-metrics v0.13.0/go.mod h1:HIr7t/HbN2sJaunvnt9wKP9xoBBVZFo1/KiHU3b0w+4= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -413,6 +420,7 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= diff --git a/internal/config/config.go b/internal/config/config.go index da6d8b8..93367f9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,6 +3,7 @@ package config import ( "fmt" + "log/slog" "os" "path/filepath" "strings" @@ -48,29 +49,9 @@ func New(_ fx.Lifecycle, params Params) (*Config, error) { log := params.Logger.Get() name := params.Globals.Appname - var sc *smartconfig.Config - var err error - - // Try loading config from standard locations - configPaths := []string{ - fmt.Sprintf("/etc/%s/config.yml", name), - fmt.Sprintf("/etc/%s/config.yaml", name), - filepath.Join(os.Getenv("HOME"), ".config", name, "config.yml"), - filepath.Join(os.Getenv("HOME"), ".config", name, "config.yaml"), - "config.yml", - "config.yaml", - } - - for _, path := range configPaths { - if _, statErr := os.Stat(path); statErr == nil { - sc, err = smartconfig.NewFromConfigPath(path) - if err == nil { - log.Info("loaded config file", "path", path) - - break - } - log.Warn("failed to parse config file", "path", path, "error", err) - } + sc, err := loadConfigFile(log, name) + if err != nil { + return nil, err } if sc == nil { @@ -103,6 +84,48 @@ func New(_ fx.Lifecycle, params Params) (*Config, error) { return c, nil } +// loadConfigFile loads configuration from PIXA_CONFIG_PATH env var or standard locations. +func loadConfigFile(log *slog.Logger, appName string) (*smartconfig.Config, error) { + // Check for explicit config path from environment + if envPath := os.Getenv("PIXA_CONFIG_PATH"); envPath != "" { + sc, err := smartconfig.NewFromConfigPath(envPath) + if err != nil { + return nil, fmt.Errorf("failed to load config from %s: %w", envPath, err) + } + + log.Info("loaded config file", "path", envPath) + + return sc, nil + } + + // Try loading config from standard locations + configPaths := []string{ + fmt.Sprintf("/etc/%s/config.yml", appName), + fmt.Sprintf("/etc/%s/config.yaml", appName), + filepath.Join(os.Getenv("HOME"), ".config", appName, "config.yml"), + filepath.Join(os.Getenv("HOME"), ".config", appName, "config.yaml"), + "config.yml", + "config.yaml", + } + + for _, path := range configPaths { + if _, statErr := os.Stat(path); statErr == nil { + sc, err := smartconfig.NewFromConfigPath(path) + if err != nil { + log.Warn("failed to parse config file", "path", path, "error", err) + + continue + } + + log.Info("loaded config file", "path", path) + + return sc, nil + } + } + + return nil, nil //nolint:nilnil // nil config is valid (use defaults) +} + func getString(sc *smartconfig.Config, key, defaultVal string) string { if sc == nil { return defaultVal