locations.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright (C) 2019 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package locations
  7. import (
  8. "fmt"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "strings"
  13. "time"
  14. "github.com/syncthing/syncthing/lib/build"
  15. "github.com/syncthing/syncthing/lib/fs"
  16. )
  17. type LocationEnum string
  18. // Use strings as keys to make printout and serialization of the locations map
  19. // more meaningful.
  20. const (
  21. ConfigFile LocationEnum = "config"
  22. CertFile LocationEnum = "certFile"
  23. KeyFile LocationEnum = "keyFile"
  24. HTTPSCertFile LocationEnum = "httpsCertFile"
  25. HTTPSKeyFile LocationEnum = "httpsKeyFile"
  26. Database LocationEnum = "database"
  27. LogFile LocationEnum = "logFile"
  28. CsrfTokens LocationEnum = "csrfTokens"
  29. PanicLog LocationEnum = "panicLog"
  30. AuditLog LocationEnum = "auditLog"
  31. GUIAssets LocationEnum = "guiAssets"
  32. DefFolder LocationEnum = "defFolder"
  33. )
  34. type BaseDirEnum string
  35. const (
  36. // Overridden by --home flag
  37. ConfigBaseDir BaseDirEnum = "config"
  38. DataBaseDir BaseDirEnum = "data"
  39. // User's home directory, *not* --home flag
  40. UserHomeBaseDir BaseDirEnum = "userHome"
  41. LevelDBDir = "index-v0.14.0.db"
  42. )
  43. // Platform dependent directories
  44. var baseDirs = make(map[BaseDirEnum]string, 3)
  45. func init() {
  46. userHome := userHomeDir()
  47. config := defaultConfigDir(userHome)
  48. baseDirs[UserHomeBaseDir] = userHome
  49. baseDirs[ConfigBaseDir] = config
  50. baseDirs[DataBaseDir] = defaultDataDir(userHome, config)
  51. err := expandLocations()
  52. if err != nil {
  53. fmt.Println(err)
  54. panic("Failed to expand locations at init time")
  55. }
  56. }
  57. // Set overrides a location to the given path, making sure to it points to an
  58. // absolute path first. Only the special "-" value will be used verbatim.
  59. func Set(locationName LocationEnum, path string) error {
  60. if !filepath.IsAbs(path) && path != "-" {
  61. var err error
  62. path, err = filepath.Abs(path)
  63. if err != nil {
  64. return err
  65. }
  66. }
  67. _, ok := locationTemplates[locationName]
  68. if !ok {
  69. return fmt.Errorf("unknown location: %s", locationName)
  70. }
  71. locations[locationName] = filepath.Clean(path)
  72. return nil
  73. }
  74. func SetBaseDir(baseDirName BaseDirEnum, path string) error {
  75. if !filepath.IsAbs(path) {
  76. var err error
  77. path, err = filepath.Abs(path)
  78. if err != nil {
  79. return err
  80. }
  81. }
  82. _, ok := baseDirs[baseDirName]
  83. if !ok {
  84. return fmt.Errorf("unknown base dir: %s", baseDirName)
  85. }
  86. baseDirs[baseDirName] = filepath.Clean(path)
  87. return expandLocations()
  88. }
  89. func Get(location LocationEnum) string {
  90. return locations[location]
  91. }
  92. func GetBaseDir(baseDir BaseDirEnum) string {
  93. return baseDirs[baseDir]
  94. }
  95. // Use the variables from baseDirs here
  96. var locationTemplates = map[LocationEnum]string{
  97. ConfigFile: "${config}/config.xml",
  98. CertFile: "${config}/cert.pem",
  99. KeyFile: "${config}/key.pem",
  100. HTTPSCertFile: "${config}/https-cert.pem",
  101. HTTPSKeyFile: "${config}/https-key.pem",
  102. Database: "${data}/" + LevelDBDir,
  103. LogFile: "${data}/syncthing.log", // --logfile on Windows
  104. CsrfTokens: "${data}/csrftokens.txt",
  105. PanicLog: "${data}/panic-${timestamp}.log",
  106. AuditLog: "${data}/audit-${timestamp}.log",
  107. GUIAssets: "${config}/gui",
  108. DefFolder: "${userHome}/Sync",
  109. }
  110. var locations = make(map[LocationEnum]string)
  111. // expandLocations replaces the variables in the locations map with actual
  112. // directory locations.
  113. func expandLocations() error {
  114. newLocations := make(map[LocationEnum]string)
  115. for key, dir := range locationTemplates {
  116. for varName, value := range baseDirs {
  117. dir = strings.ReplaceAll(dir, "${"+string(varName)+"}", value)
  118. }
  119. var err error
  120. dir, err = fs.ExpandTilde(dir)
  121. if err != nil {
  122. return err
  123. }
  124. newLocations[key] = filepath.Clean(dir)
  125. }
  126. locations = newLocations
  127. return nil
  128. }
  129. // ListExpandedPaths returns a machine-readable mapping of the currently configured locations.
  130. func ListExpandedPaths() map[string]string {
  131. res := make(map[string]string, len(locations))
  132. for key, path := range baseDirs {
  133. res["baseDir-"+string(key)] = path
  134. }
  135. for key, path := range locations {
  136. res[string(key)] = path
  137. }
  138. return res
  139. }
  140. // PrettyPaths returns a nicely formatted, human-readable listing
  141. func PrettyPaths() string {
  142. var b strings.Builder
  143. fmt.Fprintf(&b, "Configuration file:\n\t%s\n\n", Get(ConfigFile))
  144. fmt.Fprintf(&b, "Device private key & certificate files:\n\t%s\n\t%s\n\n", Get(KeyFile), Get(CertFile))
  145. fmt.Fprintf(&b, "GUI / API HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", Get(HTTPSKeyFile), Get(HTTPSCertFile))
  146. fmt.Fprintf(&b, "Database location:\n\t%s\n\n", Get(Database))
  147. fmt.Fprintf(&b, "Log file:\n\t%s\n\n", Get(LogFile))
  148. fmt.Fprintf(&b, "GUI override directory:\n\t%s\n\n", Get(GUIAssets))
  149. fmt.Fprintf(&b, "CSRF tokens file:\n\t%s\n\n", Get(CsrfTokens))
  150. fmt.Fprintf(&b, "Default sync folder directory:\n\t%s\n\n", Get(DefFolder))
  151. return b.String()
  152. }
  153. // defaultConfigDir returns the default configuration directory, as figured
  154. // out by various the environment variables present on each platform, or dies
  155. // trying.
  156. func defaultConfigDir(userHome string) string {
  157. switch runtime.GOOS {
  158. case build.Windows:
  159. if p := os.Getenv("LocalAppData"); p != "" {
  160. return filepath.Join(p, "Syncthing")
  161. }
  162. return filepath.Join(os.Getenv("AppData"), "Syncthing")
  163. case build.Darwin:
  164. return filepath.Join(userHome, "Library/Application Support/Syncthing")
  165. default:
  166. if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
  167. return filepath.Join(xdgCfg, "syncthing")
  168. }
  169. return filepath.Join(userHome, ".config/syncthing")
  170. }
  171. }
  172. // defaultDataDir returns the default data directory, which usually is the
  173. // config directory but might be something else.
  174. func defaultDataDir(userHome, config string) string {
  175. if build.IsWindows || build.IsDarwin {
  176. return config
  177. }
  178. // If a database exists at the "normal" location, use that anyway.
  179. if _, err := os.Lstat(filepath.Join(config, LevelDBDir)); err == nil {
  180. return config
  181. }
  182. // Always use this env var, as it's explicitly set by the user
  183. if xdgHome := os.Getenv("XDG_DATA_HOME"); xdgHome != "" {
  184. return filepath.Join(xdgHome, "syncthing")
  185. }
  186. // Only use the XDG default, if a syncthing specific dir already
  187. // exists. Existence of ~/.local/share is not deemed enough, as
  188. // it may also exist erroneously on non-XDG systems.
  189. xdgDefault := filepath.Join(userHome, ".local/share/syncthing")
  190. if _, err := os.Lstat(xdgDefault); err == nil {
  191. return xdgDefault
  192. }
  193. // FYI: XDG_DATA_DIRS is not relevant, as it is for system-wide
  194. // data dirs, not user specific ones.
  195. return config
  196. }
  197. // userHomeDir returns the user's home directory, or dies trying.
  198. func userHomeDir() string {
  199. userHome, err := fs.ExpandTilde("~")
  200. if err != nil {
  201. fmt.Println(err)
  202. panic("Failed to get user home dir")
  203. }
  204. return userHome
  205. }
  206. func GetTimestamped(key LocationEnum) string {
  207. // We take the roundtrip via "${timestamp}" instead of passing the path
  208. // directly through time.Format() to avoid issues when the path we are
  209. // expanding contains numbers; otherwise for example
  210. // /home/user2006/.../panic-20060102-150405.log would get both instances of
  211. // 2006 replaced by 2015...
  212. tpl := locations[key]
  213. now := time.Now().Format("20060102-150405")
  214. return strings.ReplaceAll(tpl, "${timestamp}", now)
  215. }