cmd/syncthing, lib/locations: Separate data and config dirs (fixes #4924) (#6309)

This commit is contained in:
Simon Frei 2020-03-18 20:58:11 +01:00 committed by GitHub
parent 00b2340f9a
commit e25e71cdde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 52 deletions

View File

@ -65,6 +65,12 @@ I.e. to prefix each log line with date and time, set -logflags=3 (1 + 2 from
above). The value 0 is used to disable all of the above. The default is to above). The value 0 is used to disable all of the above. The default is to
show time only (2). show time only (2).
Logging always happens to the command line (stdout) and optionally to the
file at the path specified by -logfile=path. In addition to an path, the special
values "default" and "-" may be used. The former logs to DATADIR/syncthing.log
(see -data-dir), which is the default on Windows, and the latter only to stdout,
no file, which is the default anywhere else.
Development Settings Development Settings
-------------------- --------------------
@ -149,7 +155,9 @@ var (
type RuntimeOptions struct { type RuntimeOptions struct {
syncthing.Options syncthing.Options
homeDir string
confDir string confDir string
dataDir string
resetDatabase bool resetDatabase bool
showVersion bool showVersion bool
showPaths bool showPaths bool
@ -197,11 +205,13 @@ func defaultRuntimeOptions() RuntimeOptions {
options.logFlags = logger.DebugFlags options.logFlags = logger.DebugFlags
} }
if runtime.GOOS != "windows" { // On non-Windows, we explicitly default to "-" which means stdout. On
// On non-Windows, we explicitly default to "-" which means stdout. On // Windows, the "default" options.logFile will later be replaced with the
// Windows, the blank options.logFile will later be replaced with the // default path, unless the user has manually specified "-" or
// default path, unless the user has manually specified "-" or // something else.
// something else. if runtime.GOOS == "windows" {
options.logFile = "default"
} else {
options.logFile = "-" options.logFile = "-"
} }
@ -214,7 +224,9 @@ func parseCommandLineOptions() RuntimeOptions {
flag.StringVar(&options.generateDir, "generate", "", "Generate key and config in specified dir, then exit") flag.StringVar(&options.generateDir, "generate", "", "Generate key and config in specified dir, then exit")
flag.StringVar(&options.guiAddress, "gui-address", options.guiAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")") flag.StringVar(&options.guiAddress, "gui-address", options.guiAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")")
flag.StringVar(&options.guiAPIKey, "gui-apikey", options.guiAPIKey, "Override GUI API key") flag.StringVar(&options.guiAPIKey, "gui-apikey", options.guiAPIKey, "Override GUI API key")
flag.StringVar(&options.confDir, "home", "", "Set configuration directory") flag.StringVar(&options.homeDir, "home", "", "Set configuration and data directory")
flag.StringVar(&options.confDir, "config", "", "Set configuration directory (config and keys)")
flag.StringVar(&options.dataDir, "data", "", "Set data directory (database and logs)")
flag.IntVar(&options.logFlags, "logflags", options.logFlags, "Select information in log line prefix (see below)") flag.IntVar(&options.logFlags, "logflags", options.logFlags, "Select information in log line prefix (see below)")
flag.BoolVar(&options.noBrowser, "no-browser", false, "Do not start browser") flag.BoolVar(&options.noBrowser, "no-browser", false, "Do not start browser")
flag.BoolVar(&options.browserOnly, "browser-only", false, "Open GUI in browser") flag.BoolVar(&options.browserOnly, "browser-only", false, "Open GUI in browser")
@ -232,7 +244,7 @@ func parseCommandLineOptions() RuntimeOptions {
flag.BoolVar(&options.Verbose, "verbose", false, "Print verbose log output") flag.BoolVar(&options.Verbose, "verbose", false, "Print verbose log output")
flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused") flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused")
flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused") flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (still always logs to stdout).") flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (see below).")
flag.IntVar(&options.logMaxSize, "log-max-size", options.logMaxSize, "Maximum size of any file (zero to disable log rotation).") flag.IntVar(&options.logMaxSize, "log-max-size", options.logMaxSize, "Maximum size of any file (zero to disable log rotation).")
flag.IntVar(&options.logMaxFiles, "log-max-old-files", options.logMaxFiles, "Number of old files to keep (zero to keep only current).") flag.IntVar(&options.logMaxFiles, "log-max-old-files", options.logMaxFiles, "Number of old files to keep (zero to keep only current).")
flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)") flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)")
@ -254,6 +266,17 @@ func parseCommandLineOptions() RuntimeOptions {
return options return options
} }
func setLocation(enum locations.BaseDirEnum, loc string) error {
if !filepath.IsAbs(loc) {
var err error
loc, err = filepath.Abs(loc)
if err != nil {
return err
}
}
return locations.SetBaseDir(enum, loc)
}
func main() { func main() {
options := parseCommandLineOptions() options := parseCommandLineOptions()
l.SetFlags(options.logFlags) l.SetFlags(options.logFlags)
@ -271,25 +294,33 @@ func main() {
osutil.HideConsole() osutil.HideConsole()
} }
if options.confDir != "" { // Not set as default above because the strings can be really long.
// Not set as default above because the string can be really long. var err error
if !filepath.IsAbs(options.confDir) { homeSet := options.homeDir != ""
var err error confSet := options.confDir != ""
options.confDir, err = filepath.Abs(options.confDir) dataSet := options.dataDir != ""
if err != nil { switch {
l.Warnln("Failed to make options path absolute:", err) case dataSet != confSet:
os.Exit(syncthing.ExitError.AsInt()) err = errors.New("either both or none of -conf and -data must be given, use -home to set both at once")
} case homeSet && dataSet:
err = errors.New("-home must not be used together with -conf and -data")
case homeSet:
if err = setLocation(locations.ConfigBaseDir, options.homeDir); err == nil {
err = setLocation(locations.DataBaseDir, options.homeDir)
} }
if err := locations.SetBaseDir(locations.ConfigBaseDir, options.confDir); err != nil { case dataSet:
l.Warnln(err) if err = setLocation(locations.ConfigBaseDir, options.confDir); err == nil {
os.Exit(syncthing.ExitError.AsInt()) err = setLocation(locations.DataBaseDir, options.dataDir)
} }
} }
if err != nil {
l.Warnln("Command line options:", err)
os.Exit(syncthing.ExitError.AsInt())
}
if options.logFile == "" { if options.logFile == "default" || options.logFile == "" {
// Blank means use the default logfile location. We must set this // We must set this *after* expandLocations above.
// *after* expandLocations above. // Handling an empty value is for backwards compatibility (<1.4.1).
options.logFile = locations.Get(locations.LogFile) options.logFile = locations.Get(locations.LogFile)
} }

View File

@ -2,7 +2,7 @@
Name=Start Syncthing Name=Start Syncthing
GenericName=File synchronization GenericName=File synchronization
Comment=Starts the main syncthing process in the background. Comment=Starts the main syncthing process in the background.
Exec=/usr/bin/syncthing -no-browser -logfile=~/.config/syncthing/syncthing.log Exec=/usr/bin/syncthing -no-browser -logfile=default
Icon=syncthing Icon=syncthing
Terminal=false Terminal=false
Type=Application Type=Application

View File

@ -39,11 +39,23 @@ const (
type BaseDirEnum string type BaseDirEnum string
const ( const (
// Overridden by -home flag
ConfigBaseDir BaseDirEnum = "config" ConfigBaseDir BaseDirEnum = "config"
HomeBaseDir BaseDirEnum = "home" DataBaseDir BaseDirEnum = "data"
// User's home directory, *not* -home flag
UserHomeBaseDir BaseDirEnum = "userHome"
) )
// Platform dependent directories
var baseDirs = make(map[BaseDirEnum]string, 3)
func init() { func init() {
userHome := userHomeDir()
config := defaultConfigDir(userHome)
baseDirs[UserHomeBaseDir] = userHome
baseDirs[ConfigBaseDir] = config
baseDirs[DataBaseDir] = defaultDataDir(userHome, config)
err := expandLocations() err := expandLocations()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -68,11 +80,7 @@ func GetBaseDir(baseDir BaseDirEnum) string {
return baseDirs[baseDir] return baseDirs[baseDir]
} }
// Platform dependent directories var databaseDirname = "index-v0.14.0.db"
var baseDirs = map[BaseDirEnum]string{
ConfigBaseDir: defaultConfigDir(), // Overridden by -home flag
HomeBaseDir: homeDir(), // User's home directory, *not* -home flag
}
// Use the variables from baseDirs here // Use the variables from baseDirs here
var locationTemplates = map[LocationEnum]string{ var locationTemplates = map[LocationEnum]string{
@ -81,13 +89,13 @@ var locationTemplates = map[LocationEnum]string{
KeyFile: "${config}/key.pem", KeyFile: "${config}/key.pem",
HTTPSCertFile: "${config}/https-cert.pem", HTTPSCertFile: "${config}/https-cert.pem",
HTTPSKeyFile: "${config}/https-key.pem", HTTPSKeyFile: "${config}/https-key.pem",
Database: "${config}/index-v0.14.0.db", Database: "${data}/" + databaseDirname,
LogFile: "${config}/syncthing.log", // -logfile on Windows LogFile: "${data}/syncthing.log", // -logfile on Windows
CsrfTokens: "${config}/csrftokens.txt", CsrfTokens: "${data}/csrftokens.txt",
PanicLog: "${config}/panic-${timestamp}.log", PanicLog: "${data}/panic-${timestamp}.log",
AuditLog: "${config}/audit-${timestamp}.log", AuditLog: "${data}/audit-${timestamp}.log",
GUIAssets: "${config}/gui", GUIAssets: "${config}/gui",
DefFolder: "${home}/Sync", DefFolder: "${userHome}/Sync",
} }
var locations = make(map[LocationEnum]string) var locations = make(map[LocationEnum]string)
@ -114,7 +122,7 @@ func expandLocations() error {
// defaultConfigDir returns the default configuration directory, as figured // defaultConfigDir returns the default configuration directory, as figured
// out by various the environment variables present on each platform, or dies // out by various the environment variables present on each platform, or dies
// trying. // trying.
func defaultConfigDir() string { func defaultConfigDir(userHome string) string {
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
if p := os.Getenv("LocalAppData"); p != "" { if p := os.Getenv("LocalAppData"); p != "" {
@ -123,34 +131,53 @@ func defaultConfigDir() string {
return filepath.Join(os.Getenv("AppData"), "Syncthing") return filepath.Join(os.Getenv("AppData"), "Syncthing")
case "darwin": case "darwin":
dir, err := fs.ExpandTilde("~/Library/Application Support/Syncthing") return filepath.Join(userHome, "Library/Application Support/Syncthing")
if err != nil {
fmt.Println(err)
panic("Failed to get default config dir")
}
return dir
default: default:
if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" { if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
return filepath.Join(xdgCfg, "syncthing") return filepath.Join(xdgCfg, "syncthing")
} }
dir, err := fs.ExpandTilde("~/.config/syncthing") return filepath.Join(userHome, ".config/syncthing")
if err != nil {
fmt.Println(err)
panic("Failed to get default config dir")
}
return dir
} }
} }
// homeDir returns the user's home directory, or dies trying. // defaultDataDir returns the default data directory, which usually is the
func homeDir() string { // config directory but might be something else.
home, err := fs.ExpandTilde("~") func defaultDataDir(userHome, config string) string {
switch runtime.GOOS {
case "windows", "darwin":
return config
default:
// If a database exists at the "normal" location, use that anyway.
if _, err := os.Lstat(filepath.Join(config, databaseDirname)); err == nil {
return config
}
// Always use this env var, as it's explicitly set by the user
if xdgHome := os.Getenv("XDG_DATA_HOME"); xdgHome != "" {
return filepath.Join(xdgHome, "syncthing")
}
// Only use the XDG default, if a syncthing specific dir already
// exists. Existence of ~/.local/share is not deemed enough, as
// it may also exist erroneously on non-XDG systems.
xdgDefault := filepath.Join(userHome, ".local/share/syncthing")
if _, err := os.Lstat(xdgDefault); err == nil {
return xdgDefault
}
// FYI: XDG_DATA_DIRS is not relevant, as it is for system-wide
// data dirs, not user specific ones.
return config
}
}
// userHomeDir returns the user's home directory, or dies trying.
func userHomeDir() string {
userHome, err := fs.ExpandTilde("~")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
panic("Failed to get user home dir") panic("Failed to get user home dir")
} }
return home return userHome
} }
func GetTimestamped(key LocationEnum) string { func GetTimestamped(key LocationEnum) string {