mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00:00
config: make config file relative to the config dir
a configuration parsing error is now fatal
This commit is contained in:
parent
2a9ed0abca
commit
95c6d41c35
7 changed files with 87 additions and 54 deletions
|
@ -64,7 +64,7 @@ func getCustomServeFlags() []string {
|
|||
result = append(result, "--"+configDirFlag)
|
||||
result = append(result, configDir)
|
||||
}
|
||||
if configFile != "" {
|
||||
if configFile != defaultConfigFile {
|
||||
result = append(result, "--"+configFileFlag)
|
||||
result = append(result, configFile)
|
||||
}
|
||||
|
|
|
@ -124,6 +124,7 @@ Please take a look at the usage below to customize the serving parameters`,
|
|||
}
|
||||
service := service.Service{
|
||||
ConfigDir: filepath.Clean(defaultConfigDir),
|
||||
ConfigFile: defaultConfigFile,
|
||||
LogFilePath: portableLogFile,
|
||||
LogMaxSize: defaultLogMaxSize,
|
||||
LogMaxBackups: defaultLogMaxBackup,
|
||||
|
|
35
cmd/root.go
35
cmd/root.go
|
@ -15,6 +15,7 @@ const (
|
|||
configDirFlag = "config-dir"
|
||||
configDirKey = "config_dir"
|
||||
configFileFlag = "config-file"
|
||||
configFileKey = "config_file"
|
||||
logFilePathFlag = "log-file-path"
|
||||
logFilePathKey = "log_file_path"
|
||||
logMaxSizeFlag = "log-max-size"
|
||||
|
@ -38,6 +39,7 @@ const (
|
|||
loadDataCleanFlag = "loaddata-clean"
|
||||
loadDataCleanKey = "loaddata_clean"
|
||||
defaultConfigDir = "."
|
||||
defaultConfigFile = ""
|
||||
defaultLogFile = "sftpgo.log"
|
||||
defaultLogMaxSize = 10
|
||||
defaultLogMaxBackup = 5
|
||||
|
@ -92,22 +94,35 @@ func addConfigFlags(cmd *cobra.Command) {
|
|||
viper.SetDefault(configDirKey, defaultConfigDir)
|
||||
viper.BindEnv(configDirKey, "SFTPGO_CONFIG_DIR") //nolint:errcheck // err is not nil only if the key to bind is missing
|
||||
cmd.Flags().StringVarP(&configDir, configDirFlag, "c", viper.GetString(configDirKey),
|
||||
`Location for SFTPGo config dir. This directory
|
||||
should contain the "sftpgo" configuration file.
|
||||
It is used as the base for files with a relative path
|
||||
(eg. the private keys for the SFTP server, the SQLite
|
||||
database if you use SQLite as data provider).
|
||||
`Location for the config dir. This directory
|
||||
is used as the base for files with a relative
|
||||
path, eg. the private keys for the SFTP
|
||||
server or the SQLite database if you use
|
||||
SQLite as data provider.
|
||||
The configuration file, if not explicitly set,
|
||||
is looked for in this dir. We support reading
|
||||
from JSON, TOML, YAML, HCL, envfile and Java
|
||||
properties config files. The default config
|
||||
file name is "sftpgo" and therefore
|
||||
"sftpgo.json", "sftpgo.yaml" and so on are
|
||||
searched.
|
||||
This flag can be set using SFTPGO_CONFIG_DIR
|
||||
env var too.`)
|
||||
viper.BindPFlag(configDirKey, cmd.Flags().Lookup(configDirFlag)) //nolint:errcheck
|
||||
|
||||
cmd.Flags().StringVar(&configFile, configFileFlag, os.Getenv("SFTPGO_CONFIG_FILE"),
|
||||
`Path to SFTPGo configuration file. It must be
|
||||
an absolute path to a file or a path relative to the working directory.
|
||||
The specified file name must have a supported extension
|
||||
(JSON, YAML, TOML or Java properties).
|
||||
viper.SetDefault(configFileKey, defaultConfigFile)
|
||||
viper.BindEnv(configFileKey, "SFTPGO_CONFIG_FILE") //nolint:errcheck
|
||||
cmd.Flags().StringVar(&configFile, configFileFlag, viper.GetString(configFileKey),
|
||||
`Path to SFTPGo configuration file.
|
||||
This flag explicitly defines the path, name
|
||||
and extension of the config file. If must be
|
||||
an absolute path or a path relative to the
|
||||
configuration directory. The specified file
|
||||
name must have a supported extension (JSON,
|
||||
YAML, TOML, HCL or Java properties).
|
||||
This flag can be set using SFTPGO_CONFIG_FILE
|
||||
env var too.`)
|
||||
viper.BindPFlag(configFileKey, cmd.Flags().Lookup(configFileFlag)) //nolint:errcheck
|
||||
}
|
||||
|
||||
func addServeFlags(cmd *cobra.Command) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
@ -22,11 +23,11 @@ import (
|
|||
|
||||
const (
|
||||
logSender = "config"
|
||||
// configName defines the name for the default config file.
|
||||
// This is the file name without extension, we use viper and so we
|
||||
// support all the config files format supported by viper
|
||||
// configName defines the name for config file.
|
||||
// This name does not include the extension, viper will search for files
|
||||
// with supported extensions such as "sftpgo.json", "sftpgo.yaml" and so on
|
||||
configName = "sftpgo"
|
||||
// ConfigEnvPrefix defines a prefix that ENVIRONMENT variables will use
|
||||
// ConfigEnvPrefix defines a prefix that environment variables will use
|
||||
configEnvPrefix = "sftpgo"
|
||||
)
|
||||
|
||||
|
@ -286,26 +287,35 @@ func getRedactedGlobalConf() globalConfig {
|
|||
return conf
|
||||
}
|
||||
|
||||
func setConfigFile(configDir, configFile string) {
|
||||
if configFile == "" {
|
||||
return
|
||||
}
|
||||
if !filepath.IsAbs(configFile) && utils.IsFileInputValid(configFile) {
|
||||
configFile = filepath.Join(configDir, configFile)
|
||||
}
|
||||
viper.SetConfigFile(configFile)
|
||||
}
|
||||
|
||||
// LoadConfig loads the configuration
|
||||
// configDir will be added to the configuration search paths.
|
||||
// The search path contains by default the current directory and on linux it contains
|
||||
// $HOME/.config/sftpgo and /etc/sftpgo too.
|
||||
// configFile is an absolute or relative path (to the working directory) to the configuration file.
|
||||
// configFile is an absolute or relative path (to the config dir) to the configuration file.
|
||||
func LoadConfig(configDir, configFile string) error {
|
||||
var err error
|
||||
viper.AddConfigPath(configDir)
|
||||
setViperAdditionalConfigPaths()
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetConfigFile(configFile)
|
||||
setConfigFile(configDir, configFile)
|
||||
if err = viper.ReadInConfig(); err != nil {
|
||||
logger.Warn(logSender, "", "error loading configuration file: %v", err)
|
||||
logger.WarnToConsole("error loading configuration file: %v", err)
|
||||
}
|
||||
err = viper.Unmarshal(&globalConf)
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "error parsing configuration file: %v. Default configuration will be used: %+v",
|
||||
err, getRedactedGlobalConf())
|
||||
logger.WarnToConsole("error parsing configuration file: %v. Default configuration will be used.", err)
|
||||
logger.Warn(logSender, "", "error parsing configuration file: %v", err)
|
||||
logger.WarnToConsole("error parsing configuration file: %v", err)
|
||||
return err
|
||||
}
|
||||
checkCommonParamsCompatibility()
|
||||
|
@ -322,34 +332,34 @@ func LoadConfig(configDir, configFile string) error {
|
|||
logger.WarnToConsole("Configuration error: %v", err)
|
||||
}
|
||||
if globalConf.Common.UploadMode < 0 || globalConf.Common.UploadMode > 2 {
|
||||
err = fmt.Errorf("invalid upload_mode 0, 1 and 2 are supported, configured: %v reset upload_mode to 0",
|
||||
warn := fmt.Sprintf("invalid upload_mode 0, 1 and 2 are supported, configured: %v reset upload_mode to 0",
|
||||
globalConf.Common.UploadMode)
|
||||
globalConf.Common.UploadMode = 0
|
||||
logger.Warn(logSender, "", "Configuration error: %v", err)
|
||||
logger.WarnToConsole("Configuration error: %v", err)
|
||||
logger.Warn(logSender, "", "Configuration error: %v", warn)
|
||||
logger.WarnToConsole("Configuration error: %v", warn)
|
||||
}
|
||||
if globalConf.Common.ProxyProtocol < 0 || globalConf.Common.ProxyProtocol > 2 {
|
||||
err = fmt.Errorf("invalid proxy_protocol 0, 1 and 2 are supported, configured: %v reset proxy_protocol to 0",
|
||||
warn := fmt.Sprintf("invalid proxy_protocol 0, 1 and 2 are supported, configured: %v reset proxy_protocol to 0",
|
||||
globalConf.Common.ProxyProtocol)
|
||||
globalConf.Common.ProxyProtocol = 0
|
||||
logger.Warn(logSender, "", "Configuration error: %v", err)
|
||||
logger.WarnToConsole("Configuration error: %v", err)
|
||||
logger.Warn(logSender, "", "Configuration error: %v", warn)
|
||||
logger.WarnToConsole("Configuration error: %v", warn)
|
||||
}
|
||||
if globalConf.ProviderConf.ExternalAuthScope < 0 || globalConf.ProviderConf.ExternalAuthScope > 7 {
|
||||
err = fmt.Errorf("invalid external_auth_scope: %v reset to 0", globalConf.ProviderConf.ExternalAuthScope)
|
||||
warn := fmt.Sprintf("invalid external_auth_scope: %v reset to 0", globalConf.ProviderConf.ExternalAuthScope)
|
||||
globalConf.ProviderConf.ExternalAuthScope = 0
|
||||
logger.Warn(logSender, "", "Configuration error: %v", err)
|
||||
logger.WarnToConsole("Configuration error: %v", err)
|
||||
logger.Warn(logSender, "", "Configuration error: %v", warn)
|
||||
logger.WarnToConsole("Configuration error: %v", warn)
|
||||
}
|
||||
if len(globalConf.ProviderConf.CredentialsPath) == 0 {
|
||||
err = fmt.Errorf("invalid credentials path, reset to \"credentials\"")
|
||||
if globalConf.ProviderConf.CredentialsPath == "" {
|
||||
warn := "invalid credentials path, reset to \"credentials\""
|
||||
globalConf.ProviderConf.CredentialsPath = "credentials"
|
||||
logger.Warn(logSender, "", "Configuration error: %v", err)
|
||||
logger.WarnToConsole("Configuration error: %v", err)
|
||||
logger.Warn(logSender, "", "Configuration error: %v", warn)
|
||||
logger.WarnToConsole("Configuration error: %v", warn)
|
||||
}
|
||||
checkHostKeyCompatibility()
|
||||
logger.Debug(logSender, "", "config file used: '%#v', config loaded: %+v", viper.ConfigFileUsed(), getRedactedGlobalConf())
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkHostKeyCompatibility() {
|
||||
|
|
|
@ -42,16 +42,16 @@ func TestLoadConfigTest(t *testing.T) {
|
|||
assert.NotEqual(t, httpclient.Config{}, config.GetHTTPConfig())
|
||||
confName := tempConfigName + ".json"
|
||||
configFilePath := filepath.Join(configDir, confName)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, []byte("{invalid json}"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, []byte("{\"sftpd\": {\"bind_port\": \"a\"}}"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
assert.NotNil(t, err)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.Error(t, err)
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ func TestEmptyBanner(t *testing.T) {
|
|||
jsonConf, _ := json.Marshal(c)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
sftpdConf = config.GetSFTPDConfig()
|
||||
assert.NotEmpty(t, strings.TrimSpace(sftpdConf.Banner))
|
||||
|
@ -85,7 +85,7 @@ func TestEmptyBanner(t *testing.T) {
|
|||
jsonConf, _ = json.Marshal(c1)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
ftpdConf = config.GetFTPDConfig()
|
||||
assert.NotEmpty(t, strings.TrimSpace(ftpdConf.Banner))
|
||||
|
@ -109,8 +109,9 @@ func TestInvalidUploadMode(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
assert.NotNil(t, err)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, config.GetCommonConfig().UploadMode)
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -131,8 +132,9 @@ func TestInvalidExternalAuthScope(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
assert.NotNil(t, err)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, config.GetProviderConf().ExternalAuthScope)
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -153,8 +155,9 @@ func TestInvalidCredentialsPath(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
assert.NotNil(t, err)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "credentials", config.GetProviderConf().CredentialsPath)
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -175,8 +178,9 @@ func TestInvalidProxyProtocol(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
assert.NotNil(t, err)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, config.GetCommonConfig().ProxyProtocol)
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -197,8 +201,9 @@ func TestInvalidUsersBaseDir(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
assert.NotNil(t, err)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, config.GetProviderConf().UsersBaseDir)
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -225,7 +230,7 @@ func TestCommonParamsCompatibility(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
commonConf := config.GetCommonConfig()
|
||||
assert.Equal(t, 21, commonConf.IdleTimeout)
|
||||
|
@ -263,7 +268,7 @@ func TestHostKeyCompatibility(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, configFilePath)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
assert.NoError(t, err)
|
||||
sftpdConf = config.GetSFTPDConfig()
|
||||
assert.Equal(t, 2, len(sftpdConf.HostKeys))
|
||||
|
|
|
@ -24,8 +24,9 @@ Flags:
|
|||
|
||||
The `serve` command supports the following flags:
|
||||
|
||||
- `--config-dir` string. Location of the config dir. This directory should contain the configuration file and is used as the base directory for any files that use a relative path (eg. the private keys for the SFTP server, the SQLite or bblot database if you use SQLite or bbolt as data provider). The default value is "." or the value of `SFTPGO_CONFIG_DIR` environment variable.
|
||||
- `--config-file` string. Name of the configuration file. It must be the name of a file stored in `config-dir`, not the absolute path to the configuration file. The specified file name must have no extension because we automatically append JSON, YAML, TOML, HCL and Java extensions when we search for the file. The default value is "sftpgo" (and therefore `sftpgo.json`, `sftpgo.yaml` and so on are searched) or the value of `SFTPGO_CONFIG_FILE` environment variable.
|
||||
- `--config-dir` string. Location of the config dir. This directory is used as the base for files with a relative path, eg. the private keys for the SFTP server or the SQLite database if you use SQLite as data provider. The configuration file, if not explicitly set, is looked for in this dir. We support reading from JSON, TOML, YAML, HCL, envfile and Java properties config files. The default config file name is `sftpgo` and therefore `sftpgo.json`, `sftpgo.yaml` and so on are searched. The default value is the working directory (".") or the value of `SFTPGO_CONFIG_DIR` environment variable.
|
||||
- `--config-file` string. This flag explicitly defines the path, name and extension of the config file. If must be an absolute path or a path relative to the configuration directory. The specified file name must have a supported extension (JSON, YAML, TOML, HCL or Java properties). The default value is empty or the value of `SFTPGO_CONFIG_FILE` environment variable.
|
||||
- `--loaddata-from` string. Load users and folders from this file. The file must be specified as absolute path and it must contain a backup obtained using the `dumpdata` REST API or compatible content. The default value is empty or the value of `SFTPGO_LOADDATA_FROM` environment variable.
|
||||
- `--loaddata-from` string. Load users and folders from this file. The file must be specified as absolute path and it must contain a backup obtained using the `dumpdata` REST API or compatible content. The default value is empty or the value of `SFTPGO_LOADDATA_FROM` environment variable.
|
||||
- `--loaddata-clean` boolean. Determine if the loaddata-from file should be removed after a successful load. Default `false` or the value of `SFTPGO_LOADDATA_CLEAN` environment variable (1 or `true`, 0 or `false`).
|
||||
- `--loaddata-mode`, integer. Restore mode for data to load. 0 means new users are added, existing users are updated. 1 means new users are added, existing users are not modified. Default 1 or the value of `SFTPGO_LOADDATA_MODE` environment variable.
|
||||
|
|
|
@ -72,6 +72,7 @@ func (s *Service) Start() error {
|
|||
err := config.LoadConfig(s.ConfigDir, s.ConfigFile)
|
||||
if err != nil {
|
||||
logger.Error(logSender, "", "error loading configuration: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !config.HasServicesToStart() {
|
||||
|
|
Loading…
Reference in a new issue