2014-11-16 21:13:20 +01:00
// Copyright (C) 2014 The Syncthing Authors.
2014-09-29 21:43:32 +02:00
//
2015-03-07 21:36:35 +01:00
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
2014-06-01 22:50:14 +02:00
2013-12-15 11:43:31 +01:00
package main
import (
2015-10-03 17:25:21 +02:00
"bytes"
2013-12-15 11:43:31 +01:00
"crypto/tls"
2015-10-31 12:31:25 +01:00
"errors"
2014-01-26 14:28:41 +01:00
"flag"
2014-01-08 14:37:33 +01:00
"fmt"
2017-01-03 09:54:28 +01:00
"io"
2015-04-30 21:33:32 +02:00
"io/ioutil"
2013-12-15 11:43:31 +01:00
"log"
"net"
"net/http"
2016-05-28 16:08:26 +02:00
"net/url"
2013-12-15 11:43:31 +01:00
"os"
2015-12-14 09:57:49 +01:00
"os/signal"
2016-05-28 16:08:26 +02:00
"path"
2014-03-28 14:36:57 +01:00
"path/filepath"
2014-06-23 10:38:50 +02:00
"regexp"
2014-01-10 00:09:27 +01:00
"runtime"
2014-04-14 12:13:50 +02:00
"runtime/pprof"
2015-10-03 17:25:21 +02:00
"sort"
2014-04-18 13:20:42 +02:00
"strconv"
2013-12-15 11:43:31 +01:00
"strings"
2015-12-14 09:57:49 +01:00
"syscall"
2013-12-15 11:43:31 +01:00
"time"
2014-04-08 13:45:18 +02:00
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/config"
2015-09-23 22:03:36 +02:00
"github.com/syncthing/syncthing/lib/connections"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/db"
2015-10-13 20:52:22 +02:00
"github.com/syncthing/syncthing/lib/dialer"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
2015-10-03 17:25:21 +02:00
"github.com/syncthing/syncthing/lib/logger"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
2015-09-22 19:38:46 +02:00
"github.com/syncthing/syncthing/lib/protocol"
2016-05-26 09:02:56 +02:00
"github.com/syncthing/syncthing/lib/rand"
2016-09-23 21:33:54 +02:00
"github.com/syncthing/syncthing/lib/sha256"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/symlinks"
2015-09-02 22:05:54 +02:00
"github.com/syncthing/syncthing/lib/tlsutil"
2015-08-06 11:29:25 +02:00
"github.com/syncthing/syncthing/lib/upgrade"
2015-07-17 22:22:07 +02:00
2015-04-25 11:19:53 +02:00
"github.com/thejerf/suture"
2016-11-11 23:23:48 +01:00
_ "net/http/pprof" // Need to import this to support STPROFILER.
2013-12-15 11:43:31 +01:00
)
2014-04-19 16:38:11 +02:00
var (
2016-01-10 18:41:15 +01:00
Version = "unknown-dev"
2016-07-04 12:58:45 +02:00
Codename = "Dysprosium Dragonfly"
2016-01-10 18:41:15 +01:00
BuildStamp = "0"
BuildDate time . Time
BuildHost = "unknown"
BuildUser = "unknown"
IsRelease bool
IsBeta bool
LongVersion string
2016-03-10 13:19:00 +01:00
allowedVersionExp = regexp . MustCompile ( ` ^v\d+\.\d+\.\d+(-[a-z0-9]+)*(\.\d+)*(\+\d+-g[0-9a-f]+)?(-[^\s]+)?$ ` )
2014-04-19 16:38:11 +02:00
)
2014-09-02 13:08:24 +02:00
const (
exitSuccess = 0
exitError = 1
exitNoUpgradeAvailable = 2
exitRestarting = 3
2014-09-13 16:25:39 +02:00
exitUpgrading = 4
2014-09-02 13:08:24 +02:00
)
2015-03-26 23:26:51 +01:00
const (
2015-09-02 22:05:54 +02:00
bepProtocolName = "bep/1.0"
tlsDefaultCommonName = "syncthing"
2015-11-27 09:09:39 +01:00
httpsRSABits = 2048
bepRSABits = 0 // 384 bit ECDSA used instead
2015-09-02 22:05:54 +02:00
pingEventInterval = time . Minute
2015-10-03 17:25:21 +02:00
maxSystemErrors = 5
initialSystemLog = 10
maxSystemLog = 250
2015-03-26 23:26:51 +01:00
)
2015-03-05 15:58:16 +01:00
2015-09-30 22:24:06 +02:00
// The discovery results are sorted by their source priority.
const (
ipv6LocalDiscoveryPriority = iota
ipv4LocalDiscoveryPriority
globalDiscoveryPriority
)
2014-04-19 16:38:11 +02:00
func init ( ) {
2014-06-23 10:38:50 +02:00
if Version != "unknown-dev" {
// If not a generic dev build, version string should come from git describe
2016-01-10 18:41:15 +01:00
if ! allowedVersionExp . MatchString ( Version ) {
l . Fatalf ( "Invalid version string %q;\n\tdoes not match regexp %v" , Version , allowedVersionExp )
2014-06-23 10:38:50 +02:00
}
}
2015-04-21 02:06:10 +02:00
// Check for a clean release build. A release is something like "v0.1.2",
// with an optional suffix of letters and dot separated numbers like
// "-beta3.47". If there's more stuff, like a plus sign and a commit hash
// and so on, then it's not a release. If there's a dash anywhere in
// there, it's some kind of beta or prerelease version.
exp := regexp . MustCompile ( ` ^v\d+\.\d+\.\d+(-[a-z]+[\d\.]+)?$ ` )
2014-10-23 19:09:53 +02:00
IsRelease = exp . MatchString ( Version )
2015-04-21 02:06:10 +02:00
IsBeta = strings . Contains ( Version , "-" )
2014-10-23 19:09:53 +02:00
2014-04-19 16:38:11 +02:00
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" )
2015-11-24 20:46:07 +01:00
LongVersion = fmt . Sprintf ( ` syncthing %s "%s" (%s %s-%s) %s@%s %s ` , Version , Codename , runtime . Version ( ) , runtime . GOOS , runtime . GOARCH , BuildUser , BuildHost , date )
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 (
2015-12-18 17:34:39 +01:00
myID protocol . DeviceID
stop = make ( chan int )
lans [ ] * net . IPNet
2014-01-26 14:28:41 +01:00
)
2014-03-09 08:48:29 +01:00
const (
usage = "syncthing [options]"
2014-10-06 17:55:54 +02:00
extraUsage = `
The - logflags value is a sum of the following :
2014-06-04 10:24:30 +02:00
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 ) .
2014-03-12 10:12:35 +01:00
2014-10-06 17:55:54 +02:00
Development Settings
-- -- -- -- -- -- -- -- -- --
2014-08-27 23:38:36 +02:00
2017-01-10 08:50:11 +01:00
The following environment variables modify Syncthing ' s behavior in ways that
2014-10-06 17:55:54 +02:00
are mostly useful for developers . Use with care .
2014-08-16 00:24:24 +02:00
2016-01-09 02:11:06 +01:00
STNODEFAULTFOLDER Don ' t create a default folder when starting for the first
2017-01-10 08:50:11 +01:00
time . This variable will be ignored anytime after the first
2016-01-09 02:11:06 +01:00
run .
2014-04-14 12:13:50 +02:00
2016-01-09 02:11:06 +01:00
STGUIASSETS Directory to load GUI assets from . Overrides compiled in
assets .
2014-04-14 12:13:50 +02:00
2016-01-09 02:11:06 +01:00
STTRACE A comma separated string of facilities to trace . The valid
facility strings listed below .
2014-08-13 14:38:23 +02:00
2017-01-10 08:50:11 +01:00
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start
the profiler with HTTP access .
2014-08-13 14:38:23 +02:00
2016-01-09 02:11:06 +01:00
STCPUPROFILE Write a CPU profile to cpu - $ pid . pprof on exit .
2014-08-13 14:38:23 +02:00
2016-01-09 02:11:06 +01:00
STHEAPPROFILE Write heap profiles to heap - $ pid - $ timestamp . pprof each time
heap usage increases .
2014-08-13 14:38:23 +02:00
2016-01-09 02:11:06 +01:00
STBLOCKPROFILE Write block profiles to block - $ pid - $ timestamp . pprof every 20
seconds .
2014-11-26 19:48:31 +01:00
2016-01-09 02:11:06 +01:00
STPERFSTATS Write running performance statistics to perf - $ pid . csv . Not
supported on Windows .
2015-01-27 22:21:39 +01:00
2017-01-10 08:50:11 +01:00
STDEADLOCK Used for debugging internal deadlocks . Use only under
direction of a developer .
STDEADLOCKTIMEOUT Used for debugging internal deadlocks ; sets debug
sensitivity . Use only under direction of a developer .
STDEADLOCKTHRESHOLD Used for debugging internal deadlocks ; sets debug
sensitivity . Use only under direction of a developer .
STNORESTART Equivalent to the - no - restart argument . Disable the
Syncthing monitor process which handles restarts for some
configuration changes , upgrades , crashes and also log file
writing ( stdout is still written ) .
2016-01-09 02:11:06 +01:00
STNOUPGRADE Disable automatic upgrades .
2015-03-29 19:05:22 +02:00
2016-09-23 21:33:54 +02:00
STHASHING Select the SHA256 hashing package to use . Possible values
are "standard" for the Go standard library implementation ,
"minio" for the github . com / minio / sha256 - simd implementation ,
and blank ( the default ) for auto detection .
2016-01-09 02:11:06 +01:00
GOMAXPROCS Set the maximum number of CPU cores to use . Defaults to all
available CPU cores .
GOGC Percentage of heap growth at which to trigger GC . Default is
100. Lower numbers keep peak memory usage down , at the price
2017-01-10 08:50:11 +01:00
of CPU usage ( i . e . performance ) .
2015-10-03 17:25:21 +02:00
Debugging Facilities
-- -- -- -- -- -- -- -- -- --
The following are valid values for the STTRACE variable :
% s `
2014-03-09 08:48:29 +01:00
)
2015-12-17 23:40:46 +01:00
// Environment options
2014-09-02 13:08:24 +02:00
var (
2016-01-09 02:11:06 +01:00
noUpgrade = os . Getenv ( "STNOUPGRADE" ) != ""
innerProcess = os . Getenv ( "STNORESTART" ) != "" || os . Getenv ( "STMONITORED" ) != ""
noDefaultFolder = os . Getenv ( "STNODEFAULTFOLDER" ) != ""
2015-12-17 23:40:46 +01:00
)
type RuntimeOptions struct {
confDir string
2016-12-26 13:48:54 +01:00
resetDatabase bool
resetDeltaIdxs bool
2015-09-30 09:32:17 +02:00
showVersion bool
2016-02-02 10:27:01 +01:00
showPaths bool
2015-09-30 09:32:17 +02:00
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
2015-12-16 16:11:01 +01:00
browserOnly bool
2015-12-18 18:28:02 +01:00
hideConsole bool
2015-09-30 09:32:17 +02:00
logFile string
auditEnabled bool
2017-01-03 09:54:28 +01:00
auditFile string
2015-09-30 09:32:17 +02:00
verbose bool
paused bool
2016-12-21 19:41:25 +01:00
unpaused bool
2015-12-16 21:33:14 +01:00
guiAddress string
guiAPIKey string
generateDir string
2015-12-17 23:40:46 +01:00
noRestart bool
profiler string
2015-12-18 18:00:59 +01:00
assetDir string
2015-12-17 23:40:46 +01:00
cpuProfile bool
stRestarting bool
2015-12-18 17:34:39 +01:00
logFlags int
2015-12-17 23:40:46 +01:00
}
2014-09-02 13:08:24 +02:00
2015-12-17 23:40:46 +01:00
func defaultRuntimeOptions ( ) RuntimeOptions {
options := RuntimeOptions {
noRestart : os . Getenv ( "STNORESTART" ) != "" ,
profiler : os . Getenv ( "STPROFILER" ) ,
2015-12-18 18:00:59 +01:00
assetDir : os . Getenv ( "STGUIASSETS" ) ,
2015-12-17 23:40:46 +01:00
cpuProfile : os . Getenv ( "STCPUPROFILE" ) != "" ,
stRestarting : os . Getenv ( "STRESTART" ) != "" ,
2015-12-18 17:34:39 +01:00
logFlags : log . Ltime ,
2015-12-17 23:40:46 +01:00
}
2014-12-04 21:25:35 +01:00
2015-12-18 17:34:39 +01:00
if os . Getenv ( "STTRACE" ) != "" {
options . logFlags = log . Ltime | log . Ldate | log . Lmicroseconds | log . Lshortfile
}
2015-12-21 11:55:58 +01:00
if runtime . GOOS != "windows" {
// On non-Windows, we explicitly default to "-" which means stdout. On
// Windows, the blank options.logFile will later be replaced with the
// default path, unless the user has manually specified "-" or
// something else.
options . logFile = "-"
2014-10-19 14:57:03 +02:00
}
2015-12-17 23:40:46 +01:00
return options
}
func parseCommandLineOptions ( ) RuntimeOptions {
options := defaultRuntimeOptions ( )
flag . StringVar ( & options . generateDir , "generate" , "" , "Generate key and config in specified dir, then exit" )
flag . StringVar ( & options . guiAddress , "gui-address" , options . guiAddress , "Override GUI address (e.g. \"http://192.0.2.42:8443\")" )
flag . StringVar ( & options . guiAPIKey , "gui-apikey" , options . guiAPIKey , "Override GUI API key" )
flag . StringVar ( & options . confDir , "home" , "" , "Set configuration directory" )
2015-12-18 17:34:39 +01:00
flag . IntVar ( & options . logFlags , "logflags" , options . logFlags , "Select information in log line prefix (see below)" )
2015-12-17 23:40:46 +01:00
flag . BoolVar ( & options . noBrowser , "no-browser" , false , "Do not start browser" )
flag . BoolVar ( & options . browserOnly , "browser-only" , false , "Open GUI in browser" )
2017-01-10 08:50:11 +01:00
flag . BoolVar ( & options . noRestart , "no-restart" , options . noRestart , "Disable monitor process, managed restarts and log file writing" )
2016-12-26 13:48:54 +01:00
flag . BoolVar ( & options . resetDatabase , "reset-database" , false , "Reset the database, forcing a full rescan and resync" )
flag . BoolVar ( & options . resetDeltaIdxs , "reset-deltas" , false , "Reset delta index IDs, forcing a full index exchange" )
2015-12-17 23:40:46 +01:00
flag . BoolVar ( & options . doUpgrade , "upgrade" , false , "Perform upgrade" )
flag . BoolVar ( & options . doUpgradeCheck , "upgrade-check" , false , "Check for available upgrade" )
flag . BoolVar ( & options . showVersion , "version" , false , "Show version" )
2016-02-02 10:27:01 +01:00
flag . BoolVar ( & options . showPaths , "paths" , false , "Show configuration paths" )
2015-12-17 23:40:46 +01:00
flag . StringVar ( & options . upgradeTo , "upgrade-to" , options . upgradeTo , "Force upgrade directly from specified URL" )
flag . BoolVar ( & options . auditEnabled , "audit" , false , "Write events to audit file" )
flag . BoolVar ( & options . verbose , "verbose" , false , "Print verbose log output" )
2016-12-21 19:41:25 +01:00
flag . BoolVar ( & options . paused , "paused" , false , "Start with all devices and folders paused" )
flag . BoolVar ( & options . unpaused , "unpaused" , false , "Start with all devices and folders unpaused" )
2015-12-17 23:40:46 +01:00
flag . StringVar ( & options . logFile , "logfile" , options . logFile , "Log file name (use \"-\" for stdout)" )
2017-01-03 09:54:28 +01:00
flag . StringVar ( & options . auditFile , "auditfile" , options . auditFile , "Specify audit file (use \"-\" for stdout, \"--\" for stderr)" )
2015-12-17 23:40:46 +01:00
if runtime . GOOS == "windows" {
// Allow user to hide the console window
2015-12-18 18:28:02 +01:00
flag . BoolVar ( & options . hideConsole , "no-console" , false , "Hide console window" )
2015-12-17 23:40:46 +01:00
}
2014-10-06 17:55:54 +02:00
2016-02-02 10:27:01 +01:00
longUsage := fmt . Sprintf ( extraUsage , debugFacilities ( ) )
2015-10-03 17:25:21 +02:00
flag . Usage = usageFor ( flag . CommandLine , usage , longUsage )
2014-01-26 14:28:41 +01:00
flag . Parse ( )
2015-12-17 23:40:46 +01:00
2016-11-27 12:21:05 +01:00
if len ( flag . Args ( ) ) > 0 {
flag . Usage ( )
os . Exit ( 2 )
}
2015-12-17 23:40:46 +01:00
return options
2015-12-16 21:33:14 +01:00
}
func main ( ) {
2015-12-17 23:40:46 +01:00
options := parseCommandLineOptions ( )
2015-12-18 17:34:39 +01:00
l . SetFlags ( options . logFlags )
2014-01-08 14:37:33 +01:00
2015-12-17 23:40:46 +01:00
if options . guiAddress != "" {
2015-10-12 15:27:57 +02:00
// The config picks this up from the environment.
2015-12-17 23:40:46 +01:00
os . Setenv ( "STGUIADDRESS" , options . guiAddress )
2015-10-12 15:27:57 +02:00
}
2015-12-17 23:40:46 +01:00
if options . guiAPIKey != "" {
2015-10-12 15:27:57 +02:00
// The config picks this up from the environment.
2015-12-17 23:40:46 +01:00
os . Setenv ( "STGUIAPIKEY" , options . guiAPIKey )
2015-10-12 15:27:57 +02:00
}
2017-01-18 13:19:22 +01:00
// Check for options which are not compatible with each other
// Have to check logfile before it's set to the default below - we only want to complain if they set -logfile explicitly, not if it's set to its default location
if options . noRestart && options . logFile != "" {
l . Fatalln ( "-logfile may not be used with -no-restart or STNORESTART" )
}
2015-12-18 18:28:02 +01:00
if options . hideConsole {
2014-12-04 21:25:35 +01:00
osutil . HideConsole ( )
}
2015-12-17 23:40:46 +01:00
if options . confDir != "" {
2014-10-06 17:55:54 +02:00
// Not set as default above because the string can be really long.
2015-12-17 23:40:46 +01:00
baseDirs [ "config" ] = options . confDir
2015-03-29 12:55:27 +02:00
}
2015-04-03 20:22:39 +02:00
if err := expandLocations ( ) ; err != nil {
l . Fatalln ( err )
}
2015-12-21 11:55:58 +01:00
if options . logFile == "" {
// Blank means use the default logfile location. We must set this
// *after* expandLocations above.
options . logFile = locations [ locLogFile ]
}
2015-12-26 13:31:58 +01:00
if options . assetDir == "" {
// The asset dir is blank if STGUIASSETS wasn't set, in which case we
// should look for extra assets in the default place.
options . assetDir = locations [ locGUIAssets ]
}
2015-12-17 23:40:46 +01:00
if options . 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
2016-02-02 10:27:01 +01:00
if options . showPaths {
showPaths ( )
return
}
2015-12-17 23:40:46 +01:00
if options . browserOnly {
2015-12-16 16:11:01 +01:00
openGUI ( )
return
}
2015-12-17 23:40:46 +01:00
if options . generateDir != "" {
generate ( options . generateDir )
2014-08-03 09:41:08 +02:00
return
2014-07-31 21:29:44 +02:00
}
2014-09-18 00:19:23 +02:00
// Ensure that our home directory exists.
2015-03-29 12:55:27 +02:00
ensureDir ( baseDirs [ "config" ] , 0700 )
2014-09-18 00:19:23 +02:00
2015-12-17 23:40:46 +01:00
if options . upgradeTo != "" {
err := upgrade . ToURL ( options . upgradeTo )
2014-12-22 12:07:04 +01:00
if err != nil {
l . Fatalln ( "Upgrade:" , err ) // exits 1
}
2016-01-16 23:01:57 +01:00
l . Infoln ( "Upgraded from" , options . upgradeTo )
2014-12-22 12:07:04 +01:00
return
}
2015-12-17 23:40:46 +01:00
if options . doUpgradeCheck {
2015-12-16 20:29:37 +01:00
checkUpgrade ( )
return
}
2014-12-08 16:36:15 +01:00
2015-12-17 23:40:46 +01:00
if options . doUpgrade {
2015-12-16 20:29:37 +01:00
release := checkUpgrade ( )
performUpgrade ( release )
2014-12-08 16:36:15 +01:00
return
2014-07-31 10:26:45 +02:00
}
2016-12-26 13:48:54 +01:00
if options . resetDatabase {
2015-04-03 20:06:03 +02:00
resetDB ( )
2014-09-02 13:08:24 +02:00
return
2014-08-03 09:41:08 +02:00
}
2015-12-17 23:40:46 +01:00
if options . noRestart {
syncthingMain ( options )
2014-09-02 13:08:24 +02:00
} else {
2015-12-17 23:40:46 +01:00
monitorMain ( options )
2014-09-02 13:08:24 +02:00
}
}
2015-12-16 16:11:01 +01:00
func openGUI ( ) {
2015-12-19 00:39:36 +01:00
cfg , _ := loadConfig ( )
2015-12-16 16:11:01 +01:00
if cfg . GUI ( ) . Enabled {
openURL ( cfg . GUI ( ) . URL ( ) )
} else {
l . Warnln ( "Browser: GUI is currently disabled" )
}
}
2015-11-22 07:34:04 +01:00
func generate ( generateDir string ) {
dir , err := osutil . ExpandTilde ( generateDir )
if err != nil {
l . Fatalln ( "generate:" , err )
}
2015-12-18 18:47:26 +01:00
ensureDir ( dir , 0700 )
2015-11-22 07:34:04 +01:00
certFile , keyFile := filepath . Join ( dir , "cert.pem" ) , filepath . Join ( dir , "key.pem" )
cert , err := tls . LoadX509KeyPair ( certFile , keyFile )
if err == nil {
l . Warnln ( "Key exists; will not overwrite." )
l . Infoln ( "Device ID:" , protocol . NewDeviceID ( cert . Certificate [ 0 ] ) )
} else {
2015-11-27 09:09:39 +01:00
cert , err = tlsutil . NewCertificate ( certFile , keyFile , tlsDefaultCommonName , bepRSABits )
2015-11-22 07:34:04 +01:00
if err != nil {
l . Fatalln ( "Create certificate:" , err )
}
myID = protocol . NewDeviceID ( cert . Certificate [ 0 ] )
if err != nil {
l . Fatalln ( "Load certificate:" , err )
}
if err == nil {
l . Infoln ( "Device ID:" , protocol . NewDeviceID ( cert . Certificate [ 0 ] ) )
}
}
cfgFile := filepath . Join ( dir , "config.xml" )
if _ , err := os . Stat ( cfgFile ) ; err == nil {
l . Warnln ( "Config exists; will not overwrite." )
return
}
var myName , _ = os . Hostname ( )
var newCfg = defaultConfig ( myName )
var cfg = config . Wrap ( cfgFile , newCfg )
err = cfg . Save ( )
if err != nil {
l . Warnln ( "Failed to save config" , err )
}
}
2015-10-03 17:25:21 +02:00
func debugFacilities ( ) string {
facilities := l . Facilities ( )
// Get a sorted list of names
var names [ ] string
maxLen := 0
for name := range facilities {
names = append ( names , name )
if len ( name ) > maxLen {
maxLen = len ( name )
}
}
sort . Strings ( names )
// Format the choices
b := new ( bytes . Buffer )
for _ , name := range names {
fmt . Fprintf ( b , " %-*s - %s\n" , maxLen , name , facilities [ name ] )
}
return b . String ( )
}
2015-12-16 20:29:37 +01:00
func checkUpgrade ( ) upgrade . Release {
2015-12-19 00:39:36 +01:00
cfg , _ := loadConfig ( )
releasesURL := cfg . Options ( ) . ReleasesURL
2015-12-16 20:29:37 +01:00
release , err := upgrade . LatestRelease ( releasesURL , Version )
if err != nil {
l . Fatalln ( "Upgrade:" , err )
}
if upgrade . CompareVersions ( release . Tag , Version ) <= 0 {
noUpgradeMessage := "No upgrade available (current %q >= latest %q)."
l . Infof ( noUpgradeMessage , Version , release . Tag )
os . Exit ( exitNoUpgradeAvailable )
}
l . Infof ( "Upgrade available (current %q < latest %q)" , Version , release . Tag )
return release
}
func performUpgrade ( release upgrade . Release ) {
// Use leveldb database locks to protect against concurrent upgrades
_ , err := db . Open ( locations [ locDatabase ] )
if err == nil {
err = upgrade . To ( release )
if err != nil {
l . Fatalln ( "Upgrade:" , err )
}
2016-01-16 23:01:57 +01:00
l . Infof ( "Upgraded to %q" , release . Tag )
2015-12-16 20:29:37 +01:00
} else {
l . Infoln ( "Attempting upgrade through running Syncthing..." )
err = upgradeViaRest ( )
if err != nil {
l . Fatalln ( "Upgrade:" , err )
}
2016-01-16 23:01:57 +01:00
l . Infoln ( "Syncthing upgrading" )
2015-12-16 20:29:37 +01:00
os . Exit ( exitUpgrading )
}
}
2015-04-30 21:33:32 +02:00
func upgradeViaRest ( ) error {
2015-12-19 00:39:36 +01:00
cfg , _ := loadConfig ( )
2016-05-28 16:08:26 +02:00
u , err := url . Parse ( cfg . GUI ( ) . URL ( ) )
if err != nil {
return err
}
u . Path = path . Join ( u . Path , "rest/system/upgrade" )
target := u . String ( )
r , _ := http . NewRequest ( "POST" , target , nil )
2016-02-02 11:12:25 +01:00
r . Header . Set ( "X-API-Key" , cfg . GUI ( ) . APIKey )
2015-04-30 21:33:32 +02:00
tr := & http . Transport {
2015-10-13 20:52:22 +02:00
Dial : dialer . Dial ,
Proxy : http . ProxyFromEnvironment ,
2015-04-30 21:33:32 +02:00
TLSClientConfig : & tls . Config { InsecureSkipVerify : true } ,
}
client := & http . Client {
Transport : tr ,
Timeout : 60 * time . Second ,
}
resp , err := client . Do ( r )
if err != nil {
return err
}
if resp . StatusCode != 200 {
bs , err := ioutil . ReadAll ( resp . Body )
defer resp . Body . Close ( )
if err != nil {
return err
}
return errors . New ( string ( bs ) )
}
return err
}
2015-12-17 23:40:46 +01:00
func syncthingMain ( runtimeOptions RuntimeOptions ) {
2015-12-14 09:57:49 +01:00
setupSignalHandling ( )
2015-04-25 11:19:53 +02:00
// Create a main service manager. We'll add things to this as we go along.
2015-07-11 03:12:20 +02:00
// We want any logging it does to go through our log system.
2015-12-23 16:31:12 +01:00
mainService := suture . New ( "main" , suture . Spec {
2015-04-25 11:19:53 +02:00
Log : func ( line string ) {
2015-10-03 17:25:21 +02:00
l . Debugln ( line )
2015-04-25 11:19:53 +02:00
} ,
} )
2015-12-23 16:31:12 +01:00
mainService . ServeBackground ( )
2014-09-02 13:08:24 +02:00
2015-04-25 11:21:47 +02:00
// Set a log prefix similar to the ID we will have later on, or early log
// lines look ugly.
l . SetPrefix ( "[start] " )
2015-12-17 23:40:46 +01:00
if runtimeOptions . auditEnabled {
2017-01-03 09:54:28 +01:00
startAuditing ( mainService , runtimeOptions . auditFile )
2015-04-25 11:21:47 +02:00
}
2015-12-17 23:40:46 +01:00
if runtimeOptions . verbose {
2015-12-23 16:31:12 +01:00
mainService . Add ( newVerboseService ( ) )
2015-04-30 20:21:48 +02:00
}
2015-10-03 17:25:21 +02:00
errors := logger . NewRecorder ( l , logger . LevelWarn , maxSystemErrors , 0 )
systemLog := logger . NewRecorder ( l , logger . LevelDebug , maxSystemLog , initialSystemLog )
2016-07-15 16:23:20 +02:00
// Event subscription for the API; must start early to catch the early
// events. The LocalChangeDetected event might overwhelm the event
// receiver in some situations so we will not subscribe to it here.
2016-12-21 17:35:20 +01:00
apiSub := events . NewBufferedSubscription ( events . Default . Subscribe ( events . AllEvents &^ events . LocalChangeDetected &^ events . RemoteChangeDetected ) , 1000 )
diskSub := events . NewBufferedSubscription ( events . Default . Subscribe ( events . LocalChangeDetected | events . RemoteChangeDetected ) , 1000 )
2015-06-16 09:17:58 +02:00
2014-01-10 00:09:27 +01:00
if len ( os . Getenv ( "GOMAXPROCS" ) ) == 0 {
runtime . GOMAXPROCS ( runtime . NumCPU ( ) )
}
2015-08-15 15:20:58 +02:00
// Attempt to increase the limit on number of open files to the maximum
// allowed, in case we have many peers. We don't really care enough to
// report the error if there is one.
osutil . MaximizeOpenFileLimit ( )
2014-09-18 00:19:23 +02:00
// Ensure that that we have a certificate and key.
2015-04-25 11:19:53 +02:00
cert , err := tls . LoadX509KeyPair ( locations [ locCertFile ] , locations [ locKeyFile ] )
2013-12-15 11:43:31 +01:00
if err != nil {
2015-11-27 09:09:39 +01:00
l . Infof ( "Generating ECDSA key and certificate for %s..." , tlsDefaultCommonName )
cert , err = tlsutil . NewCertificate ( locations [ locCertFile ] , locations [ locKeyFile ] , tlsDefaultCommonName , bepRSABits )
2014-09-20 15:42:20 +02:00
if err != nil {
2015-09-02 22:05:54 +02:00
l . Fatalln ( err )
2014-09-20 15:42:20 +02:00
}
2013-12-15 11:43:31 +01:00
}
2014-09-28 13:00:38 +02:00
myID = protocol . NewDeviceID ( cert . Certificate [ 0 ] )
2014-06-30 01:42:03 +02:00
l . SetPrefix ( fmt . Sprintf ( "[%s] " , myID . String ( ) [ : 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 )
2016-09-23 21:33:54 +02:00
sha256 . SelectAlgo ( )
sha256 . Report ( )
2014-02-03 16:01:17 +01:00
2015-06-18 15:22:45 +02:00
// Emit the Starting event, now that we know who we are.
events . Default . Log ( events . Starting , map [ string ] string {
"home" : baseDirs [ "config" ] ,
"myID" : myID . String ( ) ,
} )
2015-12-19 00:39:36 +01:00
cfg := loadOrCreateConfig ( )
2014-10-08 13:52:05 +02:00
2015-04-09 12:53:13 +02:00
if err := checkShortIDs ( cfg ) ; err != nil {
2015-12-20 17:58:51 +01:00
l . Fatalln ( "Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one of the following:\n " , err )
2015-04-09 12:53:13 +02:00
}
2015-12-17 23:40:46 +01:00
if len ( runtimeOptions . profiler ) > 0 {
2013-12-15 11:43:31 +01:00
go func ( ) {
2015-12-17 23:40:46 +01:00
l . Debugln ( "Starting profiler on" , runtimeOptions . profiler )
2014-05-20 18:41:01 +02:00
runtime . SetBlockProfileRate ( 1 )
2015-12-17 23:40:46 +01:00
err := http . ListenAndServe ( runtimeOptions . 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
} ( )
}
// 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 } ,
2015-03-05 15:58:16 +01:00
NextProtos : [ ] string { bepProtocolName } ,
2014-01-09 09:28:08 +01:00
ClientAuth : tls . RequestClientCert ,
SessionTicketsDisabled : true ,
InsecureSkipVerify : true ,
MinVersion : tls . VersionTLS12 ,
2014-11-12 10:47:34 +01:00
CipherSuites : [ ] uint16 {
2016-12-18 22:07:44 +01:00
0xCCA8 , // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, Go 1.8
0xCCA9 , // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, Go 1.8
tls . TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,
tls . TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ,
2014-11-12 10:47:34 +01:00
tls . TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ,
tls . TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA ,
tls . TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA ,
tls . TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA ,
} ,
2013-12-15 11:43:31 +01:00
}
2014-10-06 09:25:45 +02:00
opts := cfg . Options ( )
2014-12-01 11:26:54 +01:00
if ! opts . SymlinksEnabled {
2014-11-30 23:10:32 +01:00
symlinks . Supported = false
}
2015-03-19 12:07:20 +01:00
if ( opts . MaxRecvKbps > 0 || opts . MaxSendKbps > 0 ) && ! opts . LimitBandwidthInLan {
2015-03-08 19:36:59 +01:00
lans , _ = osutil . GetLans ( )
2015-09-11 15:07:38 +02:00
for _ , lan := range opts . AlwaysLocalNets {
_ , ipnet , err := net . ParseCIDR ( lan )
if err != nil {
l . Infoln ( "Network" , lan , "is malformed:" , err )
continue
}
2015-10-24 07:14:25 +02:00
lans = append ( lans , ipnet )
}
networks := make ( [ ] string , len ( lans ) )
for i , lan := range lans {
networks [ i ] = lan . String ( )
2015-09-11 15:07:38 +02:00
}
2015-03-19 12:07:20 +01:00
l . Infoln ( "Local networks:" , strings . Join ( networks , ", " ) )
2015-03-08 19:36:59 +01:00
}
2013-12-15 11:43:31 +01:00
2015-03-29 12:55:27 +02:00
dbFile := locations [ locDatabase ]
2015-10-31 12:31:25 +01:00
ldb , err := db . Open ( dbFile )
2016-01-03 19:08:19 +01:00
2014-07-06 14:46:48 +02:00
if err != nil {
2014-08-17 01:03:41 +02:00
l . Fatalln ( "Cannot open database:" , err , "- Is another copy of Syncthing already running?" )
2014-07-06 14:46:48 +02:00
}
2014-08-31 13:34:17 +02:00
2016-12-26 13:48:54 +01:00
if runtimeOptions . resetDeltaIdxs {
l . Infoln ( "Reinitializing delta index IDs" )
ldb . DropDeltaIndexIDs ( )
}
2015-10-19 02:13:58 +02:00
protectedFiles := [ ] string {
2015-11-08 18:06:06 +01:00
locations [ locDatabase ] ,
locations [ locConfigFile ] ,
locations [ locCertFile ] ,
locations [ locKeyFile ] ,
2015-10-19 02:13:58 +02:00
}
2014-09-28 13:00:38 +02:00
// Remove database entries for folders that no longer exist in the config
2014-10-06 09:25:45 +02:00
folders := cfg . Folders ( )
2015-10-31 12:31:25 +01:00
for _ , folder := range ldb . ListFolders ( ) {
2014-10-06 09:25:45 +02:00
if _ , ok := folders [ folder ] ; ! ok {
2014-09-28 13:00:38 +02:00
l . Infof ( "Cleaning data for dropped folder %q" , folder )
2015-01-12 14:50:30 +01:00
db . DropFolder ( ldb , folder )
2014-08-31 13:34:17 +02:00
}
}
2016-11-12 10:34:18 +01:00
if cfg . RawCopy ( ) . OriginalVersion == 15 {
2016-08-05 09:13:52 +02:00
// The config version 15->16 migration is about handling ignores and
// delta indexes and requires that we drop existing indexes that
// have been incorrectly ignore filtered.
ldb . DropDeltaIndexIDs ( )
}
2015-12-19 00:39:36 +01:00
m := model . NewModel ( cfg , myID , myDeviceName ( cfg ) , "syncthing" , Version , ldb , protectedFiles )
2014-04-01 20:36:54 +02:00
2015-04-08 14:35:03 +02:00
if t := os . Getenv ( "STDEADLOCKTIMEOUT" ) ; len ( t ) > 0 {
it , err := strconv . Atoi ( t )
if err == nil {
m . StartDeadlockDetector ( time . Duration ( it ) * time . Second )
}
} else if ! IsRelease || IsBeta {
2015-07-24 20:55:52 +02:00
m . StartDeadlockDetector ( 20 * time . Minute )
2015-04-08 14:35:03 +02:00
}
2016-12-21 19:41:25 +01:00
if runtimeOptions . unpaused {
setPauseState ( cfg , false )
} else if runtimeOptions . paused {
setPauseState ( cfg , true )
2015-08-23 22:03:58 +02:00
}
2016-07-23 14:46:31 +02:00
// Add and start folders
2014-10-06 09:25:45 +02:00
for _ , folderCfg := range cfg . Folders ( ) {
2016-12-21 19:41:25 +01:00
if folderCfg . Paused {
continue
}
2015-03-28 15:25:42 +01:00
m . AddFolder ( folderCfg )
2016-05-04 12:47:33 +02:00
m . StartFolder ( folderCfg . ID )
2014-08-05 12:20:50 +02:00
}
2015-12-23 16:31:12 +01:00
mainService . Add ( m )
2015-06-20 20:04:47 +02:00
2015-09-20 15:30:25 +02:00
// Start discovery
cachedDiscovery := discover . NewCachingMux ( )
2015-12-23 16:31:12 +01:00
mainService . Add ( cachedDiscovery )
2015-09-20 15:30:25 +02:00
2016-03-25 08:35:18 +01:00
// Start connection management
2016-05-04 21:38:12 +02:00
connectionsService := connections . NewService ( cfg , myID , m , tlsCfg , cachedDiscovery , bepProtocolName , tlsDefaultCommonName , lans )
mainService . Add ( connectionsService )
2016-03-25 08:35:18 +01:00
2015-09-20 15:30:25 +02:00
if cfg . Options ( ) . GlobalAnnEnabled {
for _ , srv := range cfg . GlobalDiscoveryServers ( ) {
l . Infoln ( "Using discovery server" , srv )
2016-05-04 21:38:12 +02:00
gd , err := discover . NewGlobal ( srv , cert , connectionsService )
2015-09-20 15:30:25 +02:00
if err != nil {
l . Warnln ( "Global discovery:" , err )
continue
}
// Each global discovery server gets its results cached for five
// minutes, and is not asked again for a minute when it's returned
// unsuccessfully.
2015-09-30 22:24:06 +02:00
cachedDiscovery . Add ( gd , 5 * time . Minute , time . Minute , globalDiscoveryPriority )
2015-09-20 15:30:25 +02:00
}
}
if cfg . Options ( ) . LocalAnnEnabled {
// v4 broadcasts
2016-05-04 21:38:12 +02:00
bcd , err := discover . NewLocal ( myID , fmt . Sprintf ( ":%d" , cfg . Options ( ) . LocalAnnPort ) , connectionsService )
2015-09-20 15:30:25 +02:00
if err != nil {
l . Warnln ( "IPv4 local discovery:" , err )
} else {
2015-09-30 22:24:06 +02:00
cachedDiscovery . Add ( bcd , 0 , 0 , ipv4LocalDiscoveryPriority )
2015-09-20 15:30:25 +02:00
}
// v6 multicasts
2016-05-04 21:38:12 +02:00
mcd , err := discover . NewLocal ( myID , cfg . Options ( ) . LocalAnnMCAddr , connectionsService )
2015-09-20 15:30:25 +02:00
if err != nil {
l . Warnln ( "IPv6 local discovery:" , err )
} else {
2015-09-30 22:24:06 +02:00
cachedDiscovery . Add ( mcd , 0 , 0 , ipv6LocalDiscoveryPriority )
2015-09-20 15:30:25 +02:00
}
}
// GUI
2016-09-28 17:54:13 +02:00
setupGUI ( mainService , cfg , m , apiSub , diskSub , cachedDiscovery , connectionsService , errors , systemLog , runtimeOptions )
2015-09-20 15:30:25 +02:00
2015-12-17 23:40:46 +01:00
if runtimeOptions . cpuProfile {
2014-08-13 14:38:23 +02:00
f , err := os . Create ( fmt . Sprintf ( "cpu-%d.pprof" , os . Getpid ( ) ) )
2014-04-14 12:13:50 +02:00
if err != nil {
log . Fatal ( err )
}
pprof . StartCPUProfile ( f )
}
2014-10-06 09:25:45 +02:00
for _ , device := range cfg . Devices ( ) {
2014-09-28 13:00:38 +02:00
if len ( device . Name ) > 0 {
l . Infof ( "Device %s is %q at %v" , device . DeviceID , device . Name , device . Addresses )
2014-05-24 21:39:08 +02:00
}
}
2014-10-06 09:25:45 +02:00
if opts . URAccepted > 0 && opts . URAccepted < usageReportVersion {
2014-06-11 20:04:23 +02:00
l . Infoln ( "Anonymous usage report has changed; revoking acceptance" )
2014-10-06 09:25:45 +02:00
opts . URAccepted = 0
2014-11-27 10:00:07 +01:00
opts . URUniqueID = ""
2014-10-06 09:25:45 +02:00
cfg . SetOptions ( opts )
2014-06-11 20:04:23 +02:00
}
2014-10-06 09:25:45 +02:00
if opts . URAccepted >= usageReportVersion {
2014-11-27 10:00:07 +01:00
if opts . URUniqueID == "" {
// Previously the ID was generated from the node ID. We now need
// to generate a new one.
2016-05-26 09:02:56 +02:00
opts . URUniqueID = rand . String ( 8 )
2014-12-07 15:49:11 +01:00
cfg . SetOptions ( opts )
cfg . Save ( )
2014-11-27 10:00:07 +01:00
}
2014-06-11 20:04:23 +02:00
}
2015-05-12 09:35:37 +02:00
// The usageReportingManager registers itself to listen to configuration
// changes, and there's nothing more we need to tell it from the outside.
// Hence we don't keep the returned pointer.
2015-09-29 20:05:22 +02:00
newUsageReportingManager ( cfg , m )
2015-05-12 09:35:37 +02:00
2014-10-06 09:25:45 +02:00
if opts . RestartOnWakeup {
2014-09-10 22:24:53 +02:00
go standbyMonitor ( )
}
2014-08-27 23:38:36 +02:00
2014-10-06 09:25:45 +02:00
if opts . AutoUpgradeIntervalH > 0 {
2014-11-26 19:48:31 +01:00
if noUpgrade {
l . Infof ( "No automatic upgrades; STNOUPGRADE environment variable defined." )
2014-10-23 19:09:53 +02:00
} else {
2016-06-02 15:01:59 +02:00
go autoUpgrade ( cfg )
2014-10-23 19:09:53 +02:00
}
2014-09-26 00:51:12 +02:00
}
2015-06-16 09:27:06 +02:00
events . Default . Log ( events . StartupComplete , map [ string ] string {
"myID" : myID . String ( ) ,
} )
2015-03-26 23:26:51 +01:00
go generatePingEvents ( )
2014-07-13 21:07:24 +02:00
2015-04-07 09:25:28 +02:00
cleanConfigDirectory ( )
2014-09-02 13:08:24 +02:00
code := <- stop
2014-07-13 21:07:24 +02:00
2015-12-23 16:31:12 +01:00
mainService . Stop ( )
2015-04-25 11:19:53 +02:00
2016-01-16 23:01:57 +01:00
l . Infoln ( "Exiting" )
2015-07-20 15:34:40 +02:00
2015-12-17 23:40:46 +01:00
if runtimeOptions . cpuProfile {
2015-07-20 15:34:40 +02:00
pprof . StopCPUProfile ( )
}
2014-09-02 13:08:24 +02:00
os . Exit ( code )
2014-05-24 12:28:36 +02:00
}
2015-12-19 00:39:36 +01:00
func myDeviceName ( cfg * config . Wrapper ) string {
devices := cfg . Devices ( )
myName := devices [ myID ] . Name
if myName == "" {
myName , _ = os . Hostname ( )
}
return myName
}
2015-12-14 09:57:49 +01:00
func setupSignalHandling ( ) {
// Exit cleanly with "restarting" code on SIGHUP.
restartSign := make ( chan os . Signal , 1 )
sigHup := syscall . Signal ( 1 )
signal . Notify ( restartSign , sigHup )
go func ( ) {
<- restartSign
stop <- exitRestarting
} ( )
// Exit with "success" code (no restart) on INT/TERM
stopSign := make ( chan os . Signal , 1 )
sigTerm := syscall . Signal ( 15 )
signal . Notify ( stopSign , os . Interrupt , sigTerm )
go func ( ) {
<- stopSign
stop <- exitSuccess
} ( )
}
2015-12-19 00:39:36 +01:00
func loadConfig ( ) ( * config . Wrapper , error ) {
cfgFile := locations [ locConfigFile ]
cfg , err := config . Load ( cfgFile , myID )
2015-09-29 20:05:22 +02:00
if err != nil {
2015-12-19 00:39:36 +01:00
myName , _ := os . Hostname ( )
newCfg := defaultConfig ( myName )
cfg = config . Wrap ( cfgFile , newCfg )
2015-09-29 20:05:22 +02:00
}
2015-12-19 00:39:36 +01:00
return cfg , err
}
func loadOrCreateConfig ( ) * config . Wrapper {
cfg , err := loadConfig ( )
if os . IsNotExist ( err ) {
cfg . Save ( )
l . Infof ( "Defaults saved. Edit %s to taste or use the GUI\n" , cfg . ConfigPath ( ) )
} else if err != nil {
l . Fatalln ( "Config:" , err )
2015-09-29 20:05:22 +02:00
}
2016-11-12 10:34:18 +01:00
if cfg . RawCopy ( ) . OriginalVersion != config . CurrentVersion {
2015-12-24 01:12:24 +01:00
err = archiveAndSaveConfig ( cfg )
if err != nil {
l . Fatalln ( "Config archive:" , err )
}
2015-09-29 20:05:22 +02:00
}
2015-12-19 00:39:36 +01:00
return cfg
2015-09-29 20:05:22 +02:00
}
2015-12-24 01:12:24 +01:00
func archiveAndSaveConfig ( cfg * config . Wrapper ) error {
2016-09-03 23:29:32 +02:00
// Copy the existing config to an archive copy
2016-11-12 10:34:18 +01:00
archivePath := cfg . ConfigPath ( ) + fmt . Sprintf ( ".v%d" , cfg . RawCopy ( ) . OriginalVersion )
2015-12-24 01:12:24 +01:00
l . Infoln ( "Archiving a copy of old config file format at:" , archivePath )
2016-09-03 23:29:32 +02:00
if err := copyFile ( cfg . ConfigPath ( ) , archivePath ) ; err != nil {
2015-12-24 01:12:24 +01:00
return err
2015-09-29 20:05:22 +02:00
}
2016-09-03 23:29:32 +02:00
// Do a regular atomic config sve
2015-12-24 01:12:24 +01:00
return cfg . Save ( )
2015-09-29 20:05:22 +02:00
}
2016-09-03 23:29:32 +02:00
func copyFile ( src , dst string ) error {
bs , err := ioutil . ReadFile ( src )
if err != nil {
return err
}
if err := ioutil . WriteFile ( dst , bs , 0600 ) ; err != nil {
// Attempt to clean up
os . Remove ( dst )
return err
}
return nil
}
2017-01-03 09:54:28 +01:00
func startAuditing ( mainService * suture . Supervisor , auditFile string ) {
var fd io . Writer
var err error
var auditDest string
var auditFlags int
if auditFile == "-" {
fd = os . Stdout
auditDest = "stdout"
} else if auditFile == "--" {
fd = os . Stderr
auditDest = "stderr"
} else {
if auditFile == "" {
auditFile = timestampedLoc ( locAuditLog )
auditFlags = os . O_WRONLY | os . O_CREATE | os . O_EXCL
} else {
auditFlags = os . O_WRONLY | os . O_CREATE | os . O_APPEND
}
fd , err = os . OpenFile ( auditFile , auditFlags , 0600 )
if err != nil {
l . Fatalln ( "Audit:" , err )
}
auditDest = auditFile
2015-04-25 11:21:47 +02:00
}
2015-12-23 16:31:12 +01:00
auditService := newAuditService ( fd )
mainService . Add ( auditService )
2015-04-25 11:21:47 +02:00
// We wait for the audit service to fully start before we return, to
// ensure we capture all events from the start.
2015-12-23 16:31:12 +01:00
auditService . WaitForStart ( )
2015-04-25 11:21:47 +02:00
2017-01-03 09:54:28 +01:00
l . Infoln ( "Audit log in" , auditDest )
2015-04-25 11:21:47 +02:00
}
2016-09-28 17:54:13 +02:00
func setupGUI ( mainService * suture . Supervisor , cfg * config . Wrapper , m * model . Model , apiSub events . BufferedSubscription , diskSub events . BufferedSubscription , discoverer discover . CachingMux , connectionsService * connections . Service , errors , systemLog logger . Recorder , runtimeOptions RuntimeOptions ) {
2015-09-30 09:32:17 +02:00
guiCfg := cfg . GUI ( )
2014-10-10 17:32:43 +02:00
2015-09-30 09:32:17 +02:00
if ! guiCfg . Enabled {
return
}
2014-10-10 17:32:43 +02:00
2015-11-16 21:33:55 +01:00
if guiCfg . InsecureAdminAccess {
l . Warnln ( "Insecure admin access is enabled." )
}
2016-09-28 17:54:13 +02:00
api := newAPIService ( myID , cfg , locations [ locHTTPSCertFile ] , locations [ locHTTPSKeyFile ] , runtimeOptions . assetDir , m , apiSub , diskSub , discoverer , connectionsService , errors , systemLog )
2015-10-12 15:27:57 +02:00
cfg . Subscribe ( api )
2015-12-23 16:31:12 +01:00
mainService . Add ( api )
2015-09-30 09:32:17 +02:00
2015-12-17 23:40:46 +01:00
if cfg . Options ( ) . StartBrowser && ! runtimeOptions . noBrowser && ! runtimeOptions . stRestarting {
2015-10-12 15:27:57 +02:00
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
2016-10-07 05:10:26 +02:00
<- api . startedOnce
2015-10-12 15:27:57 +02:00
go openURL ( guiCfg . URL ( ) )
2014-10-10 17:32:43 +02:00
}
}
func defaultConfig ( myName string ) config . Configuration {
2016-01-09 02:11:06 +01:00
var defaultFolder config . FolderConfiguration
if ! noDefaultFolder {
l . Infoln ( "Default folder created and/or linked to new config" )
2016-11-22 09:18:43 +01:00
defaultFolder = config . NewFolderConfiguration ( "default" , locations [ locDefFolder ] )
defaultFolder . Label = "Default Folder"
2016-01-09 02:11:06 +01:00
defaultFolder . RescanIntervalS = 60
defaultFolder . MinDiskFreePct = 1
defaultFolder . Devices = [ ] config . FolderDeviceConfiguration { { DeviceID : myID } }
defaultFolder . AutoNormalize = true
defaultFolder . MaxConflicts = - 1
} else {
l . Infoln ( "We will skip creation of a default folder on first start since the proper envvar is set" )
}
2015-11-07 09:47:31 +01:00
thisDevice := config . NewDeviceConfiguration ( myID , myName )
thisDevice . Addresses = [ ] string { "dynamic" }
2014-10-10 17:32:43 +02:00
newCfg := config . New ( myID )
2016-01-09 02:11:06 +01:00
if ! noDefaultFolder {
newCfg . Folders = [ ] config . FolderConfiguration { defaultFolder }
}
2015-11-07 09:47:31 +01:00
newCfg . Devices = [ ] config . DeviceConfiguration { thisDevice }
2014-10-10 17:32:43 +02:00
2015-03-26 21:36:06 +01:00
port , err := getFreePort ( "127.0.0.1" , 8384 )
2014-10-10 17:32:43 +02:00
if err != nil {
l . Fatalln ( "get free port (GUI):" , err )
}
2015-10-12 15:27:57 +02:00
newCfg . GUI . RawAddress = fmt . Sprintf ( "127.0.0.1:%d" , port )
2014-10-10 17:32:43 +02:00
port , err = getFreePort ( "0.0.0.0" , 22000 )
if err != nil {
l . Fatalln ( "get free port (BEP):" , err )
}
2016-05-04 21:38:12 +02:00
if port == 22000 {
newCfg . Options . ListenAddresses = [ ] string { "default" }
} else {
newCfg . Options . ListenAddresses = [ ] string {
fmt . Sprintf ( "tcp://%s" , net . JoinHostPort ( "0.0.0.0" , strconv . Itoa ( port ) ) ) ,
"dynamic+https://relays.syncthing.net/endpoint" ,
}
}
2014-10-10 17:32:43 +02:00
return newCfg
}
2015-03-26 23:26:51 +01:00
func generatePingEvents ( ) {
2014-07-13 21:07:24 +02:00
for {
2015-03-26 23:26:51 +01:00
time . Sleep ( pingEventInterval )
2014-07-13 21:07:24 +02:00
events . Default . Log ( events . Ping , nil )
}
}
2015-04-03 20:06:03 +02:00
func resetDB ( ) error {
return os . RemoveAll ( locations [ locDatabase ] )
2014-07-15 14:27:46 +02:00
}
2014-02-12 12:10:44 +01:00
func restart ( ) {
2014-05-15 02:08:56 +02:00
l . Infoln ( "Restarting" )
2014-09-02 13:08:24 +02:00
stop <- exitRestarting
2014-02-12 12:10:44 +01:00
}
2014-05-12 01:16:27 +02:00
func shutdown ( ) {
2014-09-02 13:08:24 +02:00
l . Infoln ( "Shutting down" )
stop <- exitSuccess
2014-05-12 01:16:27 +02:00
}
2015-12-16 21:03:20 +01:00
func ensureDir ( dir string , mode os . FileMode ) {
err := osutil . MkdirAll ( dir , mode )
if err != nil {
l . Fatalln ( err )
}
2015-12-21 08:35:24 +01:00
if fi , err := os . Stat ( dir ) ; err == nil {
// Apprently the stat may fail even though the mkdirall passed. If it
// does, we'll just assume things are in order and let other things
// fail (like loading or creating the config...).
currentMode := fi . Mode ( ) & 0777
if currentMode != mode {
err := os . Chmod ( dir , mode )
// This can fail on crappy filesystems, nothing we can do about it.
if err != nil {
l . Warnln ( err )
}
2014-09-20 15:41:52 +02:00
}
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
}
2014-07-31 21:29:44 +02:00
addr := c . Addr ( ) . ( * net . TCPAddr )
2014-05-11 20:21:41 +02:00
c . Close ( )
2014-07-31 21:29:44 +02:00
return addr . Port , nil
}
2014-05-11 20:21:41 +02:00
2014-08-27 23:38:36 +02:00
func standbyMonitor ( ) {
2016-12-17 15:37:11 +01:00
restartDelay := 60 * time . Second
2014-08-27 23:38:36 +02:00
now := time . Now ( )
for {
time . Sleep ( 10 * time . Second )
if time . Since ( now ) > 2 * time . Minute {
2014-09-27 14:44:15 +02:00
l . Infof ( "Paused state detected, possibly woke up from standby. Restarting in %v." , restartDelay )
2014-09-10 22:20:03 +02:00
// We most likely just woke from standby. If we restart
// immediately chances are we won't have networking ready. Give
// things a moment to stabilize.
2014-09-11 20:25:08 +02:00
time . Sleep ( restartDelay )
2014-09-10 22:20:03 +02:00
2014-08-27 23:38:36 +02:00
restart ( )
2014-09-10 22:20:03 +02:00
return
2014-08-27 23:38:36 +02:00
}
now = time . Now ( )
}
}
2014-09-26 00:51:12 +02:00
2015-09-29 20:05:22 +02:00
func autoUpgrade ( cfg * config . Wrapper ) {
2014-12-27 00:12:12 +01:00
timer := time . NewTimer ( 0 )
sub := events . Default . Subscribe ( events . DeviceConnected )
2014-09-26 00:51:12 +02:00
for {
2014-12-27 00:12:12 +01:00
select {
case event := <- sub . C ( ) :
data , ok := event . Data . ( map [ string ] string )
if ! ok || data [ "clientName" ] != "syncthing" || upgrade . CompareVersions ( data [ "clientVersion" ] , Version ) != upgrade . Newer {
continue
}
l . Infof ( "Connected to device %s with a newer version (current %q < remote %q). Checking for upgrades." , data [ "id" ] , Version , data [ "clientVersion" ] )
case <- timer . C :
2014-09-26 00:51:12 +02:00
}
2015-09-10 14:16:44 +02:00
rel , err := upgrade . LatestRelease ( cfg . Options ( ) . ReleasesURL , Version )
2014-10-01 00:01:32 +02:00
if err == upgrade . ErrUpgradeUnsupported {
2014-12-27 00:12:12 +01:00
events . Default . Unsubscribe ( sub )
2014-10-01 00:01:32 +02:00
return
}
2014-09-26 00:51:12 +02:00
if err != nil {
2014-09-30 17:38:12 +02:00
// Don't complain too loudly here; we might simply not have
// internet connectivity, or the upgrade server might be down.
l . Infoln ( "Automatic upgrade:" , err )
2014-12-27 00:12:12 +01:00
timer . Reset ( time . Duration ( cfg . Options ( ) . AutoUpgradeIntervalH ) * time . Hour )
2014-09-26 00:51:12 +02:00
continue
}
2014-12-29 12:21:08 +01:00
if upgrade . CompareVersions ( rel . Tag , Version ) != upgrade . Newer {
// Skip equal, older or majorly newer (incompatible) versions
2014-12-27 00:12:12 +01:00
timer . Reset ( time . Duration ( cfg . Options ( ) . AutoUpgradeIntervalH ) * time . Hour )
2014-09-26 00:51:12 +02:00
continue
}
l . Infof ( "Automatic upgrade (current %q < latest %q)" , Version , rel . Tag )
2014-12-08 16:36:15 +01:00
err = upgrade . To ( rel )
2014-09-26 00:51:12 +02:00
if err != nil {
l . Warnln ( "Automatic upgrade:" , err )
2014-12-27 00:12:12 +01:00
timer . Reset ( time . Duration ( cfg . Options ( ) . AutoUpgradeIntervalH ) * time . Hour )
2014-09-26 00:51:12 +02:00
continue
}
2014-12-27 00:12:12 +01:00
events . Default . Unsubscribe ( sub )
2014-09-26 00:51:12 +02:00
l . Warnf ( "Automatically upgraded to version %q. Restarting in 1 minute." , rel . Tag )
time . Sleep ( time . Minute )
stop <- exitUpgrading
return
}
}
2015-04-07 09:25:28 +02:00
// cleanConfigDirectory removes old, unused configuration and index formats, a
// suitable time after they have gone out of fashion.
func cleanConfigDirectory ( ) {
patterns := map [ string ] time . Duration {
2016-08-22 14:19:19 +02:00
"panic-*.log" : 7 * 24 * time . Hour , // keep panic logs for a week
"audit-*.log" : 7 * 24 * time . Hour , // keep audit logs for a week
"index" : 14 * 24 * time . Hour , // keep old index format for two weeks
"index-v0.11.0.db" : 14 * 24 * time . Hour , // keep old index format for two weeks
"index-v0.13.0.db" : 14 * 24 * time . Hour , // keep old index format for two weeks
"index*.converted" : 14 * 24 * time . Hour , // keep old converted indexes for two weeks
"config.xml.v*" : 30 * 24 * time . Hour , // old config versions for a month
"*.idx.gz" : 30 * 24 * time . Hour , // these should for sure no longer exist
"backup-of-v0.8" : 30 * 24 * time . Hour , // these neither
"tmp-index-sorter.*" : time . Minute , // these should never exist on startup
2015-04-07 09:25:28 +02:00
}
for pat , dur := range patterns {
pat = filepath . Join ( baseDirs [ "config" ] , pat )
2015-04-26 16:41:04 +02:00
files , err := osutil . Glob ( pat )
2015-04-07 09:25:28 +02:00
if err != nil {
l . Infoln ( "Cleaning:" , err )
continue
}
for _ , file := range files {
2015-04-14 12:31:25 +02:00
info , err := osutil . Lstat ( file )
2015-04-07 09:25:28 +02:00
if err != nil {
l . Infoln ( "Cleaning:" , err )
continue
}
if time . Since ( info . ModTime ( ) ) > dur {
if err = os . RemoveAll ( file ) ; err != nil {
l . Infoln ( "Cleaning:" , err )
} else {
l . Infoln ( "Cleaned away old file" , filepath . Base ( file ) )
}
}
}
}
}
2015-04-09 12:53:13 +02:00
// checkShortIDs verifies that the configuration won't result in duplicate
// short ID:s; that is, that the devices in the cluster all have unique
// initial 64 bits.
func checkShortIDs ( cfg * config . Wrapper ) error {
2016-01-20 20:10:22 +01:00
exists := make ( map [ protocol . ShortID ] protocol . DeviceID )
2015-04-09 12:53:13 +02:00
for deviceID := range cfg . Devices ( ) {
shortID := deviceID . Short ( )
if otherID , ok := exists [ shortID ] ; ok {
return fmt . Errorf ( "%v in conflict with %v" , deviceID , otherID )
}
exists [ shortID ] = deviceID
}
return nil
}
2016-02-02 10:27:01 +01:00
func showPaths ( ) {
fmt . Printf ( "Configuration file:\n\t%s\n\n" , locations [ locConfigFile ] )
fmt . Printf ( "Database directory:\n\t%s\n\n" , locations [ locDatabase ] )
fmt . Printf ( "Device private key & certificate files:\n\t%s\n\t%s\n\n" , locations [ locKeyFile ] , locations [ locCertFile ] )
fmt . Printf ( "HTTPS private key & certificate files:\n\t%s\n\t%s\n\n" , locations [ locHTTPSKeyFile ] , locations [ locHTTPSCertFile ] )
fmt . Printf ( "Log file:\n\t%s\n\n" , locations [ locLogFile ] )
fmt . Printf ( "GUI override directory:\n\t%s\n\n" , locations [ locGUIAssets ] )
fmt . Printf ( "Default sync folder directory:\n\t%s\n\n" , locations [ locDefFolder ] )
}
2016-12-21 19:41:25 +01:00
func setPauseState ( cfg * config . Wrapper , paused bool ) {
raw := cfg . RawCopy ( )
for i := range raw . Devices {
raw . Devices [ i ] . Paused = paused
}
for i := range raw . Folders {
raw . Folders [ i ] . Paused = paused
}
if err := cfg . Replace ( raw ) ; err != nil {
l . Fatalln ( "Cannot adjust paused state:" , err )
}
}