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