diff options
Diffstat (limited to 'main.go')
-rw-r--r-- | main.go | 197 |
1 files changed, 124 insertions, 73 deletions
@@ -23,17 +23,12 @@ import ( "github.com/calmh/syncthing/protocol" ) -var opts Options +var cfg Configuration var Version string = "unknown-dev" -const ( - confFileName = "syncthing.ini" -) - var ( - myID string - config ini.Config - nodeAddrs = make(map[string][]string) + myID string + config ini.Config ) var ( @@ -89,46 +84,75 @@ func main() { log.SetPrefix("[" + myID[0:5] + "] ") logger.SetPrefix("[" + myID[0:5] + "] ") + // Prepare to be able to save configuration + + cfgFile := path.Join(confDir, "config.xml") + go saveConfigLoop(cfgFile) + // Load the configuration file, if it exists. // If it does not, create a template. - cfgFile := path.Join(confDir, confFileName) cf, err := os.Open(cfgFile) - - if err != nil { - infoln("My ID:", myID) - - infoln("No config file; creating a template") - - loadConfig(nil, &opts) //loads defaults - fd, err := os.Create(cfgFile) + if err == nil { + // Read config.xml + cfg, err = readConfigXML(cf) if err != nil { fatalln(err) } + cf.Close() + } else { + // No config.xml, let's try the old syncthing.ini + iniFile := path.Join(confDir, "syncthing.ini") + cf, err := os.Open(iniFile) + if err == nil { + infoln("Migrating syncthing.ini to config.xml") + iniCfg := ini.Parse(cf) + cf.Close() + os.Rename(iniFile, path.Join(confDir, "migrated_syncthing.ini")) + + cfg, _ = readConfigXML(nil) + cfg.Repositories = []RepositoryConfiguration{ + {Directory: iniCfg.Get("repository", "dir")}, + } + readConfigINI(iniCfg.OptionMap("settings"), &cfg.Options) + for name, addrs := range iniCfg.OptionMap("nodes") { + n := NodeConfiguration{ + NodeID: name, + Addresses: strings.Fields(addrs), + } + cfg.Repositories[0].Nodes = append(cfg.Repositories[0].Nodes, n) + } - writeConfig(fd, "~/Sync", map[string]string{myID: "dynamic"}, opts, true) - fd.Close() - infof("Edit %s to suit and restart syncthing.", cfgFile) - - os.Exit(0) + saveConfig() + } } - config = ini.Parse(cf) - cf.Close() + if len(cfg.Repositories) == 0 { + infoln("No config file; starting with empty defaults") + + cfg, err = readConfigXML(nil) + cfg.Repositories = []RepositoryConfiguration{ + { + Directory: "~/Sync", + Nodes: []NodeConfiguration{ + {NodeID: myID, Addresses: []string{"dynamic"}}, + }, + }, + } - loadConfig(config.OptionMap("settings"), &opts) + saveConfig() + infof("Edit %s to taste or use the GUI\n", cfgFile) + } if showConfig { - writeConfig(os.Stdout, - config.Get("repository", "dir"), - config.OptionMap("nodes"), opts, false) + writeConfigXML(os.Stdout, cfg) os.Exit(0) } infoln("Version", Version) infoln("My ID:", myID) - var dir = expandTilde(config.Get("repository", "dir")) + var dir = expandTilde(cfg.Repositories[0].Directory) if len(dir) == 0 { fatalln("No repository directory. Set dir under [repository] in syncthing.ini.") } @@ -145,7 +169,7 @@ func main() { // The TLS configuration is used for both the listening socket and outgoing // connections. - cfg := &tls.Config{ + tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{"bep/1.0"}, ServerName: myID, @@ -155,35 +179,27 @@ func main() { MinVersion: tls.VersionTLS12, } - // Create a map of desired node connections based on the configuration file - // directives. - - for nodeID, addrs := range config.OptionMap("nodes") { - addrs := strings.Fields(addrs) - nodeAddrs[nodeID] = addrs - } - ensureDir(dir, -1) - m := model.NewModel(dir, opts.MaxChangeBW*1000) + m := model.NewModel(dir, cfg.Options.MaxChangeKbps*1000) for _, t := range strings.Split(trace, ",") { m.Trace(t) } - if opts.LimitRate > 0 { - m.LimitRate(opts.LimitRate) + if cfg.Options.MaxSendKbps > 0 { + m.LimitRate(cfg.Options.MaxSendKbps) } // GUI - if opts.GUI && opts.GUIAddr != "" { - host, port, err := net.SplitHostPort(opts.GUIAddr) + if cfg.Options.GUIEnabled && cfg.Options.GUIAddress != "" { + host, port, err := net.SplitHostPort(cfg.Options.GUIAddress) if err != nil { - warnf("Cannot start GUI on %q: %v", opts.GUIAddr, err) + warnf("Cannot start GUI on %q: %v", cfg.Options.GUIAddress, err) } else { if len(host) > 0 { - infof("Starting web GUI on http://%s", opts.GUIAddr) + infof("Starting web GUI on http://%s", cfg.Options.GUIAddress) } else { infof("Starting web GUI on port %s", port) } - startGUI(opts.GUIAddr, m) + startGUI(cfg.Options.GUIAddress, m) } } @@ -196,22 +212,22 @@ func main() { // Routine to listen for incoming connections infoln("Listening for incoming connections") - go listen(myID, opts.Listen, m, cfg) + go listen(myID, cfg.Options.ListenAddress, m, tlsCfg) // Routine to connect out to configured nodes infoln("Attempting to connect to other nodes") - go connect(myID, opts.Listen, nodeAddrs, m, cfg) + go connect(myID, cfg.Options.ListenAddress, m, tlsCfg) // Routine to pull blocks from other nodes to synchronize the local // repository. Does not run when we are in read only (publish only) mode. - if !opts.ReadOnly { - if opts.Delete { + if !cfg.Options.ReadOnly { + if cfg.Options.AllowDelete { infoln("Deletes from peer nodes are allowed") } else { infoln("Deletes from peer nodes will be ignored") } okln("Ready to synchronize (read-write)") - m.StartRW(opts.Delete, opts.ParallelRequests) + m.StartRW(cfg.Options.AllowDelete, cfg.Options.ParallelRequests) } else { okln("Ready to synchronize (read only; no external updates accepted)") } @@ -219,9 +235,10 @@ func main() { // Periodically scan the repository and update the local model. // XXX: Should use some fsnotify mechanism. go func() { + td := time.Duration(cfg.Options.RescanIntervalS) * time.Second for { - time.Sleep(opts.ScanInterval) - if m.LocalAge() > opts.ScanInterval.Seconds()/2 { + time.Sleep(td) + if m.LocalAge() > (td / 2).Seconds() { updateLocalModel(m) } } @@ -233,6 +250,40 @@ func main() { select {} } +var saveConfigCh = make(chan struct{}) + +func saveConfigLoop(cfgFile string) { + for _ = range saveConfigCh { + fd, err := os.Create(cfgFile + ".tmp") + if err != nil { + warnln(err) + continue + } + + err = writeConfigXML(fd, cfg) + if err != nil { + warnln(err) + fd.Close() + continue + } + + err = fd.Close() + if err != nil { + warnln(err) + continue + } + + err = os.Rename(cfgFile+".tmp", cfgFile) + if err != nil { + warnln(err) + } + } +} + +func saveConfig() { + saveConfigCh <- struct{}{} +} + func printStatsLoop(m *model.Model) { var lastUpdated int64 var lastStats = make(map[string]model.ConnectionInfo) @@ -264,8 +315,8 @@ func printStatsLoop(m *model.Model) { } } -func listen(myID string, addr string, m *model.Model, cfg *tls.Config) { - l, err := tls.Listen("tcp", addr, cfg) +func listen(myID string, addr string, m *model.Model, tlsCfg *tls.Config) { + l, err := tls.Listen("tcp", addr, tlsCfg) fatalErr(err) connOpts := map[string]string{ @@ -305,8 +356,8 @@ listen: warnf("Connect from connected node (%s)", remoteID) } - for nodeID := range nodeAddrs { - if nodeID == remoteID { + for _, nodeCfg := range cfg.Repositories[0].Nodes { + if nodeCfg.NodeID == remoteID { protoConn := protocol.NewConnection(remoteID, conn, conn, m, connOpts) m.AddConnection(conn, protoConn) continue listen @@ -316,24 +367,24 @@ listen: } } -func connect(myID string, addr string, nodeAddrs map[string][]string, m *model.Model, cfg *tls.Config) { +func connect(myID string, addr string, m *model.Model, tlsCfg *tls.Config) { _, portstr, err := net.SplitHostPort(addr) fatalErr(err) port, _ := strconv.Atoi(portstr) - if !opts.LocalDiscovery { + if !cfg.Options.LocalAnnEnabled { port = -1 } else { infoln("Sending local discovery announcements") } - if !opts.ExternalDiscovery { - opts.ExternalServer = "" + if !cfg.Options.GlobalAnnEnabled { + cfg.Options.GlobalAnnServer = "" } else { infoln("Sending external discovery announcements") } - disc, err := discover.NewDiscoverer(myID, port, opts.ExternalServer) + disc, err := discover.NewDiscoverer(myID, port, cfg.Options.GlobalAnnServer) if err != nil { warnf("No discovery possible (%v)", err) @@ -346,18 +397,18 @@ func connect(myID string, addr string, nodeAddrs map[string][]string, m *model.M for { nextNode: - for nodeID, addrs := range nodeAddrs { - if nodeID == myID { + for _, nodeCfg := range cfg.Repositories[0].Nodes { + if nodeCfg.NodeID == myID { continue } - if m.ConnectedTo(nodeID) { + if m.ConnectedTo(nodeCfg.NodeID) { continue } - for _, addr := range addrs { + for _, addr := range nodeCfg.Addresses { if addr == "dynamic" { var ok bool if disc != nil { - addr, ok = disc.Lookup(nodeID) + addr, ok = disc.Lookup(nodeCfg.NodeID) } if !ok { continue @@ -365,9 +416,9 @@ func connect(myID string, addr string, nodeAddrs map[string][]string, m *model.M } if strings.Contains(trace, "connect") { - debugln("NET: Dial", nodeID, addr) + debugln("NET: Dial", nodeCfg.NodeID, addr) } - conn, err := tls.Dial("tcp", addr, cfg) + conn, err := tls.Dial("tcp", addr, tlsCfg) if err != nil { if strings.Contains(trace, "connect") { debugln("NET:", err) @@ -376,8 +427,8 @@ func connect(myID string, addr string, nodeAddrs map[string][]string, m *model.M } remoteID := certId(conn.ConnectionState().PeerCertificates[0].Raw) - if remoteID != nodeID { - warnln("Unexpected nodeID", remoteID, "!=", nodeID) + if remoteID != nodeCfg.NodeID { + warnln("Unexpected nodeID", remoteID, "!=", nodeCfg.NodeID) conn.Close() continue } @@ -388,12 +439,12 @@ func connect(myID string, addr string, nodeAddrs map[string][]string, m *model.M } } - time.Sleep(opts.ConnInterval) + time.Sleep(time.Duration(cfg.Options.ReconnectIntervalS) * time.Second) } } func updateLocalModel(m *model.Model) { - files, _ := m.Walk(opts.Symlinks) + files, _ := m.Walk(cfg.Options.FollowSymlinks) m.ReplaceLocal(files) saveIndex(m) } |