2018-01-13 00:30:54 +08:00
|
|
|
// Package config reads, writes and edits the config file and deals with command line flags
|
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
2020-11-06 00:59:59 +08:00
|
|
|
"context"
|
2018-01-13 00:30:54 +08:00
|
|
|
"encoding/json"
|
2021-11-04 18:12:57 +08:00
|
|
|
"errors"
|
2018-01-13 00:30:54 +08:00
|
|
|
"fmt"
|
2018-02-20 01:59:27 +08:00
|
|
|
mathrand "math/rand"
|
2018-01-13 00:30:54 +08:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
2018-02-20 01:59:27 +08:00
|
|
|
"time"
|
2018-01-13 00:30:54 +08:00
|
|
|
|
2020-11-08 05:05:58 +08:00
|
|
|
"github.com/mitchellh/go-homedir"
|
|
|
|
|
2019-07-29 01:47:38 +08:00
|
|
|
"github.com/rclone/rclone/fs"
|
2020-11-30 19:53:11 +08:00
|
|
|
"github.com/rclone/rclone/fs/cache"
|
2021-05-09 23:03:18 +08:00
|
|
|
"github.com/rclone/rclone/fs/config/configmap"
|
2019-07-29 01:47:38 +08:00
|
|
|
"github.com/rclone/rclone/fs/config/obscure"
|
|
|
|
"github.com/rclone/rclone/fs/fspath"
|
|
|
|
"github.com/rclone/rclone/fs/rc"
|
2021-04-09 02:59:15 +08:00
|
|
|
"github.com/rclone/rclone/lib/file"
|
2019-08-25 15:39:31 +08:00
|
|
|
"github.com/rclone/rclone/lib/random"
|
2018-01-13 00:30:54 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
configFileName = "rclone.conf"
|
|
|
|
hiddenConfigFileName = "." + configFileName
|
2021-04-18 06:09:03 +08:00
|
|
|
noConfigFile = "notfound"
|
2018-01-13 00:30:54 +08:00
|
|
|
|
|
|
|
// ConfigToken is the key used to store the token under
|
|
|
|
ConfigToken = "token"
|
|
|
|
|
|
|
|
// ConfigClientID is the config key used to store the client id
|
|
|
|
ConfigClientID = "client_id"
|
|
|
|
|
|
|
|
// ConfigClientSecret is the config key used to store the client secret
|
|
|
|
ConfigClientSecret = "client_secret"
|
|
|
|
|
|
|
|
// ConfigAuthURL is the config key used to store the auth server endpoint
|
|
|
|
ConfigAuthURL = "auth_url"
|
|
|
|
|
|
|
|
// ConfigTokenURL is the config key used to store the token server endpoint
|
|
|
|
ConfigTokenURL = "token_url"
|
|
|
|
|
2022-07-26 14:28:37 +08:00
|
|
|
// ConfigClientCredentials - use OAUTH2 client credentials
|
|
|
|
ConfigClientCredentials = "client_credentials"
|
|
|
|
|
2020-01-15 01:33:35 +08:00
|
|
|
// ConfigEncoding is the config key to change the encoding for a backend
|
|
|
|
ConfigEncoding = "encoding"
|
|
|
|
|
|
|
|
// ConfigEncodingHelp is the help for ConfigEncoding
|
2021-09-12 03:10:46 +08:00
|
|
|
ConfigEncodingHelp = "The encoding for the backend.\n\nSee the [encoding section in the overview](/overview/#encoding) for more info."
|
2020-01-15 01:33:35 +08:00
|
|
|
|
2019-01-17 23:01:13 +08:00
|
|
|
// ConfigAuthorize indicates that we just want "rclone authorize"
|
|
|
|
ConfigAuthorize = "config_authorize"
|
2019-10-27 03:19:22 +08:00
|
|
|
|
|
|
|
// ConfigAuthNoBrowser indicates that we do not want to open browser
|
|
|
|
ConfigAuthNoBrowser = "config_auth_no_browser"
|
2023-02-24 23:08:38 +08:00
|
|
|
|
|
|
|
// ConfigTemplate is the template content to be used in the authorization webserver
|
|
|
|
ConfigTemplate = "config_template"
|
|
|
|
|
|
|
|
// ConfigTemplateFile is the path to a template file to read into the value of `ConfigTemplate` above
|
|
|
|
ConfigTemplateFile = "config_template_file"
|
2018-01-13 00:30:54 +08:00
|
|
|
)
|
|
|
|
|
2021-03-10 23:40:34 +08:00
|
|
|
// Storage defines an interface for loading and saving config to
|
|
|
|
// persistent storage. Rclone provides a default implementation to
|
|
|
|
// load and save to a config file when this is imported
|
|
|
|
//
|
|
|
|
// import "github.com/rclone/rclone/fs/config/configfile"
|
2021-04-27 05:37:49 +08:00
|
|
|
// configfile.Install()
|
2020-11-08 05:05:58 +08:00
|
|
|
type Storage interface {
|
|
|
|
// GetSectionList returns a slice of strings with names for all the
|
|
|
|
// sections
|
|
|
|
GetSectionList() []string
|
|
|
|
|
|
|
|
// HasSection returns true if section exists in the config file
|
|
|
|
HasSection(section string) bool
|
|
|
|
|
|
|
|
// DeleteSection removes the named section and all config from the
|
|
|
|
// config file
|
|
|
|
DeleteSection(section string)
|
|
|
|
|
|
|
|
// GetKeyList returns the keys in this section
|
|
|
|
GetKeyList(section string) []string
|
|
|
|
|
2021-03-10 23:40:34 +08:00
|
|
|
// GetValue returns the key in section with a found flag
|
|
|
|
GetValue(section string, key string) (value string, found bool)
|
2020-11-08 05:05:58 +08:00
|
|
|
|
|
|
|
// SetValue sets the value under key in section
|
|
|
|
SetValue(section string, key string, value string)
|
|
|
|
|
|
|
|
// DeleteKey removes the key under section
|
|
|
|
DeleteKey(section string, key string) bool
|
|
|
|
|
|
|
|
// Load the config from permanent storage
|
|
|
|
Load() error
|
|
|
|
|
|
|
|
// Save the config to permanent storage
|
|
|
|
Save() error
|
|
|
|
|
|
|
|
// Serialize the config into a string
|
|
|
|
Serialize() (string, error)
|
|
|
|
}
|
|
|
|
|
2018-01-13 00:30:54 +08:00
|
|
|
// Global
|
|
|
|
var (
|
2019-08-28 18:46:35 +08:00
|
|
|
// Password can be used to configure the random password generator
|
|
|
|
Password = random.Password
|
2018-01-13 00:30:54 +08:00
|
|
|
)
|
|
|
|
|
2021-04-27 05:37:49 +08:00
|
|
|
var (
|
|
|
|
configPath string
|
2021-09-10 21:35:53 +08:00
|
|
|
cacheDir string
|
2021-04-27 05:37:49 +08:00
|
|
|
data Storage
|
|
|
|
dataLoaded bool
|
|
|
|
)
|
2021-04-08 23:49:47 +08:00
|
|
|
|
2018-01-13 00:30:54 +08:00
|
|
|
func init() {
|
2018-05-15 01:06:57 +08:00
|
|
|
// Set the function pointers up in fs
|
2023-11-04 22:34:23 +08:00
|
|
|
fs.ConfigFileGet = FileGetValue
|
2019-08-15 00:06:13 +08:00
|
|
|
fs.ConfigFileSet = SetValueAndSave
|
2022-09-13 03:43:53 +08:00
|
|
|
fs.ConfigFileHasSection = func(section string) bool {
|
|
|
|
return LoadedData().HasSection(section)
|
|
|
|
}
|
2021-04-08 23:49:47 +08:00
|
|
|
configPath = makeConfigPath()
|
2021-09-10 21:35:53 +08:00
|
|
|
cacheDir = makeCacheDir() // Has fallback to tempDir, so set that first
|
2021-05-04 05:01:06 +08:00
|
|
|
data = newDefaultStorage()
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
|
2021-04-10 02:36:25 +08:00
|
|
|
// 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 ""
|
2019-06-04 03:14:32 +08:00
|
|
|
}
|
2021-04-10 02:36:25 +08:00
|
|
|
return path
|
|
|
|
}
|
2019-06-04 03:14:32 +08:00
|
|
|
|
2021-04-10 02:36:25 +08:00
|
|
|
// 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)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
2021-04-10 02:36:25 +08:00
|
|
|
return
|
|
|
|
}
|
2018-01-13 00:30:54 +08:00
|
|
|
|
2021-04-10 02:36:25 +08:00
|
|
|
// Return the path to the configuration file
|
|
|
|
func makeConfigPath() string {
|
|
|
|
// Look for existing rclone.conf in prioritized list of known locations
|
|
|
|
// Also get configuration directory to use for new config file when no existing is found.
|
|
|
|
var (
|
|
|
|
configFile string
|
|
|
|
configDir string
|
|
|
|
primaryConfigDir string
|
|
|
|
fallbackConfigDir string
|
|
|
|
)
|
|
|
|
// <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
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
}
|
2021-04-10 02:36:25 +08:00
|
|
|
// $XDG_CONFIG_HOME/rclone/rclone.conf
|
|
|
|
// Also looking for this on Windows, for backwards compatibility reasons.
|
|
|
|
if configDir, configFile = findXDGConfig(); configFile != "" {
|
|
|
|
return configFile
|
|
|
|
}
|
|
|
|
if runtime.GOOS != "windows" {
|
|
|
|
// On Unix this is also the default location for new config when no existing is found
|
|
|
|
primaryConfigDir = configDir
|
|
|
|
}
|
|
|
|
// ~/.config/rclone/rclone.conf
|
|
|
|
// This is also the fallback location for new config
|
|
|
|
// (when $AppData on Windows and $XDG_CONFIG_HOME on Unix is not defined)
|
|
|
|
homeDir, homeDirErr := findHomeDir()
|
|
|
|
if fallbackConfigDir, configFile = findDotConfigConfig(homeDir); configFile != "" {
|
|
|
|
return configFile
|
|
|
|
}
|
|
|
|
// ~/.rclone.conf
|
|
|
|
if _, configFile = findOldHomeConfig(homeDir); configFile != "" {
|
|
|
|
return configFile
|
|
|
|
}
|
2018-01-13 00:30:54 +08:00
|
|
|
|
2021-04-10 02:36:25 +08:00
|
|
|
// No existing config file found, prepare proper default for a new one.
|
2024-03-14 01:12:39 +08:00
|
|
|
// But first check if user supplied a --config variable or environment
|
2021-04-10 02:36:25 +08:00
|
|
|
// variable, since then we skip actually trying to create the default
|
|
|
|
// and report any errors related to it (we can't use pflag for this because
|
|
|
|
// it isn't initialised yet so we search the command line manually).
|
2018-09-03 22:30:53 +08:00
|
|
|
_, configSupplied := os.LookupEnv("RCLONE_CONFIG")
|
|
|
|
if !configSupplied {
|
|
|
|
for _, item := range os.Args {
|
|
|
|
if item == "--config" || strings.HasPrefix(item, "--config=") {
|
|
|
|
configSupplied = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-04-10 02:36:25 +08:00
|
|
|
// If we found a configuration directory to be used for new config during search
|
|
|
|
// above, then create it to be ready for rclone.conf file to be written into it
|
|
|
|
// later, and also as a test of permissions to use fallback if not even able to
|
|
|
|
// create the directory.
|
|
|
|
if primaryConfigDir != "" {
|
|
|
|
configDir = primaryConfigDir
|
|
|
|
} else if fallbackConfigDir != "" {
|
|
|
|
configDir = fallbackConfigDir
|
|
|
|
} else {
|
|
|
|
configDir = ""
|
|
|
|
}
|
|
|
|
if configDir != "" {
|
|
|
|
configFile = filepath.Join(configDir, configFileName)
|
2018-09-03 22:30:53 +08:00
|
|
|
if configSupplied {
|
2021-04-10 02:36:25 +08:00
|
|
|
// 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
|
2018-09-03 22:30:53 +08:00
|
|
|
}
|
2021-04-10 02:36:25 +08:00
|
|
|
var mkdirErr error
|
2021-06-11 06:46:36 +08:00
|
|
|
if mkdirErr = file.MkdirAll(configDir, os.ModePerm); mkdirErr == nil {
|
2021-04-10 02:36:25 +08:00
|
|
|
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")
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
}
|
2021-04-10 02:36:25 +08:00
|
|
|
// No known location that can be used: Did possibly find a configDir
|
|
|
|
// (XDG_CONFIG_HOME or APPDATA) which couldn't be created, but in any case
|
|
|
|
// did not find a home directory!
|
|
|
|
// Report it as an error, and return as last resort the path relative to current
|
|
|
|
// working directory, of .rclone.conf (legacy hidden filename).
|
2018-07-15 19:39:11 +08:00
|
|
|
if !configSupplied {
|
|
|
|
fs.Errorf(nil, "Defaulting to storing config in current directory.")
|
|
|
|
fs.Errorf(nil, "Use --config flag to workaround.")
|
|
|
|
}
|
2018-01-13 00:30:54 +08:00
|
|
|
return hiddenConfigFileName
|
|
|
|
}
|
|
|
|
|
2021-04-08 23:49:47 +08:00
|
|
|
// GetConfigPath returns the current config file path
|
|
|
|
func GetConfigPath() string {
|
|
|
|
return configPath
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetConfigPath sets new config file path
|
|
|
|
//
|
|
|
|
// Checks for empty string, os null device, or special path, all of which indicates in-memory config.
|
|
|
|
func SetConfigPath(path string) (err error) {
|
2021-04-09 02:59:15 +08:00
|
|
|
var cfgPath string
|
2021-04-08 23:49:47 +08:00
|
|
|
if path == "" || path == os.DevNull {
|
2021-04-09 02:59:15 +08:00
|
|
|
cfgPath = ""
|
2021-04-18 06:09:03 +08:00
|
|
|
} else if filepath.Base(path) == noConfigFile {
|
|
|
|
cfgPath = ""
|
2021-04-09 02:59:15 +08:00
|
|
|
} else if err = file.IsReserved(path); err != nil {
|
|
|
|
return err
|
2021-04-18 06:09:03 +08:00
|
|
|
} else if cfgPath, err = filepath.Abs(path); err != nil {
|
|
|
|
return err
|
2021-04-08 23:49:47 +08:00
|
|
|
}
|
2021-04-09 02:59:15 +08:00
|
|
|
configPath = cfgPath
|
2021-04-08 23:49:47 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-27 05:37:49 +08:00
|
|
|
// SetData sets new config file storage
|
|
|
|
func SetData(newData Storage) {
|
2021-07-14 00:41:28 +08:00
|
|
|
// If no config file, use in-memory config (which is the default)
|
|
|
|
if configPath == "" {
|
|
|
|
return
|
|
|
|
}
|
2021-04-27 05:37:49 +08:00
|
|
|
data = newData
|
|
|
|
dataLoaded = false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Data returns current config file storage
|
|
|
|
func Data() Storage {
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
2023-11-04 22:34:23 +08:00
|
|
|
// ErrorConfigFileNotFound is returned when the config file is not found
|
|
|
|
var ErrorConfigFileNotFound = errors.New("config file not found")
|
|
|
|
|
2021-04-27 05:37:49 +08:00
|
|
|
// LoadedData ensures the config file storage is loaded and returns it
|
|
|
|
func LoadedData() Storage {
|
|
|
|
if !dataLoaded {
|
|
|
|
// Set RCLONE_CONFIG_DIR for backend config and subprocesses
|
|
|
|
// If empty configPath (in-memory only) the value will be "."
|
|
|
|
_ = os.Setenv("RCLONE_CONFIG_DIR", filepath.Dir(configPath))
|
|
|
|
// Load configuration from file (or initialize sensible default if no file or error)
|
|
|
|
if err := data.Load(); err == nil {
|
|
|
|
fs.Debugf(nil, "Using config file from %q", configPath)
|
|
|
|
dataLoaded = true
|
|
|
|
} else if err == ErrorConfigFileNotFound {
|
|
|
|
if configPath == "" {
|
|
|
|
fs.Debugf(nil, "Config is memory-only - using defaults")
|
|
|
|
} else {
|
|
|
|
fs.Logf(nil, "Config file %q not found - using defaults", configPath)
|
|
|
|
}
|
|
|
|
dataLoaded = true
|
2021-04-08 23:49:47 +08:00
|
|
|
} else {
|
2024-08-18 22:58:35 +08:00
|
|
|
fs.Fatalf(nil, "Failed to load config file %q: %v", configPath, err)
|
2021-04-08 23:49:47 +08:00
|
|
|
}
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
2021-04-27 05:37:49 +08:00
|
|
|
return data
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
|
2018-02-20 01:59:27 +08:00
|
|
|
// SaveConfig calling function which saves configuration file.
|
2021-03-10 22:13:01 +08:00
|
|
|
// if SaveConfig returns error trying again after sleep.
|
2018-02-20 01:59:27 +08:00
|
|
|
func SaveConfig() {
|
2020-11-05 19:33:32 +08:00
|
|
|
ctx := context.Background()
|
|
|
|
ci := fs.GetConfig(ctx)
|
2018-02-20 01:59:27 +08:00
|
|
|
var err error
|
2020-11-05 19:33:32 +08:00
|
|
|
for i := 0; i < ci.LowLevelRetries+1; i++ {
|
2021-04-27 05:37:49 +08:00
|
|
|
if err = LoadedData().Save(); err == nil {
|
2018-02-20 01:59:27 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
waitingTimeMs := mathrand.Intn(1000)
|
|
|
|
time.Sleep(time.Duration(waitingTimeMs) * time.Millisecond)
|
|
|
|
}
|
2021-03-13 20:36:38 +08:00
|
|
|
fs.Errorf(nil, "Failed to save config after %d tries: %v", ci.LowLevelRetries, err)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
|
2023-11-04 22:34:23 +08:00
|
|
|
// FileSections returns the sections in the config file
|
|
|
|
func FileSections() []string {
|
|
|
|
return LoadedData().GetSectionList()
|
|
|
|
}
|
|
|
|
|
|
|
|
// FileGetValue gets the config key under section returning the
|
|
|
|
// the value and true if found and or ("", false) otherwise
|
|
|
|
func FileGetValue(section, key string) (string, bool) {
|
|
|
|
return LoadedData().GetValue(section, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FileSetValue sets the key in section to value.
|
|
|
|
// It doesn't save the config file.
|
|
|
|
func FileSetValue(section, key, value string) {
|
|
|
|
LoadedData().SetValue(section, key, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FileDeleteKey deletes the config key in the config file.
|
|
|
|
// It returns true if the key was deleted,
|
|
|
|
// or returns false if the section or key didn't exist.
|
|
|
|
func FileDeleteKey(section, key string) bool {
|
|
|
|
return LoadedData().DeleteKey(section, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValue gets the value for a config key from environment
|
|
|
|
// or config file under section returning the default if not set.
|
|
|
|
//
|
|
|
|
// Emulates the preference documented and normally used by rclone via
|
|
|
|
// configmap, which means environment variables before config file.
|
|
|
|
func GetValue(remote, key string) string {
|
|
|
|
envKey := fs.ConfigToEnv(remote, key)
|
|
|
|
value, found := os.LookupEnv(envKey)
|
|
|
|
if found {
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
value, _ = LoadedData().GetValue(remote, key)
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
2018-01-13 00:30:54 +08:00
|
|
|
// SetValueAndSave sets the key to the value and saves just that
|
|
|
|
// value in the config file. It loads the old config file in from
|
|
|
|
// disk first and overwrites the given value only.
|
2023-11-04 22:34:23 +08:00
|
|
|
func SetValueAndSave(remote, key, value string) error {
|
2018-01-13 00:30:54 +08:00
|
|
|
// Set the value in config in case we fail to reload it
|
2023-11-04 22:34:23 +08:00
|
|
|
FileSetValue(remote, key, value)
|
2018-01-13 00:30:54 +08:00
|
|
|
// Save it again
|
|
|
|
SaveConfig()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-11-04 22:49:15 +08:00
|
|
|
// Remote defines a remote with a name, type, source and description
|
2023-11-04 22:34:23 +08:00
|
|
|
type Remote struct {
|
2023-11-04 22:49:15 +08:00
|
|
|
Name string `json:"name"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Source string `json:"source"`
|
|
|
|
Description string `json:"description"`
|
2023-11-04 22:34:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
var remoteEnvRe = regexp.MustCompile(`^RCLONE_CONFIG_(.+?)_TYPE=(.+)$`)
|
|
|
|
|
|
|
|
// GetRemotes returns the list of remotes defined in environment and config file.
|
|
|
|
//
|
|
|
|
// Emulates the preference documented and normally used by rclone via
|
|
|
|
// configmap, which means environment variables before config file.
|
|
|
|
func GetRemotes() []Remote {
|
|
|
|
var remotes []Remote
|
|
|
|
for _, item := range os.Environ() {
|
|
|
|
matches := remoteEnvRe.FindStringSubmatch(item)
|
|
|
|
if len(matches) == 3 {
|
|
|
|
remotes = append(remotes, Remote{
|
|
|
|
Name: strings.ToLower(matches[1]),
|
|
|
|
Type: strings.ToLower(matches[2]),
|
|
|
|
Source: "environment",
|
|
|
|
})
|
|
|
|
}
|
2021-03-10 23:40:34 +08:00
|
|
|
}
|
2023-11-04 22:34:23 +08:00
|
|
|
remoteExists := func(name string) bool {
|
|
|
|
for _, remote := range remotes {
|
|
|
|
if name == remote.Name {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
sections := LoadedData().GetSectionList()
|
|
|
|
for _, section := range sections {
|
|
|
|
if !remoteExists(section) {
|
|
|
|
typeValue, found := LoadedData().GetValue(section, "type")
|
|
|
|
if found {
|
2023-11-04 22:49:15 +08:00
|
|
|
description, _ := LoadedData().GetValue(section, "description")
|
2023-11-04 22:34:23 +08:00
|
|
|
remotes = append(remotes, Remote{
|
2023-11-04 22:49:15 +08:00
|
|
|
Name: section,
|
|
|
|
Type: typeValue,
|
|
|
|
Source: "file",
|
|
|
|
Description: description,
|
2023-11-04 22:34:23 +08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return remotes
|
2021-03-10 23:40:34 +08:00
|
|
|
}
|
|
|
|
|
2023-11-04 09:48:42 +08:00
|
|
|
// GetRemoteNames returns the names of remotes defined in environment and config file.
|
|
|
|
func GetRemoteNames() []string {
|
|
|
|
remotes := GetRemotes()
|
|
|
|
var remoteNames []string
|
|
|
|
for _, remote := range remotes {
|
|
|
|
remoteNames = append(remoteNames, remote.Name)
|
|
|
|
}
|
|
|
|
return remoteNames
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:45:30 +08:00
|
|
|
// UpdateRemoteOpt configures the remote update
|
|
|
|
type UpdateRemoteOpt struct {
|
|
|
|
// Treat all passwords as plain that need obscuring
|
|
|
|
Obscure bool `json:"obscure"`
|
|
|
|
// Treat all passwords as obscured
|
|
|
|
NoObscure bool `json:"noObscure"`
|
|
|
|
// Don't interact with the user - return questions
|
|
|
|
NonInteractive bool `json:"nonInteractive"`
|
|
|
|
// If set then supply state and result parameters to continue the process
|
|
|
|
Continue bool `json:"continue"`
|
2021-05-09 23:03:18 +08:00
|
|
|
// If set then ask all the questions, not just the post config questions
|
|
|
|
All bool `json:"all"`
|
2021-05-11 20:36:34 +08:00
|
|
|
// State to restart with - used with Continue
|
|
|
|
State string `json:"state"`
|
|
|
|
// Result to return - used with Continue
|
|
|
|
Result string `json:"result"`
|
2021-05-11 21:57:37 +08:00
|
|
|
// If set then edit existing values
|
|
|
|
Edit bool `json:"edit"`
|
2021-05-05 18:45:30 +08:00
|
|
|
}
|
|
|
|
|
2021-05-11 21:57:37 +08:00
|
|
|
func updateRemote(ctx context.Context, name string, keyValues rc.Params, opt UpdateRemoteOpt) (out *fs.ConfigOut, err error) {
|
2021-05-05 18:45:30 +08:00
|
|
|
if opt.Obscure && opt.NoObscure {
|
|
|
|
return nil, errors.New("can't use --obscure and --no-obscure together")
|
2020-05-12 21:24:53 +08:00
|
|
|
}
|
2021-05-11 21:57:37 +08:00
|
|
|
|
2021-05-05 18:45:30 +08:00
|
|
|
err = fspath.CheckConfigName(name)
|
2019-09-05 18:01:04 +08:00
|
|
|
if err != nil {
|
2021-05-05 18:45:30 +08:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
interactive := !(opt.NonInteractive || opt.Continue)
|
2021-05-09 23:03:18 +08:00
|
|
|
if interactive && !opt.All {
|
2021-05-05 18:45:30 +08:00
|
|
|
ctx = suppressConfirm(ctx)
|
2019-09-05 18:01:04 +08:00
|
|
|
}
|
2019-06-11 00:58:47 +08:00
|
|
|
|
2023-11-04 22:34:23 +08:00
|
|
|
fsType := GetValue(name, "type")
|
2021-05-05 18:45:30 +08:00
|
|
|
if fsType == "" {
|
|
|
|
return nil, errors.New("couldn't find type field in config")
|
|
|
|
}
|
|
|
|
|
|
|
|
ri, err := fs.Find(fsType)
|
|
|
|
if err != nil {
|
2021-11-04 18:12:57 +08:00
|
|
|
return nil, fmt.Errorf("couldn't find backend for type %q", fsType)
|
2021-05-05 18:45:30 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 23:03:18 +08:00
|
|
|
// Work out which options need to be obscured
|
|
|
|
needsObscure := map[string]struct{}{}
|
|
|
|
if !opt.NoObscure {
|
|
|
|
for _, option := range ri.Options {
|
|
|
|
if option.IsPassword {
|
|
|
|
needsObscure[option.Name] = struct{}{}
|
2021-05-05 18:45:30 +08:00
|
|
|
}
|
|
|
|
}
|
2021-05-09 23:03:18 +08:00
|
|
|
}
|
2021-05-05 18:45:30 +08:00
|
|
|
|
2021-05-09 23:03:18 +08:00
|
|
|
choices := configmap.Simple{}
|
2024-07-02 01:06:49 +08:00
|
|
|
m := fs.ConfigMap(ri.Prefix, ri.Options, name, nil)
|
2021-05-09 23:03:18 +08:00
|
|
|
|
|
|
|
// Set the config
|
|
|
|
for k, v := range keyValues {
|
|
|
|
vStr := fmt.Sprint(v)
|
2023-04-27 19:46:43 +08:00
|
|
|
if strings.ContainsAny(k, "\n\r") || strings.ContainsAny(vStr, "\n\r") {
|
|
|
|
return nil, fmt.Errorf("update remote: invalid key or value contains \\n or \\r")
|
|
|
|
}
|
2021-05-09 23:03:18 +08:00
|
|
|
// Obscure parameter if necessary
|
|
|
|
if _, ok := needsObscure[k]; ok {
|
|
|
|
_, err := obscure.Reveal(vStr)
|
|
|
|
if err != nil || opt.Obscure {
|
|
|
|
// If error => not already obscured, so obscure it
|
|
|
|
// or we are forced to obscure
|
|
|
|
vStr, err = obscure.Obscure(vStr)
|
|
|
|
if err != nil {
|
2023-04-27 19:46:43 +08:00
|
|
|
return nil, fmt.Errorf("update remote: obscure failed: %w", err)
|
2019-06-11 00:58:47 +08:00
|
|
|
}
|
|
|
|
}
|
2021-05-09 23:03:18 +08:00
|
|
|
}
|
|
|
|
choices.Set(k, vStr)
|
|
|
|
if !strings.HasPrefix(k, fs.ConfigKeyEphemeralPrefix) {
|
|
|
|
m.Set(k, vStr)
|
2019-06-11 00:58:47 +08:00
|
|
|
}
|
|
|
|
}
|
2021-05-11 21:57:37 +08:00
|
|
|
if opt.Edit {
|
|
|
|
choices[fs.ConfigEdit] = "true"
|
|
|
|
}
|
2019-06-11 00:58:47 +08:00
|
|
|
|
2021-05-05 18:45:30 +08:00
|
|
|
if interactive {
|
2021-05-09 23:03:18 +08:00
|
|
|
var state = ""
|
|
|
|
if opt.All {
|
|
|
|
state = fs.ConfigAll
|
|
|
|
}
|
|
|
|
err = backendConfig(ctx, name, m, ri, choices, state)
|
2021-05-05 18:45:30 +08:00
|
|
|
} else {
|
|
|
|
// Start the config state machine
|
2021-05-11 20:36:34 +08:00
|
|
|
in := fs.ConfigIn{
|
|
|
|
State: opt.State,
|
|
|
|
Result: opt.Result,
|
2019-06-11 00:58:47 +08:00
|
|
|
}
|
2021-05-09 23:03:18 +08:00
|
|
|
if in.State == "" && opt.All {
|
|
|
|
in.State = fs.ConfigAll
|
|
|
|
}
|
|
|
|
out, err = fs.BackendConfig(ctx, name, m, ri, choices, in)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
2021-04-07 04:27:34 +08:00
|
|
|
if err != nil {
|
2021-05-05 18:45:30 +08:00
|
|
|
return nil, err
|
2021-04-07 04:27:34 +08:00
|
|
|
}
|
2018-01-13 00:30:54 +08:00
|
|
|
SaveConfig()
|
2020-11-30 19:53:11 +08:00
|
|
|
cache.ClearConfig(name) // remove any remotes based on this config from the cache
|
2021-05-05 18:45:30 +08:00
|
|
|
return out, nil
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
|
2021-05-11 21:57:37 +08:00
|
|
|
// UpdateRemote adds the keyValues passed in to the remote of name.
|
|
|
|
// keyValues should be key, value pairs.
|
|
|
|
func UpdateRemote(ctx context.Context, name string, keyValues rc.Params, opt UpdateRemoteOpt) (out *fs.ConfigOut, err error) {
|
|
|
|
opt.Edit = true
|
|
|
|
return updateRemote(ctx, name, keyValues, opt)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateRemote creates a new remote with name, type and a list of
|
2018-01-13 00:30:54 +08:00
|
|
|
// parameters which are key, value pairs. If update is set then it
|
|
|
|
// adds the new keys rather than replacing all of them.
|
2021-05-11 21:57:37 +08:00
|
|
|
func CreateRemote(ctx context.Context, name string, Type string, keyValues rc.Params, opts UpdateRemoteOpt) (out *fs.ConfigOut, err error) {
|
2021-05-05 18:45:30 +08:00
|
|
|
err = fspath.CheckConfigName(name)
|
2019-09-05 18:01:04 +08:00
|
|
|
if err != nil {
|
2021-05-05 18:45:30 +08:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !opts.Continue {
|
|
|
|
// Delete the old config if it exists
|
|
|
|
LoadedData().DeleteSection(name)
|
|
|
|
// Set the type
|
2021-05-11 21:57:37 +08:00
|
|
|
LoadedData().SetValue(name, "type", Type)
|
2019-09-05 18:01:04 +08:00
|
|
|
}
|
2018-01-13 00:30:54 +08:00
|
|
|
// Set the remaining values
|
2021-05-05 18:45:30 +08:00
|
|
|
return UpdateRemote(ctx, name, keyValues, opts)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// PasswordRemote adds the keyValues passed in to the remote of name.
|
|
|
|
// keyValues should be key, value pairs.
|
2020-11-06 02:02:26 +08:00
|
|
|
func PasswordRemote(ctx context.Context, name string, keyValues rc.Params) error {
|
2020-11-05 19:33:32 +08:00
|
|
|
ctx = suppressConfirm(ctx)
|
2019-09-05 18:01:04 +08:00
|
|
|
err := fspath.CheckConfigName(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-11-05 02:23:12 +08:00
|
|
|
for k, v := range keyValues {
|
|
|
|
keyValues[k] = obscure.MustObscure(fmt.Sprint(v))
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
2021-05-05 18:45:30 +08:00
|
|
|
_, err = UpdateRemote(ctx, name, keyValues, UpdateRemoteOpt{
|
|
|
|
NoObscure: true,
|
|
|
|
})
|
|
|
|
return err
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// JSONListProviders prints all the providers and options in JSON format
|
|
|
|
func JSONListProviders() error {
|
|
|
|
b, err := json.MarshalIndent(fs.Registry, "", " ")
|
|
|
|
if err != nil {
|
2021-11-04 18:12:57 +08:00
|
|
|
return fmt.Errorf("failed to marshal examples: %w", err)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
_, err = os.Stdout.Write(b)
|
|
|
|
if err != nil {
|
2021-11-04 18:12:57 +08:00
|
|
|
return fmt.Errorf("failed to write providers list: %w", err)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fsOption returns an Option describing the possible remotes
|
|
|
|
func fsOption() *fs.Option {
|
|
|
|
o := &fs.Option{
|
2021-08-23 04:43:51 +08:00
|
|
|
Name: "Storage",
|
|
|
|
Help: "Type of storage to configure.",
|
|
|
|
Default: "",
|
|
|
|
Required: true,
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
for _, item := range fs.Registry {
|
2022-02-07 23:09:03 +08:00
|
|
|
if item.Hide {
|
|
|
|
continue
|
|
|
|
}
|
2018-01-13 00:30:54 +08:00
|
|
|
example := fs.OptionExample{
|
|
|
|
Value: item.Name,
|
|
|
|
Help: item.Description,
|
|
|
|
}
|
|
|
|
o.Examples = append(o.Examples, example)
|
|
|
|
}
|
|
|
|
o.Examples.Sort()
|
|
|
|
return o
|
|
|
|
}
|
|
|
|
|
2018-11-05 02:23:12 +08:00
|
|
|
// DumpRcRemote dumps the config for a single remote
|
|
|
|
func DumpRcRemote(name string) (dump rc.Params) {
|
|
|
|
params := rc.Params{}
|
2021-04-27 05:37:49 +08:00
|
|
|
for _, key := range LoadedData().GetKeyList(name) {
|
2023-11-04 22:34:23 +08:00
|
|
|
params[key] = GetValue(name, key)
|
2018-11-05 02:23:12 +08:00
|
|
|
}
|
|
|
|
return params
|
|
|
|
}
|
|
|
|
|
|
|
|
// DumpRcBlob dumps all the config as an unstructured blob suitable
|
|
|
|
// for the rc
|
|
|
|
func DumpRcBlob() (dump rc.Params) {
|
|
|
|
dump = rc.Params{}
|
2021-04-27 05:37:49 +08:00
|
|
|
for _, name := range LoadedData().GetSectionList() {
|
2018-11-05 02:23:12 +08:00
|
|
|
dump[name] = DumpRcRemote(name)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
2018-11-05 02:23:12 +08:00
|
|
|
return dump
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dump dumps all the config as a JSON file
|
|
|
|
func Dump() error {
|
|
|
|
dump := DumpRcBlob()
|
2018-01-13 00:30:54 +08:00
|
|
|
b, err := json.MarshalIndent(dump, "", " ")
|
|
|
|
if err != nil {
|
2021-11-04 18:12:57 +08:00
|
|
|
return fmt.Errorf("failed to marshal config dump: %w", err)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
_, err = os.Stdout.Write(b)
|
|
|
|
if err != nil {
|
2021-11-04 18:12:57 +08:00
|
|
|
return fmt.Errorf("failed to write config dump: %w", err)
|
2018-01-13 00:30:54 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// makeCacheDir returns a directory to use for caching.
|
|
|
|
func makeCacheDir() (dir string) {
|
2022-04-11 18:40:16 +08:00
|
|
|
dir, err := os.UserCacheDir()
|
|
|
|
if err != nil || dir == "" {
|
|
|
|
fs.Debugf(nil, "Failed to find user cache dir, using temporary directory: %v", err)
|
|
|
|
// if no dir found then use TempDir - we will have a cachedir!
|
2018-01-13 00:30:54 +08:00
|
|
|
dir = os.TempDir()
|
|
|
|
}
|
|
|
|
return filepath.Join(dir, "rclone")
|
|
|
|
}
|
2021-09-10 20:44:13 +08:00
|
|
|
|
2021-09-10 21:35:53 +08:00
|
|
|
// GetCacheDir returns the default directory for cache
|
|
|
|
//
|
|
|
|
// The directory is neither guaranteed to exist nor have accessible permissions.
|
|
|
|
// Users of this should make a subdirectory and use MkdirAll() to create it
|
|
|
|
// and any parents.
|
|
|
|
func GetCacheDir() string {
|
|
|
|
return cacheDir
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetCacheDir sets new default directory for cache
|
|
|
|
func SetCacheDir(path string) (err error) {
|
|
|
|
cacheDir, err = filepath.Abs(path)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-09-10 20:44:13 +08:00
|
|
|
// SetTempDir sets new default directory to use for temporary files.
|
|
|
|
//
|
|
|
|
// Assuming golang's os.TempDir is used to get the directory:
|
|
|
|
// "On Unix systems, it returns $TMPDIR if non-empty, else /tmp. On Windows,
|
|
|
|
// it uses GetTempPath, returning the first non-empty value from %TMP%, %TEMP%,
|
|
|
|
// %USERPROFILE%, or the Windows directory."
|
|
|
|
//
|
|
|
|
// To override the default we therefore set environment variable TMPDIR
|
|
|
|
// on Unix systems, and both TMP and TEMP on Windows (they are almost exclusively
|
|
|
|
// aliases for the same path, and programs may refer to to either of them).
|
|
|
|
// This should make all libraries and forked processes use the same.
|
|
|
|
func SetTempDir(path string) (err error) {
|
|
|
|
var tempDir string
|
|
|
|
if tempDir, err = filepath.Abs(path); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
if err = os.Setenv("TMP", tempDir); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = os.Setenv("TEMP", tempDir); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return os.Setenv("TMPDIR", tempDir)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|