diff --git a/README.md b/README.md index 377f64a2..e872fb65 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,31 @@ Alternately you can use distro packages: ## Configuration -The `sftpgo` executable supports the following command line flags: +The `sftpgo` executable can be used this way: -- `--config-dir` string. Location of the config dir. This directory should contain the `sftpgo.conf` configuration file and 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). The default value is "." or the value of `SFTPGO_CONFIG_DIR` environment variable -- `--config-file-name` 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 default value is "sftpgo.conf" or the value of `SFTPGO_CONFIG_FILE_NAME` environment variable +```bash +Usage: + sftpgo [command] + +Available Commands: + help Help about any command + serve Start the SFTP Server + +Flags: + -h, --help help for sftpgo + -v, --version +``` + +The `serve` subcommand supports the following flags: + +- `--config-dir` string. Location of the config dir. This directory should contain the `sftpgo` configuration file and 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). 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 we automatically load JSON, YAML, TOML, HCL and Java properties. 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 +- `--log-compress` boolean. Determine if the rotated log files should be compressed using gzip. Default `false` or the value of `SFTPGO_LOG_COMPRESS` environment variable (1 or `true`, 0 or `false`) - `--log-file-path` string. Location for the log file, default "sftpgo.log" or the value of `SFTPGO_LOG_FILE_PATH` environment variable -- `--log-max-size` int. Maximum size in megabytes of the log file before it gets rotated. Default 10 or the value of `SFTPGO_LOG_MAX_SIZE` environment variable -- `--log-max-backups` int. Maximum number of old log files to retain. Default 5 or the value of `SFTPGO_LOG_MAX_BACKUPS` environment variable - `--log-max-age` int. Maximum number of days to retain old log files. Default 28 or the value of `SFTPGO_LOG_MAX_AGE` environment variable -- `--log-compress` boolean. Determine if the rotated log files should be compressed using gzip. Default `false` or the integer value of `SFTPGO_LOG_COMPRESS` environment variable (> 0 is `true`, 0 or invalid integer is `false`) -- `--log-verbose` boolean. Enable verbose logs. Default `true` or the integer value of `SFTPGO_LOG_VERBOSE` environment variable (> 0 is `true`, 0 or invalid integer is `false`) +- `--log-max-backups` int. Maximum number of old log files to retain. Default 5 or the value of `SFTPGO_LOG_MAX_BACKUPS` environment variable +- `--log-max-size` int. Maximum size in megabytes of the log file before it gets rotated. Default 10 or the value of `SFTPGO_LOG_MAX_SIZE` environment variable +- `--log-verbose` boolean. Enable verbose logs. Default `true` or the value of `SFTPGO_LOG_VERBOSE` environment variable (1 or `true`, 0 or `false`) If you don't configure any private host keys, the daemon will use `id_rsa` in the configuration directory. If that file doesn't exist, the daemon will attempt to autogenerate it (if the user that executes SFTPGo has write access to the config-dir). The server supports any private key format supported by [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/keys.go#L32). @@ -65,7 +80,7 @@ Before starting `sftpgo` a dataprovider must be configured. Sample SQL scripts to create the required database structure can be found insite the source tree [sql](https://github.com/drakkan/sftpgo/tree/master/sql "sql") directory. The SQL scripts filename's is, by convention, the date as `YYYYMMDD` and the suffix `.sql`. You need to apply all the SQL scripts for your database ordered by name, for example `20190706.sql` must be applied before `20190728.sql` and so on. -The `sftpgo.conf` configuration file contains the following sections: +The `sftpgo` configuration file contains the following sections: - **"sftpd"**, the configuration for the SFTP server - `bind_port`, integer. The port used for serving SFTP requests. Default: 2022 @@ -108,7 +123,7 @@ The `sftpgo.conf` configuration file contains the following sections: - `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080 - `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "127.0.0.1" -Here is a full example showing the default config: +Here is a full example showing the default config in json format: ```json { @@ -159,6 +174,21 @@ If you want to use a private key that use an algorithm different from RSA or mor ] ``` +The configuration can be read from JSON, TOML, YAML, HCL, envfile and Java properties config files, if your config-file flag is set to `sftpgo` (default value) you need to create a configuration file called `sftpgo.json` or `sftpgo.yaml` and so on inside `config-dir`. + +You can also configure all the available options using environment variables, sftpgo will check for a environment variable with a name matching the key uppercased and prefixed with the `SFTPGO_`. You need to use `__` to traverse a struct. + +Let's see some examples: + +- To set sftpd `bind_port` you need to define the env var `SFTPGO_SFTPD__BIND_PORT` +- To set the `execute_on` actions you need to define the env var `SFTPGO_SFTPD__ACTIONS__EXECUTE_ON` for example `SFTPGO_SFTPD__ACTIONS__EXECUTE_ON=upload,download` + +To start the SFTP Server with the default values for the command line flags simply use: + +```bash +sftpgo serve +``` + ## Account's configuration properties For each account the following properties can be configured: @@ -183,20 +213,43 @@ For each account the following properties can be configured: - `upload_bandwidth` maximum upload bandwidth as KB/s, 0 means unlimited - `download_bandwidth` maximum download bandwidth as KB/s, 0 means unlimited -These properties are stored inside the data provider. If you want to use your existing accounts, you can create a database view. Since a view is read only, you have to disable user management and quota tracking so sftpgo will never try to write to the view. +These properties are stored inside the data provider. If you want to use your existing accounts, you can create a database view. Since a view is read only, you have to disable user management and quota tracking so SFTPGo will never try to write to the view. ## REST API SFTPGo exposes REST API to manage users and quota and to get real time reports for the active connections with possibility of forcibly closing a connection. -If quota tracking is enabled in `sftpgo.conf` configuration file, then the used size and number of files are updated each time a file is added/removed. If files are added/removed not using SFTP or if you change `track_quota` from `2` to `1`, you can rescan the user home dir and update the used quota using the REST API. +If quota tracking is enabled in `sftpgo` configuration file, then the used size and number of files are updated each time a file is added/removed. If files are added/removed not using SFTP or if you change `track_quota` from `2` to `1`, you can rescan the user home dir and update the used quota using the REST API. -REST API is designed to run on localhost or on a trusted network, if you need https or authentication you can setup a reverse proxy using an HTTP Server such as Apache or NGNIX. +REST API is designed to run on localhost or on a trusted network, if you need HTTPS or authentication you can setup a reverse proxy using an HTTP Server such as Apache or NGNIX. + +For example you can setup a reverse proxy using apache this way: + +``` +ProxyPass /api/v1 http://127.0.0.1:8080/api/v1 +ProxyPassReverse /api/v1 http://127.0.0.1:8080/api/v1 +``` + +and you can add authentication with something like this: + +``` + + AuthType Digest + AuthName "Private" + AuthBasicProvider file + AuthUserFile "/etc/httpd/conf/auth_digest" + Require valid-user + +``` + +and, of course, you can configure the web server to use HTTPS. The OpenAPI 3 schema for the exposed API can be found inside the source tree: [openapi.yaml](https://github.com/drakkan/sftpgo/tree/master/api/schema/openapi.yaml "OpenAPI 3 specs"). A sample CLI client for the REST API can be found inside the source tree [scripts](https://github.com/drakkan/sftpgo/tree/master/scripts "scripts") directory. +You can also generate your own REST client using an OpenAPI generator such as [swagger-codegen](https://github.com/swagger-api/swagger-codegen) or [OpenAPI Generator](https://openapi-generator.tech/) + ## Logs Inside the log file each line is a JSON struct, each struct has a `sender` fields that identify the log type. @@ -247,6 +300,8 @@ The logs can be divided into the following categories: - [go-sqlite3](https://github.com/mattn/go-sqlite3) - [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) - [lib/pq](https://github.com/lib/pq) +- [viper](https://github.com/spf13/viper) +- [cobra](https://github.com/spf13/cobra) Some code was initially taken from [Pterodactyl sftp server](https://github.com/pterodactyl/sftp-server) diff --git a/api/api.go b/api/api.go index c6cb253f..9511ac56 100644 --- a/api/api.go +++ b/api/api.go @@ -28,9 +28,9 @@ var ( // HTTPDConf httpd daemon configuration type HTTPDConf struct { // The port used for serving HTTP requests. 0 disable the HTTP server. Default: 8080 - BindPort int `json:"bind_port"` + BindPort int `json:"bind_port" mapstructure:"bind_port"` // The address to listen on. A blank value means listen on all available network interfaces. Default: "127.0.0.1" - BindAddress string `json:"bind_address"` + BindAddress string `json:"bind_address" mapstructure:"bind_address"` } type apiResponse struct { diff --git a/api/api_test.go b/api/api_test.go index 3efb858b..4589a89b 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -51,10 +51,8 @@ func TestMain(m *testing.M) { } configDir := ".." logfilePath := filepath.Join(configDir, "sftpgo_api_test.log") - confName := "sftpgo.conf" logger.InitLogger(logfilePath, 5, 1, 28, false, zerolog.DebugLevel) - configFilePath := filepath.Join(configDir, confName) - config.LoadConfig(configFilePath) + config.LoadConfig(configDir, "") providerConf := config.GetProviderConf() err := dataprovider.Initialize(providerConf, configDir) diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 00000000..9f5cdb1f --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/drakkan/sftpgo/utils" + "github.com/spf13/cobra" +) + +const ( + logSender = "cmd" +) + +var ( + rootCmd = &cobra.Command{ + Use: "sftpgo", + Short: "Full featured and highly configurable SFTP server", + } +) + +func init() { + rootCmd.Flags().BoolP("version", "v", false, "") + rootCmd.Version = utils.GetAppVersion() + rootCmd.SetVersionTemplate(`{{printf "SFTPGo version "}}{{printf "%s" .Version}} +`) +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/serve.go b/cmd/serve.go new file mode 100644 index 00000000..e3ed0d42 --- /dev/null +++ b/cmd/serve.go @@ -0,0 +1,181 @@ +package cmd + +import ( + "fmt" + "net/http" + "os" + "time" + + "github.com/drakkan/sftpgo/api" + "github.com/drakkan/sftpgo/config" + "github.com/drakkan/sftpgo/dataprovider" + "github.com/drakkan/sftpgo/logger" + "github.com/drakkan/sftpgo/sftpd" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +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" + logMaxSizeKey = "log_max_size" + logMaxBackupFlag = "log-max-backups" + logMaxBackupKey = "log_max_backups" + logMaxAgeFlag = "log-max-age" + logMaxAgeKey = "log_max_age" + logCompressFlag = "log-compress" + logCompressKey = "log_compress" + logVerboseFlag = "log-verbose" + logVerboseKey = "log_verbose" +) + +var ( + configDir string + configFile string + logFilePath string + logMaxSize int + logMaxBackups int + logMaxAge int + logCompress bool + logVerbose bool + testVar string + serveCmd = &cobra.Command{ + Use: "serve", + Short: "Start the SFTP Server", + Long: `To start the SFTP Server with the default values for the command line flags simply use: + +sftpgo serve + +Please take a look at the usage below to customize the startup options`, + Run: func(cmd *cobra.Command, args []string) { + startServe() + }, + } +) + +func init() { + rootCmd.AddCommand(serveCmd) + + viper.SetDefault(configDirKey, ".") + viper.BindEnv(configDirKey, "SFTPGO_CONFIG_DIR") + serveCmd.Flags().StringVarP(&configDir, configDirFlag, "c", viper.GetString(configDirKey), + "Location for SFTPGo config dir. This directory should contain the \"sftpgo\" configuration file or the configured "+ + "config-file and 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). This flag can be set using SFTPGO_CONFIG_DIR env var too.") + viper.BindPFlag(configDirKey, serveCmd.Flags().Lookup(configDirFlag)) + + viper.SetDefault(configFileKey, config.DefaultConfigName) + viper.BindEnv(configFileKey, "SFTPGO_CONFIG_FILE") + serveCmd.Flags().StringVarP(&configFile, configFileFlag, "f", viper.GetString(configFileKey), + "Name for SFTPGo 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 we automatically load JSON, YAML, TOML, HCL and "+ + "Java properties. Therefore if you set \"sftpgo\" then \"sftpgo.json\", \"sftpgo.yaml\" and so on are searched. "+ + "This flag can be set using SFTPGO_CONFIG_FILE env var too.") + viper.BindPFlag(configFileKey, serveCmd.Flags().Lookup(configFileFlag)) + + viper.SetDefault(logFilePathKey, "sftpgo.log") + viper.BindEnv(logFilePathKey, "SFTPGO_LOG_FILE_PATH") + serveCmd.Flags().StringVarP(&logFilePath, logFilePathFlag, "l", viper.GetString(logFilePathKey), + "Location for the log file. This flag can be set using SFTPGO_LOG_FILE_PATH env var too.") + viper.BindPFlag(logFilePathKey, serveCmd.Flags().Lookup(logFilePathFlag)) + + viper.SetDefault(logMaxSizeKey, 10) + viper.BindEnv(logMaxSizeKey, "SFTPGO_LOG_MAX_SIZE") + serveCmd.Flags().IntVarP(&logMaxSize, logMaxSizeFlag, "s", viper.GetInt(logMaxSizeKey), + "Maximum size in megabytes of the log file before it gets rotated. This flag can be set using SFTPGO_LOG_MAX_SIZE "+ + "env var too.") + viper.BindPFlag(logMaxSizeKey, serveCmd.Flags().Lookup(logMaxSizeFlag)) + + viper.SetDefault(logMaxBackupKey, 5) + viper.BindEnv(logMaxBackupKey, "SFTPGO_LOG_MAX_BACKUPS") + serveCmd.Flags().IntVarP(&logMaxBackups, "log-max-backups", "b", viper.GetInt(logMaxBackupKey), + "Maximum number of old log files to retain. This flag can be set using SFTPGO_LOG_MAX_BACKUPS env var too.") + viper.BindPFlag(logMaxBackupKey, serveCmd.Flags().Lookup(logMaxBackupFlag)) + + viper.SetDefault(logMaxAgeKey, 28) + viper.BindEnv(logMaxAgeKey, "SFTPGO_LOG_MAX_AGE") + serveCmd.Flags().IntVarP(&logMaxAge, "log-max-age", "a", viper.GetInt(logMaxAgeKey), + "Maximum number of days to retain old log files. This flag can be set using SFTPGO_LOG_MAX_AGE env var too.") + viper.BindPFlag(logMaxAgeKey, serveCmd.Flags().Lookup(logMaxAgeFlag)) + + viper.SetDefault(logCompressKey, false) + viper.BindEnv(logCompressKey, "SFTPGO_LOG_COMPRESS") + serveCmd.Flags().BoolVarP(&logCompress, logCompressFlag, "z", viper.GetBool(logCompressKey), "Determine if the rotated "+ + "log files should be compressed using gzip. This flag can be set using SFTPGO_LOG_COMPRESS env var too.") + viper.BindPFlag(logCompressKey, serveCmd.Flags().Lookup(logCompressFlag)) + + viper.SetDefault(logVerboseKey, true) + viper.BindEnv(logVerboseKey, "SFTPGO_LOG_VERBOSE") + serveCmd.Flags().BoolVarP(&logVerbose, logVerboseFlag, "v", viper.GetBool(logVerboseKey), "Enable verbose logs. "+ + "This flag can be set using SFTPGO_LOG_VERBOSE env var too.") + viper.BindPFlag(logVerboseKey, serveCmd.Flags().Lookup(logVerboseFlag)) +} + +func startServe() { + logLevel := zerolog.DebugLevel + if !logVerbose { + logLevel = zerolog.InfoLevel + } + logger.InitLogger(logFilePath, logMaxSize, logMaxBackups, logMaxAge, logCompress, logLevel) + logger.Info(logSender, "starting SFTPGo, config dir: %v, config file: %v, log max size: %v log max backups: %v "+ + "log max age: %v log verbose: %v, log compress: %v", configDir, configFile, logMaxSize, logMaxBackups, logMaxAge, + logVerbose, logCompress) + config.LoadConfig(configDir, configFile) + providerConf := config.GetProviderConf() + + err := dataprovider.Initialize(providerConf, configDir) + if err != nil { + logger.Error(logSender, "error initializing data provider: %v", err) + logger.ErrorToConsole("error initializing data provider: %v", err) + os.Exit(1) + } + + dataProvider := dataprovider.GetProvider() + sftpdConf := config.GetSFTPDConfig() + httpdConf := config.GetHTTPDConfig() + + sftpd.SetDataProvider(dataProvider) + + shutdown := make(chan bool) + + go func() { + logger.Debug(logSender, "initializing SFTP server with config %+v", sftpdConf) + if err := sftpdConf.Initialize(configDir); err != nil { + logger.Error(logSender, "could not start SFTP server: %v", err) + logger.ErrorToConsole("could not start SFTP server: %v", err) + } + shutdown <- true + }() + + if httpdConf.BindPort > 0 { + router := api.GetHTTPRouter() + api.SetDataProvider(dataProvider) + + go func() { + logger.Debug(logSender, "initializing HTTP server with config %+v", httpdConf) + s := &http.Server{ + Addr: fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort), + Handler: router, + ReadTimeout: 300 * time.Second, + WriteTimeout: 300 * time.Second, + MaxHeaderBytes: 1 << 20, // 1MB + } + if err := s.ListenAndServe(); err != nil { + logger.Error(logSender, "could not start HTTP server: %v", err) + logger.ErrorToConsole("could not start HTTP server: %v", err) + } + shutdown <- true + }() + } else { + logger.Debug(logSender, "HTTP server not started, disabled in config file") + logger.DebugToConsole("HTTP server not started, disabled in config file") + } + + <-shutdown +} diff --git a/config/config.go b/config/config.go index 0134fb81..138def4d 100644 --- a/config/config.go +++ b/config/config.go @@ -6,20 +6,26 @@ package config import ( - "encoding/json" "fmt" - "os" + "runtime" "strings" "github.com/drakkan/sftpgo/api" "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/sftpd" + "github.com/spf13/viper" ) const ( logSender = "config" defaultBanner = "SFTPGo" + // DefaultConfigName 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 + DefaultConfigName = "sftpgo" + // ConfigEnvPrefix defines a prefix that ENVIRONMENT variables will use + configEnvPrefix = "sftpgo" ) var ( @@ -27,9 +33,9 @@ var ( ) type globalConfig struct { - SFTPD sftpd.Configuration `json:"sftpd"` - ProviderConf dataprovider.Config `json:"data_provider"` - HTTPDConfig api.HTTPDConf `json:"httpd"` + SFTPD sftpd.Configuration `json:"sftpd" mapstructure:"sftpd"` + ProviderConf dataprovider.Config `json:"data_provider" mapstructure:"data_provider"` + HTTPDConfig api.HTTPDConf `json:"httpd" mapstructure:"httpd"` } func init() { @@ -68,6 +74,17 @@ func init() { BindAddress: "127.0.0.1", }, } + + viper.SetEnvPrefix(configEnvPrefix) + replacer := strings.NewReplacer(".", "__") + viper.SetEnvKeyReplacer(replacer) + viper.SetConfigName(DefaultConfigName) + if runtime.GOOS == "linux" { + viper.AddConfigPath("$HOME/.config/sftpgo") + viper.AddConfigPath("/etc/sftpgo") + } + viper.AddConfigPath(".") + viper.AutomaticEnv() } // GetSFTPDConfig returns the configuration for the SFTP server @@ -85,17 +102,21 @@ func GetProviderConf() dataprovider.Config { return globalConf.ProviderConf } -// LoadConfig loads the configuration from sftpgo.conf or use the default configuration. -func LoadConfig(configPath string) error { - logger.Debug(logSender, "load config from path: %v", configPath) - file, err := os.Open(configPath) - if err != nil { +// 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. +// configName is the name of the configuration to search without extension +func LoadConfig(configDir, configName string) error { + var err error + viper.AddConfigPath(configDir) + viper.SetConfigName(configName) + if err = viper.ReadInConfig(); err != nil { logger.Warn(logSender, "error loading configuration file: %v. Default configuration will be used: %+v", err, globalConf) logger.WarnToConsole("error loading configuration file: %v. Default configuration will be used.", err) return err } - defer file.Close() - err = json.NewDecoder(file).Decode(&globalConf) + err = viper.Unmarshal(&globalConf) if err != nil { logger.Warn(logSender, "error parsing configuration file: %v. Default configuration will be used: %+v", err, globalConf) logger.WarnToConsole("error parsing configuration file: %v. Default configuration will be used.", err) @@ -111,6 +132,6 @@ func LoadConfig(configPath string) error { logger.Warn(logSender, "Configuration error: %v", err) logger.WarnToConsole("Configuration error: %v", err) } - logger.Debug(logSender, "config loaded: %+v", globalConf) + logger.Debug(logSender, "config file used: '%v', config loaded: %+v", viper.ConfigFileUsed(), globalConf) return err } diff --git a/config/config_test.go b/config/config_test.go index 82b760e5..d35f8186 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -14,11 +14,13 @@ import ( "github.com/drakkan/sftpgo/sftpd" ) +const ( + tempConfigName = "temp" +) + func TestLoadConfigTest(t *testing.T) { configDir := ".." - confName := "sftpgo.conf" - configFilePath := filepath.Join(configDir, confName) - err := config.LoadConfig(configFilePath) + err := config.LoadConfig(configDir, "") if err != nil { t.Errorf("error loading config") } @@ -34,25 +36,30 @@ func TestLoadConfigTest(t *testing.T) { if config.GetSFTPDConfig().BindPort == emptySFTPDConf.BindPort { t.Errorf("error loading SFTPD conf") } - confName = "sftpgo.conf.missing" - configFilePath = filepath.Join(configDir, confName) - err = config.LoadConfig(configFilePath) + confName := tempConfigName + ".json" + configFilePath := filepath.Join(configDir, confName) + err = config.LoadConfig(configDir, tempConfigName) if err == nil { t.Errorf("loading a non existent config file must fail") } ioutil.WriteFile(configFilePath, []byte("{invalid json}"), 0666) - err = config.LoadConfig(configFilePath) + err = config.LoadConfig(configDir, tempConfigName) if err == nil { t.Errorf("loading an invalid config file must fail") } + ioutil.WriteFile(configFilePath, []byte("{\"sftpd\": {\"bind_port\": \"a\"}}"), 0666) + err = config.LoadConfig(configDir, tempConfigName) + if err == nil { + t.Errorf("loading a config with an invalid bond_port must fail") + } os.Remove(configFilePath) } func TestEmptyBanner(t *testing.T) { configDir := ".." - confName := "temp.conf" + confName := tempConfigName + ".json" configFilePath := filepath.Join(configDir, confName) - config.LoadConfig(configFilePath) + config.LoadConfig(configDir, "") sftpdConf := config.GetSFTPDConfig() sftpdConf.Banner = " " c := make(map[string]sftpd.Configuration) @@ -62,7 +69,7 @@ func TestEmptyBanner(t *testing.T) { if err != nil { t.Errorf("error saving temporary configuration") } - config.LoadConfig(configFilePath) + config.LoadConfig(configDir, tempConfigName) sftpdConf = config.GetSFTPDConfig() if strings.TrimSpace(sftpdConf.Banner) == "" { t.Errorf("SFTPD banner cannot be empty") @@ -72,9 +79,9 @@ func TestEmptyBanner(t *testing.T) { func TestInvalidUploadMode(t *testing.T) { configDir := ".." - confName := "temp.conf" + confName := tempConfigName + ".json" configFilePath := filepath.Join(configDir, confName) - config.LoadConfig(configFilePath) + config.LoadConfig(configDir, "") sftpdConf := config.GetSFTPDConfig() sftpdConf.UploadMode = 10 c := make(map[string]sftpd.Configuration) @@ -84,7 +91,7 @@ func TestInvalidUploadMode(t *testing.T) { if err != nil { t.Errorf("error saving temporary configuration") } - err = config.LoadConfig(configFilePath) + err = config.LoadConfig(configDir, tempConfigName) if err == nil { t.Errorf("Loading configuration with invalid upload_mode must fail") } diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index 9efe176e..6f8b6c1a 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -44,37 +44,37 @@ var ( // Config provider configuration type Config struct { // Driver name, must be one of the SupportedProviders - Driver string `json:"driver"` + Driver string `json:"driver" mapstructure:"driver"` // Database name - Name string `json:"name"` + Name string `json:"name" mapstructure:"name"` // Database host - Host string `json:"host"` + Host string `json:"host" mapstructure:"host"` // Database port - Port int `json:"port"` + Port int `json:"port" mapstructure:"port"` // Database username - Username string `json:"username"` + Username string `json:"username" mapstructure:"username"` // Database password - Password string `json:"password"` + Password string `json:"password" mapstructure:"password"` // Used for drivers mysql and postgresql. // 0 disable SSL/TLS connections. // 1 require ssl. // 2 set ssl mode to verify-ca for driver postgresql and skip-verify for driver mysql. // 3 set ssl mode to verify-full for driver postgresql and preferred for driver mysql. - SSLMode int `json:"sslmode"` + SSLMode int `json:"sslmode" mapstructure:"sslmode"` // Custom database connection string. // If not empty this connection string will be used instead of build one using the previous parameters - ConnectionString string `json:"connection_string"` + ConnectionString string `json:"connection_string" mapstructure:"connection_string"` // Database table for SFTP users - UsersTable string `json:"users_table"` + UsersTable string `json:"users_table" mapstructure:"users_table"` // Set to 0 to disable users management, 1 to enable - ManageUsers int `json:"manage_users"` + ManageUsers int `json:"manage_users" mapstructure:"manage_users"` // Set the preferred way to track users quota between the following choices: // 0, disable quota tracking. REST API to scan user dir and update quota will do nothing // 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions // 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions. // With this configuration the "quota scan" REST API can still be used to periodically update space usage // for users without quota restrictions - TrackQuota int `json:"track_quota"` + TrackQuota int `json:"track_quota" mapstructure:"track_quota"` } // ValidationError raised if input data is not valid diff --git a/dataprovider/mysql.go b/dataprovider/mysql.go index e6986f68..7fc3fbfa 100644 --- a/dataprovider/mysql.go +++ b/dataprovider/mysql.go @@ -25,12 +25,12 @@ func initializeMySQLProvider() error { dbHandle, err = sql.Open("mysql", connectionString) if err == nil { numCPU := runtime.NumCPU() - logger.Debug(logSender, "mysql database handle created, connection string: \"%v\", pool size: %v", connectionString, numCPU) + logger.Debug(logSender, "mysql database handle created, connection string: '%v', pool size: %v", connectionString, numCPU) dbHandle.SetMaxIdleConns(numCPU) dbHandle.SetMaxOpenConns(numCPU) dbHandle.SetConnMaxLifetime(1800 * time.Second) } else { - logger.Warn(logSender, "error creating mysql database handler, connection string: \"%v\", error: %v", connectionString, err) + logger.Warn(logSender, "error creating mysql database handler, connection string: '%v', error: %v", connectionString, err) } return err } diff --git a/dataprovider/pgsql.go b/dataprovider/pgsql.go index 753bfff9..fa839fa4 100644 --- a/dataprovider/pgsql.go +++ b/dataprovider/pgsql.go @@ -24,11 +24,11 @@ func initializePGSQLProvider() error { dbHandle, err = sql.Open("postgres", connectionString) if err == nil { numCPU := runtime.NumCPU() - logger.Debug(logSender, "postgres database handle created, connection string: \"%v\", pool size: %v", connectionString, numCPU) + logger.Debug(logSender, "postgres database handle created, connection string: '%v', pool size: %v", connectionString, numCPU) dbHandle.SetMaxIdleConns(numCPU) dbHandle.SetMaxOpenConns(numCPU) } else { - logger.Warn(logSender, "error creating postgres database handler, connection string: \"%v\", error: %v", connectionString, err) + logger.Warn(logSender, "error creating postgres database handler, connection string: '%v', error: %v", connectionString, err) } return err } diff --git a/dataprovider/sqlite.go b/dataprovider/sqlite.go index b9f84dbc..c6cddc66 100644 --- a/dataprovider/sqlite.go +++ b/dataprovider/sqlite.go @@ -38,10 +38,10 @@ func initializeSQLiteProvider(basePath string) error { } dbHandle, err = sql.Open("sqlite3", connectionString) if err == nil { - logger.Debug(logSender, "sqlite database handle created, connection string: \"%v\"", connectionString) + logger.Debug(logSender, "sqlite database handle created, connection string: '%v'", connectionString) dbHandle.SetMaxOpenConns(1) } else { - logger.Warn(logSender, "error creating sqlite database handler, connection string: \"%v\", error: %v", connectionString, err) + logger.Warn(logSender, "error creating sqlite database handler, connection string: '%v', error: %v", connectionString, err) } return err } diff --git a/go.mod b/go.mod index 9276e596..67e945c1 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,8 @@ require ( github.com/pkg/sftp v1.10.0 github.com/rs/xid v1.2.1 github.com/rs/zerolog v1.14.3 + github.com/spf13/cobra v0.0.5 + github.com/spf13/viper v1.4.0 github.com/stretchr/testify v1.3.0 // indirect golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 gopkg.in/natefinch/lumberjack.v2 v2.0.0 diff --git a/go.sum b/go.sum index 8b74854d..c39aec52 100644 --- a/go.sum +++ b/go.sum @@ -1,47 +1,183 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexedwards/argon2id v0.0.0-20190612080829-01a59b2b8802 h1:RwMM1q/QSKYIGbHfOkf843hE8sSUJtf1dMwFPtEDmm0= github.com/alexedwards/argon2id v0.0.0-20190612080829-01a59b2b8802/go.mod h1:4dsm7ufQm1Gwl8S2ss57u+2J7KlxIL2QUmFGlGtWogY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs= github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.0 h1:DGA1KlA9esU6WcicH+P8PxFZOl15O6GYtab1cIJdOlE= github.com/pkg/sftp v1.10.0/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0= github.com/rs/zerolog v1.14.3/go.mod h1:3WXPzbXEEliJ+a6UFE4vhIxV8qR1EML6ngzP9ug4eYg= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/init/sftpgo.service b/init/sftpgo.service index c51cd0da..8dae1580 100644 --- a/init/sftpgo.service +++ b/init/sftpgo.service @@ -10,7 +10,7 @@ WorkingDirectory=/etc/sftpgo Environment=SFTPGO_CONFIG_DIR=/etc/sftpgo/ Environment=SFTPGO_LOG_FILE_PATH=/var/log/sftpgo.log EnvironmentFile=-/etc/sftpgo/sftpgo.env -ExecStart=/usr/bin/sftpgo +ExecStart=/usr/bin/sftpgo serve KillMode=mixed Restart=always RestartSec=10s diff --git a/logger/logger.go b/logger/logger.go index 09b9e8c6..5f644d7b 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -11,6 +11,7 @@ package logger import ( "fmt" "os" + "runtime" "github.com/rs/zerolog" lumberjack "gopkg.in/natefinch/lumberjack.v2" @@ -44,7 +45,7 @@ func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge consoleOutput := zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: dateFormat, - NoColor: true, + NoColor: runtime.GOOS == "windows", } consoleLogger = zerolog.New(consoleOutput).With().Timestamp().Logger().Level(level) } diff --git a/main.go b/main.go index e55eeb51..1604364e 100644 --- a/main.go +++ b/main.go @@ -4,112 +4,12 @@ package main // import "github.com/drakkan/sftpgo" import ( - "flag" - "fmt" - "net/http" - "os" - "path/filepath" - "time" - + "github.com/drakkan/sftpgo/cmd" _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" - - "github.com/drakkan/sftpgo/api" - "github.com/drakkan/sftpgo/config" - "github.com/drakkan/sftpgo/dataprovider" - "github.com/drakkan/sftpgo/logger" - "github.com/drakkan/sftpgo/sftpd" - "github.com/drakkan/sftpgo/utils" - - "github.com/rs/zerolog" ) func main() { - logSender := "main" - var ( - configDir string - configFileName string - logFilePath string - logMaxSize int - logMaxBackups int - logMaxAge int - logCompress bool - logVerbose bool - ) - flag.StringVar(&configDir, "config-dir", utils.GetEnvVar("SFTPGO_CONFIG_DIR", "."), "Location for SFTPGo config dir. It must contain "+ - "sftpgo.conf or the configured config-file-name and 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).") - flag.StringVar(&configFileName, "config-file-name", utils.GetEnvVar("SFTPGO_CONFIG_FILE_NAME", "sftpgo.conf"), "Name for SFTPGo "+ - "configuration file. It must be the name of a file stored in config-dir not the absolute path to the configuration file") - flag.StringVar(&logFilePath, "log-file-path", utils.GetEnvVar("SFTPGO_LOG_FILE_PATH", "sftpgo.log"), "Location for the log file") - flag.IntVar(&logMaxSize, "log-max-size", utils.GetEnvVarAsInt("SFTPGO_LOG_MAX_SIZE", 10), "Maximum size in megabytes of the log file "+ - "before it gets rotated.") - flag.IntVar(&logMaxBackups, "log-max-backups", utils.GetEnvVarAsInt("SFTPGO_LOG_MAX_BACKUPS", 5), "Maximum number of old log files "+ - "to retain") - flag.IntVar(&logMaxAge, "log-max-age", utils.GetEnvVarAsInt("SFTPGO_LOG_MAX_AGE", 28), "Maximum number of days to retain old log files") - flag.BoolVar(&logCompress, "log-compress", utils.GetEnvVarAsInt("SFTPGO_LOG_COMPRESS", 0) > 0, "Determine if the rotated log files "+ - "should be compressed using gzip") - flag.BoolVar(&logVerbose, "log-verbose", utils.GetEnvVarAsInt("SFTPGO_LOG_VERBOSE", 1) > 0, "Enable verbose logs") - flag.Parse() - - configFilePath := filepath.Join(configDir, configFileName) - logLevel := zerolog.DebugLevel - if !logVerbose { - logLevel = zerolog.InfoLevel - } - logger.InitLogger(logFilePath, logMaxSize, logMaxBackups, logMaxAge, logCompress, logLevel) - logger.Info(logSender, "starting SFTPGo, config dir: %v", configDir) - config.LoadConfig(configFilePath) - providerConf := config.GetProviderConf() - - err := dataprovider.Initialize(providerConf, configDir) - if err != nil { - logger.Error(logSender, "error initializing data provider: %v", err) - logger.ErrorToConsole("error initializing data provider: %v", err) - os.Exit(1) - } - - dataProvider := dataprovider.GetProvider() - sftpdConf := config.GetSFTPDConfig() - httpdConf := config.GetHTTPDConfig() - - sftpd.SetDataProvider(dataProvider) - - shutdown := make(chan bool) - - go func() { - logger.Debug(logSender, "initializing SFTP server with config %+v", sftpdConf) - if err := sftpdConf.Initialize(configDir); err != nil { - logger.Error(logSender, "could not start SFTP server: %v", err) - logger.ErrorToConsole("could not start SFTP server: %v", err) - } - shutdown <- true - }() - - if httpdConf.BindPort > 0 { - router := api.GetHTTPRouter() - api.SetDataProvider(dataProvider) - - go func() { - logger.Debug(logSender, "initializing HTTP server with config %+v", httpdConf) - s := &http.Server{ - Addr: fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort), - Handler: router, - ReadTimeout: 300 * time.Second, - WriteTimeout: 300 * time.Second, - MaxHeaderBytes: 1 << 20, // 1MB - } - if err := s.ListenAndServe(); err != nil { - logger.Error(logSender, "could not start HTTP server: %v", err) - logger.ErrorToConsole("could not start HTTP server: %v", err) - } - shutdown <- true - }() - } else { - logger.Debug(logSender, "HTTP server not started, disabled in config file") - logger.DebugToConsole("HTTP server not started, disabled in config file") - } - - <-shutdown + cmd.Execute() } diff --git a/sftpd/server.go b/sftpd/server.go index b5330ca6..a0d836a6 100644 --- a/sftpd/server.go +++ b/sftpd/server.go @@ -30,34 +30,34 @@ const defaultPrivateKeyName = "id_rsa" // Configuration for the SFTP server type Configuration struct { // Identification string used by the server - Banner string `json:"banner"` + Banner string `json:"banner" mapstructure:"banner"` // The port used for serving SFTP requests - BindPort int `json:"bind_port"` + BindPort int `json:"bind_port" mapstructure:"bind_port"` // The address to listen on. A blank value means listen on all available network interfaces. - BindAddress string `json:"bind_address"` + BindAddress string `json:"bind_address" mapstructure:"bind_address"` // Maximum idle timeout as minutes. If a client is idle for a time that exceeds this setting it will be disconnected - IdleTimeout int `json:"idle_timeout"` + IdleTimeout int `json:"idle_timeout" mapstructure:"idle_timeout"` // Maximum number of authentication attempts permitted per connection. // If set to a negative number, the number of attempts are unlimited. // If set to zero, the number of attempts are limited to 6. - MaxAuthTries int `json:"max_auth_tries"` + MaxAuthTries int `json:"max_auth_tries" mapstructure:"max_auth_tries"` // Umask for new files - Umask string `json:"umask"` + Umask string `json:"umask" mapstructure:"umask"` // UploadMode 0 means standard, the files are uploaded directly to the requested path. // 1 means atomic: the files are uploaded to a temporary path and renamed to the requested path // when the client ends the upload. Atomic mode avoid problems such as a web server that // serves partial files when the files are being uploaded. - UploadMode int `json:"upload_mode"` + UploadMode int `json:"upload_mode" mapstructure:"upload_mode"` // Actions to execute on SFTP create, download, delete and rename - Actions Actions `json:"actions"` + Actions Actions `json:"actions" mapstructure:"actions"` // Keys are a list of host keys - Keys []Key `json:"keys"` + Keys []Key `json:"keys" mapstructure:"keys"` } // Key contains information about host keys type Key struct { // The private key path relative to the configuration directory or absolute - PrivateKey string `json:"private_key"` + PrivateKey string `json:"private_key" mapstructure:"private_key"` } // Initialize the SFTP server and add a persistent listener to handle inbound SFTP connections. diff --git a/sftpd/sftpd.go b/sftpd/sftpd.go index a7b57ce6..69989d0a 100644 --- a/sftpd/sftpd.go +++ b/sftpd/sftpd.go @@ -64,11 +64,11 @@ type ActiveQuotaScan struct { // An external command can be executed and/or an HTTP notification can be fired type Actions struct { // Valid values are download, upload, delete, rename. Empty slice to disable - ExecuteOn []string `json:"execute_on"` + ExecuteOn []string `json:"execute_on" mapstructure:"execute_on"` // Absolute path to the command to execute, empty to disable - Command string `json:"command"` + Command string `json:"command" mapstructure:"command"` // The URL to notify using an HTTP GET, empty to disable - HTTPNotificationURL string `json:"http_notification_url"` + HTTPNotificationURL string `json:"http_notification_url" mapstructure:"http_notification_url"` } // ConnectionStatus status for an active connection diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index bfeefcaf..fbd82a5f 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -74,19 +74,17 @@ RLFFQ/5nclJSdzPBOmQouC0OBcMFSrYtMeknJ4VvueVvve5HcHFaEsaMc7ABAGaLYaBQOm iixITGvaNZh/tjAAAACW5pY29sYUBwMQE= -----END OPENSSH PRIVATE KEY-----` configDir = ".." - confName = "sftpgo.conf" ) var ( - allPerms = []string{dataprovider.PermAny} - homeBasePath string - configFilePath = filepath.Join(configDir, confName) + allPerms = []string{dataprovider.PermAny} + homeBasePath string ) func TestMain(m *testing.M) { logfilePath := filepath.Join(configDir, "sftpgo_sftpd_test.log") logger.InitLogger(logfilePath, 5, 1, 28, false, zerolog.DebugLevel) - config.LoadConfig(configFilePath) + config.LoadConfig(configDir, "") providerConf := config.GetProviderConf() err := dataprovider.Initialize(providerConf, configDir) @@ -144,7 +142,7 @@ func TestMain(m *testing.M) { } func TestInitialization(t *testing.T) { - config.LoadConfig(configFilePath) + config.LoadConfig(configDir, "") sftpdConf := config.GetSFTPDConfig() sftpdConf.Umask = "invalid umask" err := sftpdConf.Initialize(configDir) diff --git a/sftpgo.conf b/sftpgo.json similarity index 100% rename from sftpgo.conf rename to sftpgo.json diff --git a/utils/utils.go b/utils/utils.go index 227ec5ef..3983cc98 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" "runtime" - "strconv" "time" "github.com/drakkan/sftpgo/logger" @@ -13,6 +12,12 @@ import ( const logSender = "utils" +var ( + version = "dev" + commit = "" + date = "" +) + // IsStringInSlice searches a string in a slice and returns true if the string is found func IsStringInSlice(obj string, list []string) bool { for _, v := range list { @@ -71,22 +76,14 @@ func SetPathPermissions(path string, uid int, gid int) { } } -// GetEnvVar retrieves the value of the environment variable named -// by the key. If the variable is present in the environment the it -// returns the fallback value -func GetEnvVar(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value +// GetAppVersion returns the app version +func GetAppVersion() string { + v := version + if len(commit) > 0 { + v += "-" + commit } - return fallback -} - -// GetEnvVarAsInt retrieves the value of the environment variable named -// by the key and returns its value or fallback -func GetEnvVarAsInt(key string, fallback int) int { - stringValue := GetEnvVar(key, strconv.Itoa(fallback)) - if value, err := strconv.Atoi(stringValue); err == nil { - return value + if len(date) > 0 { + v += "-" + date } - return fallback + return v }