From 4ee77c0bde7ec9ee5fd248afb9191289f5c5b317 Mon Sep 17 00:00:00 2001 From: Glymphie Date: Wed, 8 Apr 2026 16:16:49 +0200 Subject: [PATCH 1/4] Added XDG support Added a new default for where the .cntb.yaml lives. New default is `$XDG_CONFIG_HOME/cntb/.cntb.yaml`, with backwards support for `$HOME/.cntb.yaml` files. --- README.md | 6 ++---- cmd/root.go | 22 +++++++++++++++++++--- go.mod | 3 ++- go.sum | 12 ++++++++---- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c14829c..1b20977 100644 --- a/README.md +++ b/README.md @@ -129,12 +129,10 @@ make build For `cntb` to work it uses some additional files and folders. -* default file for storing settings / preferences is `~/.cntb.yaml` +* default file for storing settings / preferences is `$XDG_CONFIG_HOME/cntb/.cntb.yaml` + * if `$XDG_CONFIG_HOME` isn't declared, default file for storing settings / preferences is `~/.cntb.yaml` * caching folder is `~/.cache/cntb/` ## License GNU GENERAL PUBLIC LICENSE, Version 3 - - - diff --git a/cmd/root.go b/cmd/root.go index 09fc9a2..a811eaa 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ import ( "fmt" "net/url" "os" + "path/filepath" "contabo.com/cli/cntb/config" "github.com/minio/minio-go/v7" @@ -12,6 +13,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/adrg/xdg" ) var ( @@ -94,7 +96,7 @@ func init() { // will be global for your application. rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", - "config file (Looks up /etc/cntb/.cntb.yaml then $HOME/.cntb.yaml)") + "config file (Looks up $XDG_CONFIG_HOME/cntb/.cntb.yaml then /etc/cntb/.cntb.yaml then $HOME/.cntb.yaml)") rootCmd.PersistentFlags().StringVarP(&DebugLevel, "debug", "d", "warn", "debug level [fatal|error|warn|info|debug|trace]") @@ -145,8 +147,22 @@ func initConfig() { home, err := homedir.Dir() cobra.CheckErr(err) - cfgFile = home + "/.cntb.yaml" - // Search config in home directory with name ".cli" (without extension). + cfgFile = filepath.Join(home, ".cntb.yaml") + + // Add $XDG_CONFIG_HOME/cntb as default config path. + if (os.Getenv("XDG_CONFIG_HOME") != "") { + viper.AddConfigPath(filepath.Join(xdg.ConfigHome, "cntb")) + + // Prioritize the old $HOME/.cntb.yaml for backwards compatibility. + if _, err := os.Stat(cfgFile); os.IsNotExist(err) { + xdgCfgFile, err := xdg.ConfigFile("cntb/.cntb.yaml") + cobra.CheckErr(err) + cfgFile = xdgCfgFile + } + + } + + // Search config in home directory with name ".cntb" (without extension). viper.AddConfigPath("/etc/cntb/") viper.AddConfigPath(home) viper.SetConfigName(".cntb") diff --git a/go.mod b/go.mod index 2ff6630..3afaa1c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23 require ( contabo.com/cli/cntb/openapi v0.0.0-00010101000000-000000000000 github.com/PaesslerAG/jsonpath v0.1.1 + github.com/adrg/xdg v0.5.3 github.com/cheggaaa/pb v1.0.29 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/hprose/hprose-go v0.0.0-20161031134501-83de97da5004 @@ -48,7 +49,7 @@ require ( github.com/subosito/gotenv v1.2.0 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/go.sum b/go.sum index ca7c2fa..27d72a7 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/PaesslerAG/gval v1.1.2/go.mod h1:Fa8gfkCmUsELXgayr8sfL/sw+VzCVoa03dcO github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk= github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -325,8 +327,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -520,8 +523,8 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -740,8 +743,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 4c69e81ca5bda5c98ec8e1edfac47fd01ba6ff19 Mon Sep 17 00:00:00 2001 From: Glymphie Date: Wed, 8 Apr 2026 16:54:48 +0200 Subject: [PATCH 2/4] Added XDG support for cache token Added XDG support for how the `token` is stored in cache. New default is `$XDG_CACHE_HOME/cntb/token`. Which resolves to `$HOME/.cache/cntb/token` if `$XDG_CACHE_HOME` isn't declared. --- oauth2Client/cacheToken.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/oauth2Client/cacheToken.go b/oauth2Client/cacheToken.go index 4e958a5..8295866 100644 --- a/oauth2Client/cacheToken.go +++ b/oauth2Client/cacheToken.go @@ -10,9 +10,9 @@ import ( "time" "github.com/hprose/hprose-go" - "github.com/mitchellh/go-homedir" log "github.com/sirupsen/logrus" "golang.org/x/oauth2" + "github.com/adrg/xdg" ) func cacheToken(token *oauth2.Token) { @@ -79,15 +79,11 @@ func RestoreTokenFromCache(oauth2User string) *oauth2.Token { } func getCacheFile() string { - home, err := homedir.Dir() + // Create token cache file. Respects $XDG_CACHE_HOME, else defaults to $HOME/.cache/cntb/token. + tokenCacheFileName, err := xdg.CacheFile("cntb/token") if err != nil { - log.Fatal(fmt.Sprintf("Could not determine home dir: %v", err)) + log.Fatal(fmt.Sprintf("Could not ensure cache file: %v", err)) } - tokenCacheDirName := home + "/.cache/cntb" - err = os.MkdirAll(tokenCacheDirName, os.ModePerm) - if err != nil { - log.Fatal(fmt.Sprintf("Could not ensure cache folder: %v", err)) - } - tokenCacheFileName := home + "/.cache/cntb/token" + return tokenCacheFileName } From 7537bfc5319de848cdd3d8944f78048373b2d7fb Mon Sep 17 00:00:00 2001 From: Glymphie Date: Wed, 8 Apr 2026 17:01:36 +0200 Subject: [PATCH 3/4] Rewrote the README for xdg Clarified how the xdg default works. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b20977..5a39702 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,9 @@ make build For `cntb` to work it uses some additional files and folders. * default file for storing settings / preferences is `$XDG_CONFIG_HOME/cntb/.cntb.yaml` - * if `$XDG_CONFIG_HOME` isn't declared, default file for storing settings / preferences is `~/.cntb.yaml` -* caching folder is `~/.cache/cntb/` + * default file for storing settings / preferences is `~/.cntb.yaml` if `$XDG_CONFIG_HOME` isn't declared +* caching folder is `$XDG_CACHE_HOME/cntb/` + * caching folder is `~/.cache/cntb/` if `$XDG_CACHE_HOME` isn't declared ## License From 79f66b930eedf2ded84fd1596bc793065bd6395f Mon Sep 17 00:00:00 2001 From: Glymphie Date: Fri, 10 Apr 2026 13:01:42 +0200 Subject: [PATCH 4/4] Corrected help text to reflect xdg support Corrected help text to reflect xdg support: The first checked config is `$XDG_CONFIG_HOME/cntb/.cntb.yaml`. --- cmd/config.go | 9 +++++---- cmd/setCredentials.go | 2 +- cmd/setDebugLevel.go | 2 +- cmd/view.go | 9 +++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index aeae67f..41f1fdf 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -16,10 +16,11 @@ var configCmd = &cobra.Command{ Short: "Manage config files", Long: `View and edit config files. Configuration is merged from four different sources in this order: - 1. file /etc/.cntb.yml - 2. file $HOME/.cntb.yml - 3. environment variables starting with CNTB_ - 4. arguments passed to command line + 1. file $XDG_CONFIG_HOME/cntb/.cntb.yml + 2. file /etc/.cntb.yml + 3. file $HOME/.cntb.yml + 4. environment variables starting with CNTB_ + 5. arguments passed to command line This means that the arguments have the highest priority. You are free to edit the contents of configuration files with your favorite editor. For the most convenient usage specify the oauht2 credentials (oauth2-client-secret, oauth2-clientid, oauth2-password, oauth2-user).`, diff --git a/cmd/setCredentials.go b/cmd/setCredentials.go index 359b002..74df945 100644 --- a/cmd/setCredentials.go +++ b/cmd/setCredentials.go @@ -12,7 +12,7 @@ var setCredentialsCmd = &cobra.Command{ Use: "set-credentials", Short: "Stores the OAuth2 credentials in specified config file", Long: `Stores the OAuth2 credentials in specified config file. - * If no config file is specified with --config argument $HOME/.cntb.yml is used + * If no config file is specified with --config argument $XDG_CONFIG_HOME/cntb/.cntb.yml is used * The OAuth2 credentials are taken over from the --oauth2-* arguments `, Run: func(cmd *cobra.Command, args []string) { diff --git a/cmd/setDebugLevel.go b/cmd/setDebugLevel.go index 8ea516f..93ce330 100644 --- a/cmd/setDebugLevel.go +++ b/cmd/setDebugLevel.go @@ -12,7 +12,7 @@ var setDebugLevelCmd = &cobra.Command{ Use: "set-debug-level", Short: "Stores the debug level in specified config file", Long: `Stores the debug level in specified config file. - * If no config file is specified with --config argument $HOME/.cntb.yml is used + * If no config file is specified with --config argument $XDG_CONFIG_HOME/cntb/.cntb.yml is used * debug level is stored by providing --debug argument `, Run: func(cmd *cobra.Command, args []string) { diff --git a/cmd/view.go b/cmd/view.go index c2f1094..c284e2b 100644 --- a/cmd/view.go +++ b/cmd/view.go @@ -11,10 +11,11 @@ var viewCmd = &cobra.Command{ Use: "view", Short: "Displays current merged configuration.", Long: `Shows the current configuration from various sources, namely: - 1. file /etc/.cntb.yml - 2. file $HOME/.cntb.yml - 3. environment variables starting with CNTB_ - 4. arguments passed to command line + 1. file $XDG_CONFIG_HOME/cntb/.cntb.yml + 2. file /etc/.cntb.yml + 3. file $HOME/.cntb.yml + 4. environment variables starting with CNTB_ + 5. arguments passed to command line `, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("--api %v\n", ApiBaseUrl)