func defaultConfig(myName string) config.Configuration { newCfg := config.New(myID) newCfg.Folders = []config.FolderConfiguration{ { ID: "default", RawPath: locations[locDefFolder], RescanIntervalS: 60, Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}}, }, } newCfg.Devices = []config.DeviceConfiguration{ { DeviceID: myID, Addresses: []string{"dynamic"}, Name: myName, }, } port, err := getFreePort("127.0.0.1", 8384) if err != nil { l.Fatalln("get free port (GUI):", err) } newCfg.GUI.Address = fmt.Sprintf("127.0.0.1:%d", port) port, err = getFreePort("0.0.0.0", 22000) if err != nil { l.Fatalln("get free port (BEP):", err) } newCfg.Options.ListenAddress = []string{fmt.Sprintf("0.0.0.0:%d", port)} return newCfg }
func TestDeviceRename(t *testing.T) { ccm := protocol.ClusterConfigMessage{ ClientName: "syncthing", ClientVersion: "v0.9.4", } defer os.Remove("tmpconfig.xml") rawCfg := config.New(device1) rawCfg.Devices = []config.DeviceConfiguration{ { DeviceID: device1, }, } cfg := config.Wrap("tmpconfig.xml", rawCfg) db, _ := leveldb.Open(storage.NewMemStorage(), nil) m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db) m.ServeBackground() if cfg.Devices()[device1].Name != "" { t.Errorf("Device already has a name") } m.ClusterConfig(device1, ccm) if cfg.Devices()[device1].Name != "" { t.Errorf("Device already has a name") } ccm.Options = []protocol.Option{ { Key: "name", Value: "tester", }, } m.ClusterConfig(device1, ccm) if cfg.Devices()[device1].Name != "tester" { t.Errorf("Device did not get a name") } ccm.Options[0].Value = "tester2" m.ClusterConfig(device1, ccm) if cfg.Devices()[device1].Name != "tester" { t.Errorf("Device name got overwritten") } cfgw, err := config.Load("tmpconfig.xml", protocol.LocalDeviceID) if err != nil { t.Error(err) return } if cfgw.Devices()[device1].Name != "tester" { t.Errorf("Device name not saved in config") } }
func TestDeviceRename(t *testing.T) { ccm := protocol.ClusterConfigMessage{ ClientName: "syncthing", ClientVersion: "v0.9.4", } cfg := config.New(device1) cfg.Devices = []config.DeviceConfiguration{ { DeviceID: device1, }, } db, _ := leveldb.Open(storage.NewMemStorage(), nil) m := NewModel(config.Wrap("/tmp/test", cfg), "device", "syncthing", "dev", db) if cfg.Devices[0].Name != "" { t.Errorf("Device already has a name") } m.ClusterConfig(device1, ccm) if cfg.Devices[0].Name != "" { t.Errorf("Device already has a name") } ccm.Options = []protocol.Option{ { Key: "name", Value: "tester", }, } m.ClusterConfig(device1, ccm) if cfg.Devices[0].Name != "tester" { t.Errorf("Device did not get a name") } ccm.Options[0].Value = "tester2" m.ClusterConfig(device1, ccm) if cfg.Devices[0].Name != "tester" { t.Errorf("Device name got overwritten") } }
func TestClusterConfig(t *testing.T) { cfg := config.New(device1) cfg.Devices = []config.DeviceConfiguration{ { DeviceID: device1, Introducer: true, }, { DeviceID: device2, }, } cfg.Folders = []config.FolderConfiguration{ { ID: "folder1", Devices: []config.FolderDeviceConfiguration{ {DeviceID: device1}, {DeviceID: device2}, }, }, { ID: "folder2", Devices: []config.FolderDeviceConfiguration{ {DeviceID: device1}, {DeviceID: device2}, }, }, } db, _ := leveldb.Open(storage.NewMemStorage(), nil) m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db) m.AddFolder(cfg.Folders[0]) m.AddFolder(cfg.Folders[1]) m.ServeBackground() cm := m.clusterConfig(device2) if l := len(cm.Folders); l != 2 { t.Fatalf("Incorrect number of folders %d != 2", l) } r := cm.Folders[0] if r.ID != "folder1" { t.Errorf("Incorrect folder %q != folder1", r.ID) } if l := len(r.Devices); l != 2 { t.Errorf("Incorrect number of devices %d != 2", l) } if id := r.Devices[0].ID; bytes.Compare(id, device1[:]) != 0 { t.Errorf("Incorrect device ID %x != %x", id, device1) } if r.Devices[0].Flags&protocol.FlagIntroducer == 0 { t.Error("Device1 should be flagged as Introducer") } if id := r.Devices[1].ID; bytes.Compare(id, device2[:]) != 0 { t.Errorf("Incorrect device ID %x != %x", id, device2) } if r.Devices[1].Flags&protocol.FlagIntroducer != 0 { t.Error("Device2 should not be flagged as Introducer") } r = cm.Folders[1] if r.ID != "folder2" { t.Errorf("Incorrect folder %q != folder2", r.ID) } if l := len(r.Devices); l != 2 { t.Errorf("Incorrect number of devices %d != 2", l) } if id := r.Devices[0].ID; bytes.Compare(id, device1[:]) != 0 { t.Errorf("Incorrect device ID %x != %x", id, device1) } if r.Devices[0].Flags&protocol.FlagIntroducer == 0 { t.Error("Device1 should be flagged as Introducer") } if id := r.Devices[1].ID; bytes.Compare(id, device2[:]) != 0 { t.Errorf("Incorrect device ID %x != %x", id, device2) } if r.Devices[1].Flags&protocol.FlagIntroducer != 0 { t.Error("Device2 should not be flagged as Introducer") } }
func syncthingMain() { var err error if len(os.Getenv("GOGC")) == 0 { debug.SetGCPercent(25) } if len(os.Getenv("GOMAXPROCS")) == 0 { runtime.GOMAXPROCS(runtime.NumCPU()) } events.Default.Log(events.Starting, map[string]string{"home": confDir}) // Ensure that that we have a certificate and key. cert, err = loadCert(confDir, "") if err != nil { newCertificate(confDir, "") cert, err = loadCert(confDir, "") if err != nil { l.Fatalln("load cert:", err) } } myID = protocol.NewDeviceID(cert.Certificate[0]) l.SetPrefix(fmt.Sprintf("[%s] ", myID.String()[:5])) l.Infoln(LongVersion) l.Infoln("My ID:", myID) // Prepare to be able to save configuration cfgFile := filepath.Join(confDir, "config.xml") var myName string // Load the configuration file, if it exists. // If it does not, create a template. cfg, err = config.Load(cfgFile, myID) if err == nil { myCfg := cfg.Devices()[myID] if myCfg.Name == "" { myName, _ = os.Hostname() } else { myName = myCfg.Name } } else { l.Infoln("No config file; starting with empty defaults") myName, _ = os.Hostname() defaultFolder, err := osutil.ExpandTilde("~/Sync") if err != nil { l.Fatalln("home:", err) } newCfg := config.New(myID) newCfg.Folders = []config.FolderConfiguration{ { ID: "default", Path: defaultFolder, RescanIntervalS: 60, Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}}, }, } newCfg.Devices = []config.DeviceConfiguration{ { DeviceID: myID, Addresses: []string{"dynamic"}, Name: myName, }, } port, err := getFreePort("127.0.0.1", 8080) if err != nil { l.Fatalln("get free port (GUI):", err) } newCfg.GUI.Address = fmt.Sprintf("127.0.0.1:%d", port) port, err = getFreePort("0.0.0.0", 22000) if err != nil { l.Fatalln("get free port (BEP):", err) } newCfg.Options.ListenAddress = []string{fmt.Sprintf("0.0.0.0:%d", port)} cfg = config.Wrap(cfgFile, newCfg) cfg.Save() l.Infof("Edit %s to taste or use the GUI\n", cfgFile) } if cfg.Raw().OriginalVersion != config.CurrentVersion { l.Infoln("Archiving a copy of old config file format") // Archive a copy osutil.Rename(cfgFile, cfgFile+fmt.Sprintf(".v%d", cfg.Raw().OriginalVersion)) // Save the new version cfg.Save() } if len(profiler) > 0 { go func() { l.Debugln("Starting profiler on", profiler) runtime.SetBlockProfileRate(1) err := http.ListenAndServe(profiler, nil) if err != nil { l.Fatalln(err) } }() } // The TLS configuration is used for both the listening socket and outgoing // connections. tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{"bep/1.0"}, ServerName: myID.String(), ClientAuth: tls.RequestClientCert, SessionTicketsDisabled: true, InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, } // If the read or write rate should be limited, set up a rate limiter for it. // This will be used on connections created in the connect and listen routines. opts := cfg.Options() if opts.MaxSendKbps > 0 { writeRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxSendKbps), int64(5*1000*opts.MaxSendKbps)) } if opts.MaxRecvKbps > 0 { readRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxRecvKbps), int64(5*1000*opts.MaxRecvKbps)) } // If this is the first time the user runs v0.9, archive the old indexes and config. archiveLegacyConfig() db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{CachedOpenFiles: 100}) if err != nil { l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?") } // Remove database entries for folders that no longer exist in the config folders := cfg.Folders() for _, folder := range files.ListFolders(db) { if _, ok := folders[folder]; !ok { l.Infof("Cleaning data for dropped folder %q", folder) files.DropFolder(db, folder) } } m := model.NewModel(cfg, myName, "syncthing", Version, db) nextFolder: for id, folder := range cfg.Folders() { if folder.Invalid != "" { continue } folder.Path, err = osutil.ExpandTilde(folder.Path) if err != nil { l.Fatalln("home:", err) } m.AddFolder(folder) fi, err := os.Stat(folder.Path) if m.CurrentLocalVersion(id) > 0 { // Safety check. If the cached index contains files but the // folder doesn't exist, we have a problem. We would assume // that all files have been deleted which might not be the case, // so mark it as invalid instead. if err != nil || !fi.IsDir() { l.Warnf("Stopping folder %q - path does not exist, but has files in index", folder.ID) cfg.InvalidateFolder(id, "folder path missing") continue nextFolder } } else if os.IsNotExist(err) { // If we don't have any files in the index, and the directory // doesn't exist, try creating it. err = os.MkdirAll(folder.Path, 0700) } if err != nil { // If there was another error or we could not create the // path, the folder is invalid. l.Warnf("Stopping folder %q - %v", err) cfg.InvalidateFolder(id, err.Error()) continue nextFolder } } // GUI guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey) if guiCfg.Enabled && guiCfg.Address != "" { addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address) if err != nil { l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err) } else { var hostOpen, hostShow string switch { case addr.IP == nil: hostOpen = "localhost" hostShow = "0.0.0.0" case addr.IP.IsUnspecified(): hostOpen = "localhost" hostShow = addr.IP.String() default: hostOpen = addr.IP.String() hostShow = hostOpen } var proto = "http" if guiCfg.UseTLS { proto = "https" } urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port))) l.Infoln("Starting web GUI on", urlShow) err := startGUI(guiCfg, guiAssets, m) if err != nil { l.Fatalln("Cannot start GUI:", err) } if opts.StartBrowser && !noBrowser && !stRestarting { urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port))) openURL(urlOpen) } } } // Clear out old indexes for other devices. Otherwise we'll start up and // start needing a bunch of files which are nowhere to be found. This // needs to be changed when we correctly do persistent indexes. for _, folderCfg := range cfg.Folders() { if folderCfg.Invalid != "" { continue } for _, device := range folderCfg.DeviceIDs() { if device == myID { continue } m.Index(device, folderCfg.ID, nil) } } // Remove all .idx* files that don't belong to an active folder. validIndexes := make(map[string]bool) for _, folder := range cfg.Folders() { dir, err := osutil.ExpandTilde(folder.Path) if err != nil { l.Fatalln("home:", err) } id := fmt.Sprintf("%x", sha1.Sum([]byte(dir))) validIndexes[id] = true } allIndexes, err := filepath.Glob(filepath.Join(confDir, "*.idx*")) if err == nil { for _, idx := range allIndexes { bn := filepath.Base(idx) fs := strings.Split(bn, ".") if len(fs) > 1 { if _, ok := validIndexes[fs[0]]; !ok { l.Infoln("Removing old index", bn) os.Remove(idx) } } } } // The default port we announce, possibly modified by setupUPnP next. addr, err := net.ResolveTCPAddr("tcp", opts.ListenAddress[0]) if err != nil { l.Fatalln("Bad listen address:", err) } externalPort = addr.Port // UPnP if opts.UPnPEnabled { setupUPnP() } // Routine to connect out to configured devices discoverer = discovery(externalPort) go listenConnect(myID, m, tlsCfg) for _, folder := range cfg.Folders() { if folder.Invalid != "" { continue } // Routine to pull blocks from other devices to synchronize the local // folder. Does not run when we are in read only (publish only) mode. if folder.ReadOnly { l.Okf("Ready to synchronize %s (read only; no external updates accepted)", folder.ID) m.StartFolderRO(folder.ID) } else { l.Okf("Ready to synchronize %s (read-write)", folder.ID) m.StartFolderRW(folder.ID) } } if cpuProfile { f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid())) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } for _, device := range cfg.Devices() { if len(device.Name) > 0 { l.Infof("Device %s is %q at %v", device.DeviceID, device.Name, device.Addresses) } } if opts.URAccepted > 0 && opts.URAccepted < usageReportVersion { l.Infoln("Anonymous usage report has changed; revoking acceptance") opts.URAccepted = 0 cfg.SetOptions(opts) } if opts.URAccepted >= usageReportVersion { go usageReportingLoop(m) go func() { time.Sleep(10 * time.Minute) err := sendUsageReport(m) if err != nil { l.Infoln("Usage report:", err) } }() } if opts.RestartOnWakeup { go standbyMonitor() } if opts.AutoUpgradeIntervalH > 0 { go autoUpgrade() } events.Default.Log(events.StartupComplete, nil) go generateEvents() code := <-stop l.Okln("Exiting") os.Exit(code) }