Allow to read env vars from files inside the "env.d" directory

This makes it easier to set environment variables on some operating systems.
Setting configuration options from environment variables is recommended if
you want to avoid the time-consuming task of merging your changes with the
default configuration file after upgrading SFTPGo

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-10-13 18:43:58 +02:00
parent 3822b7d3f7
commit 13ee236884
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
11 changed files with 83 additions and 17 deletions

View file

@ -465,6 +465,12 @@ You can select `sha256-simd` setting the environment variable `SFTPGO_MINIO_SHA2
`sha256-simd` is particularly useful if you have an Intel CPU with SHA extensions or an ARM CPU with Cryptography Extensions. `sha256-simd` is particularly useful if you have an Intel CPU with SHA extensions or an ARM CPU with Cryptography Extensions.
The SFTPGo configuration file can change between different versions and merging your custom settings with the default config file may be time-consuming. For this reason we suggest to set your custom settings using environment variables. This eliminates the need to merge your changes with the default configuration file after each update, you have to just check that your configuration key still exists.
Setting configuration options from environment variables is natural in Docker/Kubernetes.
If you install SFTPGo on Linux using the official deb/rpm packages you can set your custom environment variables in the file `/etc/sftpgo/sftpgo.env` (create this file if it does not exist).
SFTPGo also reads files inside the `env.d` directory relative to config dir and then export the valid variables into environment variables if they are not already set. With this method you can override any configuration options, set environment variables for SFTPGo plugins but you cannot set command flags because these files are read after that SFTPGo starts and the config dir must already be set.
Of course you can also set environment variables with the method provided by the operating system of your choice.
</details> </details>
<details><summary><font size=5>Binding to privileged ports</font></summary> <details><summary><font size=5>Binding to privileged ports</font></summary>

2
go.mod
View file

@ -58,6 +58,7 @@ require (
github.com/spf13/viper v1.13.0 github.com/spf13/viper v1.13.0
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
github.com/studio-b12/gowebdav v0.0.0-20221012160928-e70a598e946e github.com/studio-b12/gowebdav v0.0.0-20221012160928-e70a598e946e
github.com/subosito/gotenv v1.4.1
github.com/unrolled/secure v1.13.0 github.com/unrolled/secure v1.13.0
github.com/wagslane/go-password-validator v0.3.0 github.com/wagslane/go-password-validator v0.3.0
github.com/xhit/go-simple-mail/v2 v2.12.0 github.com/xhit/go-simple-mail/v2 v2.12.0
@ -148,7 +149,6 @@ require (
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect

View file

@ -24,6 +24,7 @@ import (
"strings" "strings"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/subosito/gotenv"
"github.com/drakkan/sftpgo/v2/internal/acme" "github.com/drakkan/sftpgo/v2/internal/acme"
"github.com/drakkan/sftpgo/v2/internal/command" "github.com/drakkan/sftpgo/v2/internal/command"
@ -634,6 +635,30 @@ func setConfigFile(configDir, configFile string) {
viper.SetConfigFile(configFile) viper.SetConfigFile(configFile)
} }
// readEnvFiles reads files inside the "env.d" directory relative to configDir
// and then export the valid variables into environment variables if they do
// not exist
func readEnvFiles(configDir string) {
envd := filepath.Join(configDir, "env.d")
entries, err := os.ReadDir(envd)
if err != nil {
logger.Info(logSender, "", "unable to read env files from %q: %v", envd, err)
return
}
for _, entry := range entries {
info, err := entry.Info()
if err == nil && info.Mode().IsRegular() {
envFile := filepath.Join(envd, entry.Name())
err = gotenv.Load(envFile)
if err != nil {
logger.Error(logSender, "", "unable to load env vars from file %q, err: %v", envFile, err)
} else {
logger.Info(logSender, "", "set env vars from file %q", envFile)
}
}
}
}
// LoadConfig loads the configuration // LoadConfig loads the configuration
// configDir will be added to the configuration search paths. // configDir will be added to the configuration search paths.
// The search path contains by default the current directory and on linux it contains // The search path contains by default the current directory and on linux it contains
@ -641,6 +666,7 @@ func setConfigFile(configDir, configFile string) {
// configFile is an absolute or relative path (to the config dir) 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 { func LoadConfig(configDir, configFile string) error {
var err error var err error
readEnvFiles(configDir)
viper.AddConfigPath(configDir) viper.AddConfigPath(configDir)
setViperAdditionalConfigPaths() setViperAdditionalConfigPaths()
viper.AddConfigPath(".") viper.AddConfigPath(".")

View file

@ -89,6 +89,29 @@ func TestLoadConfigFileNotFound(t *testing.T) {
assert.Len(t, mfaConf.TOTP, 1) assert.Len(t, mfaConf.TOTP, 1)
} }
func TestReadEnvFiles(t *testing.T) {
reset()
envd := filepath.Join(configDir, "env.d")
err := os.Mkdir(envd, os.ModePerm)
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(envd, "env1"), []byte("SFTPGO_SFTPD__MAX_AUTH_TRIES = 10"), 0666)
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(envd, "env2"), []byte(`{"invalid env": "value"}`), 0666)
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
assert.Equal(t, 10, config.GetSFTPDConfig().MaxAuthTries)
_, ok := os.LookupEnv("SFTPGO_SFTPD__MAX_AUTH_TRIES")
assert.True(t, ok)
err = os.Unsetenv("SFTPGO_SFTPD__MAX_AUTH_TRIES")
assert.NoError(t, err)
os.RemoveAll(envd)
}
func TestEmptyBanner(t *testing.T) { func TestEmptyBanner(t *testing.T) {
reset() reset()

View file

@ -93,6 +93,9 @@ contents:
- dst: "/var/lib/sftpgo" - dst: "/var/lib/sftpgo"
type: dir type: dir
- dst: "/etc/sftpgo/env.d"
type: dir
overrides: overrides:
deb: deb:
recommends: recommends:

View file

@ -22,6 +22,7 @@ Install-ChocolateyPackage @packageArgs
$DefaultDataPath = Join-Path -Path $ENV:ProgramData -ChildPath "SFTPGo" $DefaultDataPath = Join-Path -Path $ENV:ProgramData -ChildPath "SFTPGo"
$DefaultConfigurationFilePath = Join-Path -Path $DefaultDataPath -ChildPath "sftpgo.json" $DefaultConfigurationFilePath = Join-Path -Path $DefaultDataPath -ChildPath "sftpgo.json"
$EnvDirPath = Join-Path -Path $DefaultDataPath -ChildPath "env.d"
# `t = tab # `t = tab
Write-Output "---------------------------" Write-Output "---------------------------"
@ -38,7 +39,8 @@ Write-Output "Default data location:"
Write-Output "`t$DefaultDataPath" Write-Output "`t$DefaultDataPath"
Write-Output "Default configuration file location:" Write-Output "Default configuration file location:"
Write-Output "`t$DefaultConfigurationFilePath" Write-Output "`t$DefaultConfigurationFilePath"
Write-Output "" Write-Output "Directory to create environment variable files to set configuration options:"
Write-Output "`t$EnvDirPath"
Write-Output "If the SFTPGo service does not start, make sure that TCP ports 2022 and 8080 are" Write-Output "If the SFTPGo service does not start, make sure that TCP ports 2022 and 8080 are"
Write-Output "not used by other services or change the SFTPGo configuration to suit your needs." Write-Output "not used by other services or change the SFTPGo configuration to suit your needs."
Write-Output "" Write-Output ""

View file

@ -24,15 +24,15 @@ if [ "$1" = "configure" ]; then
sftpgo initprovider -c /etc/sftpgo sftpgo initprovider -c /etc/sftpgo
# ensure files and folders have the appropriate permissions # ensure files and folders have the appropriate permissions
chown -R sftpgo:sftpgo /etc/sftpgo /var/lib/sftpgo /srv/sftpgo chown -R sftpgo:sftpgo /etc/sftpgo /var/lib/sftpgo /srv/sftpgo
chmod 750 /etc/sftpgo /var/lib/sftpgo /srv/sftpgo chmod 750 /etc/sftpgo /etc/sftpgo/env.d /var/lib/sftpgo /srv/sftpgo
chmod 640 /etc/sftpgo/sftpgo.json chmod 640 /etc/sftpgo/sftpgo.json
fi fi
# we added /srv/sftpgo after 1.1.0, we should check if we are upgrading # we added /etc/sftpgo/env.d in v2.4.0, we should check if we are upgrading
# from this version but a non-recursive chmod/chown shouldn't hurt # from a previous version but a non-recursive chmod/chown shouldn't hurt
if [ -d /srv/sftpgo ]; then if [ -d /etc/sftpgo/env.d ]; then
chown sftpgo:sftpgo /srv/sftpgo chown sftpgo:sftpgo /etc/sftpgo/env.d
chmod 750 /srv/sftpgo chmod 750 /etc/sftpgo/env.d
fi fi
# set the cap_net_bind_service capability so the service can bind to privileged ports # set the cap_net_bind_service capability so the service can bind to privileged ports

View file

@ -1,2 +1,3 @@
/var/lib/sftpgo /var/lib/sftpgo
/srv/sftpgo /srv/sftpgo
/etc/sftpgo/env.d

View file

@ -24,15 +24,15 @@ if [ "$1" = "configure" ]; then
sftpgo initprovider -c /etc/sftpgo sftpgo initprovider -c /etc/sftpgo
# ensure files and folders have the appropriate permissions # ensure files and folders have the appropriate permissions
chown -R sftpgo:sftpgo /etc/sftpgo /var/lib/sftpgo /srv/sftpgo chown -R sftpgo:sftpgo /etc/sftpgo /var/lib/sftpgo /srv/sftpgo
chmod 750 /etc/sftpgo /var/lib/sftpgo /srv/sftpgo chmod 750 /etc/sftpgo /etc/sftpgo/env.d /var/lib/sftpgo /srv/sftpgo
chmod 640 /etc/sftpgo/sftpgo.json chmod 640 /etc/sftpgo/sftpgo.json
fi fi
# we added /srv/sftpgo after 1.1.0, we should check if we are upgrading # we added /etc/sftpgo/env.d in v2.4.0, we should check if we are upgrading
# from this version but a non-recursive chmod/chown shouldn't hurt # from a previous version but a non-recursive chmod/chown shouldn't hurt
if [ -d /srv/sftpgo ]; then if [ -d /etc/sftpgo/env.d ]; then
chown sftpgo:sftpgo /srv/sftpgo chown sftpgo:sftpgo /etc/sftpgo/env.d
chmod 750 /srv/sftpgo chmod 750 /etc/sftpgo/env.d
fi fi
# set the cap_net_bind_service capability so the service can bind to privileged ports # set the cap_net_bind_service capability so the service can bind to privileged ports

View file

@ -17,16 +17,21 @@ if [ $1 -eq 1 ]; then
/usr/bin/sftpgo initprovider -c /etc/sftpgo /usr/bin/sftpgo initprovider -c /etc/sftpgo
# ensure files and folders have the appropriate permissions # ensure files and folders have the appropriate permissions
/usr/bin/chown -R sftpgo:sftpgo /etc/sftpgo /var/lib/sftpgo /srv/sftpgo /usr/bin/chown -R sftpgo:sftpgo /etc/sftpgo /var/lib/sftpgo /srv/sftpgo
/usr/bin/chmod 750 /etc/sftpgo /var/lib/sftpgo /srv/sftpgo /usr/bin/chmod 750 /etc/sftpgo /etc/sftpgo/env.d /var/lib/sftpgo /srv/sftpgo
/usr/bin/chmod 640 /etc/sftpgo/sftpgo.json /usr/bin/chmod 640 /etc/sftpgo/sftpgo.json
fi fi
# adjust permissions for /srv/sftpgo and /var/lib/sftpgo # adjust permissions for /srv/sftpgo, /etc/sftpgo/env.d and /var/lib/sftpgo
if [ -d /srv/sftpgo ]; then if [ -d /srv/sftpgo ]; then
/usr/bin/chown sftpgo:sftpgo /srv/sftpgo /usr/bin/chown sftpgo:sftpgo /srv/sftpgo
/usr/bin/chmod 750 /srv/sftpgo /usr/bin/chmod 750 /srv/sftpgo
fi fi
if [ -d /etc/sftpgo/env.d ]; then
/usr/bin/chown sftpgo:sftpgo /etc/sftpgo/env.d
/usr/bin/chmod 750 /etc/sftpgo/env.d
fi
if [ -d /var/lib/sftpgo ]; then if [ -d /var/lib/sftpgo ]; then
/usr/bin/chown sftpgo:sftpgo /var/lib/sftpgo /usr/bin/chown sftpgo:sftpgo /var/lib/sftpgo
/usr/bin/chmod 750 /var/lib/sftpgo /usr/bin/chmod 750 /var/lib/sftpgo

View file

@ -73,7 +73,7 @@ Source: "README.txt"; DestDir: "{app}"; Flags: ignoreversion isreadme
[Dirs] [Dirs]
Name: "{commonappdata}\{#MyAppName}\logs"; Permissions: everyone-full Name: "{commonappdata}\{#MyAppName}\logs"; Permissions: everyone-full
Name: "{commonappdata}\{#MyAppName}\backups"; Permissions: everyone-full Name: "{commonappdata}\{#MyAppName}\backups"; Permissions: everyone-full
Name: "{commonappdata}\{#MyAppName}\credentials"; Permissions: everyone-full Name: "{commonappdata}\{#MyAppName}\env.d"; Permissions: everyone-full
Name: "{commonappdata}\{#MyAppName}\certs"; Permissions: everyone-full Name: "{commonappdata}\{#MyAppName}\certs"; Permissions: everyone-full
[Icons] [Icons]