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