| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000 |
- // Copyright (C) 2014 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package main
- import (
- "bytes"
- "crypto/tls"
- "flag"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net/http"
- _ "net/http/pprof" // Need to import this to support STPROFILER.
- "net/url"
- "os"
- "os/signal"
- "path"
- "path/filepath"
- "runtime"
- "runtime/pprof"
- "sort"
- "strconv"
- "syscall"
- "time"
- "github.com/syncthing/syncthing/lib/build"
- "github.com/syncthing/syncthing/lib/config"
- "github.com/syncthing/syncthing/lib/db"
- "github.com/syncthing/syncthing/lib/dialer"
- "github.com/syncthing/syncthing/lib/events"
- "github.com/syncthing/syncthing/lib/fs"
- "github.com/syncthing/syncthing/lib/locations"
- "github.com/syncthing/syncthing/lib/logger"
- "github.com/syncthing/syncthing/lib/osutil"
- "github.com/syncthing/syncthing/lib/protocol"
- "github.com/syncthing/syncthing/lib/syncthing"
- "github.com/syncthing/syncthing/lib/tlsutil"
- "github.com/syncthing/syncthing/lib/upgrade"
- "github.com/pkg/errors"
- )
- const (
- tlsDefaultCommonName = "syncthing"
- deviceCertLifetimeDays = 20 * 365
- sigTerm = syscall.Signal(15)
- )
- const (
- usage = "syncthing [options]"
- extraUsage = `
- The -logflags value is a sum of the following:
- 1 Date
- 2 Time
- 4 Microsecond time
- 8 Long filename
- 16 Short filename
- 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
- 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
- --------------------
- The following environment variables modify Syncthing's behavior in ways that
- are mostly useful for developers. Use with care.
- STNODEFAULTFOLDER Don't create a default folder when starting for the first
- time. This variable will be ignored anytime after the first
- run.
- STGUIASSETS Directory to load GUI assets from. Overrides compiled in
- assets.
- STTRACE A comma separated string of facilities to trace. The valid
- facility strings listed below.
- STPROFILER Set to a listen address such as "127.0.0.1:9090" to start
- the profiler with HTTP access.
- STCPUPROFILE Write a CPU profile to cpu-$pid.pprof on exit.
- STHEAPPROFILE Write heap profiles to heap-$pid-$timestamp.pprof each time
- heap usage increases.
- STBLOCKPROFILE Write block profiles to block-$pid-$timestamp.pprof every 20
- seconds.
- STPERFSTATS Write running performance statistics to perf-$pid.csv. Not
- supported on Windows.
- STDEADLOCKTIMEOUT Used for debugging internal deadlocks; sets debug
- sensitivity. Use only under direction of a developer.
- STLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
- sensitivity. Use only under direction of a developer.
- STNORESTART Equivalent to the -no-restart argument.
- STNOUPGRADE Disable automatic upgrades.
- STHASHING Select the SHA256 hashing package to use. Possible values
- are "standard" for the Go standard library implementation,
- "minio" for the github.com/minio/sha256-simd implementation,
- and blank (the default) for auto detection.
- STRECHECKDBEVERY Set to a time interval to override the default database
- check interval of 30 days (720h). The interval understands
- "h", "m" and "s" abbreviations for hours minutes and seconds.
- Valid values are like "720h", "30s", etc.
- STGCINDIRECTEVERY Set to a time interval to override the default database
- indirection GC interval of 13 hours. Same format as the
- STRECHECKDBEVERY variable.
- GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
- available CPU cores.
- GOGC Percentage of heap growth at which to trigger GC. Default is
- 100. Lower numbers keep peak memory usage down, at the price
- of CPU usage (i.e. performance).
- Debugging Facilities
- --------------------
- The following are valid values for the STTRACE variable:
- %s`
- )
- var (
- // Environment options
- innerProcess = os.Getenv("STMONITORED") != ""
- noDefaultFolder = os.Getenv("STNODEFAULTFOLDER") != ""
- upgradeCheckInterval = 5 * time.Minute
- upgradeRetryInterval = time.Hour
- upgradeCheckKey = "lastUpgradeCheck"
- upgradeTimeKey = "lastUpgradeTime"
- upgradeVersionKey = "lastUpgradeVersion"
- errConcurrentUpgrade = errors.New("upgrade prevented by other running Syncthing instance")
- errTooEarlyUpgradeCheck = fmt.Errorf("last upgrade check happened less than %v ago, skipping", upgradeCheckInterval)
- errTooEarlyUpgrade = fmt.Errorf("last upgrade happened less than %v ago, skipping", upgradeRetryInterval)
- )
- type RuntimeOptions struct {
- syncthing.Options
- homeDir string
- confDir string
- dataDir string
- resetDatabase bool
- showVersion bool
- showPaths bool
- showDeviceId bool
- doUpgrade bool
- doUpgradeCheck bool
- upgradeTo string
- noBrowser bool
- browserOnly bool
- hideConsole bool
- logFile string
- logMaxSize int
- logMaxFiles int
- auditEnabled bool
- auditFile string
- paused bool
- unpaused bool
- guiAddress string
- guiAPIKey string
- generateDir string
- noRestart bool
- cpuProfile bool
- stRestarting bool
- logFlags int
- showHelp bool
- allowNewerConfig bool
- }
- func defaultRuntimeOptions() RuntimeOptions {
- options := RuntimeOptions{
- Options: syncthing.Options{
- AssetDir: os.Getenv("STGUIASSETS"),
- NoUpgrade: os.Getenv("STNOUPGRADE") != "",
- ProfilerURL: os.Getenv("STPROFILER"),
- },
- noRestart: os.Getenv("STNORESTART") != "",
- cpuProfile: os.Getenv("STCPUPROFILE") != "",
- stRestarting: os.Getenv("STRESTART") != "",
- logFlags: log.Ltime,
- logMaxSize: 10 << 20, // 10 MiB
- logMaxFiles: 3, // plus the current one
- }
- if os.Getenv("STTRACE") != "" {
- options.logFlags = logger.DebugFlags
- }
- // On non-Windows, we explicitly default to "-" which means stdout. On
- // Windows, the "default" options.logFile will later be replaced with the
- // default path, unless the user has manually specified "-" or
- // something else.
- if runtime.GOOS == "windows" {
- options.logFile = "default"
- } else {
- options.logFile = "-"
- }
- return options
- }
- func parseCommandLineOptions() RuntimeOptions {
- options := defaultRuntimeOptions()
- 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.guiAPIKey, "gui-apikey", options.guiAPIKey, "Override GUI API key")
- 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.BoolVar(&options.noBrowser, "no-browser", false, "Do not start browser")
- flag.BoolVar(&options.browserOnly, "browser-only", false, "Open GUI in browser")
- flag.BoolVar(&options.noRestart, "no-restart", options.noRestart, "Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash")
- flag.BoolVar(&options.resetDatabase, "reset-database", false, "Reset the database, forcing a full rescan and resync")
- flag.BoolVar(&options.ResetDeltaIdxs, "reset-deltas", false, "Reset delta index IDs, forcing a full index exchange")
- flag.BoolVar(&options.doUpgrade, "upgrade", false, "Perform upgrade")
- flag.BoolVar(&options.doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
- flag.BoolVar(&options.showVersion, "version", false, "Show version")
- flag.BoolVar(&options.showHelp, "help", false, "Show this help")
- flag.BoolVar(&options.showPaths, "paths", false, "Show configuration paths")
- flag.BoolVar(&options.showDeviceId, "device-id", false, "Show the device ID")
- flag.StringVar(&options.upgradeTo, "upgrade-to", options.upgradeTo, "Force upgrade directly from specified URL")
- flag.BoolVar(&options.auditEnabled, "audit", false, "Write events to audit file")
- 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.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
- 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.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.BoolVar(&options.allowNewerConfig, "allow-newer-config", false, "Allow loading newer than current config version")
- if runtime.GOOS == "windows" {
- // Allow user to hide the console window
- flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
- }
- longUsage := fmt.Sprintf(extraUsage, debugFacilities())
- flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
- flag.Parse()
- if len(flag.Args()) > 0 {
- flag.Usage()
- os.Exit(2)
- }
- 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() {
- options := parseCommandLineOptions()
- l.SetFlags(options.logFlags)
- if options.guiAddress != "" {
- // The config picks this up from the environment.
- os.Setenv("STGUIADDRESS", options.guiAddress)
- }
- if options.guiAPIKey != "" {
- // The config picks this up from the environment.
- os.Setenv("STGUIAPIKEY", options.guiAPIKey)
- }
- if options.hideConsole {
- osutil.HideConsole()
- }
- // Not set as default above because the strings can be really long.
- var err error
- homeSet := options.homeDir != ""
- confSet := options.confDir != ""
- dataSet := options.dataDir != ""
- switch {
- case dataSet != confSet:
- 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)
- }
- case dataSet:
- if err = setLocation(locations.ConfigBaseDir, options.confDir); err == nil {
- err = setLocation(locations.DataBaseDir, options.dataDir)
- }
- }
- if err != nil {
- l.Warnln("Command line options:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- if options.logFile == "default" || options.logFile == "" {
- // We must set this *after* expandLocations above.
- // Handling an empty value is for backwards compatibility (<1.4.1).
- options.logFile = locations.Get(locations.LogFile)
- }
- if options.AssetDir == "" {
- // The asset dir is blank if STGUIASSETS wasn't set, in which case we
- // should look for extra assets in the default place.
- options.AssetDir = locations.Get(locations.GUIAssets)
- }
- if options.showVersion {
- fmt.Println(build.LongVersion)
- return
- }
- if options.showHelp {
- flag.Usage()
- return
- }
- if options.showPaths {
- showPaths(options)
- return
- }
- if options.showDeviceId {
- cert, err := tls.LoadX509KeyPair(
- locations.Get(locations.CertFile),
- locations.Get(locations.KeyFile),
- )
- if err != nil {
- l.Warnln("Error reading device ID:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
- return
- }
- if options.browserOnly {
- if err := openGUI(protocol.EmptyDeviceID); err != nil {
- l.Warnln("Failed to open web UI:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- return
- }
- if options.generateDir != "" {
- if err := generate(options.generateDir); err != nil {
- l.Warnln("Failed to generate config and keys:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- return
- }
- // Ensure that our home directory exists.
- if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
- l.Warnln("Failure on home directory:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- if options.upgradeTo != "" {
- err := upgrade.ToURL(options.upgradeTo)
- if err != nil {
- l.Warnln("Error while Upgrading:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- l.Infoln("Upgraded from", options.upgradeTo)
- return
- }
- if options.doUpgradeCheck {
- if _, err := checkUpgrade(); err != nil {
- l.Warnln("Checking for upgrade:", err)
- os.Exit(exitCodeForUpgrade(err))
- }
- return
- }
- if options.doUpgrade {
- release, err := checkUpgrade()
- if err == nil {
- // Use leveldb database locks to protect against concurrent upgrades
- ldb, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
- if err != nil {
- err = upgradeViaRest()
- } else {
- _ = ldb.Close()
- err = upgrade.To(release)
- }
- }
- if err != nil {
- l.Warnln("Upgrade:", err)
- os.Exit(exitCodeForUpgrade(err))
- }
- l.Infof("Upgraded to %q", release.Tag)
- os.Exit(syncthing.ExitUpgrade.AsInt())
- }
- if options.resetDatabase {
- if err := resetDB(); err != nil {
- l.Warnln("Resetting database:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- l.Infoln("Successfully reset database - it will be rebuilt after next start.")
- return
- }
- if innerProcess {
- syncthingMain(options)
- } else {
- monitorMain(options)
- }
- }
- func openGUI(myID protocol.DeviceID) error {
- cfg, err := loadOrDefaultConfig(myID, events.NoopLogger)
- if err != nil {
- return err
- }
- if cfg.GUI().Enabled {
- if err := openURL(cfg.GUI().URL()); err != nil {
- return err
- }
- } else {
- l.Warnln("Browser: GUI is currently disabled")
- }
- return nil
- }
- func generate(generateDir string) error {
- dir, err := fs.ExpandTilde(generateDir)
- if err != nil {
- return err
- }
- if err := ensureDir(dir, 0700); err != nil {
- return err
- }
- var myID protocol.DeviceID
- certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")
- cert, err := tls.LoadX509KeyPair(certFile, keyFile)
- if err == nil {
- l.Warnln("Key exists; will not overwrite.")
- } else {
- cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
- if err != nil {
- return errors.Wrap(err, "create certificate")
- }
- }
- myID = protocol.NewDeviceID(cert.Certificate[0])
- l.Infoln("Device ID:", myID)
- cfgFile := filepath.Join(dir, "config.xml")
- if _, err := os.Stat(cfgFile); err == nil {
- l.Warnln("Config exists; will not overwrite.")
- return nil
- }
- cfg, err := syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder)
- if err != nil {
- return err
- }
- err = cfg.Save()
- if err != nil {
- return errors.Wrap(err, "save config")
- }
- return nil
- }
- func debugFacilities() string {
- facilities := l.Facilities()
- // Get a sorted list of names
- var names []string
- maxLen := 0
- for name := range facilities {
- names = append(names, name)
- if len(name) > maxLen {
- maxLen = len(name)
- }
- }
- sort.Strings(names)
- // Format the choices
- b := new(bytes.Buffer)
- for _, name := range names {
- fmt.Fprintf(b, " %-*s - %s\n", maxLen, name, facilities[name])
- }
- return b.String()
- }
- type errNoUpgrade struct {
- current, latest string
- }
- func (e errNoUpgrade) Error() string {
- return fmt.Sprintf("no upgrade available (current %q >= latest %q).", e.current, e.latest)
- }
- func checkUpgrade() (upgrade.Release, error) {
- cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
- opts := cfg.Options()
- release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
- if err != nil {
- return upgrade.Release{}, err
- }
- if upgrade.CompareVersions(release.Tag, build.Version) <= 0 {
- return upgrade.Release{}, errNoUpgrade{build.Version, release.Tag}
- }
- l.Infof("Upgrade available (current %q < latest %q)", build.Version, release.Tag)
- return release, nil
- }
- func upgradeViaRest() error {
- cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
- u, err := url.Parse(cfg.GUI().URL())
- if err != nil {
- return err
- }
- u.Path = path.Join(u.Path, "rest/system/upgrade")
- target := u.String()
- r, _ := http.NewRequest("POST", target, nil)
- r.Header.Set("X-API-Key", cfg.GUI().APIKey)
- tr := &http.Transport{
- DialContext: dialer.DialContext,
- Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- }
- client := &http.Client{
- Transport: tr,
- Timeout: 60 * time.Second,
- }
- resp, err := client.Do(r)
- if err != nil {
- return err
- }
- if resp.StatusCode != 200 {
- bs, err := ioutil.ReadAll(resp.Body)
- defer resp.Body.Close()
- if err != nil {
- return err
- }
- return errors.New(string(bs))
- }
- return err
- }
- func syncthingMain(runtimeOptions RuntimeOptions) {
- // Set a log prefix similar to the ID we will have later on, or early log
- // lines look ugly.
- l.SetPrefix("[start] ")
- // Print our version information up front, so any crash that happens
- // early etc. will have it available.
- l.Infoln(build.LongVersion)
- // Ensure that we have a certificate and key.
- cert, err := syncthing.LoadOrGenerateCertificate(
- locations.Get(locations.CertFile),
- locations.Get(locations.KeyFile),
- )
- if err != nil {
- l.Warnln("Failed to load/generate certificate:", err)
- os.Exit(1)
- }
- evLogger := events.NewLogger()
- go evLogger.Serve()
- defer evLogger.Stop()
- cfg, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
- if err != nil {
- l.Warnln("Failed to initialize config:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- // Candidate builds should auto upgrade. Make sure the option is set,
- // unless we are in a build where it's disabled or the STNOUPGRADE
- // environment variable is set.
- if build.IsCandidate && !upgrade.DisabledByCompilation && !runtimeOptions.NoUpgrade {
- l.Infoln("Automatic upgrade is always enabled for candidate releases.")
- if opts := cfg.Options(); opts.AutoUpgradeIntervalH == 0 || opts.AutoUpgradeIntervalH > 24 {
- opts.AutoUpgradeIntervalH = 12
- // Set the option into the config as well, as the auto upgrade
- // loop expects to read a valid interval from there.
- cfg.SetOptions(opts)
- cfg.Save()
- }
- // We don't tweak the user's choice of upgrading to pre-releases or
- // not, as otherwise they cannot step off the candidate channel.
- }
- dbFile := locations.Get(locations.Database)
- ldb, err := syncthing.OpenDBBackend(dbFile, cfg.Options().DatabaseTuning)
- if err != nil {
- l.Warnln("Error opening database:", err)
- os.Exit(1)
- }
- // Check if auto-upgrades should be done and if yes, do an initial
- // upgrade immedately. The auto-upgrade routine can only be started
- // later after App is initialised.
- shouldAutoUpgrade := shouldUpgrade(cfg, runtimeOptions)
- if shouldAutoUpgrade {
- // try to do upgrade directly and log the error if relevant.
- release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb))
- if err == nil {
- err = upgrade.To(release)
- }
- if err != nil {
- if _, ok := err.(errNoUpgrade); ok || err == errTooEarlyUpgradeCheck || err == errTooEarlyUpgrade {
- l.Debugln("Initial automatic upgrade:", err)
- } else {
- l.Infoln("Initial automatic upgrade:", err)
- }
- } else {
- l.Infof("Upgraded to %q, exiting now.", release.Tag)
- os.Exit(syncthing.ExitUpgrade.AsInt())
- }
- }
- if runtimeOptions.unpaused {
- setPauseState(cfg, false)
- } else if runtimeOptions.paused {
- setPauseState(cfg, true)
- }
- appOpts := runtimeOptions.Options
- if runtimeOptions.auditEnabled {
- appOpts.AuditWriter = auditWriter(runtimeOptions.auditFile)
- }
- if t := os.Getenv("STDEADLOCKTIMEOUT"); t != "" {
- secs, _ := strconv.Atoi(t)
- appOpts.DeadlockTimeoutS = secs
- }
- if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
- appOpts.DBRecheckInterval = dur
- }
- if dur, err := time.ParseDuration(os.Getenv("STGCINDIRECTEVERY")); err == nil {
- appOpts.DBIndirectGCInterval = dur
- }
- app := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
- if shouldAutoUpgrade {
- go autoUpgrade(cfg, app, evLogger)
- }
- setupSignalHandling(app)
- if len(os.Getenv("GOMAXPROCS")) == 0 {
- runtime.GOMAXPROCS(runtime.NumCPU())
- }
- if runtimeOptions.cpuProfile {
- f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
- if err != nil {
- l.Warnln("Creating profile:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- if err := pprof.StartCPUProfile(f); err != nil {
- l.Warnln("Starting profile:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- }
- if opts := cfg.Options(); opts.RestartOnWakeup {
- go standbyMonitor(app)
- }
- if err := app.Start(); err != nil {
- os.Exit(syncthing.ExitError.AsInt())
- }
- cleanConfigDirectory()
- if cfg.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting {
- // Can potentially block if the utility we are invoking doesn't
- // fork, and just execs, hence keep it in its own routine.
- go func() { _ = openURL(cfg.GUI().URL()) }()
- }
- status := app.Wait()
- if runtimeOptions.cpuProfile {
- pprof.StopCPUProfile()
- }
- os.Exit(int(status))
- }
- func setupSignalHandling(app *syncthing.App) {
- // Exit cleanly with "restarting" code on SIGHUP.
- restartSign := make(chan os.Signal, 1)
- sigHup := syscall.Signal(1)
- signal.Notify(restartSign, sigHup)
- go func() {
- <-restartSign
- app.Stop(syncthing.ExitRestart)
- }()
- // Exit with "success" code (no restart) on INT/TERM
- stopSign := make(chan os.Signal, 1)
- signal.Notify(stopSign, os.Interrupt, sigTerm)
- go func() {
- <-stopSign
- app.Stop(syncthing.ExitSuccess)
- }()
- }
- func loadOrDefaultConfig(myID protocol.DeviceID, evLogger events.Logger) (config.Wrapper, error) {
- cfgFile := locations.Get(locations.ConfigFile)
- cfg, err := config.Load(cfgFile, myID, evLogger)
- if err != nil {
- cfg, err = syncthing.DefaultConfig(cfgFile, myID, evLogger, noDefaultFolder)
- }
- return cfg, err
- }
- func auditWriter(auditFile string) io.Writer {
- var fd io.Writer
- var err error
- var auditDest string
- var auditFlags int
- if auditFile == "-" {
- fd = os.Stdout
- auditDest = "stdout"
- } else if auditFile == "--" {
- fd = os.Stderr
- auditDest = "stderr"
- } else {
- if auditFile == "" {
- auditFile = locations.GetTimestamped(locations.AuditLog)
- auditFlags = os.O_WRONLY | os.O_CREATE | os.O_EXCL
- } else {
- auditFlags = os.O_WRONLY | os.O_CREATE | os.O_APPEND
- }
- fd, err = os.OpenFile(auditFile, auditFlags, 0600)
- if err != nil {
- l.Warnln("Audit:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- auditDest = auditFile
- }
- l.Infoln("Audit log in", auditDest)
- return fd
- }
- func resetDB() error {
- return os.RemoveAll(locations.Get(locations.Database))
- }
- func ensureDir(dir string, mode fs.FileMode) error {
- fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
- err := fs.MkdirAll(".", mode)
- if err != nil {
- return err
- }
- if fi, err := fs.Stat("."); err == nil {
- // Apprently the stat may fail even though the mkdirall passed. If it
- // does, we'll just assume things are in order and let other things
- // fail (like loading or creating the config...).
- currentMode := fi.Mode() & 0777
- if currentMode != mode {
- err := fs.Chmod(".", mode)
- // This can fail on crappy filesystems, nothing we can do about it.
- if err != nil {
- l.Warnln(err)
- }
- }
- }
- return nil
- }
- func standbyMonitor(app *syncthing.App) {
- restartDelay := 60 * time.Second
- now := time.Now()
- for {
- time.Sleep(10 * time.Second)
- if time.Since(now) > 2*time.Minute {
- l.Infof("Paused state detected, possibly woke up from standby. Restarting in %v.", restartDelay)
- // We most likely just woke from standby. If we restart
- // immediately chances are we won't have networking ready. Give
- // things a moment to stabilize.
- time.Sleep(restartDelay)
- app.Stop(syncthing.ExitRestart)
- return
- }
- now = time.Now()
- }
- }
- func shouldUpgrade(cfg config.Wrapper, runtimeOptions RuntimeOptions) bool {
- if upgrade.DisabledByCompilation {
- return false
- }
- if opts := cfg.Options(); opts.AutoUpgradeIntervalH < 0 {
- return false
- }
- if runtimeOptions.NoUpgrade {
- l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
- return false
- }
- return true
- }
- func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) {
- timer := time.NewTimer(upgradeCheckInterval)
- sub := evLogger.Subscribe(events.DeviceConnected)
- for {
- select {
- case event := <-sub.C():
- data, ok := event.Data.(map[string]string)
- if !ok || data["clientName"] != "syncthing" || upgrade.CompareVersions(data["clientVersion"], build.Version) != upgrade.Newer {
- continue
- }
- l.Infof("Connected to device %s with a newer version (current %q < remote %q). Checking for upgrades.", data["id"], build.Version, data["clientVersion"])
- case <-timer.C:
- }
- opts := cfg.Options()
- checkInterval := time.Duration(opts.AutoUpgradeIntervalH) * time.Hour
- if checkInterval < time.Hour {
- // We shouldn't be here if AutoUpgradeIntervalH < 1, but for
- // safety's sake.
- checkInterval = time.Hour
- }
- rel, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
- if err == upgrade.ErrUpgradeUnsupported {
- sub.Unsubscribe()
- return
- }
- if err != nil {
- // Don't complain too loudly here; we might simply not have
- // internet connectivity, or the upgrade server might be down.
- l.Infoln("Automatic upgrade:", err)
- timer.Reset(checkInterval)
- continue
- }
- if upgrade.CompareVersions(rel.Tag, build.Version) != upgrade.Newer {
- // Skip equal, older or majorly newer (incompatible) versions
- timer.Reset(checkInterval)
- continue
- }
- l.Infof("Automatic upgrade (current %q < latest %q)", build.Version, rel.Tag)
- err = upgrade.To(rel)
- if err != nil {
- l.Warnln("Automatic upgrade:", err)
- timer.Reset(checkInterval)
- continue
- }
- sub.Unsubscribe()
- l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
- time.Sleep(time.Minute)
- app.Stop(syncthing.ExitUpgrade)
- return
- }
- }
- func initialAutoUpgradeCheck(misc *db.NamespacedKV) (upgrade.Release, error) {
- if last, ok, err := misc.Time(upgradeCheckKey); err == nil && ok && time.Since(last) < upgradeCheckInterval {
- return upgrade.Release{}, errTooEarlyUpgradeCheck
- }
- _ = misc.PutTime(upgradeCheckKey, time.Now())
- release, err := checkUpgrade()
- if err != nil {
- return upgrade.Release{}, err
- }
- if lastVersion, ok, err := misc.String(upgradeVersionKey); err == nil && ok && lastVersion == release.Tag {
- // Only check time if we try to upgrade to the same release.
- if lastTime, ok, err := misc.Time(upgradeTimeKey); err == nil && ok && time.Since(lastTime) < upgradeRetryInterval {
- return upgrade.Release{}, errTooEarlyUpgrade
- }
- }
- _ = misc.PutString(upgradeVersionKey, release.Tag)
- _ = misc.PutTime(upgradeTimeKey, time.Now())
- return release, nil
- }
- // cleanConfigDirectory removes old, unused configuration and index formats, a
- // suitable time after they have gone out of fashion.
- func cleanConfigDirectory() {
- patterns := map[string]time.Duration{
- "panic-*.log": 7 * 24 * time.Hour, // keep panic logs for a week
- "audit-*.log": 7 * 24 * time.Hour, // keep audit logs for a week
- "index": 14 * 24 * time.Hour, // keep old index format for two weeks
- "index-v0.11.0.db": 14 * 24 * time.Hour, // keep old index format for two weeks
- "index-v0.13.0.db": 14 * 24 * time.Hour, // keep old index format for two weeks
- "index*.converted": 14 * 24 * time.Hour, // keep old converted indexes for two weeks
- "config.xml.v*": 30 * 24 * time.Hour, // old config versions for a month
- "*.idx.gz": 30 * 24 * time.Hour, // these should for sure no longer exist
- "backup-of-v0.8": 30 * 24 * time.Hour, // these neither
- "tmp-index-sorter.*": time.Minute, // these should never exist on startup
- "support-bundle-*": 30 * 24 * time.Hour, // keep old support bundle zip or folder for a month
- }
- for pat, dur := range patterns {
- fs := fs.NewFilesystem(fs.FilesystemTypeBasic, locations.GetBaseDir(locations.ConfigBaseDir))
- files, err := fs.Glob(pat)
- if err != nil {
- l.Infoln("Cleaning:", err)
- continue
- }
- for _, file := range files {
- info, err := fs.Lstat(file)
- if err != nil {
- l.Infoln("Cleaning:", err)
- continue
- }
- if time.Since(info.ModTime()) > dur {
- if err = fs.RemoveAll(file); err != nil {
- l.Infoln("Cleaning:", err)
- } else {
- l.Infoln("Cleaned away old file", filepath.Base(file))
- }
- }
- }
- }
- }
- func showPaths(options RuntimeOptions) {
- fmt.Printf("Configuration file:\n\t%s\n\n", locations.Get(locations.ConfigFile))
- fmt.Printf("Database directory:\n\t%s\n\n", locations.Get(locations.Database))
- fmt.Printf("Device private key & certificate files:\n\t%s\n\t%s\n\n", locations.Get(locations.KeyFile), locations.Get(locations.CertFile))
- fmt.Printf("HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", locations.Get(locations.HTTPSKeyFile), locations.Get(locations.HTTPSCertFile))
- fmt.Printf("Log file:\n\t%s\n\n", options.logFile)
- fmt.Printf("GUI override directory:\n\t%s\n\n", options.AssetDir)
- fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder))
- }
- func setPauseState(cfg config.Wrapper, paused bool) {
- raw := cfg.RawCopy()
- for i := range raw.Devices {
- raw.Devices[i].Paused = paused
- }
- for i := range raw.Folders {
- raw.Folders[i].Paused = paused
- }
- if _, err := cfg.Replace(raw); err != nil {
- l.Warnln("Cannot adjust paused state:", err)
- os.Exit(syncthing.ExitError.AsInt())
- }
- }
- func exitCodeForUpgrade(err error) int {
- if _, ok := err.(errNoUpgrade); ok {
- return syncthing.ExitNoUpgradeAvailable.AsInt()
- }
- return syncthing.ExitError.AsInt()
- }
|