config: create config file in windows appdata directory by default (#5226)

Use %AppData% as primary default for configuration file on Windows,
which is more in line with Windows standards, while existing default
of using home directory is more Unix standards - though that made rclone
more consistent accross different OS.

Fixes #4667
This commit is contained in:
albertony 2021-04-09 20:36:25 +02:00 committed by Ivan Andreev
parent cd69f9e6e8
commit d8711cf7f9
2 changed files with 195 additions and 76 deletions

View file

@ -640,22 +640,51 @@ See `--copy-dest` and `--backup-dir`.
### --config=CONFIG_FILE ### ### --config=CONFIG_FILE ###
Specify the location of the rclone configuration file. Specify the location of the rclone configuration file, to override
the default. E.g. `rclone config --config="rclone.conf"`.
Normally the config file is in your home directory as a file called The exact default is a bit complex to describe, due to changes
`.config/rclone/rclone.conf` (or `.rclone.conf` if created with an introduced through different versions of rclone while preserving
older version). If `$XDG_CONFIG_HOME` is set it will be at backwards compatibility, but in most cases it is as simple as:
`$XDG_CONFIG_HOME/rclone/rclone.conf`.
If there is a file `rclone.conf` in the same directory as the rclone - `%APPDATA%/rclone/rclone.conf` on Windows
executable it will be preferred. This file must be created manually - `~/.config/rclone/rclone.conf` on other
for Rclone to use it, it will never be created automatically.
The complete logic is as follows: Rclone will look for an existing
configuration file in any of the following locations, in priority order:
1. `rclone.conf` (in program directory, where rclone executable is)
2. `%APPDATA%/rclone/rclone.conf` (only on Windows)
3. `$XDG_CONFIG_HOME/rclone/rclone.conf` (on all systems, including Windows)
4. `~/.config/rclone/rclone.conf` (see below for explanation of ~ symbol)
5. `~/.rclone.conf`
If no existing configuration file is found, then a new one will be created
in the following location:
- On Windows: Location 2 listed above, except in the unlikely event
that `APPDATA` is not defined, then location 4 is used instead.
- On Unix: Location 3 if `XDG_CONFIG_HOME` is defined, else location 4.
- Fallback to location 5 (on all OS), when the rclone directory cannot be
created, but if also a home directory was not found then path
`.rclone.conf` relative to current working directory will be used as
a final resort.
The `~` symbol in paths above represent the home directory of the current user
on any OS, and the value is defined as following:
- On Windows: `%HOME%` if defined, else `%USERPROFILE%`, or else `%HOMEDRIVE%\%HOMEPATH%`.
- On Unix: `$HOME` if defined, else by looking up current user in OS-specific user database
(e.g. passwd file), or else use the result from shell command `cd && pwd`.
If you run `rclone config file` you will see where the default If you run `rclone config file` you will see where the default
location is for you. location is for you.
Use this flag to override the config location, e.g. The fact that an existing file `rclone.conf` in the same directory
`rclone config --config="rclone.conf"`. as the rclone executable is always preferred, means that it is easy
to run in "portable" mode by downloading rclone executable to a
writable directory and then create an empty file `rclone.conf` in the
same directory.
If the location is set to empty string `""` or the special value If the location is set to empty string `""` or the special value
`/notfound`, or the os null device represented by value `NUL` on `/notfound`, or the os null device represented by value `NUL` on

View file

@ -126,59 +126,127 @@ func init() {
configPath = makeConfigPath() configPath = makeConfigPath()
} }
// Join directory with filename, and check if exists
func findFile(dir string, name string) string {
path := filepath.Join(dir, name)
if _, err := os.Stat(path); err != nil {
return ""
}
return path
}
// Find current user's home directory
func findHomeDir() (string, error) {
path, err := homedir.Dir()
if err != nil {
fs.Debugf(nil, "Home directory lookup failed and cannot be used as configuration location: %v", err)
} else if path == "" {
// On Unix homedir return success but empty string for user with empty home configured in passwd file
fs.Debugf(nil, "Home directory not defined and cannot be used as configuration location")
}
return path, err
}
// Find rclone executable directory and look for existing rclone.conf there
// (<rclone_exe_dir>/rclone.conf)
func findLocalConfig() (configDir string, configFile string) {
if exePath, err := os.Executable(); err == nil {
configDir = filepath.Dir(exePath)
configFile = findFile(configDir, configFileName)
}
return
}
// Get path to Windows AppData config subdirectory for rclone and look for existing rclone.conf there
// ($AppData/rclone/rclone.conf)
func findAppDataConfig() (configDir string, configFile string) {
if appDataDir := os.Getenv("APPDATA"); appDataDir != "" {
configDir = filepath.Join(appDataDir, "rclone")
configFile = findFile(configDir, configFileName)
} else {
fs.Debugf(nil, "Environment variable APPDATA is not defined and cannot be used as configuration location")
}
return
}
// Get path to XDG config subdirectory for rclone and look for existing rclone.conf there
// (see XDG Base Directory specification: https://specifications.freedesktop.org/basedir-spec/latest/).
// ($XDG_CONFIG_HOME\rclone\rclone.conf)
func findXDGConfig() (configDir string, configFile string) {
if xdgConfigDir := os.Getenv("XDG_CONFIG_HOME"); xdgConfigDir != "" {
configDir = filepath.Join(xdgConfigDir, "rclone")
configFile = findFile(configDir, configFileName)
}
return
}
// Get path to .config subdirectory for rclone and look for existing rclone.conf there
// (~/.config/rclone/rclone.conf)
func findDotConfigConfig(home string) (configDir string, configFile string) {
if home != "" {
configDir = filepath.Join(home, ".config", "rclone")
configFile = findFile(configDir, configFileName)
}
return
}
// Look for existing .rclone.conf (legacy hidden filename) in root of user's home directory
// (~/.rclone.conf)
func findOldHomeConfig(home string) (configDir string, configFile string) {
if home != "" {
configDir = home
configFile = findFile(home, hiddenConfigFileName)
}
return
}
// Return the path to the configuration file // Return the path to the configuration file
func makeConfigPath() string { func makeConfigPath() string {
// Use rclone.conf from rclone executable directory if already existing // Look for existing rclone.conf in prioritized list of known locations
exe, err := os.Executable() // Also get configuration directory to use for new config file when no existing is found.
if err == nil { var (
exedir := filepath.Dir(exe) configFile string
cfgpath := filepath.Join(exedir, configFileName) configDir string
_, err := os.Stat(cfgpath) primaryConfigDir string
if err == nil { fallbackConfigDir string
return cfgpath )
// <rclone_exe_dir>/rclone.conf
if _, configFile = findLocalConfig(); configFile != "" {
return configFile
}
// Windows: $AppData/rclone/rclone.conf
// This is also the default location for new config when no existing is found
if runtime.GOOS == "windows" {
if primaryConfigDir, configFile = findAppDataConfig(); configFile != "" {
return configFile
} }
} }
// $XDG_CONFIG_HOME/rclone/rclone.conf
// Find user's home directory // Also looking for this on Windows, for backwards compatibility reasons.
homeDir, err := homedir.Dir() if configDir, configFile = findXDGConfig(); configFile != "" {
return configFile
// Find user's configuration directory. }
// Prefer XDG config path, with fallback to $HOME/.config. if runtime.GOOS != "windows" {
// See XDG Base Directory specification // On Unix this is also the default location for new config when no existing is found
// https://specifications.freedesktop.org/basedir-spec/latest/), primaryConfigDir = configDir
xdgdir := os.Getenv("XDG_CONFIG_HOME") }
var cfgdir string // ~/.config/rclone/rclone.conf
if xdgdir != "" { // This is also the fallback location for new config
// User's configuration directory for rclone is $XDG_CONFIG_HOME/rclone // (when $AppData on Windows and $XDG_CONFIG_HOME on Unix is not defined)
cfgdir = filepath.Join(xdgdir, "rclone") homeDir, homeDirErr := findHomeDir()
} else if homeDir != "" { if fallbackConfigDir, configFile = findDotConfigConfig(homeDir); configFile != "" {
// User's configuration directory for rclone is $HOME/.config/rclone return configFile
cfgdir = filepath.Join(homeDir, ".config", "rclone") }
// ~/.rclone.conf
if _, configFile = findOldHomeConfig(homeDir); configFile != "" {
return configFile
} }
// Use rclone.conf from user's configuration directory if already existing // No existing config file found, prepare proper default for a new one.
var cfgpath string // But first check if if user supplied a --config variable or environment
if cfgdir != "" { // variable, since then we skip actually trying to create the default
cfgpath = filepath.Join(cfgdir, configFileName) // and report any errors related to it (we can't use pflag for this because
_, err := os.Stat(cfgpath) // it isn't initialised yet so we search the command line manually).
if err == nil {
return cfgpath
}
}
// Use .rclone.conf from user's home directory if already existing
var homeconf string
if homeDir != "" {
homeconf = filepath.Join(homeDir, hiddenConfigFileName)
_, err := os.Stat(homeconf)
if err == nil {
return homeconf
}
}
// Check to see if user supplied a --config variable or environment
// variable. We can't use pflag for this because it isn't initialised
// yet so we search the command line manually.
_, configSupplied := os.LookupEnv("RCLONE_CONFIG") _, configSupplied := os.LookupEnv("RCLONE_CONFIG")
if !configSupplied { if !configSupplied {
for _, item := range os.Args { for _, item := range os.Args {
@ -188,32 +256,54 @@ func makeConfigPath() string {
} }
} }
} }
// If we found a configuration directory to be used for new config during search
// If user's configuration directory was found, then try to create it // above, then create it to be ready for rclone.conf file to be written into it
// and assume rclone.conf can be written there. If user supplied config // later, and also as a test of permissions to use fallback if not even able to
// then skip creating the directory since it will not be used. // create the directory.
if cfgpath != "" { if primaryConfigDir != "" {
// cfgpath != "" implies cfgdir != "" configDir = primaryConfigDir
} else if fallbackConfigDir != "" {
configDir = fallbackConfigDir
} else {
configDir = ""
}
if configDir != "" {
configFile = filepath.Join(configDir, configFileName)
if configSupplied { if configSupplied {
return cfgpath // User supplied custom config option, just return the default path
// as is without creating any directories, since it will not be used
// anyway and we don't want to unnecessarily create empty directory.
return configFile
} }
err := os.MkdirAll(cfgdir, os.ModePerm) var mkdirErr error
if err == nil { if mkdirErr = os.MkdirAll(configDir, os.ModePerm); mkdirErr == nil {
return cfgpath return configFile
}
// Problem: Try a fallback location. If we did find a home directory then
// just assume file .rclone.conf (legacy hidden filename) can be written in
// its root (~/.rclone.conf).
if homeDir != "" {
fs.Debugf(nil, "Configuration directory could not be created and will not be used: %v", mkdirErr)
return filepath.Join(homeDir, hiddenConfigFileName)
}
if !configSupplied {
fs.Errorf(nil, "Couldn't find home directory nor create configuration directory: %v", mkdirErr)
}
} else if !configSupplied {
if homeDirErr != nil {
fs.Errorf(nil, "Couldn't find configuration directory nor home directory: %v", homeDirErr)
} else {
fs.Errorf(nil, "Couldn't find configuration directory nor home directory")
} }
} }
// No known location that can be used: Did possibly find a configDir
// Assume .rclone.conf can be written to user's home directory. // (XDG_CONFIG_HOME or APPDATA) which couldn't be created, but in any case
if homeconf != "" { // did not find a home directory!
return homeconf // Report it as an error, and return as last resort the path relative to current
} // working directory, of .rclone.conf (legacy hidden filename).
// Default to ./.rclone.conf (current working directory) if everything else fails.
if !configSupplied { if !configSupplied {
fs.Errorf(nil, "Couldn't find home directory or read HOME or XDG_CONFIG_HOME environment variables.")
fs.Errorf(nil, "Defaulting to storing config in current directory.") fs.Errorf(nil, "Defaulting to storing config in current directory.")
fs.Errorf(nil, "Use --config flag to workaround.") fs.Errorf(nil, "Use --config flag to workaround.")
fs.Errorf(nil, "Error was: %v", err)
} }
return hiddenConfigFileName return hiddenConfigFileName
} }