2013-12-15 11:43:31 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2014-01-01 22:31:04 +01:00
|
|
|
"compress/gzip"
|
2013-12-15 11:43:31 +01:00
|
|
|
"crypto/tls"
|
2014-01-26 14:28:41 +01:00
|
|
|
"flag"
|
2014-01-08 14:37:33 +01:00
|
|
|
"fmt"
|
2013-12-15 11:43:31 +01:00
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
_ "net/http/pprof"
|
|
|
|
"os"
|
2014-02-17 08:47:21 +01:00
|
|
|
"os/exec"
|
2013-12-15 11:43:31 +01:00
|
|
|
"path"
|
2014-01-10 00:09:27 +01:00
|
|
|
"runtime"
|
|
|
|
"runtime/debug"
|
2013-12-15 11:43:31 +01:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/calmh/ini"
|
|
|
|
"github.com/calmh/syncthing/discover"
|
|
|
|
"github.com/calmh/syncthing/protocol"
|
2014-03-08 23:02:01 +01:00
|
|
|
"github.com/calmh/syncthing/scanner"
|
2013-12-15 11:43:31 +01:00
|
|
|
)
|
|
|
|
|
2014-03-08 23:02:01 +01:00
|
|
|
const BlockSize = 128 * 1024
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
var cfg Configuration
|
2014-02-24 13:29:30 +01:00
|
|
|
var Version = "unknown-dev"
|
2013-12-18 19:36:28 +01:00
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
var (
|
2014-02-24 13:34:24 +01:00
|
|
|
myID string
|
2013-12-15 11:43:31 +01:00
|
|
|
)
|
|
|
|
|
2014-01-26 14:28:41 +01:00
|
|
|
var (
|
2014-03-09 08:35:38 +01:00
|
|
|
showVersion bool
|
|
|
|
confDir string
|
|
|
|
verbose bool
|
2014-01-26 14:28:41 +01:00
|
|
|
)
|
|
|
|
|
2014-03-09 08:48:29 +01:00
|
|
|
const (
|
|
|
|
usage = "syncthing [options]"
|
2014-03-12 10:12:35 +01:00
|
|
|
extraUsage = `The following enviroment variables are interpreted by syncthing:
|
|
|
|
|
|
|
|
STNORESTART Do not attempt to restart when requested to, instead just exit.
|
|
|
|
Set this variable when running under a service manager such as
|
|
|
|
runit, launchd, etc.
|
|
|
|
|
|
|
|
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
|
|
|
|
profiler with HTTP access.
|
|
|
|
|
|
|
|
STTRACE A comma separated string of facilities to trace. The valid
|
|
|
|
facility strings:
|
|
|
|
- "scanner" (the file change scanner)
|
|
|
|
- "discover" (the node discovery package)
|
|
|
|
- "net" (connecting and disconnecting, network messages)
|
|
|
|
- "idx" (index sending and receiving)
|
|
|
|
- "need" (file need calculations)
|
|
|
|
- "pull" (file pull activity)`
|
2014-03-09 08:48:29 +01:00
|
|
|
)
|
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
func main() {
|
2014-03-02 23:49:51 +01:00
|
|
|
flag.StringVar(&confDir, "home", getDefaultConfDir(), "Set configuration directory")
|
2014-01-26 14:28:41 +01:00
|
|
|
flag.BoolVar(&showVersion, "version", false, "Show version")
|
2014-02-03 16:01:17 +01:00
|
|
|
flag.BoolVar(&verbose, "v", false, "Be more verbose")
|
2014-03-09 08:48:29 +01:00
|
|
|
flag.Usage = usageFor(flag.CommandLine, usage, extraUsage)
|
2014-01-26 14:28:41 +01:00
|
|
|
flag.Parse()
|
2014-01-08 14:37:33 +01:00
|
|
|
|
2014-03-09 08:35:38 +01:00
|
|
|
if len(os.Getenv("STRESTART")) > 0 {
|
|
|
|
// Give the parent process time to exit and release sockets etc.
|
|
|
|
time.Sleep(1 * time.Second)
|
2014-02-12 12:10:44 +01:00
|
|
|
}
|
|
|
|
|
2014-01-26 14:28:41 +01:00
|
|
|
if showVersion {
|
2014-01-08 14:37:33 +01:00
|
|
|
fmt.Println(Version)
|
2013-12-18 19:36:28 +01:00
|
|
|
os.Exit(0)
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
2014-01-08 14:37:33 +01:00
|
|
|
|
2014-01-10 00:09:27 +01:00
|
|
|
if len(os.Getenv("GOGC")) == 0 {
|
|
|
|
debug.SetGCPercent(25)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(os.Getenv("GOMAXPROCS")) == 0 {
|
|
|
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
|
|
}
|
|
|
|
|
2014-01-26 14:28:41 +01:00
|
|
|
confDir = expandTilde(confDir)
|
2013-12-22 00:16:49 +01:00
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
// Ensure that our home directory exists and that we have a certificate and key.
|
|
|
|
|
2014-01-26 14:28:41 +01:00
|
|
|
ensureDir(confDir, 0700)
|
|
|
|
cert, err := loadCert(confDir)
|
2013-12-15 11:43:31 +01:00
|
|
|
if err != nil {
|
2014-01-26 14:28:41 +01:00
|
|
|
newCertificate(confDir)
|
|
|
|
cert, err = loadCert(confDir)
|
2013-12-15 11:43:31 +01:00
|
|
|
fatalErr(err)
|
|
|
|
}
|
|
|
|
|
2014-02-24 13:29:30 +01:00
|
|
|
myID = string(certID(cert.Certificate[0]))
|
2014-01-20 22:22:27 +01:00
|
|
|
log.SetPrefix("[" + myID[0:5] + "] ")
|
|
|
|
logger.SetPrefix("[" + myID[0:5] + "] ")
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-02-03 16:01:17 +01:00
|
|
|
infoln("Version", Version)
|
|
|
|
infoln("My ID:", myID)
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
// Prepare to be able to save configuration
|
|
|
|
|
|
|
|
cfgFile := path.Join(confDir, "config.xml")
|
|
|
|
go saveConfigLoop(cfgFile)
|
|
|
|
|
2014-01-22 14:28:14 +01:00
|
|
|
// Load the configuration file, if it exists.
|
|
|
|
// If it does not, create a template.
|
|
|
|
|
|
|
|
cf, err := os.Open(cfgFile)
|
2014-02-01 20:23:19 +01:00
|
|
|
if err == nil {
|
|
|
|
// Read config.xml
|
|
|
|
cfg, err = readConfigXML(cf)
|
2014-01-22 14:28:14 +01:00
|
|
|
if err != nil {
|
|
|
|
fatalln(err)
|
|
|
|
}
|
2014-02-01 20:23:19 +01:00
|
|
|
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)
|
|
|
|
}
|
2014-01-26 14:28:41 +01:00
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
saveConfig()
|
|
|
|
}
|
2014-01-22 14:28:14 +01:00
|
|
|
}
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
if len(cfg.Repositories) == 0 {
|
|
|
|
infoln("No config file; starting with empty defaults")
|
|
|
|
|
|
|
|
cfg, err = readConfigXML(nil)
|
|
|
|
cfg.Repositories = []RepositoryConfiguration{
|
|
|
|
{
|
2014-03-02 23:49:51 +01:00
|
|
|
Directory: path.Join(getHomeDir(), "Sync"),
|
2014-02-01 20:23:19 +01:00
|
|
|
Nodes: []NodeConfiguration{
|
|
|
|
{NodeID: myID, Addresses: []string{"dynamic"}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2014-01-22 14:28:14 +01:00
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
saveConfig()
|
|
|
|
infof("Edit %s to taste or use the GUI\n", cfgFile)
|
|
|
|
}
|
2014-01-26 14:28:41 +01:00
|
|
|
|
2014-02-10 20:54:37 +01:00
|
|
|
// Make sure the local node is in the node list.
|
|
|
|
cfg.Repositories[0].Nodes = cleanNodeList(cfg.Repositories[0].Nodes, myID)
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
var dir = expandTilde(cfg.Repositories[0].Directory)
|
2014-01-22 14:28:14 +01:00
|
|
|
|
2014-03-09 08:48:29 +01:00
|
|
|
if profiler := os.Getenv("STPROFILER"); len(profiler) > 0 {
|
2013-12-15 11:43:31 +01:00
|
|
|
go func() {
|
2014-03-09 09:18:28 +01:00
|
|
|
dlog.Println("Starting profiler on", profiler)
|
2014-01-26 14:28:41 +01:00
|
|
|
err := http.ListenAndServe(profiler, nil)
|
2013-12-18 19:36:28 +01:00
|
|
|
if err != nil {
|
2014-03-09 09:18:28 +01:00
|
|
|
dlog.Fatal(err)
|
2013-12-18 19:36:28 +01:00
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// The TLS configuration is used for both the listening socket and outgoing
|
|
|
|
// connections.
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
tlsCfg := &tls.Config{
|
2014-01-09 09:28:08 +01:00
|
|
|
Certificates: []tls.Certificate{cert},
|
|
|
|
NextProtos: []string{"bep/1.0"},
|
|
|
|
ServerName: myID,
|
|
|
|
ClientAuth: tls.RequestClientCert,
|
|
|
|
SessionTicketsDisabled: true,
|
|
|
|
InsecureSkipVerify: true,
|
|
|
|
MinVersion: tls.VersionTLS12,
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2013-12-22 00:16:49 +01:00
|
|
|
ensureDir(dir, -1)
|
2014-02-12 23:18:41 +01:00
|
|
|
m := NewModel(dir, cfg.Options.MaxChangeKbps*1000)
|
2014-02-01 20:23:19 +01:00
|
|
|
if cfg.Options.MaxSendKbps > 0 {
|
|
|
|
m.LimitRate(cfg.Options.MaxSendKbps)
|
2014-01-12 16:59:35 +01:00
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-01-05 23:54:57 +01:00
|
|
|
// GUI
|
2014-02-01 20:23:19 +01:00
|
|
|
if cfg.Options.GUIEnabled && cfg.Options.GUIAddress != "" {
|
2014-03-02 12:52:32 +01:00
|
|
|
addr, err := net.ResolveTCPAddr("tcp", cfg.Options.GUIAddress)
|
2014-01-09 10:40:12 +01:00
|
|
|
if err != nil {
|
2014-02-01 20:23:19 +01:00
|
|
|
warnf("Cannot start GUI on %q: %v", cfg.Options.GUIAddress, err)
|
2014-01-09 10:40:12 +01:00
|
|
|
} else {
|
2014-03-02 12:52:32 +01:00
|
|
|
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
|
2014-01-09 10:40:12 +01:00
|
|
|
}
|
2014-03-02 12:52:32 +01:00
|
|
|
|
|
|
|
infof("Starting web GUI on http://%s:%d/", hostShow, addr.Port)
|
2014-02-01 20:23:19 +01:00
|
|
|
startGUI(cfg.Options.GUIAddress, m)
|
2014-03-09 08:35:53 +01:00
|
|
|
if cfg.Options.StartBrowser && len(os.Getenv("STRESTART")) == 0 {
|
2014-03-08 23:19:33 +01:00
|
|
|
openURL(fmt.Sprintf("http://%s:%d", hostOpen, addr.Port))
|
|
|
|
}
|
2014-01-09 10:40:12 +01:00
|
|
|
}
|
2014-01-05 23:54:57 +01:00
|
|
|
}
|
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
// Walk the repository and update the local model before establishing any
|
|
|
|
// connections to other nodes.
|
|
|
|
|
2014-02-03 16:01:17 +01:00
|
|
|
if verbose {
|
|
|
|
infoln("Populating repository index")
|
|
|
|
}
|
2014-01-29 22:02:38 +01:00
|
|
|
loadIndex(m)
|
2014-03-08 23:02:01 +01:00
|
|
|
|
|
|
|
sup := &suppressor{threshold: int64(cfg.Options.MaxChangeKbps)}
|
|
|
|
w := &scanner.Walker{
|
|
|
|
Dir: m.dir,
|
|
|
|
IgnoreFile: ".stignore",
|
|
|
|
FollowSymlinks: cfg.Options.FollowSymlinks,
|
|
|
|
BlockSize: BlockSize,
|
|
|
|
TempNamer: defTempNamer,
|
2014-03-16 08:14:55 +01:00
|
|
|
Suppressor: sup,
|
|
|
|
CurrentFiler: m,
|
2014-03-08 23:02:01 +01:00
|
|
|
}
|
|
|
|
updateLocalModel(m, w)
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-02-09 23:13:06 +01:00
|
|
|
connOpts := map[string]string{
|
|
|
|
"clientId": "syncthing",
|
|
|
|
"clientVersion": Version,
|
|
|
|
"clusterHash": clusterHash(cfg.Repositories[0].Nodes),
|
|
|
|
}
|
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
// Routine to connect out to configured nodes
|
2014-02-03 16:01:17 +01:00
|
|
|
if verbose {
|
|
|
|
infoln("Attempting to connect to other nodes")
|
|
|
|
}
|
2014-03-18 17:51:55 +01:00
|
|
|
disc := discovery()
|
2014-03-23 08:45:05 +01:00
|
|
|
go listenConnect(myID, disc, m, tlsCfg, connOpts)
|
2013-12-15 11:43:31 +01:00
|
|
|
|
|
|
|
// Routine to pull blocks from other nodes to synchronize the local
|
|
|
|
// repository. Does not run when we are in read only (publish only) mode.
|
2014-02-01 20:23:19 +01:00
|
|
|
if !cfg.Options.ReadOnly {
|
2014-02-03 16:01:17 +01:00
|
|
|
if verbose {
|
|
|
|
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)")
|
2014-01-06 21:41:29 +01:00
|
|
|
}
|
2014-02-01 20:23:19 +01:00
|
|
|
m.StartRW(cfg.Options.AllowDelete, cfg.Options.ParallelRequests)
|
2014-02-03 16:01:17 +01:00
|
|
|
} else if verbose {
|
2014-01-06 21:41:29 +01:00
|
|
|
okln("Ready to synchronize (read only; no external updates accepted)")
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2014-02-12 23:18:41 +01:00
|
|
|
// Periodically scan the repository and update the local
|
2013-12-15 11:43:31 +01:00
|
|
|
// XXX: Should use some fsnotify mechanism.
|
|
|
|
go func() {
|
2014-02-01 20:23:19 +01:00
|
|
|
td := time.Duration(cfg.Options.RescanIntervalS) * time.Second
|
2013-12-15 11:43:31 +01:00
|
|
|
for {
|
2014-02-01 20:23:19 +01:00
|
|
|
time.Sleep(td)
|
|
|
|
if m.LocalAge() > (td / 2).Seconds() {
|
2014-03-08 23:02:01 +01:00
|
|
|
updateLocalModel(m, w)
|
2014-01-20 22:22:27 +01:00
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2014-02-03 16:01:17 +01:00
|
|
|
if verbose {
|
|
|
|
// Periodically print statistics
|
|
|
|
go printStatsLoop(m)
|
|
|
|
}
|
2014-01-05 16:16:37 +01:00
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
select {}
|
|
|
|
}
|
|
|
|
|
2014-02-12 12:10:44 +01:00
|
|
|
func restart() {
|
|
|
|
infoln("Restarting")
|
2014-03-12 10:12:35 +01:00
|
|
|
if os.Getenv("SMF_FMRI") != "" || os.Getenv("STNORESTART") != "" {
|
|
|
|
// Solaris SMF
|
|
|
|
infoln("Service manager detected; exit instead of restart")
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2014-03-09 08:35:38 +01:00
|
|
|
env := os.Environ()
|
|
|
|
if len(os.Getenv("STRESTART")) == 0 {
|
|
|
|
env = append(env, "STRESTART=1")
|
2014-02-12 12:10:44 +01:00
|
|
|
}
|
2014-02-17 08:47:21 +01:00
|
|
|
pgm, err := exec.LookPath(os.Args[0])
|
|
|
|
if err != nil {
|
|
|
|
warnln(err)
|
|
|
|
return
|
|
|
|
}
|
2014-03-09 08:35:38 +01:00
|
|
|
proc, err := os.StartProcess(pgm, os.Args, &os.ProcAttr{
|
|
|
|
Env: env,
|
2014-02-12 12:10:44 +01:00
|
|
|
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
fatalln(err)
|
|
|
|
}
|
|
|
|
proc.Release()
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-03-03 08:46:20 +01:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
err := os.Remove(cfgFile)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
warnln(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
err = os.Rename(cfgFile+".tmp", cfgFile)
|
|
|
|
if err != nil {
|
|
|
|
warnln(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func saveConfig() {
|
|
|
|
saveConfigCh <- struct{}{}
|
|
|
|
}
|
|
|
|
|
2014-02-12 23:18:41 +01:00
|
|
|
func printStatsLoop(m *Model) {
|
2014-01-05 16:16:37 +01:00
|
|
|
var lastUpdated int64
|
2014-02-12 23:18:41 +01:00
|
|
|
var lastStats = make(map[string]ConnectionInfo)
|
2014-01-05 16:16:37 +01:00
|
|
|
|
|
|
|
for {
|
|
|
|
time.Sleep(60 * time.Second)
|
|
|
|
|
|
|
|
for node, stats := range m.ConnectionStats() {
|
|
|
|
secs := time.Since(lastStats[node].At).Seconds()
|
|
|
|
inbps := 8 * int(float64(stats.InBytesTotal-lastStats[node].InBytesTotal)/secs)
|
|
|
|
outbps := 8 * int(float64(stats.OutBytesTotal-lastStats[node].OutBytesTotal)/secs)
|
|
|
|
|
|
|
|
if inbps+outbps > 0 {
|
2014-02-20 17:40:15 +01:00
|
|
|
infof("%s: %sb/s in, %sb/s out", node[0:5], MetricPrefix(int64(inbps)), MetricPrefix(int64(outbps)))
|
2014-01-05 16:16:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
lastStats[node] = stats
|
|
|
|
}
|
|
|
|
|
|
|
|
if lu := m.Generation(); lu > lastUpdated {
|
|
|
|
lastUpdated = lu
|
2014-01-05 23:54:57 +01:00
|
|
|
files, _, bytes := m.GlobalSize()
|
2014-01-05 16:16:37 +01:00
|
|
|
infof("%6d files, %9sB in cluster", files, BinaryPrefix(bytes))
|
2014-01-05 23:54:57 +01:00
|
|
|
files, _, bytes = m.LocalSize()
|
2014-01-05 16:16:37 +01:00
|
|
|
infof("%6d files, %9sB in local repo", files, BinaryPrefix(bytes))
|
2014-01-05 23:54:57 +01:00
|
|
|
needFiles, bytes := m.NeedFiles()
|
2014-01-06 11:11:18 +01:00
|
|
|
infof("%6d files, %9sB to synchronize", len(needFiles), BinaryPrefix(bytes))
|
2014-01-05 16:16:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-23 08:45:05 +01:00
|
|
|
func listenConnect(myID string, disc *discover.Discoverer, m *Model, tlsCfg *tls.Config, connOpts map[string]string) {
|
|
|
|
var conns = make(chan *tls.Conn)
|
|
|
|
|
|
|
|
// Listen
|
|
|
|
for _, addr := range cfg.Options.ListenAddress {
|
|
|
|
addr := addr
|
|
|
|
go func() {
|
|
|
|
if debugNet {
|
|
|
|
dlog.Println("listening on", addr)
|
|
|
|
}
|
|
|
|
l, err := tls.Listen("tcp", addr, tlsCfg)
|
|
|
|
fatalErr(err)
|
|
|
|
|
|
|
|
for {
|
|
|
|
conn, err := l.Accept()
|
|
|
|
if err != nil {
|
|
|
|
warnln(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if debugNet {
|
|
|
|
dlog.Println("connect from", conn.RemoteAddr())
|
|
|
|
}
|
|
|
|
|
|
|
|
tc := conn.(*tls.Conn)
|
|
|
|
err = tc.Handshake()
|
|
|
|
if err != nil {
|
|
|
|
warnln(err)
|
|
|
|
tc.Close()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
conns <- tc
|
|
|
|
}
|
|
|
|
}()
|
2014-02-05 23:17:17 +01:00
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-03-23 08:45:05 +01:00
|
|
|
// Connect
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
nextNode:
|
|
|
|
for _, nodeCfg := range cfg.Repositories[0].Nodes {
|
|
|
|
if nodeCfg.NodeID == myID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if m.ConnectedTo(nodeCfg.NodeID) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, addr := range nodeCfg.Addresses {
|
|
|
|
if addr == "dynamic" {
|
|
|
|
if disc != nil {
|
|
|
|
t := disc.Lookup(nodeCfg.NodeID)
|
|
|
|
if len(t) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
addr = t[0] //XXX: Handle all of them
|
|
|
|
}
|
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-03-23 08:45:05 +01:00
|
|
|
if debugNet {
|
|
|
|
dlog.Println("dial", nodeCfg.NodeID, addr)
|
|
|
|
}
|
|
|
|
conn, err := tls.Dial("tcp", addr, tlsCfg)
|
|
|
|
if err != nil {
|
|
|
|
if debugNet {
|
|
|
|
dlog.Println(err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-03-23 08:45:05 +01:00
|
|
|
conns <- conn
|
|
|
|
continue nextNode
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(time.Duration(cfg.Options.ReconnectIntervalS) * time.Second)
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
2014-03-23 08:45:05 +01:00
|
|
|
}()
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-03-23 08:45:05 +01:00
|
|
|
next:
|
|
|
|
for conn := range conns {
|
|
|
|
remoteID := certID(conn.ConnectionState().PeerCertificates[0].Raw)
|
2013-12-15 11:43:31 +01:00
|
|
|
|
|
|
|
if remoteID == myID {
|
2014-03-23 08:45:05 +01:00
|
|
|
warnf("Connected to myself (%s) - should not happen", remoteID)
|
2013-12-15 11:43:31 +01:00
|
|
|
conn.Close()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.ConnectedTo(remoteID) {
|
2014-03-23 08:45:05 +01:00
|
|
|
warnf("Connected to already connected node (%s)", remoteID)
|
|
|
|
conn.Close()
|
|
|
|
continue
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
for _, nodeCfg := range cfg.Repositories[0].Nodes {
|
|
|
|
if nodeCfg.NodeID == remoteID {
|
2014-01-23 13:12:45 +01:00
|
|
|
protoConn := protocol.NewConnection(remoteID, conn, conn, m, connOpts)
|
2014-01-09 13:58:35 +01:00
|
|
|
m.AddConnection(conn, protoConn)
|
2014-03-23 08:45:05 +01:00
|
|
|
continue next
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-18 17:51:55 +01:00
|
|
|
func discovery() *discover.Discoverer {
|
2014-02-01 20:23:19 +01:00
|
|
|
if !cfg.Options.LocalAnnEnabled {
|
2014-03-18 17:51:55 +01:00
|
|
|
return nil
|
2013-12-22 22:29:23 +01:00
|
|
|
}
|
|
|
|
|
2014-03-18 17:51:55 +01:00
|
|
|
infoln("Sending local discovery announcements")
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
if !cfg.Options.GlobalAnnEnabled {
|
|
|
|
cfg.Options.GlobalAnnServer = ""
|
2014-02-03 16:01:17 +01:00
|
|
|
} else if verbose {
|
2013-12-22 22:29:23 +01:00
|
|
|
infoln("Sending external discovery announcements")
|
|
|
|
}
|
|
|
|
|
2014-03-18 17:51:55 +01:00
|
|
|
disc, err := discover.NewDiscoverer(myID, cfg.Options.ListenAddress, cfg.Options.GlobalAnnServer)
|
2013-12-22 22:29:23 +01:00
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
if err != nil {
|
2013-12-22 22:29:23 +01:00
|
|
|
warnf("No discovery possible (%v)", err)
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2014-02-05 23:17:17 +01:00
|
|
|
return disc
|
|
|
|
}
|
|
|
|
|
2014-03-08 23:02:01 +01:00
|
|
|
func updateLocalModel(m *Model, w *scanner.Walker) {
|
|
|
|
files, _ := w.Walk()
|
2013-12-15 11:43:31 +01:00
|
|
|
m.ReplaceLocal(files)
|
|
|
|
saveIndex(m)
|
|
|
|
}
|
|
|
|
|
2014-02-12 23:18:41 +01:00
|
|
|
func saveIndex(m *Model) {
|
2014-01-06 11:11:18 +01:00
|
|
|
name := m.RepoID() + ".idx.gz"
|
2014-01-26 14:28:41 +01:00
|
|
|
fullName := path.Join(confDir, name)
|
2013-12-31 04:04:30 +01:00
|
|
|
idxf, err := os.Create(fullName + ".tmp")
|
2013-12-15 11:43:31 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2014-01-01 22:31:04 +01:00
|
|
|
|
|
|
|
gzw := gzip.NewWriter(idxf)
|
|
|
|
|
2014-02-24 13:24:03 +01:00
|
|
|
protocol.IndexMessage{
|
|
|
|
Repository: "local",
|
|
|
|
Files: m.ProtocolIndex(),
|
|
|
|
}.EncodeXDR(gzw)
|
2014-01-01 22:31:04 +01:00
|
|
|
gzw.Close()
|
2013-12-15 11:43:31 +01:00
|
|
|
idxf.Close()
|
2013-12-31 04:04:30 +01:00
|
|
|
os.Rename(fullName+".tmp", fullName)
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2014-02-12 23:18:41 +01:00
|
|
|
func loadIndex(m *Model) {
|
2014-01-06 11:11:18 +01:00
|
|
|
name := m.RepoID() + ".idx.gz"
|
2014-01-26 14:28:41 +01:00
|
|
|
idxf, err := os.Open(path.Join(confDir, name))
|
2013-12-15 11:43:31 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer idxf.Close()
|
|
|
|
|
2014-01-01 22:31:04 +01:00
|
|
|
gzr, err := gzip.NewReader(idxf)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer gzr.Close()
|
|
|
|
|
2014-02-20 17:40:15 +01:00
|
|
|
var im protocol.IndexMessage
|
|
|
|
err = im.DecodeXDR(gzr)
|
|
|
|
if err != nil || im.Repository != "local" {
|
2013-12-15 11:43:31 +01:00
|
|
|
return
|
|
|
|
}
|
2014-02-20 17:40:15 +01:00
|
|
|
m.SeedLocal(im.Files)
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2013-12-22 00:16:49 +01:00
|
|
|
func ensureDir(dir string, mode int) {
|
2013-12-15 11:43:31 +01:00
|
|
|
fi, err := os.Stat(dir)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
err := os.MkdirAll(dir, 0700)
|
|
|
|
fatalErr(err)
|
2013-12-22 00:16:49 +01:00
|
|
|
} else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
|
|
|
|
err := os.Chmod(dir, os.FileMode(mode))
|
2013-12-15 11:43:31 +01:00
|
|
|
fatalErr(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-06 19:37:26 +01:00
|
|
|
func expandTilde(p string) string {
|
2014-03-02 23:49:51 +01:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
return p
|
|
|
|
}
|
2014-03-03 08:46:20 +01:00
|
|
|
|
2014-01-06 19:37:26 +01:00
|
|
|
if strings.HasPrefix(p, "~/") {
|
2014-03-02 23:49:51 +01:00
|
|
|
return strings.Replace(p, "~", getUnixHomeDir(), 1)
|
2014-01-06 19:37:26 +01:00
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2014-03-02 23:49:51 +01:00
|
|
|
func getUnixHomeDir() string {
|
|
|
|
home := os.Getenv("HOME")
|
|
|
|
if home == "" {
|
|
|
|
fatalln("No home directory?")
|
|
|
|
}
|
|
|
|
return home
|
|
|
|
}
|
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
func getHomeDir() string {
|
2014-03-02 23:49:51 +01:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
|
|
|
if home == "" {
|
|
|
|
home = os.Getenv("USERPROFILE")
|
|
|
|
}
|
|
|
|
return home
|
2014-03-02 16:07:12 +01:00
|
|
|
}
|
2014-03-02 23:49:51 +01:00
|
|
|
return getUnixHomeDir()
|
|
|
|
}
|
2014-03-02 16:07:12 +01:00
|
|
|
|
2014-03-02 23:49:51 +01:00
|
|
|
func getDefaultConfDir() string {
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
return path.Join(os.Getenv("AppData"), "syncthing")
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
2014-03-02 23:49:51 +01:00
|
|
|
return expandTilde("~/.syncthing")
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|