2014-06-01 22:50:14 +02:00
|
|
|
// Copyright (C) 2014 Jakob Borg and other contributors. All rights reserved.
|
|
|
|
// Use of this source code is governed by an MIT-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
2014-01-26 14:28:41 +01:00
|
|
|
"flag"
|
2014-01-08 14:37:33 +01:00
|
|
|
"fmt"
|
2014-04-01 20:36:54 +02:00
|
|
|
"io"
|
2013-12-15 11:43:31 +01:00
|
|
|
"log"
|
2014-04-18 14:09:54 +02:00
|
|
|
"math/rand"
|
2013-12-15 11:43:31 +01:00
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
_ "net/http/pprof"
|
|
|
|
"os"
|
2014-02-17 08:47:21 +01:00
|
|
|
"os/exec"
|
2014-03-28 14:36:57 +01:00
|
|
|
"path/filepath"
|
2014-01-10 00:09:27 +01:00
|
|
|
"runtime"
|
|
|
|
"runtime/debug"
|
2014-04-14 12:13:50 +02:00
|
|
|
"runtime/pprof"
|
2014-04-18 13:20:42 +02:00
|
|
|
"strconv"
|
2013-12-15 11:43:31 +01:00
|
|
|
"strings"
|
|
|
|
"time"
|
2014-04-08 13:45:18 +02:00
|
|
|
|
2014-05-15 02:18:09 +02:00
|
|
|
"github.com/calmh/syncthing/config"
|
2013-12-15 11:43:31 +01:00
|
|
|
"github.com/calmh/syncthing/discover"
|
2014-05-15 02:08:56 +02:00
|
|
|
"github.com/calmh/syncthing/logger"
|
2014-05-15 05:26:55 +02:00
|
|
|
"github.com/calmh/syncthing/model"
|
2014-05-25 20:49:08 +02:00
|
|
|
"github.com/calmh/syncthing/osutil"
|
2013-12-15 11:43:31 +01:00
|
|
|
"github.com/calmh/syncthing/protocol"
|
2014-04-18 13:20:42 +02:00
|
|
|
"github.com/calmh/syncthing/upnp"
|
2014-04-01 20:36:54 +02:00
|
|
|
"github.com/juju/ratelimit"
|
2013-12-15 11:43:31 +01:00
|
|
|
)
|
|
|
|
|
2014-04-19 16:38:11 +02:00
|
|
|
var (
|
2014-04-19 16:40:19 +02:00
|
|
|
Version = "unknown-dev"
|
|
|
|
BuildStamp = "0"
|
|
|
|
BuildDate time.Time
|
2014-04-19 16:44:28 +02:00
|
|
|
BuildHost = "unknown"
|
|
|
|
BuildUser = "unknown"
|
2014-04-19 16:40:19 +02:00
|
|
|
LongVersion string
|
2014-04-19 16:38:11 +02:00
|
|
|
)
|
|
|
|
|
2014-05-15 02:08:56 +02:00
|
|
|
var l = logger.DefaultLogger
|
|
|
|
|
2014-04-19 16:38:11 +02:00
|
|
|
func init() {
|
|
|
|
stamp, _ := strconv.Atoi(BuildStamp)
|
|
|
|
BuildDate = time.Unix(int64(stamp), 0)
|
2014-04-19 16:40:19 +02:00
|
|
|
|
2014-05-11 22:26:48 +02:00
|
|
|
date := BuildDate.UTC().Format("2006-01-02 15:04:05 MST")
|
2014-04-19 16:44:28 +02:00
|
|
|
LongVersion = fmt.Sprintf("syncthing %s (%s %s-%s) %s@%s %s", Version, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildUser, BuildHost, date)
|
2014-05-15 02:08:56 +02:00
|
|
|
|
|
|
|
if os.Getenv("STTRACE") != "" {
|
2014-06-04 10:24:30 +02:00
|
|
|
logFlags = log.Ltime | log.Ldate | log.Lmicroseconds | log.Lshortfile
|
2014-05-15 02:08:56 +02:00
|
|
|
}
|
2014-04-19 16:38:11 +02:00
|
|
|
}
|
2013-12-18 19:36:28 +01:00
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
var (
|
2014-05-15 02:18:09 +02:00
|
|
|
cfg config.Configuration
|
2014-04-01 20:36:54 +02:00
|
|
|
myID string
|
|
|
|
confDir string
|
2014-06-04 10:24:30 +02:00
|
|
|
logFlags int = log.Ltime
|
2014-04-01 20:36:54 +02:00
|
|
|
rateBucket *ratelimit.Bucket
|
2014-04-14 12:13:50 +02:00
|
|
|
stop = make(chan bool)
|
2014-04-16 17:36:09 +02:00
|
|
|
discoverer *discover.Discoverer
|
2014-01-26 14:28:41 +01:00
|
|
|
)
|
|
|
|
|
2014-03-09 08:48:29 +01:00
|
|
|
const (
|
|
|
|
usage = "syncthing [options]"
|
2014-06-04 10:24:30 +02:00
|
|
|
extraUsage = `The value for the -logflags option 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).
|
|
|
|
|
|
|
|
The following enviroment variables are interpreted by syncthing:
|
2014-03-12 10:12:35 +01:00
|
|
|
|
2014-04-14 12:13:50 +02:00
|
|
|
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:
|
2014-05-15 05:26:55 +02:00
|
|
|
- "beacon" (the beacon package)
|
|
|
|
- "discover" (the discover package)
|
|
|
|
- "files" (the files package)
|
2014-05-21 20:36:21 +02:00
|
|
|
- "net" (the main package; connections & network messages)
|
|
|
|
- "model" (the model package)
|
2014-05-15 05:26:55 +02:00
|
|
|
- "scanner" (the scanner package)
|
|
|
|
- "upnp" (the upnp package)
|
2014-05-17 08:43:01 +02:00
|
|
|
- "xdr" (the xdr package)
|
2014-05-15 02:08:56 +02:00
|
|
|
- "all" (all of the above)
|
2014-04-14 12:13:50 +02:00
|
|
|
|
2014-05-22 16:12:19 +02:00
|
|
|
STCPUPROFILE Write CPU profile to the specified file.
|
|
|
|
|
|
|
|
STGUIASSETS Directory to load GUI assets from. Overrides compiled in assets.`
|
2014-03-09 08:48:29 +01:00
|
|
|
)
|
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
func main() {
|
2014-04-03 22:10:51 +02:00
|
|
|
var reset bool
|
2014-04-01 20:36:54 +02:00
|
|
|
var showVersion bool
|
2014-05-02 10:01:09 +02:00
|
|
|
var doUpgrade bool
|
2014-03-02 23:49:51 +01:00
|
|
|
flag.StringVar(&confDir, "home", getDefaultConfDir(), "Set configuration directory")
|
2014-04-03 22:10:51 +02:00
|
|
|
flag.BoolVar(&reset, "reset", false, "Prepare to resync from cluster")
|
2014-01-26 14:28:41 +01:00
|
|
|
flag.BoolVar(&showVersion, "version", false, "Show version")
|
2014-05-02 10:01:09 +02:00
|
|
|
flag.BoolVar(&doUpgrade, "upgrade", false, "Perform upgrade")
|
2014-06-04 10:24:30 +02:00
|
|
|
flag.IntVar(&logFlags, "logflags", logFlags, "Set log flags")
|
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-01-26 14:28:41 +01:00
|
|
|
if showVersion {
|
2014-04-19 16:40:19 +02:00
|
|
|
fmt.Println(LongVersion)
|
2014-04-14 12:13:50 +02:00
|
|
|
return
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
2014-01-08 14:37:33 +01:00
|
|
|
|
2014-06-04 10:24:30 +02:00
|
|
|
l.SetFlags(logFlags)
|
|
|
|
|
2014-05-02 10:01:09 +02:00
|
|
|
if doUpgrade {
|
|
|
|
err := upgrade()
|
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Fatalln(err)
|
2014-05-02 10:01:09 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2014-04-22 13:24:47 +02:00
|
|
|
if _, err := os.Stat(confDir); err != nil && confDir == getDefaultConfDir() {
|
|
|
|
// We are supposed to use the default configuration directory. It
|
|
|
|
// doesn't exist. In the past our default has been ~/.syncthing, so if
|
|
|
|
// that directory exists we move it to the new default location and
|
|
|
|
// continue. We don't much care if this fails at this point, we will
|
|
|
|
// be checking that later.
|
|
|
|
|
2014-05-24 22:45:50 +02:00
|
|
|
var oldDefault string
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
oldDefault = filepath.Join(os.Getenv("AppData"), "Syncthing")
|
|
|
|
} else {
|
|
|
|
oldDefault = expandTilde("~/.syncthing")
|
|
|
|
}
|
2014-04-22 13:24:47 +02:00
|
|
|
if _, err := os.Stat(oldDefault); err == nil {
|
|
|
|
os.MkdirAll(filepath.Dir(confDir), 0700)
|
|
|
|
if err := os.Rename(oldDefault, confDir); err == nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infoln("Moved config dir", oldDefault, "to", confDir)
|
2014-04-22 13:24:47 +02: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)
|
2014-05-21 14:04:16 +02:00
|
|
|
cert, err := loadCert(confDir, "")
|
2013-12-15 11:43:31 +01:00
|
|
|
if err != nil {
|
2014-05-21 14:04:16 +02:00
|
|
|
newCertificate(confDir, "")
|
|
|
|
cert, err = loadCert(confDir, "")
|
2014-05-15 02:08:56 +02:00
|
|
|
l.FatalErr(err)
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2014-04-18 14:09:54 +02:00
|
|
|
myID = certID(cert.Certificate[0])
|
2014-05-15 02:08:56 +02:00
|
|
|
l.SetPrefix(fmt.Sprintf("[%s] ", myID[:5]))
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infoln(LongVersion)
|
|
|
|
l.Infoln("My ID:", myID)
|
2014-02-03 16:01:17 +01:00
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
// Prepare to be able to save configuration
|
|
|
|
|
2014-03-28 14:36:57 +01:00
|
|
|
cfgFile := filepath.Join(confDir, "config.xml")
|
2014-02-01 20:23:19 +01:00
|
|
|
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
|
2014-05-15 02:18:09 +02:00
|
|
|
cfg, err = config.Load(cf, myID)
|
2014-01-22 14:28:14 +01:00
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Fatalln(err)
|
2014-01-22 14:28:14 +01:00
|
|
|
}
|
2014-02-01 20:23:19 +01:00
|
|
|
cf.Close()
|
2014-04-23 10:28:36 +02:00
|
|
|
} else {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infoln("No config file; starting with empty defaults")
|
2014-04-16 16:35:29 +02:00
|
|
|
name, _ := os.Hostname()
|
2014-04-24 10:27:43 +02:00
|
|
|
defaultRepo := filepath.Join(getHomeDir(), "Sync")
|
|
|
|
ensureDir(defaultRepo, 0755)
|
2014-02-01 20:23:19 +01:00
|
|
|
|
2014-05-15 02:18:09 +02:00
|
|
|
cfg, err = config.Load(nil, myID)
|
|
|
|
cfg.Repositories = []config.RepositoryConfiguration{
|
2014-02-01 20:23:19 +01:00
|
|
|
{
|
2014-03-30 23:11:55 +02:00
|
|
|
ID: "default",
|
2014-04-24 10:27:43 +02:00
|
|
|
Directory: defaultRepo,
|
2014-05-15 02:18:09 +02:00
|
|
|
Nodes: []config.NodeConfiguration{{NodeID: myID}},
|
2014-02-01 20:23:19 +01:00
|
|
|
},
|
|
|
|
}
|
2014-05-15 02:18:09 +02:00
|
|
|
cfg.Nodes = []config.NodeConfiguration{
|
2014-04-16 16:35:29 +02:00
|
|
|
{
|
|
|
|
NodeID: myID,
|
|
|
|
Addresses: []string{"dynamic"},
|
|
|
|
Name: name,
|
|
|
|
},
|
2014-04-08 13:45:18 +02:00
|
|
|
}
|
2014-01-22 14:28:14 +01:00
|
|
|
|
2014-05-11 20:21:41 +02:00
|
|
|
port, err := getFreePort("127.0.0.1", 8080)
|
2014-05-15 02:08:56 +02:00
|
|
|
l.FatalErr(err)
|
2014-05-11 20:21:41 +02:00
|
|
|
cfg.GUI.Address = fmt.Sprintf("127.0.0.1:%d", port)
|
|
|
|
|
2014-05-26 15:01:04 +02:00
|
|
|
port, err = getFreePort("0.0.0.0", 22000)
|
2014-05-15 02:08:56 +02:00
|
|
|
l.FatalErr(err)
|
2014-05-26 15:01:04 +02:00
|
|
|
cfg.Options.ListenAddress = []string{fmt.Sprintf("0.0.0.0:%d", port)}
|
2014-05-11 20:21:41 +02:00
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
saveConfig()
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
|
2014-02-01 20:23:19 +01:00
|
|
|
}
|
2014-01-26 14:28:41 +01:00
|
|
|
|
2014-04-03 22:10:51 +02:00
|
|
|
if reset {
|
|
|
|
resetRepositories()
|
2014-04-14 12:13:50 +02:00
|
|
|
return
|
2014-04-03 22:10:51 +02: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-05-15 02:08:56 +02:00
|
|
|
l.Debugln("Starting profiler on", profiler)
|
2014-05-20 18:41:01 +02:00
|
|
|
runtime.SetBlockProfileRate(1)
|
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-05-15 02:08:56 +02:00
|
|
|
l.Fatalln(err)
|
2013-12-18 19:36:28 +01:00
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2014-05-24 12:28:36 +02:00
|
|
|
if len(os.Getenv("STRESTART")) > 0 {
|
|
|
|
waitForParentExit()
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2014-04-01 20:36:54 +02:00
|
|
|
// If the 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.
|
|
|
|
|
2014-02-01 20:23:19 +01:00
|
|
|
if cfg.Options.MaxSendKbps > 0 {
|
2014-04-01 20:36:54 +02:00
|
|
|
rateBucket = ratelimit.NewBucketWithRate(float64(1000*cfg.Options.MaxSendKbps), int64(5*1000*cfg.Options.MaxSendKbps))
|
2014-01-12 16:59:35 +01:00
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-05-15 05:26:55 +02:00
|
|
|
m := model.NewModel(confDir, &cfg, "syncthing", Version)
|
2014-04-01 20:36:54 +02:00
|
|
|
|
2014-04-24 10:27:43 +02:00
|
|
|
for _, repo := range cfg.Repositories {
|
2014-04-27 21:53:27 +02:00
|
|
|
if repo.Invalid != "" {
|
|
|
|
continue
|
|
|
|
}
|
2014-05-23 14:31:16 +02:00
|
|
|
repo.Directory = expandTilde(repo.Directory)
|
|
|
|
m.AddRepo(repo)
|
2014-03-29 18:53:48 +01:00
|
|
|
}
|
|
|
|
|
2014-01-05 23:54:57 +01:00
|
|
|
// GUI
|
2014-04-08 15:56:12 +02:00
|
|
|
if cfg.GUI.Enabled && cfg.GUI.Address != "" {
|
|
|
|
addr, err := net.ResolveTCPAddr("tcp", cfg.GUI.Address)
|
2014-01-09 10:40:12 +01:00
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Fatalf("Cannot start GUI on %q: %v", cfg.GUI.Address, 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
|
|
|
|
2014-05-21 14:04:16 +02:00
|
|
|
var proto = "http"
|
|
|
|
if cfg.GUI.UseTLS {
|
|
|
|
proto = "https"
|
|
|
|
}
|
|
|
|
|
|
|
|
l.Infof("Starting web GUI on %s://%s:%d/", proto, hostShow, addr.Port)
|
2014-05-22 16:12:19 +02:00
|
|
|
err := startGUI(cfg.GUI, os.Getenv("STGUIASSETS"), m)
|
2014-04-30 22:52:38 +02:00
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Fatalln("Cannot start GUI:", err)
|
2014-04-30 22:52:38 +02:00
|
|
|
}
|
2014-03-09 08:35:53 +01:00
|
|
|
if cfg.Options.StartBrowser && len(os.Getenv("STRESTART")) == 0 {
|
2014-05-21 14:04:16 +02:00
|
|
|
openURL(fmt.Sprintf("%s://%s:%d", proto, hostOpen, addr.Port))
|
2014-03-08 23:19:33 +01:00
|
|
|
}
|
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-05-15 02:08:56 +02:00
|
|
|
l.Infoln("Populating repository index")
|
2014-03-29 18:53:48 +01:00
|
|
|
m.LoadIndexes(confDir)
|
2014-04-24 10:27:43 +02:00
|
|
|
|
|
|
|
for _, repo := range cfg.Repositories {
|
2014-04-27 21:53:27 +02:00
|
|
|
if repo.Invalid != "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-04-24 10:27:43 +02:00
|
|
|
dir := expandTilde(repo.Directory)
|
|
|
|
|
|
|
|
// Safety check. If the cached index contains files but the repository
|
|
|
|
// doesn't exist, we have a problem. We would assume that all files
|
|
|
|
// have been deleted which might not be the case, so abort instead.
|
|
|
|
|
|
|
|
if files, _, _ := m.LocalSize(repo.ID); files > 0 {
|
|
|
|
if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnf("Configured repository %q has index but directory %q is missing; not starting.", repo.ID, repo.Directory)
|
|
|
|
l.Fatalf("Ensure that directory is present or remove repository from configuration.")
|
2014-04-24 10:27:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that repository directories exist for newly configured repositories.
|
|
|
|
ensureDir(dir, -1)
|
|
|
|
}
|
|
|
|
|
2014-05-15 05:26:55 +02:00
|
|
|
m.CleanRepos()
|
2014-03-29 18:53:48 +01:00
|
|
|
m.ScanRepos()
|
|
|
|
m.SaveIndexes(confDir)
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-04-18 13:20:42 +02:00
|
|
|
// UPnP
|
|
|
|
|
|
|
|
var externalPort = 0
|
2014-04-18 13:39:51 +02:00
|
|
|
if cfg.Options.UPnPEnabled {
|
2014-04-18 14:09:54 +02:00
|
|
|
// We seed the random number generator with the node ID to get a
|
|
|
|
// repeatable sequence of random external ports.
|
|
|
|
rand.Seed(certSeed(cert.Certificate[0]))
|
2014-04-18 13:39:51 +02:00
|
|
|
externalPort = setupUPnP()
|
2014-04-18 13:20:42 +02:00
|
|
|
}
|
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
// Routine to connect out to configured nodes
|
2014-04-18 13:20:42 +02:00
|
|
|
discoverer = discovery(externalPort)
|
2014-04-16 17:36:09 +02:00
|
|
|
go listenConnect(myID, m, tlsCfg)
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-04-08 13:45:18 +02:00
|
|
|
for _, repo := range cfg.Repositories {
|
2014-04-27 21:53:27 +02:00
|
|
|
if repo.Invalid != "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-04-08 13:45:18 +02: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.
|
|
|
|
if repo.ReadOnly {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Okf("Ready to synchronize %s (read only; no external updates accepted)", repo.ID)
|
2014-04-08 13:45:18 +02:00
|
|
|
m.StartRepoRO(repo.ID)
|
|
|
|
} else {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Okf("Ready to synchronize %s (read-write)", repo.ID)
|
2014-04-08 13:45:18 +02:00
|
|
|
m.StartRepoRW(repo.ID, cfg.Options.ParallelRequests)
|
|
|
|
}
|
2014-02-03 16:01:17 +01:00
|
|
|
}
|
2014-01-05 16:16:37 +01:00
|
|
|
|
2014-04-14 12:13:50 +02:00
|
|
|
if cpuprof := os.Getenv("STCPUPROFILE"); len(cpuprof) > 0 {
|
|
|
|
f, err := os.Create(cpuprof)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
pprof.StartCPUProfile(f)
|
|
|
|
defer pprof.StopCPUProfile()
|
|
|
|
}
|
|
|
|
|
2014-05-24 21:39:08 +02:00
|
|
|
for _, node := range cfg.Nodes {
|
|
|
|
if len(node.Name) > 0 {
|
|
|
|
l.Infof("Node %s is %q at %v", node.NodeID, node.Name, node.Addresses)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-14 12:13:50 +02:00
|
|
|
<-stop
|
2014-05-24 12:28:36 +02:00
|
|
|
l.Okln("Exiting")
|
|
|
|
}
|
|
|
|
|
|
|
|
func waitForParentExit() {
|
|
|
|
l.Infoln("Waiting for parent to exit...")
|
|
|
|
// Wait for the listen address to become free, indicating that the parent has exited.
|
|
|
|
for {
|
|
|
|
ln, err := net.Listen("tcp", cfg.Options.ListenAddress[0])
|
|
|
|
if err == nil {
|
|
|
|
ln.Close()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
}
|
|
|
|
l.Okln("Continuing")
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2014-04-18 13:39:51 +02:00
|
|
|
func setupUPnP() int {
|
|
|
|
var externalPort = 0
|
|
|
|
if len(cfg.Options.ListenAddress) == 1 {
|
|
|
|
_, portStr, err := net.SplitHostPort(cfg.Options.ListenAddress[0])
|
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln(err)
|
2014-04-18 13:39:51 +02:00
|
|
|
} else {
|
|
|
|
// Set up incoming port forwarding, if necessary and possible
|
|
|
|
port, _ := strconv.Atoi(portStr)
|
|
|
|
igd, err := upnp.Discover()
|
|
|
|
if err == nil {
|
|
|
|
for i := 0; i < 10; i++ {
|
2014-04-18 14:09:54 +02:00
|
|
|
r := 1024 + rand.Intn(65535-1024)
|
|
|
|
err := igd.AddPortMapping(upnp.TCP, r, port, "syncthing", 0)
|
2014-04-18 13:39:51 +02:00
|
|
|
if err == nil {
|
2014-04-18 14:09:54 +02:00
|
|
|
externalPort = r
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infoln("Created UPnP port mapping - external port", externalPort)
|
2014-04-18 13:39:51 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if externalPort == 0 {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln("Failed to create UPnP port mapping")
|
2014-04-18 13:39:51 +02:00
|
|
|
}
|
|
|
|
} else {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infof("No UPnP IGD device found, no port mapping created (%v)", err)
|
2014-04-18 13:39:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln("Multiple listening addresses; not attempting UPnP port mapping")
|
2014-04-18 13:39:51 +02:00
|
|
|
}
|
|
|
|
return externalPort
|
|
|
|
}
|
|
|
|
|
2014-04-03 22:10:51 +02:00
|
|
|
func resetRepositories() {
|
|
|
|
suffix := fmt.Sprintf(".syncthing-reset-%d", time.Now().UnixNano())
|
|
|
|
for _, repo := range cfg.Repositories {
|
|
|
|
if _, err := os.Stat(repo.Directory); err == nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infof("Reset: Moving %s -> %s", repo.Directory, repo.Directory+suffix)
|
2014-04-03 22:10:51 +02:00
|
|
|
os.Rename(repo.Directory, repo.Directory+suffix)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pat := filepath.Join(confDir, "*.idx.gz")
|
|
|
|
idxs, err := filepath.Glob(pat)
|
|
|
|
if err == nil {
|
|
|
|
for _, idx := range idxs {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infof("Reset: Removing %s", idx)
|
2014-04-03 22:10:51 +02:00
|
|
|
os.Remove(idx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-12 12:10:44 +01:00
|
|
|
func restart() {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infoln("Restarting")
|
2014-03-12 10:12:35 +01:00
|
|
|
if os.Getenv("SMF_FMRI") != "" || os.Getenv("STNORESTART") != "" {
|
|
|
|
// Solaris SMF
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infoln("Service manager detected; exit instead of restart")
|
2014-04-14 12:13:50 +02:00
|
|
|
stop <- true
|
|
|
|
return
|
2014-03-12 10:12:35 +01:00
|
|
|
}
|
|
|
|
|
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 {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln(err)
|
2014-02-17 08:47:21 +01:00
|
|
|
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 {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Fatalln(err)
|
2014-02-12 12:10:44 +01:00
|
|
|
}
|
|
|
|
proc.Release()
|
2014-04-14 12:13:50 +02:00
|
|
|
stop <- true
|
2014-02-12 12:10:44 +01:00
|
|
|
}
|
|
|
|
|
2014-05-12 01:16:27 +02:00
|
|
|
func shutdown() {
|
|
|
|
stop <- true
|
|
|
|
}
|
|
|
|
|
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 {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln(err)
|
2014-02-01 20:23:19 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-05-15 02:18:09 +02:00
|
|
|
err = config.Save(fd, cfg)
|
2014-02-01 20:23:19 +01:00
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln(err)
|
2014-02-01 20:23:19 +01:00
|
|
|
fd.Close()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
err = fd.Close()
|
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln(err)
|
2014-02-01 20:23:19 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-05-25 20:49:08 +02:00
|
|
|
err = osutil.Rename(cfgFile+".tmp", cfgFile)
|
2014-02-01 20:23:19 +01:00
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln(err)
|
2014-02-01 20:23:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func saveConfig() {
|
|
|
|
saveConfigCh <- struct{}{}
|
|
|
|
}
|
|
|
|
|
2014-05-15 05:26:55 +02:00
|
|
|
func listenConnect(myID string, m *model.Model, tlsCfg *tls.Config) {
|
2014-03-23 08:45:05 +01:00
|
|
|
var conns = make(chan *tls.Conn)
|
|
|
|
|
|
|
|
// Listen
|
|
|
|
for _, addr := range cfg.Options.ListenAddress {
|
|
|
|
addr := addr
|
|
|
|
go func() {
|
|
|
|
if debugNet {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Debugln("listening on", addr)
|
2014-03-23 08:45:05 +01:00
|
|
|
}
|
2014-05-15 02:08:56 +02:00
|
|
|
listener, err := tls.Listen("tcp", addr, tlsCfg)
|
|
|
|
l.FatalErr(err)
|
2014-03-23 08:45:05 +01:00
|
|
|
|
|
|
|
for {
|
2014-05-15 02:08:56 +02:00
|
|
|
conn, err := listener.Accept()
|
2014-03-23 08:45:05 +01:00
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln(err)
|
2014-03-23 08:45:05 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if debugNet {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Debugln("connect from", conn.RemoteAddr())
|
2014-03-23 08:45:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
tc := conn.(*tls.Conn)
|
|
|
|
err = tc.Handshake()
|
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnln(err)
|
2014-03-23 08:45:05 +01:00
|
|
|
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:
|
2014-04-08 13:45:18 +02:00
|
|
|
for _, nodeCfg := range cfg.Nodes {
|
2014-03-23 08:45:05 +01:00
|
|
|
if nodeCfg.NodeID == myID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if m.ConnectedTo(nodeCfg.NodeID) {
|
|
|
|
continue
|
|
|
|
}
|
2014-04-08 13:45:18 +02:00
|
|
|
|
|
|
|
var addrs []string
|
2014-03-23 08:45:05 +01:00
|
|
|
for _, addr := range nodeCfg.Addresses {
|
|
|
|
if addr == "dynamic" {
|
2014-04-16 17:36:09 +02:00
|
|
|
if discoverer != nil {
|
|
|
|
t := discoverer.Lookup(nodeCfg.NodeID)
|
2014-03-23 08:45:05 +01:00
|
|
|
if len(t) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2014-04-08 13:45:18 +02:00
|
|
|
addrs = append(addrs, t...)
|
2014-03-23 08:45:05 +01:00
|
|
|
}
|
2014-04-08 13:45:18 +02:00
|
|
|
} else {
|
|
|
|
addrs = append(addrs, addr)
|
2014-03-23 08:45:05 +01:00
|
|
|
}
|
2014-04-08 13:45:18 +02:00
|
|
|
}
|
2013-12-15 11:43:31 +01:00
|
|
|
|
2014-04-08 13:45:18 +02:00
|
|
|
for _, addr := range addrs {
|
2014-04-16 15:28:45 +02:00
|
|
|
host, port, err := net.SplitHostPort(addr)
|
|
|
|
if err != nil && strings.HasPrefix(err.Error(), "missing port") {
|
|
|
|
// addr is on the form "1.2.3.4"
|
|
|
|
addr = net.JoinHostPort(addr, "22000")
|
|
|
|
} else if err == nil && port == "" {
|
|
|
|
// addr is on the form "1.2.3.4:"
|
|
|
|
addr = net.JoinHostPort(host, "22000")
|
|
|
|
}
|
2014-03-23 08:45:05 +01:00
|
|
|
if debugNet {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Debugln("dial", nodeCfg.NodeID, addr)
|
2014-03-23 08:45:05 +01:00
|
|
|
}
|
|
|
|
conn, err := tls.Dial("tcp", addr, tlsCfg)
|
|
|
|
if err != nil {
|
|
|
|
if debugNet {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Debugln(err)
|
2014-03-23 08:45:05 +01:00
|
|
|
}
|
|
|
|
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 {
|
2014-04-08 21:31:23 +02:00
|
|
|
certs := conn.ConnectionState().PeerCertificates
|
2014-05-15 02:08:56 +02:00
|
|
|
if cl := len(certs); cl != 1 {
|
2014-05-16 19:10:16 +02:00
|
|
|
l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, conn.RemoteAddr())
|
2014-04-08 21:31:23 +02:00
|
|
|
conn.Close()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
remoteID := certID(certs[0].Raw)
|
2013-12-15 11:43:31 +01:00
|
|
|
|
|
|
|
if remoteID == myID {
|
2014-05-16 19:10:16 +02:00
|
|
|
l.Infof("Connected to myself (%s) - should not happen", remoteID)
|
2013-12-15 11:43:31 +01:00
|
|
|
conn.Close()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.ConnectedTo(remoteID) {
|
2014-05-16 19:10:16 +02:00
|
|
|
l.Infof("Connected to already connected node (%s)", remoteID)
|
2014-03-23 08:45:05 +01:00
|
|
|
conn.Close()
|
|
|
|
continue
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2014-04-08 21:32:58 +02:00
|
|
|
for _, nodeCfg := range cfg.Nodes {
|
2014-02-01 20:23:19 +01:00
|
|
|
if nodeCfg.NodeID == remoteID {
|
2014-04-01 20:36:54 +02:00
|
|
|
var wr io.Writer = conn
|
|
|
|
if rateBucket != nil {
|
|
|
|
wr = &limitedWriter{conn, rateBucket}
|
|
|
|
}
|
2014-04-13 15:28:26 +02:00
|
|
|
protoConn := protocol.NewConnection(remoteID, conn, wr, m)
|
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
|
|
|
}
|
|
|
|
}
|
2014-05-16 19:10:16 +02:00
|
|
|
|
|
|
|
l.Infof("Connection from %s with unknown node ID %s; ignoring", conn.RemoteAddr(), remoteID)
|
2013-12-15 11:43:31 +01:00
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-18 13:20:42 +02:00
|
|
|
func discovery(extPort int) *discover.Discoverer {
|
2014-05-22 09:35:54 +02:00
|
|
|
disc, err := discover.NewDiscoverer(myID, cfg.Options.ListenAddress, cfg.Options.LocalAnnPort)
|
2014-04-16 16:49:01 +02:00
|
|
|
if err != nil {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Warnf("No discovery possible (%v)", err)
|
2014-03-18 17:51:55 +01:00
|
|
|
return nil
|
2013-12-22 22:29:23 +01:00
|
|
|
}
|
|
|
|
|
2014-04-16 16:49:01 +02:00
|
|
|
if cfg.Options.LocalAnnEnabled {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infoln("Sending local discovery announcements")
|
2014-04-16 16:49:01 +02:00
|
|
|
disc.StartLocal()
|
2013-12-22 22:29:23 +01:00
|
|
|
}
|
|
|
|
|
2014-04-16 16:49:01 +02:00
|
|
|
if cfg.Options.GlobalAnnEnabled {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Infoln("Sending global discovery announcements")
|
2014-04-18 13:20:42 +02:00
|
|
|
disc.StartGlobal(cfg.Options.GlobalAnnServer, uint16(extPort))
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
|
2014-02-05 23:17:17 +01:00
|
|
|
return disc
|
|
|
|
}
|
|
|
|
|
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)
|
2014-05-15 02:08:56 +02:00
|
|
|
l.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))
|
2014-05-15 02:08:56 +02:00
|
|
|
l.FatalErr(err)
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-22 13:24:47 +02:00
|
|
|
func getDefaultConfDir() string {
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "windows":
|
2014-05-24 22:45:50 +02:00
|
|
|
return filepath.Join(os.Getenv("LocalAppData"), "Syncthing")
|
2014-04-22 14:26:51 +02:00
|
|
|
|
2014-04-22 13:24:47 +02:00
|
|
|
case "darwin":
|
|
|
|
return expandTilde("~/Library/Application Support/Syncthing")
|
2014-04-22 14:26:51 +02:00
|
|
|
|
2014-04-22 13:24:47 +02:00
|
|
|
default:
|
|
|
|
if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
|
2014-04-22 14:26:51 +02:00
|
|
|
return filepath.Join(xdgCfg, "syncthing")
|
2014-04-22 13:24:47 +02:00
|
|
|
} else {
|
|
|
|
return expandTilde("~/.config/syncthing")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-06 19:37:26 +01:00
|
|
|
func expandTilde(p string) string {
|
2014-05-24 13:22:09 +02:00
|
|
|
if p == "~" {
|
|
|
|
return getHomeDir()
|
|
|
|
}
|
|
|
|
|
2014-05-26 16:58:03 +02:00
|
|
|
p = filepath.FromSlash(p)
|
2014-05-24 13:22:09 +02:00
|
|
|
if !strings.HasPrefix(p, fmt.Sprintf("~%c", os.PathSeparator)) {
|
2014-03-02 23:49:51 +01:00
|
|
|
return p
|
|
|
|
}
|
2014-03-03 08:46:20 +01:00
|
|
|
|
2014-04-22 14:26:51 +02:00
|
|
|
return filepath.Join(getHomeDir(), p[2:])
|
2014-03-02 23:49:51 +01:00
|
|
|
}
|
|
|
|
|
2013-12-15 11:43:31 +01:00
|
|
|
func getHomeDir() string {
|
2014-04-22 13:24:47 +02:00
|
|
|
var home string
|
|
|
|
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "windows":
|
2014-04-22 14:26:51 +02:00
|
|
|
home = filepath.Join(os.Getenv("HomeDrive"), os.Getenv("HomePath"))
|
2014-03-02 23:49:51 +01:00
|
|
|
if home == "" {
|
2014-04-22 13:24:47 +02:00
|
|
|
home = os.Getenv("UserProfile")
|
2014-03-02 23:49:51 +01:00
|
|
|
}
|
2014-04-22 13:24:47 +02:00
|
|
|
default:
|
|
|
|
home = os.Getenv("HOME")
|
2014-03-02 16:07:12 +01:00
|
|
|
}
|
|
|
|
|
2014-04-22 13:24:47 +02:00
|
|
|
if home == "" {
|
2014-05-15 02:08:56 +02:00
|
|
|
l.Fatalln("No home directory found - set $HOME (or the platform equivalent).")
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
2014-04-22 13:24:47 +02:00
|
|
|
|
|
|
|
return home
|
2013-12-15 11:43:31 +01:00
|
|
|
}
|
2014-05-11 20:21:41 +02:00
|
|
|
|
|
|
|
// getFreePort returns a free TCP port fort listening on. The ports given are
|
|
|
|
// tried in succession and the first to succeed is returned. If none succeed,
|
|
|
|
// a random high port is returned.
|
|
|
|
func getFreePort(host string, ports ...int) (int, error) {
|
|
|
|
for _, port := range ports {
|
|
|
|
c, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
|
|
|
|
if err == nil {
|
|
|
|
c.Close()
|
|
|
|
return port, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := net.Listen("tcp", host+":0")
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
addr := c.Addr().String()
|
|
|
|
c.Close()
|
|
|
|
|
|
|
|
_, portstr, err := net.SplitHostPort(addr)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
port, err := strconv.Atoi(portstr)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return port, nil
|
|
|
|
}
|