main.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. // Copyright (C) 2014 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 main
  7. import (
  8. "bytes"
  9. "context"
  10. "crypto/tls"
  11. "errors"
  12. "fmt"
  13. "io"
  14. "log"
  15. "net/http"
  16. _ "net/http/pprof" // Need to import this to support STPROFILER.
  17. "net/url"
  18. "os"
  19. "os/signal"
  20. "path"
  21. "path/filepath"
  22. "regexp"
  23. "runtime"
  24. "runtime/pprof"
  25. "sort"
  26. "strconv"
  27. "strings"
  28. "syscall"
  29. "time"
  30. "github.com/alecthomas/kong"
  31. "github.com/thejerf/suture/v4"
  32. "github.com/syncthing/syncthing/cmd/syncthing/cli"
  33. "github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
  34. "github.com/syncthing/syncthing/cmd/syncthing/decrypt"
  35. "github.com/syncthing/syncthing/cmd/syncthing/generate"
  36. "github.com/syncthing/syncthing/lib/build"
  37. "github.com/syncthing/syncthing/lib/config"
  38. "github.com/syncthing/syncthing/lib/db"
  39. "github.com/syncthing/syncthing/lib/db/backend"
  40. "github.com/syncthing/syncthing/lib/dialer"
  41. "github.com/syncthing/syncthing/lib/events"
  42. "github.com/syncthing/syncthing/lib/fs"
  43. "github.com/syncthing/syncthing/lib/locations"
  44. "github.com/syncthing/syncthing/lib/logger"
  45. "github.com/syncthing/syncthing/lib/osutil"
  46. "github.com/syncthing/syncthing/lib/protocol"
  47. "github.com/syncthing/syncthing/lib/svcutil"
  48. "github.com/syncthing/syncthing/lib/syncthing"
  49. "github.com/syncthing/syncthing/lib/upgrade"
  50. )
  51. const (
  52. sigTerm = syscall.Signal(15)
  53. )
  54. const (
  55. extraUsage = `
  56. The --logflags value is a sum of the following:
  57. 1 Date
  58. 2 Time
  59. 4 Microsecond time
  60. 8 Long filename
  61. 16 Short filename
  62. I.e. to prefix each log line with time and filename, set --logflags=18 (2 + 16
  63. from above). The value 0 is used to disable all of the above. The default is
  64. to show date and time (3).
  65. Logging always happens to the command line (stdout) and optionally to the
  66. file at the path specified by --logfile=path. In addition to an path, the special
  67. values "default" and "-" may be used. The former logs to DATADIR/syncthing.log
  68. (see --data), which is the default on Windows, and the latter only to stdout,
  69. no file, which is the default anywhere else.
  70. Development Settings
  71. --------------------
  72. The following environment variables modify Syncthing's behavior in ways that
  73. are mostly useful for developers. Use with care. See also the --debug-* options
  74. above.
  75. STTRACE A comma separated string of facilities to trace. The valid
  76. facility strings are listed below.
  77. STDEADLOCKTIMEOUT Used for debugging internal deadlocks; sets debug
  78. sensitivity. Use only under direction of a developer.
  79. STLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
  80. sensitivity. Use only under direction of a developer.
  81. STHASHING Select the SHA256 hashing package to use. Possible values
  82. are "standard" for the Go standard library implementation,
  83. "minio" for the github.com/minio/sha256-simd implementation,
  84. and blank (the default) for auto detection.
  85. GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
  86. available CPU cores.
  87. GOGC Percentage of heap growth at which to trigger GC. Default is
  88. 100. Lower numbers keep peak memory usage down, at the price
  89. of CPU usage (i.e. performance).
  90. Debugging Facilities
  91. --------------------
  92. The following are valid values for the STTRACE variable:
  93. %s
  94. `
  95. )
  96. var (
  97. upgradeCheckInterval = 5 * time.Minute
  98. upgradeRetryInterval = time.Hour
  99. upgradeCheckKey = "lastUpgradeCheck"
  100. upgradeTimeKey = "lastUpgradeTime"
  101. upgradeVersionKey = "lastUpgradeVersion"
  102. errTooEarlyUpgradeCheck = fmt.Errorf("last upgrade check happened less than %v ago, skipping", upgradeCheckInterval)
  103. errTooEarlyUpgrade = fmt.Errorf("last upgrade happened less than %v ago, skipping", upgradeRetryInterval)
  104. )
  105. // The entrypoint struct is the main entry point for the command line parser. The
  106. // commands and options here are top level commands to syncthing.
  107. // Cli is just a placeholder for the help text (see main).
  108. var entrypoint struct {
  109. Serve serveOptions `cmd:"" help:"Run Syncthing"`
  110. Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"`
  111. Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
  112. Cli struct{} `cmd:"" help:"Command line interface for Syncthing"`
  113. }
  114. // serveOptions are the options for the `syncthing serve` command.
  115. type serveOptions struct {
  116. cmdutil.CommonOptions
  117. AllowNewerConfig bool `help:"Allow loading newer than current config version"`
  118. Audit bool `help:"Write events to audit file"`
  119. AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
  120. BrowserOnly bool `help:"Open GUI in browser"`
  121. DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"`
  122. DeviceID bool `help:"Show the device ID"`
  123. GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` // DEPRECATED: replaced by subcommand!
  124. GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
  125. GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
  126. LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"`
  127. LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"`
  128. LogMaxFiles int `placeholder:"N" default:"${logMaxFiles}" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"`
  129. LogMaxSize int `placeholder:"BYTES" default:"${logMaxSize}" help:"Maximum size of any file (zero to disable log rotation)"`
  130. NoBrowser bool `help:"Do not start browser"`
  131. NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"`
  132. NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"`
  133. Paths bool `help:"Show configuration paths"`
  134. Paused bool `help:"Start with all devices and folders paused"`
  135. Unpaused bool `help:"Start with all devices and folders unpaused"`
  136. Upgrade bool `help:"Perform upgrade"`
  137. UpgradeCheck bool `help:"Check for available upgrade"`
  138. UpgradeTo string `placeholder:"URL" help:"Force upgrade directly from specified URL"`
  139. Verbose bool `help:"Print verbose log output"`
  140. Version bool `help:"Show version"`
  141. // Debug options below
  142. DebugDBIndirectGCInterval time.Duration `env:"STGCINDIRECTEVERY" help:"Database indirection GC interval"`
  143. DebugDBRecheckInterval time.Duration `env:"STRECHECKDBEVERY" help:"Database metadata recalculation interval"`
  144. DebugDeadlockTimeout int `placeholder:"SECONDS" env:"STDEADLOCKTIMEOUT" help:"Used for debugging internal deadlocks"`
  145. DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"`
  146. DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"`
  147. DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"`
  148. DebugProfileCPU bool `help:"Write a CPU profile to cpu-$pid.pprof on exit" env:"STCPUPROFILE"`
  149. DebugProfileHeap bool `env:"STHEAPPROFILE" help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases"`
  150. DebugProfilerListen string `placeholder:"ADDR" env:"STPROFILER" help:"Network profiler listen address"`
  151. DebugResetDatabase bool `name:"reset-database" help:"Reset the database, forcing a full rescan and resync"`
  152. DebugResetDeltaIdxs bool `name:"reset-deltas" help:"Reset delta index IDs, forcing a full index exchange"`
  153. // Internal options, not shown to users
  154. InternalRestarting bool `env:"STRESTART" hidden:"1"`
  155. InternalInnerProcess bool `env:"STMONITORED" hidden:"1"`
  156. }
  157. func defaultVars() kong.Vars {
  158. vars := kong.Vars{}
  159. vars["logFlags"] = strconv.Itoa(logger.DefaultFlags)
  160. vars["logMaxSize"] = strconv.Itoa(10 << 20) // 10 MiB
  161. vars["logMaxFiles"] = "3" // plus the current one
  162. if os.Getenv("STTRACE") != "" {
  163. vars["logFlags"] = strconv.Itoa(logger.DebugFlags)
  164. }
  165. // On non-Windows, we explicitly default to "-" which means stdout. On
  166. // Windows, the "default" options.logFile will later be replaced with the
  167. // default path, unless the user has manually specified "-" or
  168. // something else.
  169. if build.IsWindows {
  170. vars["logFile"] = "default"
  171. } else {
  172. vars["logFile"] = "-"
  173. }
  174. return vars
  175. }
  176. func main() {
  177. // The "cli" subcommand uses a different command line parser, and e.g. help
  178. // gets mangled when integrating it as a subcommand -> detect it here at the
  179. // beginning.
  180. if len(os.Args) > 1 && os.Args[1] == "cli" {
  181. if err := cli.Run(); err != nil {
  182. fmt.Println(err)
  183. os.Exit(1)
  184. }
  185. return
  186. }
  187. // First some massaging of the raw command line to fit the new model.
  188. // Basically this means adding the default command at the front, and
  189. // converting -options to --options.
  190. args := os.Args[1:]
  191. switch {
  192. case len(args) == 0:
  193. // Empty command line is equivalent to just calling serve
  194. args = []string{"serve"}
  195. case args[0] == "-help":
  196. // For consistency, we consider this equivalent with --help even
  197. // though kong would otherwise consider it a bad flag.
  198. args[0] = "--help"
  199. case args[0] == "-h", args[0] == "--help":
  200. // Top level request for help, let it pass as-is to be handled by
  201. // kong to list commands.
  202. case strings.HasPrefix(args[0], "-"):
  203. // There are flags not preceded by a command, so we tack on the
  204. // "serve" command and convert the old style arguments (single dash)
  205. // to new style (double dash).
  206. args = append([]string{"serve"}, convertLegacyArgs(args)...)
  207. }
  208. // Create a parser with an overridden help function to print our extra
  209. // help info.
  210. parser, err := kong.New(&entrypoint, kong.Help(helpHandler), defaultVars())
  211. if err != nil {
  212. log.Fatal(err)
  213. }
  214. ctx, err := parser.Parse(args)
  215. parser.FatalIfErrorf(err)
  216. ctx.BindTo(l, (*logger.Logger)(nil)) // main logger available to subcommands
  217. err = ctx.Run()
  218. parser.FatalIfErrorf(err)
  219. }
  220. func helpHandler(options kong.HelpOptions, ctx *kong.Context) error {
  221. if err := kong.DefaultHelpPrinter(options, ctx); err != nil {
  222. return err
  223. }
  224. if ctx.Command() == "serve" {
  225. // Help was requested for `syncthing serve`, so we add our extra
  226. // usage info afte the normal options output.
  227. fmt.Printf(extraUsage, debugFacilities())
  228. }
  229. return nil
  230. }
  231. // serveOptions.Run() is the entrypoint for `syncthing serve`
  232. func (options serveOptions) Run() error {
  233. l.SetFlags(options.LogFlags)
  234. if options.GUIAddress != "" {
  235. // The config picks this up from the environment.
  236. os.Setenv("STGUIADDRESS", options.GUIAddress)
  237. }
  238. if options.GUIAPIKey != "" {
  239. // The config picks this up from the environment.
  240. os.Setenv("STGUIAPIKEY", options.GUIAPIKey)
  241. }
  242. if options.HideConsole {
  243. osutil.HideConsole()
  244. }
  245. // Not set as default above because the strings can be really long.
  246. err := cmdutil.SetConfigDataLocationsFromFlags(options.HomeDir, options.ConfDir, options.DataDir)
  247. if err != nil {
  248. l.Warnln("Command line options:", err)
  249. os.Exit(svcutil.ExitError.AsInt())
  250. }
  251. // Treat an explicitly empty log file name as no log file
  252. if options.LogFile == "" {
  253. options.LogFile = "-"
  254. }
  255. if options.LogFile != "default" {
  256. // We must set this *after* expandLocations above.
  257. if err := locations.Set(locations.LogFile, options.LogFile); err != nil {
  258. l.Warnln("Setting log file path:", err)
  259. os.Exit(svcutil.ExitError.AsInt())
  260. }
  261. }
  262. if options.DebugGUIAssetsDir != "" {
  263. // The asset dir is blank if STGUIASSETS wasn't set, in which case we
  264. // should look for extra assets in the default place.
  265. if err := locations.Set(locations.GUIAssets, options.DebugGUIAssetsDir); err != nil {
  266. l.Warnln("Setting GUI assets path:", err)
  267. os.Exit(svcutil.ExitError.AsInt())
  268. }
  269. }
  270. if options.Version {
  271. fmt.Println(build.LongVersion)
  272. return nil
  273. }
  274. if options.Paths {
  275. fmt.Print(locations.PrettyPaths())
  276. return nil
  277. }
  278. if options.DeviceID {
  279. cert, err := tls.LoadX509KeyPair(
  280. locations.Get(locations.CertFile),
  281. locations.Get(locations.KeyFile),
  282. )
  283. if err != nil {
  284. l.Warnln("Error reading device ID:", err)
  285. os.Exit(svcutil.ExitError.AsInt())
  286. }
  287. fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
  288. return nil
  289. }
  290. if options.BrowserOnly {
  291. if err := openGUI(); err != nil {
  292. l.Warnln("Failed to open web UI:", err)
  293. os.Exit(svcutil.ExitError.AsInt())
  294. }
  295. return nil
  296. }
  297. if options.GenerateDir != "" {
  298. if err := generate.Generate(l, options.GenerateDir, "", "", options.NoDefaultFolder, options.SkipPortProbing); err != nil {
  299. l.Warnln("Failed to generate config and keys:", err)
  300. os.Exit(svcutil.ExitError.AsInt())
  301. }
  302. return nil
  303. }
  304. // Ensure that our home directory exists.
  305. if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0o700); err != nil {
  306. l.Warnln("Failure on home directory:", err)
  307. os.Exit(svcutil.ExitError.AsInt())
  308. }
  309. if options.UpgradeTo != "" {
  310. err := upgrade.ToURL(options.UpgradeTo)
  311. if err != nil {
  312. l.Warnln("Error while Upgrading:", err)
  313. os.Exit(svcutil.ExitError.AsInt())
  314. }
  315. l.Infoln("Upgraded from", options.UpgradeTo)
  316. return nil
  317. }
  318. if options.UpgradeCheck {
  319. if _, err := checkUpgrade(); err != nil {
  320. l.Warnln("Checking for upgrade:", err)
  321. os.Exit(exitCodeForUpgrade(err))
  322. }
  323. return nil
  324. }
  325. if options.Upgrade {
  326. release, err := checkUpgrade()
  327. if err == nil {
  328. // Use leveldb database locks to protect against concurrent upgrades
  329. var ldb backend.Backend
  330. ldb, err = syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
  331. if err != nil {
  332. err = upgradeViaRest()
  333. } else {
  334. _ = ldb.Close()
  335. err = upgrade.To(release)
  336. }
  337. }
  338. if err != nil {
  339. l.Warnln("Upgrade:", err)
  340. os.Exit(exitCodeForUpgrade(err))
  341. }
  342. l.Infof("Upgraded to %q", release.Tag)
  343. os.Exit(svcutil.ExitUpgrade.AsInt())
  344. }
  345. if options.DebugResetDatabase {
  346. if err := resetDB(); err != nil {
  347. l.Warnln("Resetting database:", err)
  348. os.Exit(svcutil.ExitError.AsInt())
  349. }
  350. l.Infoln("Successfully reset database - it will be rebuilt after next start.")
  351. return nil
  352. }
  353. if options.InternalInnerProcess {
  354. syncthingMain(options)
  355. } else {
  356. monitorMain(options)
  357. }
  358. return nil
  359. }
  360. func openGUI() error {
  361. cfg, err := loadOrDefaultConfig()
  362. if err != nil {
  363. return err
  364. }
  365. if guiCfg := cfg.GUI(); guiCfg.Enabled {
  366. if err := openURL(guiCfg.URL()); err != nil {
  367. return err
  368. }
  369. } else {
  370. l.Warnln("Browser: GUI is currently disabled")
  371. }
  372. return nil
  373. }
  374. func debugFacilities() string {
  375. facilities := l.Facilities()
  376. // Get a sorted list of names
  377. var names []string
  378. maxLen := 0
  379. for name := range facilities {
  380. names = append(names, name)
  381. if len(name) > maxLen {
  382. maxLen = len(name)
  383. }
  384. }
  385. sort.Strings(names)
  386. // Format the choices
  387. b := new(bytes.Buffer)
  388. for _, name := range names {
  389. fmt.Fprintf(b, " %-*s - %s\n", maxLen, name, facilities[name])
  390. }
  391. return b.String()
  392. }
  393. type errNoUpgrade struct {
  394. current, latest string
  395. }
  396. func (e *errNoUpgrade) Error() string {
  397. return fmt.Sprintf("no upgrade available (current %q >= latest %q).", e.current, e.latest)
  398. }
  399. func checkUpgrade() (upgrade.Release, error) {
  400. cfg, err := loadOrDefaultConfig()
  401. if err != nil {
  402. return upgrade.Release{}, err
  403. }
  404. opts := cfg.Options()
  405. release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
  406. if err != nil {
  407. return upgrade.Release{}, err
  408. }
  409. if upgrade.CompareVersions(release.Tag, build.Version) <= 0 {
  410. return upgrade.Release{}, &errNoUpgrade{build.Version, release.Tag}
  411. }
  412. l.Infof("Upgrade available (current %q < latest %q)", build.Version, release.Tag)
  413. return release, nil
  414. }
  415. func upgradeViaRest() error {
  416. cfg, err := loadOrDefaultConfig()
  417. if err != nil {
  418. return err
  419. }
  420. u, err := url.Parse(cfg.GUI().URL())
  421. if err != nil {
  422. return err
  423. }
  424. u.Path = path.Join(u.Path, "rest/system/upgrade")
  425. target := u.String()
  426. r, _ := http.NewRequest("POST", target, nil)
  427. r.Header.Set("X-API-Key", cfg.GUI().APIKey)
  428. tr := &http.Transport{
  429. DialContext: dialer.DialContext,
  430. Proxy: http.ProxyFromEnvironment,
  431. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  432. }
  433. client := &http.Client{
  434. Transport: tr,
  435. Timeout: 60 * time.Second,
  436. }
  437. resp, err := client.Do(r)
  438. if err != nil {
  439. return err
  440. }
  441. if resp.StatusCode != 200 {
  442. bs, err := io.ReadAll(resp.Body)
  443. defer resp.Body.Close()
  444. if err != nil {
  445. return err
  446. }
  447. return errors.New(string(bs))
  448. }
  449. return err
  450. }
  451. func syncthingMain(options serveOptions) {
  452. if options.DebugProfileBlock {
  453. startBlockProfiler()
  454. }
  455. if options.DebugProfileHeap {
  456. startHeapProfiler()
  457. }
  458. if options.DebugPerfStats {
  459. startPerfStats()
  460. }
  461. // Set a log prefix similar to the ID we will have later on, or early log
  462. // lines look ugly.
  463. l.SetPrefix("[start] ")
  464. // Print our version information up front, so any crash that happens
  465. // early etc. will have it available.
  466. l.Infoln(build.LongVersion)
  467. // Ensure that we have a certificate and key.
  468. cert, err := syncthing.LoadOrGenerateCertificate(
  469. locations.Get(locations.CertFile),
  470. locations.Get(locations.KeyFile),
  471. )
  472. if err != nil {
  473. l.Warnln("Failed to load/generate certificate:", err)
  474. os.Exit(1)
  475. }
  476. ctx, cancel := context.WithCancel(context.Background())
  477. defer cancel()
  478. // earlyService is a supervisor that runs the services needed for or
  479. // before app startup; the event logger, and the config service.
  480. spec := svcutil.SpecWithDebugLogger(l)
  481. earlyService := suture.New("early", spec)
  482. earlyService.ServeBackground(ctx)
  483. evLogger := events.NewLogger()
  484. earlyService.Add(evLogger)
  485. cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder, options.SkipPortProbing)
  486. if err != nil {
  487. l.Warnln("Failed to initialize config:", err)
  488. os.Exit(svcutil.ExitError.AsInt())
  489. }
  490. earlyService.Add(cfgWrapper)
  491. // Candidate builds should auto upgrade. Make sure the option is set,
  492. // unless we are in a build where it's disabled or the STNOUPGRADE
  493. // environment variable is set.
  494. if build.IsCandidate && !upgrade.DisabledByCompilation && !options.NoUpgrade {
  495. cfgWrapper.Modify(func(cfg *config.Configuration) {
  496. l.Infoln("Automatic upgrade is always enabled for candidate releases.")
  497. if cfg.Options.AutoUpgradeIntervalH == 0 || cfg.Options.AutoUpgradeIntervalH > 24 {
  498. cfg.Options.AutoUpgradeIntervalH = 12
  499. // Set the option into the config as well, as the auto upgrade
  500. // loop expects to read a valid interval from there.
  501. }
  502. // We don't tweak the user's choice of upgrading to pre-releases or
  503. // not, as otherwise they cannot step off the candidate channel.
  504. })
  505. }
  506. dbFile := locations.Get(locations.Database)
  507. ldb, err := syncthing.OpenDBBackend(dbFile, cfgWrapper.Options().DatabaseTuning)
  508. if err != nil {
  509. l.Warnln("Error opening database:", err)
  510. os.Exit(1)
  511. }
  512. // Check if auto-upgrades is possible, and if yes, and it's enabled do an initial
  513. // upgrade immediately. The auto-upgrade routine can only be started
  514. // later after App is initialised.
  515. autoUpgradePossible := autoUpgradePossible(options)
  516. if autoUpgradePossible && cfgWrapper.Options().AutoUpgradeEnabled() {
  517. // try to do upgrade directly and log the error if relevant.
  518. release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb))
  519. if err == nil {
  520. err = upgrade.To(release)
  521. }
  522. if err != nil {
  523. if _, ok := err.(*errNoUpgrade); ok || err == errTooEarlyUpgradeCheck || err == errTooEarlyUpgrade {
  524. l.Debugln("Initial automatic upgrade:", err)
  525. } else {
  526. l.Infoln("Initial automatic upgrade:", err)
  527. }
  528. } else {
  529. l.Infof("Upgraded to %q, exiting now.", release.Tag)
  530. os.Exit(svcutil.ExitUpgrade.AsInt())
  531. }
  532. }
  533. if options.Unpaused {
  534. setPauseState(cfgWrapper, false)
  535. } else if options.Paused {
  536. setPauseState(cfgWrapper, true)
  537. }
  538. appOpts := syncthing.Options{
  539. DeadlockTimeoutS: options.DebugDeadlockTimeout,
  540. NoUpgrade: options.NoUpgrade,
  541. ProfilerAddr: options.DebugProfilerListen,
  542. ResetDeltaIdxs: options.DebugResetDeltaIdxs,
  543. Verbose: options.Verbose,
  544. DBRecheckInterval: options.DebugDBRecheckInterval,
  545. DBIndirectGCInterval: options.DebugDBIndirectGCInterval,
  546. }
  547. if options.Audit {
  548. appOpts.AuditWriter = auditWriter(options.AuditFile)
  549. }
  550. if t := os.Getenv("STDEADLOCKTIMEOUT"); t != "" {
  551. secs, _ := strconv.Atoi(t)
  552. appOpts.DeadlockTimeoutS = secs
  553. }
  554. if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
  555. appOpts.DBRecheckInterval = dur
  556. }
  557. if dur, err := time.ParseDuration(os.Getenv("STGCINDIRECTEVERY")); err == nil {
  558. appOpts.DBIndirectGCInterval = dur
  559. }
  560. app, err := syncthing.New(cfgWrapper, ldb, evLogger, cert, appOpts)
  561. if err != nil {
  562. l.Warnln("Failed to start Syncthing:", err)
  563. os.Exit(svcutil.ExitError.AsInt())
  564. }
  565. if autoUpgradePossible {
  566. go autoUpgrade(cfgWrapper, app, evLogger)
  567. }
  568. setupSignalHandling(app)
  569. if os.Getenv("GOMAXPROCS") == "" {
  570. runtime.GOMAXPROCS(runtime.NumCPU())
  571. }
  572. if options.DebugProfileCPU {
  573. f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
  574. if err != nil {
  575. l.Warnln("Creating profile:", err)
  576. os.Exit(svcutil.ExitError.AsInt())
  577. }
  578. if err := pprof.StartCPUProfile(f); err != nil {
  579. l.Warnln("Starting profile:", err)
  580. os.Exit(svcutil.ExitError.AsInt())
  581. }
  582. }
  583. if err := app.Start(); err != nil {
  584. os.Exit(svcutil.ExitError.AsInt())
  585. }
  586. cleanConfigDirectory()
  587. if cfgWrapper.Options().StartBrowser && !options.NoBrowser && !options.InternalRestarting {
  588. // Can potentially block if the utility we are invoking doesn't
  589. // fork, and just execs, hence keep it in its own routine.
  590. go func() { _ = openURL(cfgWrapper.GUI().URL()) }()
  591. }
  592. status := app.Wait()
  593. if status == svcutil.ExitError {
  594. l.Warnln("Syncthing stopped with error:", app.Error())
  595. }
  596. if options.DebugProfileCPU {
  597. pprof.StopCPUProfile()
  598. }
  599. os.Exit(int(status))
  600. }
  601. func setupSignalHandling(app *syncthing.App) {
  602. // Exit cleanly with "restarting" code on SIGHUP.
  603. restartSign := make(chan os.Signal, 1)
  604. sigHup := syscall.Signal(1)
  605. signal.Notify(restartSign, sigHup)
  606. go func() {
  607. <-restartSign
  608. app.Stop(svcutil.ExitRestart)
  609. }()
  610. // Exit with "success" code (no restart) on INT/TERM
  611. stopSign := make(chan os.Signal, 1)
  612. signal.Notify(stopSign, os.Interrupt, sigTerm)
  613. go func() {
  614. <-stopSign
  615. app.Stop(svcutil.ExitSuccess)
  616. }()
  617. }
  618. // loadOrDefaultConfig creates a temporary, minimal configuration wrapper if no file
  619. // exists. As it disregards some command-line options, that should never be persisted.
  620. func loadOrDefaultConfig() (config.Wrapper, error) {
  621. cfgFile := locations.Get(locations.ConfigFile)
  622. cfg, _, err := config.Load(cfgFile, protocol.EmptyDeviceID, events.NoopLogger)
  623. if err != nil {
  624. newCfg := config.New(protocol.EmptyDeviceID)
  625. return config.Wrap(cfgFile, newCfg, protocol.EmptyDeviceID, events.NoopLogger), nil
  626. }
  627. return cfg, err
  628. }
  629. func auditWriter(auditFile string) io.Writer {
  630. var fd io.Writer
  631. var err error
  632. var auditDest string
  633. var auditFlags int
  634. if auditFile == "-" {
  635. fd = os.Stdout
  636. auditDest = "stdout"
  637. } else if auditFile == "--" {
  638. fd = os.Stderr
  639. auditDest = "stderr"
  640. } else {
  641. if auditFile == "" {
  642. auditFile = locations.GetTimestamped(locations.AuditLog)
  643. auditFlags = os.O_WRONLY | os.O_CREATE | os.O_EXCL
  644. } else {
  645. auditFlags = os.O_WRONLY | os.O_CREATE | os.O_APPEND
  646. }
  647. fd, err = os.OpenFile(auditFile, auditFlags, 0o600)
  648. if err != nil {
  649. l.Warnln("Audit:", err)
  650. os.Exit(svcutil.ExitError.AsInt())
  651. }
  652. auditDest = auditFile
  653. }
  654. l.Infoln("Audit log in", auditDest)
  655. return fd
  656. }
  657. func resetDB() error {
  658. return os.RemoveAll(locations.Get(locations.Database))
  659. }
  660. func autoUpgradePossible(options serveOptions) bool {
  661. if upgrade.DisabledByCompilation {
  662. return false
  663. }
  664. if options.NoUpgrade {
  665. l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
  666. return false
  667. }
  668. return true
  669. }
  670. func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) {
  671. timer := time.NewTimer(upgradeCheckInterval)
  672. sub := evLogger.Subscribe(events.DeviceConnected)
  673. for {
  674. select {
  675. case event := <-sub.C():
  676. data, ok := event.Data.(map[string]string)
  677. if !ok || data["clientName"] != "syncthing" || upgrade.CompareVersions(data["clientVersion"], build.Version) != upgrade.Newer {
  678. continue
  679. }
  680. if cfg.Options().AutoUpgradeEnabled() {
  681. l.Infof("Connected to device %s with a newer version (current %q < remote %q). Checking for upgrades.", data["id"], build.Version, data["clientVersion"])
  682. }
  683. case <-timer.C:
  684. }
  685. opts := cfg.Options()
  686. if !opts.AutoUpgradeEnabled() {
  687. timer.Reset(upgradeCheckInterval)
  688. continue
  689. }
  690. checkInterval := time.Duration(opts.AutoUpgradeIntervalH) * time.Hour
  691. rel, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
  692. if err == upgrade.ErrUpgradeUnsupported {
  693. sub.Unsubscribe()
  694. return
  695. }
  696. if err != nil {
  697. // Don't complain too loudly here; we might simply not have
  698. // internet connectivity, or the upgrade server might be down.
  699. l.Infoln("Automatic upgrade:", err)
  700. timer.Reset(checkInterval)
  701. continue
  702. }
  703. if upgrade.CompareVersions(rel.Tag, build.Version) != upgrade.Newer {
  704. // Skip equal, older or majorly newer (incompatible) versions
  705. timer.Reset(checkInterval)
  706. continue
  707. }
  708. l.Infof("Automatic upgrade (current %q < latest %q)", build.Version, rel.Tag)
  709. err = upgrade.To(rel)
  710. if err != nil {
  711. l.Warnln("Automatic upgrade:", err)
  712. timer.Reset(checkInterval)
  713. continue
  714. }
  715. sub.Unsubscribe()
  716. l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
  717. time.Sleep(time.Minute)
  718. app.Stop(svcutil.ExitUpgrade)
  719. return
  720. }
  721. }
  722. func initialAutoUpgradeCheck(misc *db.NamespacedKV) (upgrade.Release, error) {
  723. if last, ok, err := misc.Time(upgradeCheckKey); err == nil && ok && time.Since(last) < upgradeCheckInterval {
  724. return upgrade.Release{}, errTooEarlyUpgradeCheck
  725. }
  726. _ = misc.PutTime(upgradeCheckKey, time.Now())
  727. release, err := checkUpgrade()
  728. if err != nil {
  729. return upgrade.Release{}, err
  730. }
  731. if lastVersion, ok, err := misc.String(upgradeVersionKey); err == nil && ok && lastVersion == release.Tag {
  732. // Only check time if we try to upgrade to the same release.
  733. if lastTime, ok, err := misc.Time(upgradeTimeKey); err == nil && ok && time.Since(lastTime) < upgradeRetryInterval {
  734. return upgrade.Release{}, errTooEarlyUpgrade
  735. }
  736. }
  737. _ = misc.PutString(upgradeVersionKey, release.Tag)
  738. _ = misc.PutTime(upgradeTimeKey, time.Now())
  739. return release, nil
  740. }
  741. // cleanConfigDirectory removes old, unused configuration and index formats, a
  742. // suitable time after they have gone out of fashion.
  743. func cleanConfigDirectory() {
  744. patterns := map[string]time.Duration{
  745. "panic-*.log": 7 * 24 * time.Hour, // keep panic logs for a week
  746. "audit-*.log": 7 * 24 * time.Hour, // keep audit logs for a week
  747. "index": 14 * 24 * time.Hour, // keep old index format for two weeks
  748. "index-v0.11.0.db": 14 * 24 * time.Hour, // keep old index format for two weeks
  749. "index-v0.13.0.db": 14 * 24 * time.Hour, // keep old index format for two weeks
  750. "index*.converted": 14 * 24 * time.Hour, // keep old converted indexes for two weeks
  751. "config.xml.v*": 30 * 24 * time.Hour, // old config versions for a month
  752. "*.idx.gz": 30 * 24 * time.Hour, // these should for sure no longer exist
  753. "backup-of-v0.8": 30 * 24 * time.Hour, // these neither
  754. "tmp-index-sorter.*": time.Minute, // these should never exist on startup
  755. "support-bundle-*": 30 * 24 * time.Hour, // keep old support bundle zip or folder for a month
  756. }
  757. for pat, dur := range patterns {
  758. fs := fs.NewFilesystem(fs.FilesystemTypeBasic, locations.GetBaseDir(locations.ConfigBaseDir))
  759. files, err := fs.Glob(pat)
  760. if err != nil {
  761. l.Infoln("Cleaning:", err)
  762. continue
  763. }
  764. for _, file := range files {
  765. info, err := fs.Lstat(file)
  766. if err != nil {
  767. l.Infoln("Cleaning:", err)
  768. continue
  769. }
  770. if time.Since(info.ModTime()) > dur {
  771. if err = fs.RemoveAll(file); err != nil {
  772. l.Infoln("Cleaning:", err)
  773. } else {
  774. l.Infoln("Cleaned away old file", filepath.Base(file))
  775. }
  776. }
  777. }
  778. }
  779. }
  780. func setPauseState(cfgWrapper config.Wrapper, paused bool) {
  781. _, err := cfgWrapper.Modify(func(cfg *config.Configuration) {
  782. for i := range cfg.Devices {
  783. cfg.Devices[i].Paused = paused
  784. }
  785. for i := range cfg.Folders {
  786. cfg.Folders[i].Paused = paused
  787. }
  788. })
  789. if err != nil {
  790. l.Warnln("Cannot adjust paused state:", err)
  791. os.Exit(svcutil.ExitError.AsInt())
  792. }
  793. }
  794. func exitCodeForUpgrade(err error) int {
  795. if _, ok := err.(*errNoUpgrade); ok {
  796. return svcutil.ExitNoUpgradeAvailable.AsInt()
  797. }
  798. return svcutil.ExitError.AsInt()
  799. }
  800. // convertLegacyArgs returns the slice of arguments with single dash long
  801. // flags converted to double dash long flags.
  802. func convertLegacyArgs(args []string) []string {
  803. // Legacy args begin with a single dash, followed by two or more characters.
  804. legacyExp := regexp.MustCompile(`^-\w{2,}`)
  805. res := make([]string, len(args))
  806. for i, arg := range args {
  807. if legacyExp.MatchString(arg) {
  808. res[i] = "-" + arg
  809. } else {
  810. res[i] = arg
  811. }
  812. }
  813. return res
  814. }