Compare commits

...

64 Commits

Author SHA1 Message Date
Jakob Borg
eb1a234a77 Translation & docs update 2015-12-20 10:05:17 +01:00
Jakob Borg
394c2b67d6 Merge pull request #2595 from AudriusButkevicius/leaks
Always exit via error select, making sure reader routine is exits (fi…
2015-12-20 10:03:34 +01:00
Jakob Borg
aeb3af7105 Merge branch 'pr/2594'
* pr/2594:
  Move event timestamp to header for share folder prompt
  Edit device after accepting new connection (fixes #1929)
  Move new device timestamp to box header
  Fix unmatching closing span tag
2015-12-20 09:53:26 +01:00
Jakob Borg
e5cf99e31c Move event timestamp to header for share folder prompt 2015-12-20 09:52:59 +01:00
Anderson Mesquita
8120535a35 Edit device after accepting new connection (fixes #1929)
This will open the "edit device" dialogue after accepting a new device
connection. This will allow the user to specify the name of the device
or leave it blank in case they want to accept whatever the device
advertises once it connects.
2015-12-19 09:48:27 -05:00
Anderson Mesquita
ba01433381 Move new device timestamp to box header
The device ID is already long enough to make it a bit hard to read the
body message, so moving the timestamp to the header unclutters the body
a bit.
2015-12-19 09:42:33 -05:00
Anderson Mesquita
6b99cdb83a Fix unmatching closing span tag 2015-12-19 09:42:33 -05:00
Audrius Butkevicius
e7b71f8743 Merge pull request #2601 from calmh/fix2600
Don't verify free space for files when folder MinDiskFreePct==0 (fixes #2600)
2015-12-19 13:02:57 +00:00
Jakob Borg
03935b2d64 Don't verify free space for files when folder MinDiskFreePct==0 (fixes #2600) 2015-12-19 13:59:08 +01:00
Jakob Borg
2b89f33765 Merge pull request #2592 from andersonvom/remove_globals
Remove some global variables from main.go
2015-12-19 09:05:05 +01:00
Jakob Borg
9c82a4ca60 Merge pull request #2593 from AudriusButkevicius/httpfoo
Only override TLS settings if scheme is specified
2015-12-19 08:47:46 +01:00
Anderson Mesquita
7eb9b9b1c6 Merge pull request #2597 from andersonvom/html
Fix alignment of settings fields
2015-12-18 22:48:00 -05:00
Anderson Mesquita
69b35b2ede Fix alignment of settings fields
When upgrade info is not available and the "Automatic Upgrades" options
is hidden, then "Global Discovery Server" wraps around and gets
misaligned. This fixes all that.
2015-12-18 22:40:56 -05:00
AudriusButkevicius
e8016abd97 Always exit via error select, making sure reader routine is exits (fixes #2547) 2015-12-18 22:29:04 +00:00
Anderson Mesquita
e54036be25 Reuse existing ensureDir function 2015-12-18 16:24:48 -05:00
Anderson Mesquita
1dc894087c Rename noConsole to hideConsole
This avoids the double negative of having noConsole = false to represent
not hiding the console. It is also consistent with the action performed
by osutils.
2015-12-18 16:24:48 -05:00
Anderson Mesquita
b014967550 Rename guiAssets to assetDir for consistency 2015-12-18 16:24:48 -05:00
Anderson Mesquita
490962ccdb Move logFlags into RuntimeOptions 2015-12-18 16:24:48 -05:00
Anderson Mesquita
d10e81fb3d Remove most global variables from main.go
This takes advantage of the newly created parseCommandLineOptions()
function and makes it work so that it now returns a nice struct of
options rather than relying on global variables.

There are a few global variables left, but they will take a bit more
refactoring in order to be removed, so it'll happen in later commits.
2015-12-18 16:24:44 -05:00
AudriusButkevicius
33a87f54bb Only override TLS settings if scheme is specified 2015-12-18 19:42:39 +00:00
Jakob Borg
2cdfa59fbe Merge pull request #2588 from andersonvom/browser-only
Add command line option to open GUI (fixes #2210)
2015-12-17 08:41:31 +01:00
Jakob Borg
bebe74fa4a Merge pull request #2590 from andersonvom/refactor
Refactor main.go
2015-12-17 08:34:21 +01:00
Anderson Mesquita
4098f97735 Extract cmdline option parsing into a new function
Another step towards reducing the general size of the main() function
in favor of shorter, more focused functions.
2015-12-16 17:19:32 -05:00
Anderson Mesquita
a0b7ac402d Refactor main.ensureDir()
ensureDir() did not handle one last error case and there was some logic
in the main() function that belonged to ensureDir() as well. It was also
creating a directory with a hardcoded 0700 mode, regardless of what mode
was passed to it.

This refactors it a little to fix the broken behavior, avoid redundant
checks by taking advantage of the behavior of MkdirAll, and move the
extra logic from main() into ensureDir().
2015-12-16 17:19:26 -05:00
Anderson Mesquita
0ca4482977 Refactor upgrade and check upgrade cmdline options
The main() function is growing too big (142 lines as of the date of this
commit), so this attempts to extract some functionality out of there and
into their own functions to make it easier to reason about them and keep
functions short and concise.
2015-12-16 14:29:37 -05:00
Anderson Mesquita
5cf15db6e4 Add command line option to open GUI (fixes #2210)
--browser-only assumes syncthing is already running and will open the
user's browser in the URL:port currently used in the configuration if
the GUI is enabled.
2015-12-16 13:24:21 -05:00
Jakob Borg
6379d50f07 Sort and retabify AUTHORS & NICKS 2015-12-16 09:16:16 +01:00
Jakob Borg
5a2792ae24 Merge branch 'pr/2587'
* pr/2587:
  Add helper function to format JSON responses
2015-12-16 09:08:16 +01:00
Jakob Borg
9b26f3cd84 Add andersonvom 2015-12-16 09:08:03 +01:00
Anderson Mesquita
316be5ee34 Add helper function to format JSON responses
Every time a JSON object is returned in an HTTP response, the
appropriate header needs to be set and the object itself needs to be
encoded. Doing this in every function is repetitive and error prone
(getDBFile and postDBScan, for instance, never set any headers).

This adds a helper function to centralize the appropriate JSON response
handling.
2015-12-16 09:06:17 +01:00
Audrius Butkevicius
f208e6f0b6 Fix typos 2015-12-15 20:11:07 +00:00
Jakob Borg
7c8c131e1a Merge pull request #2582 from Zillode/debug-2580
Additional output on insufficient error (ref #2580)
2015-12-15 10:19:59 +01:00
Jakob Borg
81e71d7275 Merge pull request #2576 from AudriusButkevicius/reconn
More debug to reconnect loop
2015-12-15 10:18:47 +01:00
Audrius Butkevicius
fc6c4b8e16 Merge pull request #2583 from calmh/globerrors
Better error messages for globbing errors
2015-12-15 09:13:49 +00:00
Jakob Borg
2280566bca Better error messages for globbing errors 2015-12-15 10:04:13 +01:00
Jakob Borg
33173e76a0 Merge pull request #2581 from rumpelsepp/master
systemd: Remove exit code 2, due to #2578
2015-12-15 09:10:36 +01:00
Lode Hoste
cc0b9e5088 Additional output on insufficient error (ref #2580) 2015-12-15 08:45:33 +01:00
Stefan Tatschner
ecc72d7693 systemd: Remove exit code 2, due to #2578
PR #2578 enables us to remove the exit code 2 from the list of
success status codes, because SIGINT will be handled properly.
I have also converted STNORESTART to --no-restart for the sake
of consistency.
2015-12-15 08:38:08 +01:00
Jakob Borg
4ab4aeacb0 Example GUI override address (fixes #2530) 2015-12-14 14:39:07 +01:00
Audrius Butkevicius
348d12bd60 Merge pull request #2578 from calmh/sighup
Implement signal handling
2015-12-14 12:18:51 +00:00
Jakob Borg
7b686c1103 Implement signal handling
With this change, the behavior is as follows:

 - SIGTERM / SIGINT: Exit cleanly (exit code 0)
 - SIGHUP: Restart

This is the case both when hitting the monitor process or the inner
process, or if running with NORESTART (but then we won't restart,
obviously, just exit with code exitRestarting).

It enables "pkill -HUP syncthing" to do the right thing to restart all
Syncthings on package upgrade, for example.
2015-12-14 13:07:42 +01:00
Audrius Butkevicius
403583cfbb More debug to reconnect loop 2015-12-13 12:20:25 +00:00
Jakob Borg
29bff06cd6 Translation update 2015-12-13 10:39:09 +01:00
Jakob Borg
2b80057ac9 Minimal error handling refactor 2015-12-13 10:38:21 +01:00
Jakob Borg
c3625e16d7 Add wkennington 2015-12-13 10:35:34 +01:00
Audrius Butkevicius
f4642e9e66 Merge pull request #2574 from wkennington/master.upnp
upnp: Use a separate error for the error unmarshalling
2015-12-12 18:25:27 +00:00
William A. Kennington III
5bdf4c6143 upnp: Use a separate error for the error unmarshalling
Previously, when unmarshing the SOAP error code data we would overwrite
the original err, typically with null since the parsing of the error
code information succeeds. If we don't have a upnp 725 error, we would fall
back to returning null or no error. This broke our upnp error handling
logic for AddPortMappings as it would think it succeeds if it gets a 718
permission error.
2015-12-12 09:58:23 -08:00
Jakob Borg
80aaf6a065 Merge pull request #2513 from calmh/noandroidhax
Remove Android hacks (fixes #2505)
2015-12-08 12:53:10 +01:00
Jakob Borg
3025caf932 Also remove IP per disco server 2015-12-08 10:10:34 +01:00
Jakob Borg
95cfc50fbd Remove Android hacks (fixes #2505)
Also fixes what I think migh thave been a bug where we did not use the
proxy for usage reports. And removes the BuildEnv field that we don't
need any more.
2015-12-08 10:10:06 +01:00
Jakob Borg
ded0925155 Neater build.sh all 2015-12-08 09:33:34 +01:00
Jakob Borg
931408037c Build should use -i (keep built pkg:s) and -v (verbose) 2015-12-08 09:14:52 +01:00
Jakob Borg
3318651565 Correct type assertion in verbose logger, restart (fixes #2561) 2015-12-07 08:04:09 +01:00
Audrius Butkevicius
2ab07f3aac Merge pull request #2558 from canton7/feature/logging-debug
Only print 'Enabled/Disabled debug data for ...' if it was enabled/disabled
2015-12-06 17:57:17 +00:00
Antony Male
1340e54327 Only print 'Enabled/Disabled debug data for ...' if it was enabled/disabled 2015-12-06 17:15:55 +00:00
Jakob Borg
1b6e4645b1 Update docs & translations 2015-12-06 13:09:22 +01:00
Jakob Borg
a6a573f5dc Fix STTRACE=http (it should use the http debug logger) 2015-12-05 15:59:35 +01:00
Jakob Borg
415415b5b2 Response with Bad Request to bad config posts, not Internal Server Errors
The error is the clients fault, or so we think, so we should not accept
the blame.
2015-12-05 15:56:10 +01:00
Audrius Butkevicius
38e9b92c42 Merge pull request #2550 from canton7/feature/deadline
staticClient.connect(): don't handshake twice (fixes #2547, #2548)
2015-12-04 12:05:28 +00:00
Antony Male
345d727936 staticClient.connect(): don't handshake twice (fixes #2547, #2548)
The first handshake occurred before setting the Deadline, which could
cause an unintended hang.
2015-12-04 12:01:01 +00:00
Nate Morrison
86e8e5199e Filenames added to audit log in the LocalIndexUpdated event (fixes #2549) 2015-12-04 08:42:00 +01:00
Jakob Borg
739979a116 Add nrm21 2015-12-04 08:38:45 +01:00
Jakob Borg
7cbc81adca Merge pull request #2545 from rumpelsepp/master
Add syncthing-relay(7)
2015-12-01 15:54:10 +01:00
Stefan Tatschner
737d0fa23c Add syncthing-relay(7) 2015-12-01 15:34:58 +01:00
48 changed files with 1450 additions and 483 deletions

11
AUTHORS
View File

@@ -1,8 +1,9 @@
# This is the official list of Syncthing authors for copyright purposes.
Aaron Bieber <qbit@deftly.net>
Adam Piggott <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
Adam Piggott <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
Alexander Graf <register-github@alex-graf.de>
Anderson Mesquita <andersonvom@gmail.com>
Andrew Dunham <andrew@du.nham.ca>
Antony Male <antony.male@gmail.com>
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
@@ -35,8 +36,8 @@ Francois-Xavier Gsell <fxgsell@gmail.com>
Frank Isemann <frank@isemann.name>
Gilli Sigurdsson <gilli@vx.is>
Jacek Szafarkiewicz <szafar@linux.pl>
Jakob Borg <jakob@nym.se>
Jake Peterson <jake@acogdev.com>
Jakob Borg <jakob@nym.se>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
Jaroslav Malec <dzardacz@gmail.com>
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
@@ -54,6 +55,7 @@ Matt Burke <mburke@amplify.com> <burkemw3@gmail.com>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Ploujnikov <ploujj@gmail.com>
Michael Tilli <pyfisch@gmail.com>
Nate Morrison <natemorrison@gmail.com>
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
Peter Hoeg <peter@speartail.com>
Philippe Schommers <philippe@schommers.be>
@@ -62,14 +64,15 @@ Piotr Bejda <piotrb10@gmail.com>
Ryan Sullivan <kayoticsully@gmail.com>
Scott Klupfel <kluppy@going2blue.com>
Sergey Mishin <ralder@yandex.ru>
Stefan Tatschner <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
Stefan Kuntz <stefan.github@gmail.com> <Stefan.github@gmail.com>
Stefan Tatschner <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
Tim Abell <tim@timwise.co.uk>
Tobias Nygren <tnn@nygren.pp.se>
Tomas Cerveny <kozec@kozec.com>
Tully Robinson <tully@tojr.org>
Tyler Brazier <tyler@tylerbrazier.com>
Veeti Paananen <veeti.paananen@rojekti.fi>
Vil Brekin <vilbrekin@gmail.com>
Victor Buinsky <vix_booja@tut.by>
Vil Brekin <vilbrekin@gmail.com>
William A. Kennington III <william@wkennington.com>
Yannic A. <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>

25
NICKS
View File

@@ -1,19 +1,11 @@
# This file maps email addresses used in commits to nicks used the changelog.
AudriusButkevicius <audrius.butkevicius@gmail.com>
Cathryne <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
KayoticSully <kayoticsully@gmail.com>
LordLandon <lordlandon@gmail.com>
Moter8 <moter8@gmail.com>
Nutomic <me@nutomic.com>
Rewt0r <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Stefan-Code <stefan.github@gmail.com> <Stefan.github@gmail.com>
Vilbrekin <vilbrekin@gmail.com>
Zillode <zillode@zillode.be>
acogdev <jake@acogdev.com>
alex2108 <register-github@alex-graf.de>
andersonvom <andersonvom@gmail.com>
andrew-d <andrew@du.nham.ca>
asdil12 <dominik@heidler.eu>
AudriusButkevicius <audrius.butkevicius@gmail.com>
bencurthoys <ben@bencurthoys.com>
bigbear2nd <bigbear2nd@gmail.com>
brbecker <brbecker@gmail.com>
@@ -24,6 +16,7 @@ buinsky <vix_booja@tut.by>
burkemw3 <mburke@amplify.com> <burkemw3@gmail.com>
calmh <jakob@nym.se>
canton7 <antony.male@gmail.com>
Cathryne <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
cdata <chris@scriptolo.gy>
cdhowie <me@chrishowie.com>
ceh <emil@hessman.se>
@@ -41,16 +34,21 @@ jarlebring <jarlebring@gmail.com>
jedie <github.com@jensdiemer.de> <git@jensdiemer.de>
jpjp <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
kamadak <kamada@nanohz.org>
KayoticSully <kayoticsully@gmail.com>
kilburn <kilburn@la3.org>
kluppy <kluppy@going2blue.com>
kozec <kozec@kozec.com>
krozycki <rozycki.karol@gmail.com>
LordLandon <lordlandon@gmail.com>
marcindziadus <dziadus.marcin@gmail.com>
marclaporte <marc@marclaporte.com>
mateon1 <matin1111@wp.pl>
mogwa1 <devriesb@gmail.com>
moshen <moshen.colin@gmail.com>
Moter8 <moter8@gmail.com>
mvdan <mvdan@mvdan.cc>
nrm21 <natemorrison@gmail.com>
Nutomic <me@nutomic.com>
pascalj <github@pascalj.com> <mail@pascal-jungblut.com>
peterhoeg <peter@speartail.com>
philips <brandon@ifup.org>
@@ -60,16 +58,21 @@ pluby <phill.luby@newredo.com>
pyfisch <pyfisch@gmail.com>
qbit <qbit@deftly.net>
ralder <ralder@yandex.ru>
Rewt0r <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
rumpelsepp <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
sciurius <jvromans@squirrel.nl>
seehuhn <voss@seehuhn.de>
snnd <dw@risu.io>
simplypeachy <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
snnd <dw@risu.io>
Stefan-Code <stefan.github@gmail.com> <Stefan.github@gmail.com>
timabell <tim@timwise.co.uk>
tnn2 <tnn@nygren.pp.se>
tojrobinson <tully@tojr.org>
tylerbrazier <tyler@tylerbrazier.com>
uok <ueomkail@gmail.com> <uok@users.noreply.github.com>
veeti <veeti.paananen@rojekti.fi>
Vilbrekin <vilbrekin@gmail.com>
wkennington <william@wkennington.com>
wsgcsysadmin <e.meitner@willystreet.coo>
Zillode <zillode@zillode.be>
zukoo <fxgsell@gmail.com>

View File

@@ -219,7 +219,7 @@ func build(pkg string, tags []string) {
}
rmr(binary)
args := []string{"build", "-ldflags", ldflags()}
args := []string{"build", "-i", "-v", "-ldflags", ldflags()}
if len(tags) > 0 {
args = append(args, "-tags", strings.Join(tags, ","))
}
@@ -448,7 +448,6 @@ func ldflags() string {
fmt.Fprintf(b, " -X main.BuildStamp%c%d", sep, buildStamp())
fmt.Fprintf(b, " -X main.BuildUser%c%s", sep, buildUser())
fmt.Fprintf(b, " -X main.BuildHost%c%s", sep, buildHost())
fmt.Fprintf(b, " -X main.BuildEnv%c%s", sep, buildEnvironment())
return b.String()
}

View File

@@ -74,27 +74,26 @@ case "${1:-default}" in
;;
all)
build -goos darwin -goarch amd64 tar
platforms=(
darwin-amd64 dragonfly-amd64 freebsd-amd64 linux-amd64 netbsd-amd64 openbsd-amd64 solaris-amd64 windows-amd64
freebsd-386 linux-386 netbsd-386 openbsd-386 windows-386
linux-arm
)
build -goos dragonfly -goarch amd64 tar
for plat in "${platforms[@]}"; do
echo Building "$plat"
build -goos freebsd -goarch 386 tar
build -goos freebsd -goarch amd64 tar
goos="${plat%-*}"
goarch="${plat#*-}"
dist="tar"
build -goos linux -goarch 386 tar
build -goos linux -goarch amd64 tar
build -goos linux -goarch arm tar
if [[ $goos == "windows" ]]; then
dist="zip"
fi
build -goos netbsd -goarch 386 tar
build -goos netbsd -goarch amd64 tar
build -goos openbsd -goarch 386 tar
build -goos openbsd -goarch amd64 tar
build -goos solaris -goarch amd64 tar
build -goos windows -goarch 386 zip
build -goos windows -goarch amd64 zip
build -goos "$goos" -goarch "$goarch" "$dist"
echo
done
;;
test-cov)

View File

@@ -130,6 +130,11 @@ func (s *apiSvc) getListener(guiCfg config.GUIConfiguration) (net.Listener, erro
return listener, nil
}
func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(jsonObject)
}
func (s *apiSvc) Serve() {
s.stop = make(chan struct{})
@@ -317,7 +322,7 @@ func debugMiddleware(h http.Handler) http.Handler {
written = rf.Int()
}
}
l.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
httpl.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
}
})
}
@@ -366,15 +371,11 @@ func withDetailsMiddleware(id protocol.DeviceID, h http.Handler) http.Handler {
}
func (s *apiSvc) restPing(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]string{
"ping": "pong",
})
sendJSON(w, map[string]string{"ping": "pong"})
}
func (s *apiSvc) getSystemVersion(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]string{
sendJSON(w, map[string]string{
"version": Version,
"codename": Codename,
"longVersion": LongVersion,
@@ -384,11 +385,10 @@ func (s *apiSvc) getSystemVersion(w http.ResponseWriter, r *http.Request) {
}
func (s *apiSvc) getSystemDebug(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
names := l.Facilities()
enabled := l.FacilityDebugging()
sort.Strings(enabled)
json.NewEncoder(w).Encode(map[string]interface{}{
sendJSON(w, map[string]interface{}{
"facilities": names,
"enabled": enabled,
})
@@ -398,14 +398,14 @@ func (s *apiSvc) postSystemDebug(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
q := r.URL.Query()
for _, f := range strings.Split(q.Get("enable"), ",") {
if f == "" {
if f == "" || l.ShouldDebug(f) {
continue
}
l.SetDebug(f, true)
l.Infof("Enabled debug data for %q", f)
}
for _, f := range strings.Split(q.Get("disable"), ",") {
if f == "" {
if f == "" || !l.ShouldDebug(f) {
continue
}
l.SetDebug(f, false)
@@ -424,11 +424,7 @@ func (s *apiSvc) getDBBrowse(w http.ResponseWriter, r *http.Request) {
levels = -1
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
tree := s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly)
json.NewEncoder(w).Encode(tree)
sendJSON(w, s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly))
}
func (s *apiSvc) getDBCompletion(w http.ResponseWriter, r *http.Request) {
@@ -442,20 +438,15 @@ func (s *apiSvc) getDBCompletion(w http.ResponseWriter, r *http.Request) {
return
}
res := map[string]float64{
sendJSON(w, map[string]float64{
"completion": s.model.Completion(device, folder),
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
})
}
func (s *apiSvc) getDBStatus(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
res := folderSummary(s.cfg, s.model, folder)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
sendJSON(w, folderSummary(s.cfg, s.model, folder))
}
func folderSummary(cfg *config.Wrapper, m *model.Model, folder string) map[string]interface{} {
@@ -520,35 +511,26 @@ func (s *apiSvc) getDBNeed(w http.ResponseWriter, r *http.Request) {
progress, queued, rest, total := s.model.NeedFolderFiles(folder, page, perpage)
// Convert the struct to a more loose structure, and inject the size.
output := map[string]interface{}{
sendJSON(w, map[string]interface{}{
"progress": s.toNeedSlice(progress),
"queued": s.toNeedSlice(queued),
"rest": s.toNeedSlice(rest),
"total": total,
"page": page,
"perpage": perpage,
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(output)
})
}
func (s *apiSvc) getSystemConnections(w http.ResponseWriter, r *http.Request) {
var res = s.model.ConnectionStats()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
sendJSON(w, s.model.ConnectionStats())
}
func (s *apiSvc) getDeviceStats(w http.ResponseWriter, r *http.Request) {
var res = s.model.DeviceStatistics()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
sendJSON(w, s.model.DeviceStatistics())
}
func (s *apiSvc) getFolderStats(w http.ResponseWriter, r *http.Request) {
var res = s.model.FolderStatistics()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
sendJSON(w, s.model.FolderStatistics())
}
func (s *apiSvc) getDBFile(w http.ResponseWriter, r *http.Request) {
@@ -559,7 +541,7 @@ func (s *apiSvc) getDBFile(w http.ResponseWriter, r *http.Request) {
lf, _ := s.model.CurrentFolderFile(folder, file)
av := s.model.Availability(folder, file)
json.NewEncoder(w).Encode(map[string]interface{}{
sendJSON(w, map[string]interface{}{
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
"availability": av,
@@ -567,8 +549,7 @@ func (s *apiSvc) getDBFile(w http.ResponseWriter, r *http.Request) {
}
func (s *apiSvc) getSystemConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(s.cfg.Raw())
sendJSON(w, s.cfg.Raw())
}
func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
@@ -578,7 +559,7 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
to, err := config.ReadJSON(r.Body, myID)
if err != nil {
l.Warnln("decoding posted config:", err)
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
@@ -587,7 +568,7 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
if err != nil {
l.Warnln("bcrypting password:", err)
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -615,8 +596,7 @@ func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
}
func (s *apiSvc) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]bool{"configInSync": configInSync})
sendJSON(w, map[string]bool{"configInSync": configInSync})
}
func (s *apiSvc) postSystemRestart(w http.ResponseWriter, r *http.Request) {
@@ -711,13 +691,11 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
res["uptime"] = int(time.Since(startTime).Seconds())
res["startTime"] = startTime
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
sendJSON(w, res)
}
func (s *apiSvc) getSystemError(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string][]logger.Line{
sendJSON(w, map[string][]logger.Line{
"errors": s.guiErrors.Since(time.Time{}),
})
}
@@ -736,9 +714,7 @@ func (s *apiSvc) getSystemLog(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
since, err := time.Parse(time.RFC3339, q.Get("since"))
l.Debugln(err)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string][]logger.Line{
sendJSON(w, map[string][]logger.Line{
"messages": s.systemLog.Since(since),
})
}
@@ -775,7 +751,6 @@ func (s *apiSvc) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
}
func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
devices := make(map[string]discover.CacheEntry)
if s.discoverer != nil {
@@ -787,17 +762,15 @@ func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
}
}
json.NewEncoder(w).Encode(devices)
sendJSON(w, devices)
}
func (s *apiSvc) getReport(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(reportData(s.cfg, s.model))
sendJSON(w, reportData(s.cfg, s.model))
}
func (s *apiSvc) getDBIgnores(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
ignores, patterns, err := s.model.GetIgnores(qs.Get("folder"))
if err != nil {
@@ -805,7 +778,7 @@ func (s *apiSvc) getDBIgnores(w http.ResponseWriter, r *http.Request) {
return
}
json.NewEncoder(w).Encode(map[string][]string{
sendJSON(w, map[string][]string{
"ignore": ignores,
"patterns": patterns,
})
@@ -841,8 +814,6 @@ func (s *apiSvc) getEvents(w http.ResponseWriter, r *http.Request) {
s.fss.gotEventRequest()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// Flush before blocking, to indicate that we've received the request
// and that it should not be retried.
f := w.(http.Flusher)
@@ -853,7 +824,7 @@ func (s *apiSvc) getEvents(w http.ResponseWriter, r *http.Request) {
evs = evs[len(evs)-limit:]
}
json.NewEncoder(w).Encode(evs)
sendJSON(w, evs)
}
func (s *apiSvc) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
@@ -872,21 +843,20 @@ func (s *apiSvc) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
res["newer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.Newer
res["majorNewer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.MajorNewer
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
sendJSON(w, res)
}
func (s *apiSvc) getDeviceID(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
idStr := qs.Get("id")
id, err := protocol.DeviceIDFromString(idStr)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err == nil {
json.NewEncoder(w).Encode(map[string]string{
sendJSON(w, map[string]string{
"id": id.String(),
})
} else {
json.NewEncoder(w).Encode(map[string]string{
sendJSON(w, map[string]string{
"error": err.Error(),
})
}
@@ -899,8 +869,7 @@ func (s *apiSvc) getLang(w http.ResponseWriter, r *http.Request) {
parts := strings.SplitN(l, ";", 2)
langs = append(langs, strings.ToLower(strings.TrimSpace(parts[0])))
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(langs)
sendJSON(w, langs)
}
func (s *apiSvc) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
@@ -971,7 +940,7 @@ func (s *apiSvc) postDBScan(w http.ResponseWriter, r *http.Request) {
errors := s.model.ScanFolders()
if len(errors) > 0 {
http.Error(w, "Error scanning folders", 500)
json.NewEncoder(w).Encode(errors)
sendJSON(w, errors)
return
}
}
@@ -1019,12 +988,10 @@ func (s *apiSvc) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
comp[device] = int(tot[device] / count[device])
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(comp)
sendJSON(w, comp)
}
func (s *apiSvc) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
qs := r.URL.Query()
current := qs.Get("current")
search, _ := osutil.ExpandTilde(current)
@@ -1043,7 +1010,8 @@ func (s *apiSvc) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
}
}
}
json.NewEncoder(w).Encode(ret)
sendJSON(w, ret)
}
type embeddedStatic struct {

View File

@@ -19,6 +19,7 @@ import (
_ "net/http/pprof"
"net/url"
"os"
"os/signal"
"path/filepath"
"regexp"
"runtime"
@@ -26,6 +27,7 @@ import (
"sort"
"strconv"
"strings"
"syscall"
"time"
"github.com/syncthing/syncthing/lib/config"
@@ -49,7 +51,6 @@ import (
var (
Version = "unknown-dev"
Codename = "Beryllium Bedbug"
BuildEnv = "default"
BuildStamp = "0"
BuildDate time.Time
BuildHost = "unknown"
@@ -108,20 +109,14 @@ func init() {
BuildDate = time.Unix(int64(stamp), 0)
date := BuildDate.UTC().Format("2006-01-02 15:04:05 MST")
LongVersion = fmt.Sprintf(`syncthing %s "%s" (%s %s-%s %s) %s@%s %s`, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildEnv, BuildUser, BuildHost, date)
if os.Getenv("STTRACE") != "" {
logFlags = log.Ltime | log.Ldate | log.Lmicroseconds | log.Lshortfile
}
LongVersion = fmt.Sprintf(`syncthing %s "%s" (%s %s-%s) %s@%s %s`, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildUser, BuildHost, date)
}
var (
myID protocol.DeviceID
confDir string
logFlags = log.Ltime
stop = make(chan int)
cert tls.Certificate
lans []*net.IPNet
myID protocol.DeviceID
stop = make(chan int)
cert tls.Certificate
lans []*net.IPNet
)
const (
@@ -189,170 +184,180 @@ The following are valid values for the STTRACE variable:
%s`
)
// Command line and environment options
// Environment options
var (
noUpgrade = os.Getenv("STNOUPGRADE") != ""
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
)
type RuntimeOptions struct {
confDir string
reset bool
showVersion bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
noConsole bool
browserOnly bool
hideConsole bool
logFile string
auditEnabled bool
verbose bool
paused bool
noRestart = os.Getenv("STNORESTART") != ""
noUpgrade = os.Getenv("STNOUPGRADE") != ""
profiler = os.Getenv("STPROFILER")
guiAssets = os.Getenv("STGUIASSETS")
cpuProfile = os.Getenv("STCPUPROFILE") != ""
stRestarting = os.Getenv("STRESTART") != ""
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
)
guiAddress string
guiAPIKey string
generateDir string
noRestart bool
profiler string
assetDir string
cpuProfile bool
stRestarting bool
logFlags int
}
func main() {
if runtime.GOOS == "windows" {
// On Windows, we use a log file by default. Setting the -logfile flag
// to "-" disables this behavior.
flag.StringVar(&logFile, "logfile", "", "Log file name (use \"-\" for stdout)")
// We also add an option to hide the console window
flag.BoolVar(&noConsole, "no-console", false, "Hide console window")
} else {
flag.StringVar(&logFile, "logfile", "-", "Log file name (use \"-\" for stdout)")
func defaultRuntimeOptions() RuntimeOptions {
options := RuntimeOptions{
noRestart: os.Getenv("STNORESTART") != "",
profiler: os.Getenv("STPROFILER"),
assetDir: os.Getenv("STGUIASSETS"),
cpuProfile: os.Getenv("STCPUPROFILE") != "",
stRestarting: os.Getenv("STRESTART") != "",
logFile: "-", // Output to stdout
logFlags: log.Ltime,
}
var guiAddress, guiAPIKey string
var generateDir string
flag.StringVar(&generateDir, "generate", "", "Generate key and config in specified dir, then exit")
flag.StringVar(&guiAddress, "gui-address", guiAddress, "Override GUI address")
flag.StringVar(&guiAPIKey, "gui-apikey", guiAPIKey, "Override GUI API key")
flag.StringVar(&confDir, "home", "", "Set configuration directory")
flag.IntVar(&logFlags, "logflags", logFlags, "Select information in log line prefix")
flag.BoolVar(&noBrowser, "no-browser", false, "Do not start browser")
flag.BoolVar(&noRestart, "no-restart", noRestart, "Do not restart; just exit")
flag.BoolVar(&reset, "reset", false, "Reset the database")
flag.BoolVar(&doUpgrade, "upgrade", false, "Perform upgrade")
flag.BoolVar(&doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
flag.BoolVar(&showVersion, "version", false, "Show version")
flag.StringVar(&upgradeTo, "upgrade-to", upgradeTo, "Force upgrade directly from specified URL")
flag.BoolVar(&auditEnabled, "audit", false, "Write events to audit file")
flag.BoolVar(&verbose, "verbose", false, "Print verbose log output")
flag.BoolVar(&paused, "paused", false, "Start with all devices paused")
if options.assetDir != "" {
options.assetDir = locations[locGUIAssets]
}
if os.Getenv("STTRACE") != "" {
options.logFlags = log.Ltime | log.Ldate | log.Lmicroseconds | log.Lshortfile
}
if runtime.GOOS == "windows" {
options.logFile = locations[locLogFile]
}
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")
flag.IntVar(&options.logFlags, "logflags", options.logFlags, "Select information in log line prefix (see below)")
flag.BoolVar(&options.noBrowser, "no-browser", false, "Do not start browser")
flag.BoolVar(&options.browserOnly, "browser-only", false, "Open GUI in browser")
flag.BoolVar(&options.noRestart, "no-restart", options.noRestart, "Do not restart; just exit")
flag.BoolVar(&options.reset, "reset", false, "Reset the database")
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")
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")
flag.BoolVar(&options.paused, "paused", false, "Start with all devices paused")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (use \"-\" for stdout)")
if runtime.GOOS == "windows" {
// Allow user to hide the console window
flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
}
longUsage := fmt.Sprintf(extraUsage, baseDirs["config"], debugFacilities())
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
flag.Parse()
if guiAddress != "" {
return options
}
func main() {
options := parseCommandLineOptions()
l.SetFlags(options.logFlags)
if options.guiAddress != "" {
// The config picks this up from the environment.
os.Setenv("STGUIADDRESS", guiAddress)
os.Setenv("STGUIADDRESS", options.guiAddress)
}
if guiAPIKey != "" {
if options.guiAPIKey != "" {
// The config picks this up from the environment.
os.Setenv("STGUIAPIKEY", guiAPIKey)
os.Setenv("STGUIAPIKEY", options.guiAPIKey)
}
if noConsole {
if options.hideConsole {
osutil.HideConsole()
}
if confDir != "" {
if options.confDir != "" {
// Not set as default above because the string can be really long.
baseDirs["config"] = confDir
baseDirs["config"] = options.confDir
}
if err := expandLocations(); err != nil {
l.Fatalln(err)
}
if guiAssets == "" {
guiAssets = locations[locGUIAssets]
}
if logFile == "" {
// Use the default log file location
logFile = locations[locLogFile]
}
if showVersion {
if options.showVersion {
fmt.Println(LongVersion)
return
}
l.SetFlags(logFlags)
if generateDir != "" {
generate(generateDir)
if options.browserOnly {
openGUI()
return
}
if info, err := os.Stat(baseDirs["config"]); err == nil && !info.IsDir() {
l.Fatalln("Config directory", baseDirs["config"], "is not a directory")
if options.generateDir != "" {
generate(options.generateDir)
return
}
// Ensure that our home directory exists.
ensureDir(baseDirs["config"], 0700)
if upgradeTo != "" {
err := upgrade.ToURL(upgradeTo)
if options.upgradeTo != "" {
err := upgrade.ToURL(options.upgradeTo)
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
}
l.Okln("Upgraded from", upgradeTo)
l.Okln("Upgraded from", options.upgradeTo)
return
}
if doUpgrade || doUpgradeCheck {
releasesURL := "https://api.github.com/repos/syncthing/syncthing/releases?per_page=30"
if cfg, _, err := loadConfig(locations[locConfigFile]); err == nil {
releasesURL = cfg.Options().ReleasesURL
}
rel, err := upgrade.LatestRelease(releasesURL, Version)
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
}
if upgrade.CompareVersions(rel.Tag, Version) <= 0 {
l.Infof("No upgrade available (current %q >= latest %q).", Version, rel.Tag)
os.Exit(exitNoUpgradeAvailable)
}
l.Infof("Upgrade available (current %q < latest %q)", Version, rel.Tag)
if doUpgrade {
// Use leveldb database locks to protect against concurrent upgrades
_, err = db.Open(locations[locDatabase])
if err != nil {
l.Infoln("Attempting upgrade through running Syncthing...")
err = upgradeViaRest()
if err != nil {
l.Fatalln("Upgrade:", err)
}
l.Okln("Syncthing upgrading")
return
}
err = upgrade.To(rel)
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
}
l.Okf("Upgraded to %q", rel.Tag)
}
if options.doUpgradeCheck {
checkUpgrade()
return
}
if reset {
if options.doUpgrade {
release := checkUpgrade()
performUpgrade(release)
return
}
if options.reset {
resetDB()
return
}
if noRestart {
syncthingMain()
if options.noRestart {
syncthingMain(options)
} else {
monitorMain()
monitorMain(options)
}
}
func openGUI() {
cfg, _, err := loadConfig(locations[locConfigFile])
if err != nil {
l.Fatalln("Config:", err)
}
if cfg.GUI().Enabled {
openURL(cfg.GUI().URL())
} else {
l.Warnln("Browser: GUI is currently disabled")
}
}
@@ -361,17 +366,7 @@ func generate(generateDir string) {
if err != nil {
l.Fatalln("generate:", err)
}
info, err := os.Stat(dir)
if err == nil && !info.IsDir() {
l.Fatalln(dir, "is not a directory")
}
if err != nil && os.IsNotExist(err) {
err = osutil.MkdirAll(dir, 0700)
if err != nil {
l.Fatalln("generate:", err)
}
}
ensureDir(dir, 0700)
certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
@@ -428,6 +423,46 @@ func debugFacilities() string {
return b.String()
}
func checkUpgrade() upgrade.Release {
releasesURL := "https://api.github.com/repos/syncthing/syncthing/releases?per_page=30"
if cfg, _, err := loadConfig(locations[locConfigFile]); err == nil {
releasesURL = cfg.Options().ReleasesURL
}
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)
}
l.Okf("Upgraded to %q", release.Tag)
} else {
l.Infoln("Attempting upgrade through running Syncthing...")
err = upgradeViaRest()
if err != nil {
l.Fatalln("Upgrade:", err)
}
l.Okln("Syncthing upgrading")
os.Exit(exitUpgrading)
}
}
func upgradeViaRest() error {
cfg, err := config.Load(locations[locConfigFile], protocol.LocalDeviceID)
if err != nil {
@@ -462,7 +497,9 @@ func upgradeViaRest() error {
return err
}
func syncthingMain() {
func syncthingMain(runtimeOptions RuntimeOptions) {
setupSignalHandling()
// Create a main service manager. We'll add things to this as we go along.
// We want any logging it does to go through our log system.
mainSvc := suture.New("main", suture.Spec{
@@ -476,11 +513,11 @@ func syncthingMain() {
// lines look ugly.
l.SetPrefix("[start] ")
if auditEnabled {
if runtimeOptions.auditEnabled {
startAuditing(mainSvc)
}
if verbose {
if runtimeOptions.verbose {
mainSvc.Add(newVerboseSvc())
}
@@ -560,11 +597,11 @@ func syncthingMain() {
l.Fatalln("Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one if the following:\n ", err)
}
if len(profiler) > 0 {
if len(runtimeOptions.profiler) > 0 {
go func() {
l.Debugln("Starting profiler on", profiler)
l.Debugln("Starting profiler on", runtimeOptions.profiler)
runtime.SetBlockProfileRate(1)
err := http.ListenAndServe(profiler, nil)
err := http.ListenAndServe(runtimeOptions.profiler, nil)
if err != nil {
l.Fatalln(err)
}
@@ -659,7 +696,7 @@ func syncthingMain() {
m.StartDeadlockDetector(20 * time.Minute)
}
if paused {
if runtimeOptions.paused {
for device := range cfg.Devices() {
m.PauseDevice(device)
}
@@ -764,14 +801,14 @@ func syncthingMain() {
// GUI
setupGUI(mainSvc, cfg, m, apiSub, cachedDiscovery, relaySvc, errors, systemLog)
setupGUI(mainSvc, cfg, m, apiSub, cachedDiscovery, relaySvc, errors, systemLog, runtimeOptions)
// Start connection management
connectionSvc := connections.NewConnectionSvc(cfg, myID, m, tlsCfg, cachedDiscovery, relaySvc, bepProtocolName, tlsDefaultCommonName, lans)
mainSvc.Add(connectionSvc)
if cpuProfile {
if runtimeOptions.cpuProfile {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil {
log.Fatal(err)
@@ -833,13 +870,35 @@ func syncthingMain() {
l.Okln("Exiting")
if cpuProfile {
if runtimeOptions.cpuProfile {
pprof.StopCPUProfile()
}
os.Exit(code)
}
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
}()
}
// printHashRate prints the hashing performance in MB/s, formatting it with
// appropriate precision for the value, i.e. 182 MB/s, 18 MB/s, 1.8 MB/s, 0.18
// MB/s.
@@ -896,7 +955,7 @@ func startAuditing(mainSvc *suture.Supervisor) {
l.Infoln("Audit log in", auditFile)
}
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) {
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder, runtimeOptions RuntimeOptions) {
guiCfg := cfg.GUI()
if !guiCfg.Enabled {
@@ -907,14 +966,14 @@ func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, a
l.Warnln("Insecure admin access is enabled.")
}
api, err := newAPISvc(myID, cfg, guiAssets, m, apiSub, discoverer, relaySvc, errors, systemLog)
api, err := newAPISvc(myID, cfg, runtimeOptions.assetDir, m, apiSub, discoverer, relaySvc, errors, systemLog)
if err != nil {
l.Fatalln("Cannot start GUI:", err)
}
cfg.Subscribe(api)
mainSvc.Add(api)
if cfg.Options().StartBrowser && !noBrowser && !stRestarting {
if cfg.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting {
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
go openURL(guiCfg.URL())
@@ -971,15 +1030,16 @@ func shutdown() {
stop <- exitSuccess
}
func ensureDir(dir string, mode int) {
fi, err := os.Stat(dir)
if os.IsNotExist(err) {
err := osutil.MkdirAll(dir, 0700)
if err != nil {
l.Fatalln(err)
}
} else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
err := os.Chmod(dir, os.FileMode(mode))
func ensureDir(dir string, mode os.FileMode) {
err := osutil.MkdirAll(dir, mode)
if err != nil {
l.Fatalln(err)
}
fi, _ := os.Stat(dir)
currentMode := fi.Mode() & 0777
if mode >= 0 && 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)

View File

@@ -34,13 +34,14 @@ const (
logFileMaxOpenTime = time.Minute
)
func monitorMain() {
func monitorMain(runtimeOptions RuntimeOptions) {
os.Setenv("STNORESTART", "yes")
os.Setenv("STMONITORED", "yes")
l.SetPrefix("[monitor] ")
var dst io.Writer = os.Stdout
logFile := runtimeOptions.logFile
if logFile != "-" {
var fileDst io.Writer = newAutoclosedFile(logFile, logFileAutoCloseDelay, logFileMaxOpenTime)
@@ -62,9 +63,12 @@ func monitorMain() {
args := os.Args
var restarts [countRestarts]time.Time
sign := make(chan os.Signal, 1)
sigTerm := syscall.Signal(0xf)
signal.Notify(sign, os.Interrupt, sigTerm, os.Kill)
stopSign := make(chan os.Signal, 1)
sigTerm := syscall.Signal(15)
signal.Notify(stopSign, os.Interrupt, sigTerm)
restartSign := make(chan os.Signal, 1)
sigHup := syscall.Signal(1)
signal.Notify(restartSign, sigHup)
for {
if t := time.Since(restarts[0]); t < loopThreshold {
@@ -124,12 +128,17 @@ func monitorMain() {
}()
select {
case s := <-sign:
case s := <-stopSign:
l.Infof("Signal %d received; exiting", s)
cmd.Process.Kill()
<-exit
return
case s := <-restartSign:
l.Infof("Signal %d received; restarting", s)
cmd.Process.Signal(sigHup)
err = <-exit
case err = <-exit:
if err == nil {
// Successful exit indicates an intentional shutdown

View File

@@ -13,7 +13,6 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"runtime"
"sort"
@@ -184,8 +183,6 @@ func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
for _, addr := range cfg.Options().GlobalAnnServers {
if addr == "default" || addr == "default-v4" || addr == "default-v6" {
defaultAnnounceServersDNS++
} else if stringIn(addr, config.DefaultDiscoveryServersIP) {
defaultAnnounceServersIP++
} else {
otherAnnounceServers++
}
@@ -221,15 +218,6 @@ func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
return res
}
func stringIn(needle string, haystack []string) bool {
for _, s := range haystack {
if needle == s {
return true
}
}
return false
}
type usageReportingService struct {
cfg *config.Wrapper
model *model.Model
@@ -249,19 +237,14 @@ func (s *usageReportingService) sendUsageReport() error {
var b bytes.Buffer
json.NewEncoder(&b).Encode(d)
transp := &http.Transport{}
client := &http.Client{Transport: transp}
if BuildEnv == "android" {
// This works around the lack of DNS resolution on Android... :(
transp.Dial = func(network, addr string) (net.Conn, error) {
return dialer.Dial(network, "194.126.249.13:443")
}
}
if s.cfg.Options().URPostInsecurely {
transp.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
client := &http.Client{
Transport: &http.Transport{
Dial: dialer.Dial,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: s.cfg.Options().URPostInsecurely,
},
},
}
_, err := client.Post(s.cfg.Options().URURL, "application/json", &b)
return err

View File

@@ -32,8 +32,14 @@ func (s *verboseSvc) Serve() {
sub := events.Default.Subscribe(events.AllEvents)
defer events.Default.Unsubscribe(sub)
// We're ready to start processing events.
close(s.started)
select {
case <-s.started:
// The started channel has already been closed; do nothing.
default:
// This is the first time around. Indicate that we're ready to start
// processing events.
close(s.started)
}
for {
select {
@@ -96,7 +102,7 @@ func (s *verboseSvc) formatEvent(ev events.Event) string {
return fmt.Sprintf("Rejected unshared folder %q from device %v", data["folder"], data["device"])
case events.ItemStarted:
data := ev.Data.(map[string]interface{})
data := ev.Data.(map[string]string)
return fmt.Sprintf("Started syncing %q / %q (%v %v)", data["folder"], data["item"], data["action"], data["type"])
case events.ItemFinished:
data := ev.Data.(map[string]interface{})

View File

@@ -6,10 +6,9 @@ Wants=syncthing-inotify@.service
[Service]
User=%i
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing -no-browser -logflags=0
ExecStart=/usr/bin/syncthing -no-browser -no-restart -logflags=0
Restart=on-failure
SuccessExitStatus=2 3 4
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
[Install]

View File

@@ -5,10 +5,9 @@ After=network.target
Wants=syncthing-inotify.service
[Service]
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing -no-browser -logflags=0
ExecStart=/usr/bin/syncthing -no-browser -no-restart -logflags=0
Restart=on-failure
SuccessExitStatus=2 3 4
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
[Install]

View File

@@ -10,8 +10,8 @@
"Add new folder?": "Afegir nova carpeta?",
"Address": "Direcció",
"Addresses": "Direccions",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "Avançat",
"Advanced Configuration": "Configuració avançada",
"All Data": "Totes les dades",
"Allow Anonymous Usage Reporting?": "Permetre informes d'ús anònim?",
"Alphabetic": "Alfabètic",
@@ -19,7 +19,7 @@
"Anonymous Usage Reporting": "Informe d'ús anònim",
"Any devices configured on an introducer device will be added to this device as well.": "Tots els dispositius configurats en un dispositiu presentador seràn afegits també a aquest dispositiu.",
"Automatic upgrades": "Actualitzacions automàtiques",
"Be careful!": "Be careful!",
"Be careful!": "Tin precaució!",
"Bugs": "Errors (Bugs)",
"CPU Utilization": "Utilització de la CPU",
"Changelog": "Registre de canvis",
@@ -32,7 +32,7 @@
"Copied from elsewhere": "Copiat de qualsevol lloc",
"Copied from original": "Copiat de l'original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 els següents Col·laboradors:",
"Danger!": "Danger!",
"Danger!": "Perill!",
"Delete": "Esborrar",
"Deleted": "Esborrat",
"Device ID": "ID del dispositiu",
@@ -41,7 +41,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositiu {{device}} ({{address}}) vol connectar-se. Afegir nou dispositiu?",
"Devices": "Dispositius",
"Disconnected": "Desconnectat",
"Discovery": "Discovery",
"Discovery": "Descobriment",
"Documentation": "Documentació",
"Download Rate": "Velocitat de descàrrega",
"Downloaded": "Descarregat",
@@ -51,23 +51,23 @@
"Edit Folder": "Editar carpeta",
"Editing": "Editant",
"Enable UPnP": "Activar UPnp",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introdueix adreces separades per coma (\"tcp://ip:port\", \"tcp://host:port\") o \"dynamic\" per a realitzar el descobriment automàtic de l'adreça.",
"Enter ignore patterns, one per line.": "Introduïr patrons a ignorar, un per línia.",
"Error": "Error",
"External File Versioning": "Versionat extern de fitxers",
"Failed Items": "Failed Items",
"Failed Items": "Objectes fallits",
"File Pull Order": "Ordre de fitxers del pull",
"File Versioning": "Versionat de fitxer",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Els bits de permís del fitxer són ignorats quant es busquen els canvis. Utilitzar en sistemes de fitxers FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Els arxius es menejen a la carpeta .stversions quant són substituïts o esborrats per Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Els fitxers són canviats a versions amb indicació de data en una carpeta \".stversions\" quant són reemplaçats o esborrats per Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Els fitxers són protegits dels canvis fets en altres dispositius, però els canvis fets en aquest dispositiu seràn enviats a la resta del grup (cluster).",
"Folder": "Folder",
"Folder": "Carpeta",
"Folder ID": "ID de carpeta",
"Folder Master": "Carpeta principal",
"Folder Path": "Ruta de la carpeta",
"Folders": "Carpetes",
"GUI": "GUI",
"GUI": "IGU (Interfície Gràfica d'Usuari)",
"GUI Authentication Password": "Password d'autenticació de l'Interfície Gràfica d'Usuari (GUI)",
"GUI Authentication User": "Autenticació de l'usuari de l'Interfície Gràfica d'Usuari (GUI)",
"GUI Listen Addresses": "Direcció d'escolta de l'Interfície Gràfica d'Usuari (GUI)",
@@ -76,12 +76,12 @@
"Global Discovery Server": "Servidor de descobriment global",
"Global State": "Estat global",
"Help": "Ajuda",
"Home page": "Home page",
"Home page": "Pàgina inicial",
"Ignore": "Ignorar",
"Ignore Patterns": "Patrons a ignorar",
"Ignore Permissions": "Permisos a ignorar",
"Incoming Rate Limit (KiB/s)": "Límit de descàrrega (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "La configuración incorrecta pot danyar el contingut de la teua carpeta i deixar Syncthing inoperatiu.",
"Introducer": "Presentador",
"Inversion of the given condition (i.e. do not exclude)": "Inversió de la condició donada (per exemple no excloure)",
"Keep Versions": "Mantindre versions",
@@ -95,7 +95,7 @@
"Major Upgrade": "Actualització important",
"Maximum Age": "Edat màxima",
"Metadata Only": "Sols metadades",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Espai minim de disc lliure",
"Move to top of queue": "Moure al principi de la cua",
"Multi level wildcard (matches multiple directory levels)": "Comodí multinivell (coincideix amb múltiples nivells de directoris)",
"Never": "Mai",
@@ -108,37 +108,37 @@
"OK": "OK",
"Off": "Off",
"Oldest First": "El més vell primer",
"Options": "Options",
"Out of Sync": "Out of Sync",
"Options": "Opcions",
"Out of Sync": "Sense sincronització",
"Out of Sync Items": "Dispositius sense sincronitzar",
"Outgoing Rate Limit (KiB/s)": "Límit de pujada (KiB/s)",
"Override Changes": "Sobreescriure els canvis",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ruta a la carpeta local en l'ordinador. Es crearà si no existeix. El caràcter tilde (~) es pot utilitzar com a drecera",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ruta on les versions deurien estar emmagatzemades (deixar buit per a la carpeta .stversions en la carpeta).",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Pausa",
"Paused": "Pausat",
"Please consult the release notes before performing a major upgrade.": "Per favor, consultar les notes de la versió abans de fer una actualització important.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Please set a GUI Authentication User and Password in the Settings dialog.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Per favor, estableix un usuari i password per a l'Interfície Gràfica d'Usuari en el menú d'Adjustos.",
"Please wait": "Per favor, espere",
"Preview": "Vista prèvia",
"Preview Usage Report": "Informe d'ús de vista prèvia",
"Quick guide to supported patterns": "Guía ràpida de patrons suportats",
"RAM Utilization": "Utilització de la RAM",
"Random": "Aleatori",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Transmitit via",
"Relays": "Transmissions",
"Release Notes": "Notes de la versió",
"Remove": "Remove",
"Remove": "Eliminar",
"Rescan": "Tornar a buscar",
"Rescan All": "Tornar a buscar tot",
"Rescan Interval": "Interval de nova busca",
"Restart": "Reiniciar",
"Restart Needed": "Reinici necesari",
"Restarting": "Reiniciant",
"Resume": "Resume",
"Resume": "Continuar",
"Reused": "Reutilitzat",
"Save": "Gravar",
"Scan Time Remaining": "Scan Time Remaining",
"Scan Time Remaining": "Temps d'escaneig restant",
"Scanning": "Rastrejant",
"Select the devices to share this folder with.": "Selecciona els dispositius amb els que compartir aquesta carpeta.",
"Select the folders to share with this device.": "Selecciona les carpetes per a compartir amb aquest dispositiu.",
@@ -161,7 +161,7 @@
"Source Code": "Codi font",
"Staggered File Versioning": "Versionat de fitxers escalonat",
"Start Browser": "Iniciar navegador",
"Statistics": "Statistics",
"Statistics": "Estadístiques",
"Stopped": "Parat",
"Support": "Suport",
"Sync Protocol Listen Addresses": "Direccions d'escolta del protocol de sincronització",
@@ -172,7 +172,7 @@
"Syncthing is upgrading.": "Syncthing està actualitzant-se.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing pareix apagat o hi ha un problema amb la connexió a Internet. Tornant a intentar...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing pareix que té un problema processant la seua sol·licitud. Per favor, refresque la pàgina o reinicie Syncthing si el problema persistix.",
"The Syncthing admin interface is configured to allow remote access without a password.": "The Syncthing admin interface is configured to allow remote access without a password.",
"The Syncthing admin interface is configured to allow remote access without a password.": "L'interfície d'administració de Syncthing està configurat per a permetre l'accés remot sense una contrasenya.",
"The aggregated statistics are publicly available at {%url%}.": "Les estadístiques agregades estan disponibles públicament en {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuració ha sigut gravada però no activada. Syncthing deu reiniciar per tal d'activar la nova configuració.",
"The device ID cannot be blank.": "L'ID del dispositiu no pot estar buida.",
@@ -185,19 +185,19 @@
"The folder ID must be unique.": "L'ID de la carpeta deu ser única.",
"The folder path cannot be blank.": "La ruta de la carpeta no pot estar buida.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "S'utilitzen els següents intervals: per a la primera hora es guarda una versió cada 30 segons, per al primer dia es guarda una versió cada hora, per als primers 30 dies es guarda una versió diaria, fins l'edat màxima es guarda una versió cada setmana.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Els següents objectes no s'han pogut sincronitzar.",
"The maximum age must be a number and cannot be blank.": "L'edat màxima deu ser un nombre i no pot estar buida.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "El temps màxim per a guardar una versió (en dies, ficar 0 per a guardar les versions per a sempre).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "El porcentatge d'espai mínim lliure en el disc deu ser un nombre no negatiu entre 0 i 100 (ambdós inclosos).",
"The number of days must be a number and cannot be blank.": "El nombre de dies deu ser un nombre i no pot estar en blanc.",
"The number of days to keep files in the trash can. Zero means forever.": "El nombre de dies per a mantindre els arxius a la paperera. Cero vol dir \"per a sempre\".",
"The number of old versions to keep, per file.": "El nombre de versions antigues per a guardar, per cada fitxer.",
"The number of versions must be a number and cannot be blank.": "El nombre de versions deu ser un nombre i no pot estar buit.",
"The path cannot be blank.": "La ruta no pot estar buida.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "El llímit del ritme deu ser un nombre no negatiu (0: sense llímit)",
"The rescan interval must be a non-negative number of seconds.": "L'interval de reescaneig deu ser un nombre positiu de segons.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",
"They are retried automatically and will be synced when the error is resolved.": "Es reintenta automàticament i es sincronitzaràn quant el resolga l'error.",
"This can easily give hackers access to read and change any files on your computer.": "Açò pot donar accés fàcilment als hackers per a llegir i canviar qualsevol fitxer al teu ordinador.",
"This is a major version upgrade.": "Aquesta és una actualització important de la versió.",
"Trash Can File Versioning": "Versionat d'arxius de la paperera",
"Unknown": "Desconegut",
@@ -218,7 +218,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quant s'afig una nova carpeta, hi ha que tindre en compte que l'ID de la carpeta s'utilitza per a juntar les carpetes entre dispositius. Són sensibles a les majúscules i deuen coincidir exactament entre tots els dispositius.",
"Yes": "Sí",
"You must keep at least one version.": "Es deu mantindre al menys una versió.",
"days": "days",
"days": "dies",
"full documentation": "Documentació completa",
"items": "Elements",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartit la carpeta \"{{folder}}\"."

View File

@@ -0,0 +1,225 @@
{
"A negative number of days doesn't make sense.": "Et negativt antal dage giver ikke mening.",
"A new major version may not be compatible with previous versions.": "En ny versionsudgivelse er måske ikke kompatibel med tidligere versioner.",
"API Key": "API-nøgle",
"About": "Om",
"Actions": "Handlinger.",
"Add": "Tilføj",
"Add Device": "Tilføj enhed",
"Add Folder": "Tilføj mappe",
"Add new folder?": "Tilføj ny mappe",
"Address": "Adresse",
"Addresses": "Adresser",
"Advanced": "Avanceret",
"Advanced Configuration": "Avanceret konfiguration",
"All Data": "Alt data",
"Allow Anonymous Usage Reporting?": "Tillad anonym brugerstatistik?",
"Alphabetic": "Alfabetisk",
"An external command handles the versioning. It has to remove the file from the synced folder.": " ",
"Anonymous Usage Reporting": "Anonym brugerstatistik",
"Any devices configured on an introducer device will be added to this device as well.": "Alle enheder som er konfigueret som en introducerende enhed, vil også blive tilføjet til denne enhed.",
"Automatic upgrades": "Automatisk opdatering",
"Be careful!": "Vær forsigtig!",
"Bugs": "Fejl",
"CPU Utilization": "CPU-forbrug",
"Changelog": "Udgivelsesnoter",
"Clean out after": "Rens efter",
"Close": "Luk",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentering som bruges i starten af en linje",
"Compression": "Anvend komprimering",
"Connection Error": "Tilslutnings fejl",
"Copied from elsewhere": "Kopieret fra et andet sted",
"Copied from original": "Kopieret fra originalen",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 alle bidragsydere:",
"Danger!": "Fare!",
"Delete": "Slet",
"Deleted": "Slettet",
"Device ID": "Enheds-ID",
"Device Identification": "Enhedsidentifikation",
"Device Name": "Enhedsnavn",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enheden {{device}} ({{address}}) ønsker at forbinde. Tilføj denne enhed?",
"Devices": "Enheder",
"Disconnected": "Ikke tilsluttet",
"Discovery": "Opslag",
"Documentation": "Dokumentation",
"Download Rate": "Downloadhastighed",
"Downloaded": "Downloadet",
"Downloading": "Downloader",
"Edit": "Rediger",
"Edit Device": "Rediger enhed",
"Edit Folder": "Rediger mappe",
"Editing": "Redigerer",
"Enable UPnP": "Anvend UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Angiv kommaseparerede adresser (\"tcp://ip:port\", \"tcp://host:port\") eller \"dynamic\" for at benytte automatisk opdagelse af adressen.",
"Enter ignore patterns, one per line.": "Vælg ignorer maske, én per linje.",
"Error": "Fejl",
"External File Versioning": "Ekstern fil-versionskontrol",
"Failed Items": "Mislykkede filer",
"File Pull Order": "Filhentnings rækkefølge",
"File Versioning": "Filversionering",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filtilladelses bits ignoreres når der søges efter ændringer. Bruges på FAT filsystemer. ",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til .stversions mappen når de erstattes eller slettes af Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til data-stemplede versioner i en .stversions mappe når de erstattes eller slettes af Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer er beskyttet fra ændringer foretaget på andre enheder, men ændringerne på denne enhed vil blive sendt til alle andre tilknyttede enheder.",
"Folder": "Mappe",
"Folder ID": "Mappe-ID",
"Folder Master": "Mastermappe",
"Folder Path": "Mappesti",
"Folders": "Mapper",
"GUI": "GUI",
"GUI Authentication Password": "GUI-kodeord",
"GUI Authentication User": "GUI-brugernavn",
"GUI Listen Addresses": "GUI-lytteadresse",
"Generate": "Opret",
"Global Discovery": "Globalt opslag",
"Global Discovery Server": "Global opslagsserver",
"Global State": "Global tilstand",
"Help": "Hjælp",
"Home page": "Hjem",
"Ignore": "Ignorer",
"Ignore Patterns": "Ignoreringsmaske",
"Ignore Permissions": "Ignorér filrettigheder",
"Incoming Rate Limit (KiB/s)": "Indgående hastighedsbegrænsning (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Ukorrekt opsætning kan skade dine data og gøre Syncthing ude af stand til at fungere.",
"Introducer": "Introducerende enhed",
"Inversion of the given condition (i.e. do not exclude)": "Det omvendte (dvs. undlad ikke)",
"Keep Versions": "Behold versioner",
"Largest First": "Største først",
"Last File Received": "Sidste modtaget fil",
"Last seen": "Sidst set",
"Later": "Senere",
"Local Discovery": "Lokal opslag",
"Local State": "Lokal tilstand",
"Local State (Total)": "Lokal tilstand (total)",
"Major Upgrade": "Ny version",
"Maximum Age": "Maks alder",
"Metadata Only": "Kun metadata",
"Minimum Free Disk Space": "Mindst ledig diskplads",
"Move to top of queue": "Flyt til toppen af køen",
"Multi level wildcard (matches multiple directory levels)": "Flerniveau wildcard (matcher flere biblioteksniveauer)",
"Never": "Aldrig",
"New Device": "Ny enhed",
"New Folder": "Ny mappe",
"Newest First": "Nyeste først",
"No": "Nej",
"No File Versioning": "Ingen filversion",
"Notice": "OBS",
"OK": "OK",
"Off": "Slå fra",
"Oldest First": "Ældste først",
"Options": "Indstillinger",
"Out of Sync": "Ikke synkroniseret",
"Out of Sync Items": "Endnu ikke synkroniserede filer",
"Outgoing Rate Limit (KiB/s)": "Udgående hastighedsbegrænsning (KiB/s)",
"Override Changes": "Overskriv ændringer",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sti til den lokale mappe. Vil blive oprettet hvis den ikke findes. Tilde tegnet (~) kan bruges som en forkortelse for",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sti hvor versioner skal gemmes (efterlad tom for default .stversions mappe)",
"Pause": "Pause",
"Paused": "Pauset",
"Please consult the release notes before performing a major upgrade.": "Tjek venligst udgivelsesnoterne før opgradering til en ny version.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Sæt vensligt en GUI bruger og kodeord i opsætningsdialogen.",
"Please wait": "Vent venligst",
"Preview": "Forhåndsvisning",
"Preview Usage Report": "Forhåndsvisning af forbrugsrapport",
"Quick guide to supported patterns": "Hurtig guide til supporteret mønstre",
"RAM Utilization": "RAM-forbrug",
"Random": "Tilfældig",
"Relayed via": "Passeret gennem",
"Relays": "Passager",
"Release Notes": "Udgivelsesnoter",
"Remove": "Fjern",
"Rescan": "Skan igen",
"Rescan All": "Skan alt igen",
"Rescan Interval": "Genskannings interval",
"Restart": "Genstart",
"Restart Needed": "Programmet kræver genstart",
"Restarting": "Genstarter",
"Resume": "Genoptag",
"Reused": "Genbrugt",
"Save": "Gem",
"Scan Time Remaining": "Tid tilbage af skanningen",
"Scanning": "Opdaterer",
"Select the devices to share this folder with.": "Vælg hvilke enheder du vil dele denne mappe med",
"Select the folders to share with this device.": "Vælg hvilke mapper du vil dele med denne enhed.",
"Settings": "Indstillinger",
"Share": "Del",
"Share Folder": "Delt mappe",
"Share Folders With Device": "Del mappe med enhed",
"Share With Devices": "Del med enhed",
"Share this folder?": "Del denne mappe",
"Shared With": "Delt med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort navn for mappen. Skal være det samme på alle tilknyttede enheder.",
"Show ID": "Vis ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vist istedet for Enheds ID i klynge status. Vil blive vist på andre enheder som valgfrit standard navn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist istedet for Enheds ID i klynge status. Vil blive opdateret til det navn som enheden viser, hvis det ikke er udfyldt.",
"Shutdown": "Luk ned",
"Shutdown Complete": "Nedlukning fuldført",
"Simple File Versioning": "Simpel fil versioner",
"Single level wildcard (matches within a directory only)": "Enkeltnivau wildcard (matcher kun inden for en mapp)",
"Smallest First": "Mindste først",
"Source Code": "Kildekode",
"Staggered File Versioning": "Forskudte filversioner",
"Start Browser": "Start browser",
"Statistics": "Statistikker",
"Stopped": "Stoppet",
"Support": "Support",
"Sync Protocol Listen Addresses": "Lytteadresser for indgående forbindelser",
"Syncing": "Synkroniserer",
"Syncthing has been shut down.": "Syncthing er blevet lukket ned.",
"Syncthing includes the following software or portions thereof:": "Syncthing indeholder følgende software eller dele heraf:",
"Syncthing is restarting.": "Syncthing genstarter",
"Syncthing is upgrading.": "Syncthing opgraderer",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ser ud til at være stoppet eller oplever problemer med din internetforbindels. Prøver igen...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Det ser ud til at Syncthiing har problemer med at udføre opgaven. Prøv at genopfriske siden eller genstarte Synching hvis problemet vedbliver.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing administationsdelen er konfigureret til at blive fjernstyret uden kodeord.",
"The aggregated statistics are publicly available at {%url%}.": "Samlet statistik er offentligt tilgængelig på {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen er gemt, men ikke aktiveret. Syncthing skal genstarte for at aktivere den nye konfiguration.",
"The device ID cannot be blank.": "Enhedens ID må ikke være tom.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Enheds ID som som skal bruges her, kan du finde i \"Rediger > Vis ID\"-dialogen på den anden enhed. Mellemrum og bindestreg er valgfri (ignoreres).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterede forbrugsrapport sendes dagligt. Den benyttes til at spore anvendte platforme, mappestørrelser og versioner. Hvis det typen af opsamlet data ændres på et senere tidspunkt, vil du blive spurgt om tilladelse igen.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det indtastede node ID ser ikke gyldigt ud. Det skal være en 52 eller 56 tegn streng, bestående af tal og bogstaver, eventuelt med mellemrum og bindestreger.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den første kommandolinjeparameter er mappestien og den anden parameter er den relative sti til mappen.",
"The folder ID cannot be blank.": "Mappe-ID må ikke være tom",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Mappe-ID skal være en kort identificierende streng (64 karakterer eller mindre) bestående af bogstav-, tal-, punktum- (.), bindestreg- (-) og understregskarakteren (_).",
"The folder ID must be unique.": "Mappe-ID skal være unik",
"The folder path cannot be blank.": "Mappestien må ikke være tom",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De følgende intervaller er brugt: i den første time, er en version gemt hvert 30. sekund, for første da er en version gemt hver time, for de første 30 dage er en version gemt hver dag og indtil den maksimale periode er en version hver uge gemt.",
"The following items could not be synchronized.": "Følgende filer kunne ikke synkroniseres.",
"The maximum age must be a number and cannot be blank.": "Maks alder skal være et nummer og må ikke være tom",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maks tid gamle udgaver skal gemmes (i dage, sæt lig med 0 for at beholde gamle versioner for altid)",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Procentsatsen på den mindste ledige diskplads må ikke være negative og skal være mellem 0 og 100 (inklusiv).",
"The number of days must be a number and cannot be blank.": "Antallet af dage skal være et nummer og feltet må ikke være tomt",
"The number of days to keep files in the trash can. Zero means forever.": "Antal dage filer gemmes i skraldespanden. Nul betyder for evigt.",
"The number of old versions to keep, per file.": "Antallet af gamle versioner som gemmes, per fil.",
"The number of versions must be a number and cannot be blank.": "Antallet af versioner skal være et tal, og kan ikke være blankt.",
"The path cannot be blank.": "Stien må ikke være tom",
"The rate limit must be a non-negative number (0: no limit)": "Ratebegrænsningen må ikke være negative tal (0: ingen begrænsning)",
"The rescan interval must be a non-negative number of seconds.": "Genskanningsintervallet skal være et ikke-negativt antal sekunder",
"They are retried automatically and will be synced when the error is resolved.": "De prøves igen automatisk og vil blive synkroniseret når fejlen er løst.",
"This can easily give hackers access to read and change any files on your computer.": "Dette gør det nemt for hackere at få adgang til at læse og ændre filer på din computer.",
"This is a major version upgrade.": "Dette er en ny version",
"Trash Can File Versioning": "Skraldespand fil versioner",
"Unknown": "Ukendt",
"Unshared": "Ikke delt",
"Unused": "Ubrugt",
"Up to Date": "Fuldt opdateret",
"Updated": "Opdateret",
"Upgrade": "Upgradér",
"Upgrade To {%version%}": "Opgradér til {{version}}",
"Upgrading": "Opgradere",
"Upload Rate": "Uploadhastighed",
"Uptime": "Oppetid",
"Use HTTPS for GUI": "Anvend HTTPS til GUI adgang",
"Version": "Version",
"Versions Path": "Versions-sti",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner slettes automatisk, hvis de er ældre end den satte maksimum alder eller overstiger det tilladte antal filer i et interval.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Når der tilføjes en ny enhed, vær da opmærksom på, at denne enhed også skal tilføjes på den anden side.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Når der tilføjes en ny enhed, vær da opmærksom på at samme ID bruges til at forbinde mapperne på de forskellige enheder. Der er forskel på store og små bogstaver, og ID skal være fuldstændig identisk på alle enheder.",
"Yes": "Ja",
"You must keep at least one version.": "Du skal beholde mindst én version.",
"days": "dage",
"full documentation": "Fuld dokumentation",
"items": "poster",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker at dele mappen \"{{folder}}\". "
}

View File

@@ -68,9 +68,9 @@
"Folder Path": "Map-paad",
"Folders": "Mappen",
"GUI": "GUI",
"GUI Authentication Password": "GUI-wachtwurd foar ferifikaasje",
"GUI Authentication User": "GUI-brûker foar ferifikaasje",
"GUI Listen Addresses": "GUI-harkadres",
"GUI Authentication Password": "Wachtwurd foar ferifikaasje yn GUI",
"GUI Authentication User": "Brûkers-ID foar ferifikaasje yn GUI",
"GUI Listen Addresses": "Harkadres foar GUI",
"Generate": "Generearje",
"Global Discovery": "Wrâldwide ûntdekking",
"Global Discovery Server": "Wrâldwide ûntdekkingstsjinner",
@@ -152,7 +152,7 @@
"Short identifier for the folder. Must be the same on all cluster devices.": "Koart opskrift foar de map. Moat op alle apparaten itselde wêze.",
"Show ID": "ID sjen litte",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wurd ynstee fan apparaat-ID sjen litten by de bondeltastân. Wurd nei oare apparaten advertearre as in mooglike standertnamme.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wurd ynstee fan apparaat-ID sjen litten by de bondeltastân. As it leech litten wurd, wurd it fernijt nei de namme die it apparaat advertearret.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wurd yn de bondel-tastân sjen litten ynstee fan apparaat-ID. Wannear't leech litten wurd, wurd it fernijt nei de namme die it apparaat útstjoert.",
"Shutdown": "Ofslute",
"Shutdown Complete": "Ofsluten klear",
"Simple File Versioning": "Ienfâldich triemferzjebehear",
@@ -160,7 +160,7 @@
"Smallest First": "Lytste earst",
"Source Code": "Boarnekoade",
"Staggered File Versioning": "Sprieden triemferzjebehear",
"Start Browser": "Browser starte",
"Start Browser": "Browser iepenje wannear't Syncthing start",
"Statistics": "Statistiken",
"Stopped": "Stoppe",
"Support": "Understeuning",

View File

@@ -148,12 +148,12 @@
"Share Folders With Device": "Del Mapper Med Enhet",
"Share With Devices": "Del Med Enheter",
"Share this folder?": "Dele denne mappen?",
"Shared With": "Delt Med",
"Shared With": "Delt med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort kjennemerke på mappen. Må være det samme på alle enheter i en gruppe.",
"Show ID": "Vis ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vis i stedet for Eining ID i gruppestatus. Vil bli kringkastet til andre enheter som et valgfritt standardnavn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist i stedet for Mappe-ID i gruppestatus. Vil bli oppdatert til navnet enheten kringkaster dersom tomt.",
"Shutdown": "Slå Av",
"Shutdown": "Avslutt",
"Shutdown Complete": "Avslutning fullført",
"Simple File Versioning": "Enkel Versjonskontroll",
"Single level wildcard (matches within a directory only)": "Enkeltnivåsøk (søker kun i en mappe)",
@@ -220,6 +220,6 @@
"You must keep at least one version.": "Du må beholde minst én versjon",
"days": "dager",
"full documentation": "all dokumentasjon",
"items": "element",
"items": "elementer",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\"."
}

View File

@@ -138,7 +138,7 @@
"Resume": "Возобновить",
"Reused": "Повторно использовано",
"Save": "Сохранить",
"Scan Time Remaining": "Scan Time Remaining",
"Scan Time Remaining": "Оставшееся время сканирования",
"Scanning": "Сканирование",
"Select the devices to share this folder with.": "Выберите устройства, для которых будет доступна эта папка.",
"Select the folders to share with this device.": "Выберите папку для предоставления доступа данному устройству",

View File

@@ -1,5 +1,5 @@
{
"A negative number of days doesn't make sense.": "天数不能为负",
"A negative number of days doesn't make sense.": "天数不能为负",
"A new major version may not be compatible with previous versions.": "重大更新可能与之前的版本之间无法兼容",
"API Key": "API Key",
"About": "关于",
@@ -20,7 +20,7 @@
"Any devices configured on an introducer device will be added to this device as well.": "在介绍人设备上被添加的其它设备,也将会被添加到本机。",
"Automatic upgrades": "自动升级",
"Be careful!": "小心!",
"Bugs": "Bug汇报",
"Bugs": "问题回报",
"CPU Utilization": "CPU使用率",
"Changelog": "更新日志",
"Clean out after": "在该时间后清除:",
@@ -68,12 +68,12 @@
"Folder Path": "文件夹路径",
"Folders": "文件夹",
"GUI": "图形界面",
"GUI Authentication Password": "登陆web管理页面的密码",
"GUI Authentication User": "登陆web管理页面的用户名",
"GUI Listen Addresses": "web管理面监听地址",
"GUI Authentication Password": "图形管理界面密码",
"GUI Authentication User": "图形管理界面用户名",
"GUI Listen Addresses": "图形管理面监听地址",
"Generate": "生成",
"Global Discovery": "在互联网上寻找设备\n",
"Global Discovery Server": "用以在互联网上寻找设备的Announce服务器地址",
"Global Discovery Server": "全球发现服务器",
"Global State": "全局状态",
"Help": "帮助",
"Home page": "主页",
@@ -138,7 +138,7 @@
"Resume": "恢复",
"Reused": "复用",
"Save": "保存",
"Scan Time Remaining": "Scan Time Remaining",
"Scan Time Remaining": "扫描剩余时间",
"Scanning": "扫描中",
"Select the devices to share this folder with.": "选择将本文件夹共享给哪些设备",
"Select the folders to share with this device.": "选择与该设备共享的文件夹。",
@@ -172,7 +172,7 @@
"Syncthing is upgrading.": "Syncthing 正在升级",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing 似乎关闭了,或者您的网络连接存在故障。重试中...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing 在处理您的请求时似乎遇到了问题。如果问题持续,请刷新页面,或重启 Syncthing。",
"The Syncthing admin interface is configured to allow remote access without a password.": "The Syncthing admin interface is configured to allow remote access without a password.",
"The Syncthing admin interface is configured to allow remote access without a password.": "当前配置允许在不使用密码的情况下远程访问 Syncthing 管理界面",
"The aggregated statistics are publicly available at {%url%}.": "全局统计公布于 {{url}}",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "设置已经保存但是还未生效。Syncthing 需要重启以启用新的设置。",
"The device ID cannot be blank.": "设备标识不能为空",
@@ -184,7 +184,7 @@
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "文件夹标识不得长于 64 字符,且仅能包含字母、数字、半角句号(.)、横线(-和下划线_。",
"The folder ID must be unique.": "文件夹标识不得重复",
"The folder path cannot be blank.": "文件夹路径不能为空",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "保留的历史版本会满足以下条件:最近一小时内的历史版本,更新间隔小于30秒的仅保留一份。最近一天内的历史版本,更新间隔小于1小时的仅保留一份。最近一个月内的历史版本,更新间隔小于1天的仅保留一份。距离现在超过一个月且小于最长保留时间的,更新间隔小于1周的仅保留一份。",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "保留的历史版本会遵循以下条件:最近一小时内的历史版本,更新间隔小于三十秒的仅保留一份。最近一天内的历史版本,更新间隔小于小时的仅保留一份。最近一个月内的历史版本,更新间隔小于天的仅保留一份。距离现在超过一个月且小于最长保留时间的,更新间隔小于周的仅保留一份。",
"The following items could not be synchronized.": "下列项目无法被同步。",
"The maximum age must be a number and cannot be blank.": "最长保留时间必须为数字,且不能为空。",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "历史版本保留的最长天数0为永久保存",
@@ -197,8 +197,8 @@
"The rate limit must be a non-negative number (0: no limit)": "传输速度限制为非负整数0 表示不限制)",
"The rescan interval must be a non-negative number of seconds.": "扫描间隔单位为秒,且不能为负数。",
"They are retried automatically and will be synced when the error is resolved.": "系统将会自动重试,当错误被解决时,它们将会被同步。",
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",
"This is a major version upgrade.": "这是一个重大版本更新",
"This can easily give hackers access to read and change any files on your computer.": "这会让骇客能够轻而易举地访问及修改您的文件",
"This is a major version upgrade.": "这是一个重大版本更新",
"Trash Can File Versioning": "回收站式版本控制",
"Unknown": "未知",
"Unshared": "未共享",
@@ -210,14 +210,14 @@
"Upgrading": "升级中",
"Upload Rate": "上传速度",
"Uptime": "已启动",
"Use HTTPS for GUI": "使用HTTPS连接web管理页面",
"Use HTTPS for GUI": "使用加密连接到图形管理页面",
"Version": "版本",
"Versions Path": "历史版本路径",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "超过最长保留时间,或者不满足下列条件的历史版本,将会被删除。",
"When adding a new device, keep in mind that this device must be added on the other side too.": "若您在本机添加新设备,记住您也必须在这个设备上添加本机。",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "若你添加了新文件夹,记住文件夹标识是用以在不同设备间建立联系的。在不同设备间拥有相同标识的文件夹将会被同步。且文件夹标识大小写敏感。",
"Yes": "是",
"You must keep at least one version.": "您必须保留至少一个版本",
"You must keep at least one version.": "您必须保留至少一个版本",
"days": "天",
"full documentation": "完整文档",
"items": "条目",

View File

@@ -1 +1 @@
var langPrettyprint = {"bg":"Bulgarian","ca":"Catalan","ca@valencia":"Catalan (Valencian)","cs":"Czech","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","es-ES":"Spanish (Spain)","fi":"Finnish","fr":"French","fr-CA":"French (Canada)","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ro-RO":"Romanian (Romania)","ru":"Russian","sv":"Swedish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
var langPrettyprint = {"bg":"Bulgarian","ca":"Catalan","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","es-ES":"Spanish (Spain)","fi":"Finnish","fr":"French","fr-CA":"French (Canada)","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ro-RO":"Romanian (Romania)","ru":"Russian","sv":"Swedish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}

View File

@@ -1 +1 @@
var validLangs = ["bg","ca","ca@valencia","cs","de","el","en","en-GB","es","es-ES","fi","fr","fr-CA","fy","hu","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ro-RO","ru","sv","uk","zh-CN","zh-TW"]
var validLangs = ["bg","ca","ca@valencia","cs","da","de","el","en","en-GB","es","es-ES","fi","fr","fr-CA","fy","hu","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ro-RO","ru","sv","uk","zh-CN","zh-TW"]

View File

@@ -121,21 +121,22 @@
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<identicon data-value="device"></identicon>&nbsp;<span translate>New Device</span>
<identicon data-value="device"></identicon>
<span translate>New Device</span>
<span class="pull-right">{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}:</small>
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
Device {%device%} ({%address%}) wants to connect. Add new device?
<span>
</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addNewDeviceID(device)">
<span class="fa fa-check"></span>&nbsp;<span translate>Add</span>
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(device)">
<span class="fa fa-plus"></span>&nbsp;<span translate>Add Device</span>
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreRejectedDevice(device)">
<span class="fa fa-times"></span>&nbsp;<span translate>Ignore</span>
@@ -158,11 +159,11 @@
<h3 class="panel-title"><span class="fa fa-folder"></span>
<span translate ng-if="!folders[event.data.folder]">New Folder</span>
<span translate ng-if="folders[event.data.folder]">Share Folder</span>
<span class="pull-right">{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}:</small>
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
{%device%} wants to share folder "{%folder%}".
</span>

View File

@@ -15,6 +15,7 @@
<li class="auto-generated">Aaron Bieber</li>
<li class="auto-generated">Adam Piggott</li>
<li class="auto-generated">Alexander Graf</li>
<li class="auto-generated">Anderson Mesquita</li>
<li class="auto-generated">Andrew Dunham</li>
<li class="auto-generated">Antony Male</li>
<li class="auto-generated">Arthur Axel fREW Schmidt</li>
@@ -66,6 +67,7 @@
<li class="auto-generated">Michael Jephcote</li>
<li class="auto-generated">Michael Ploujnikov</li>
<li class="auto-generated">Michael Tilli</li>
<li class="auto-generated">Nate Morrison</li>
<li class="auto-generated">Pascal Jungblut</li>
<li class="auto-generated">Peter Hoeg</li>
<li class="auto-generated">Philippe Schommers</li>
@@ -84,6 +86,7 @@
<li class="auto-generated">Veeti Paananen</li>
<li class="auto-generated">Victor Buinsky</li>
<li class="auto-generated">Vil Brekin</li>
<li class="auto-generated">William A. Kennington III</li>
<li class="auto-generated">Yannic A.</li>
</ul>
</div>

View File

@@ -970,13 +970,14 @@ angular.module('syncthing.core')
$('#idqr').modal('show');
};
$scope.addDevice = function () {
$http.get(urlbase + '/system/discovery')
$scope.addDevice = function (deviceID) {
return $http.get(urlbase + '/system/discovery')
.success(function (registry) {
$scope.discovery = registry;
})
.then(function () {
$scope.currentDevice = {
deviceID: deviceID,
_addressesStr: 'dynamic',
compression: 'metadata',
introducer: false,
@@ -1016,18 +1017,7 @@ angular.module('syncthing.core')
$scope.saveDevice = function () {
$('#editDevice').modal('hide');
$scope.saveDeviceConfig($scope.currentDevice);
};
$scope.addNewDeviceID = function (device) {
var deviceCfg = {
deviceID: device,
_addressesStr: 'dynamic',
compression: 'metadata',
introducer: false,
selectedFolders: {}
};
$scope.saveDeviceConfig(deviceCfg);
$scope.dismissDeviceRejection(device);
$scope.dismissDeviceRejection($scope.currentDevice.deviceID);
};
$scope.saveDeviceConfig = function (deviceCfg) {

View File

@@ -48,13 +48,6 @@
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label ng-if="upgradeInfo">
<input id="AutoUpgradeEnabled" type="checkbox" ng-model="tmpOptions.autoUpgradeEnabled"> <span translate>Automatic upgrades</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
@@ -62,7 +55,15 @@
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label ng-if="upgradeInfo">
<input id="AutoUpgradeEnabled" type="checkbox" ng-model="tmpOptions.autoUpgradeEnabled"> <span translate>Automatic upgrades</span>
</label>
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="form-group">
<label translate for="GlobalAnnServersStr">Global Discovery Server</label>
<input ng-disabled="!tmpOptions.globalAnnounceEnabled" id="GlobalAnnServersStr" class="form-control" type="text" ng-model="tmpOptions._globalAnnounceServersStr">

View File

File diff suppressed because one or more lines are too long

View File

@@ -48,17 +48,6 @@ var (
// DefaultDiscoveryServers should be substituted when the configuration
// contains <globalAnnounceServer>default</globalAnnounceServer>.
DefaultDiscoveryServers = append(DefaultDiscoveryServersV4, DefaultDiscoveryServersV6...)
// DefaultDiscoveryServersIP is used by the usage reporting.
// XXX: Detect Android, and use this is we still don't have working DNS?
DefaultDiscoveryServersIP = []string{
"https://194.126.249.5/?id=SR7AARM-TCBUZ5O-VFAXY4D-CECGSDE-3Q6IZ4G-XG7AH75-OBIXJQV-QJ6NLQA",
"https://45.55.230.38/?id=AQEHEO2-XOS7QRA-X2COH5K-PO6OPVA-EWOSEGO-KZFMD32-XJ4ZV46-CUUVKAS",
"https://128.199.95.124/?id=VK6HNJ3-VVMM66S-HRVWSCR-IXEHL2H-U4AQ4MW-UCPQBWX-J2L2UBK-NVZRDQZ",
"https://[2001:470:28:4d6::5]/?id=SR7AARM-TCBUZ5O-VFAXY4D-CECGSDE-3Q6IZ4G-XG7AH75-OBIXJQV-QJ6NLQA",
"https://[2604:a880:800:10::182:a001]/?id=AQEHEO2-XOS7QRA-X2COH5K-PO6OPVA-EWOSEGO-KZFMD32-XJ4ZV46-CUUVKAS",
"https://[2400:6180:0:d0::d9:d001]/?id=VK6HNJ3-VVMM66S-HRVWSCR-IXEHL2H-U4AQ4MW-UCPQBWX-J2L2UBK-NVZRDQZ",
}
)
func New(myID protocol.DeviceID) Configuration {

View File

@@ -44,7 +44,7 @@ func (c GUIConfiguration) Address() string {
}
func (c GUIConfiguration) UseTLS() bool {
if override := os.Getenv("STGUIADDRESS"); override != "" {
if override := os.Getenv("STGUIADDRESS"); override != "" && strings.HasPrefix(override, "http") {
return strings.HasPrefix(override, "https:")
}
return c.RawUseTLS

View File

@@ -260,12 +260,15 @@ next:
func (s *connectionSvc) connect() {
delay := time.Second
for {
l.Debugln("Reconnect loop")
nextDevice:
for deviceID, deviceCfg := range s.cfg.Devices() {
if deviceID == s.myID {
continue
}
l.Debugln("Reconnect loop for", deviceID)
if s.model.IsPaused(deviceID) {
continue
}
@@ -277,6 +280,7 @@ func (s *connectionSvc) connect() {
relaysEnabled := s.relaysEnabled
s.mut.RUnlock()
if connected && ok && ct.IsDirect() {
l.Debugln("Already connected to", deviceID, "via", ct.String())
continue
}
@@ -284,6 +288,7 @@ func (s *connectionSvc) connect() {
for _, addr := range addrs {
if conn := s.connectDirect(deviceID, addr); conn != nil {
l.Debugln("Connecting to", deviceID, "via", addr, "succeeded")
if connected {
s.model.Close(deviceID, fmt.Errorf("switching connections"))
}
@@ -292,6 +297,7 @@ func (s *connectionSvc) connect() {
}
continue nextDevice
}
l.Debugln("Connecting to", deviceID, "via", addr, "failed")
}
// Only connect via relays if not already connected
@@ -300,6 +306,7 @@ func (s *connectionSvc) connect() {
// wait up to RelayReconnectIntervalM to connect again.
// Also, do not try relays if we are explicitly told not to.
if connected || len(relays) == 0 || !relaysEnabled {
l.Debugln("Not connecting via relay", connected, len(relays) == 0, !relaysEnabled)
continue nextDevice
}
@@ -307,19 +314,21 @@ func (s *connectionSvc) connect() {
if last, ok := s.lastRelayCheck[deviceID]; ok && time.Since(last) < reconIntv {
l.Debugln("Skipping connecting via relay to", deviceID, "last checked at", last)
continue nextDevice
} else {
l.Debugln("Trying relay connections to", deviceID, relays)
}
l.Debugln("Trying relay connections to", deviceID, relays)
s.lastRelayCheck[deviceID] = time.Now()
for _, addr := range relays {
if conn := s.connectViaRelay(deviceID, addr); conn != nil {
l.Debugln("Connecting to", deviceID, "via", addr, "succeeded")
s.conns <- model.IntermediateConnection{
conn, model.ConnectionTypeRelayDial,
}
continue nextDevice
}
l.Debugln("Connecting to", deviceID, "via", addr, "failed")
}
}

View File

@@ -1128,10 +1128,16 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
m.fmut.RUnlock()
files.Update(protocol.LocalDeviceID, fs)
filenames := make([]string, len(fs))
for i, file := range fs {
filenames[i] = file.Name
}
events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
"folder": folder,
"items": len(fs),
"version": files.LocalVersion(protocol.LocalDeviceID),
"folder": folder,
"items": len(fs),
"filenames": filenames,
"version": files.LocalVersion(protocol.LocalDeviceID),
})
}
@@ -1753,8 +1759,11 @@ func (m *Model) CheckFolderHealth(id string) error {
// Check for free space, if it isn't a master folder. We aren't
// going to change the contents of master folders, so we don't
// care about the amount of free space there.
if free, errDfp := osutil.DiskFreePercentage(folder.Path()); errDfp == nil && free < folder.MinDiskFreePct {
err = errors.New("insufficient free space")
diskFreeP, errDfp := osutil.DiskFreePercentage(folder.Path())
if errDfp == nil && diskFreeP < folder.MinDiskFreePct {
diskFreeBytes, _ := osutil.DiskFreeBytes(folder.Path())
str := fmt.Sprintf("insufficient free space (%d MiB, %.2f%%)", diskFreeBytes/1024/1024, diskFreeP)
err = errors.New(str)
}
}
} else {

View File

@@ -79,19 +79,20 @@ type rwFolder struct {
progressEmitter *ProgressEmitter
virtualMtimeRepo *db.VirtualMtimeRepo
folder string
dir string
scanIntv time.Duration
versioner versioner.Versioner
ignorePerms bool
copiers int
pullers int
shortID uint64
order config.PullOrder
maxConflicts int
sleep time.Duration
pause time.Duration
allowSparse bool
folder string
dir string
scanIntv time.Duration
versioner versioner.Versioner
ignorePerms bool
copiers int
pullers int
shortID uint64
order config.PullOrder
maxConflicts int
sleep time.Duration
pause time.Duration
allowSparse bool
checkFreeSpace bool
stop chan struct{}
queue *jobQueue
@@ -117,16 +118,17 @@ func newRWFolder(m *Model, shortID uint64, cfg config.FolderConfiguration) *rwFo
progressEmitter: m.progressEmitter,
virtualMtimeRepo: db.NewVirtualMtimeRepo(m.db, cfg.ID),
folder: cfg.ID,
dir: cfg.Path(),
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
ignorePerms: cfg.IgnorePerms,
copiers: cfg.Copiers,
pullers: cfg.Pullers,
shortID: shortID,
order: cfg.Order,
maxConflicts: cfg.MaxConflicts,
allowSparse: !cfg.DisableSparseFiles,
folder: cfg.ID,
dir: cfg.Path(),
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
ignorePerms: cfg.IgnorePerms,
copiers: cfg.Copiers,
pullers: cfg.Pullers,
shortID: shortID,
order: cfg.Order,
maxConflicts: cfg.MaxConflicts,
allowSparse: !cfg.DisableSparseFiles,
checkFreeSpace: cfg.MinDiskFreePct != 0,
stop: make(chan struct{}),
queue: newJobQueue(),
@@ -967,10 +969,12 @@ func (p *rwFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocks
}
}
if free, err := osutil.DiskFreeBytes(p.dir); err == nil && free < file.Size() {
l.Warnf(`Folder "%s": insufficient disk space in %s for %s: have %.2f MiB, need %.2f MiB`, p.folder, p.dir, file.Name, float64(free)/1024/1024, float64(file.Size())/1024/1024)
p.newError(file.Name, errors.New("insufficient space"))
return
if p.checkFreeSpace {
if free, err := osutil.DiskFreeBytes(p.dir); err == nil && free < file.Size() {
l.Warnf(`Folder "%s": insufficient disk space in %s for %s: have %.2f MiB, need %.2f MiB`, p.folder, p.dir, file.Name, float64(free)/1024/1024, float64(file.Size())/1024/1024)
p.newError(file.Name, errors.New("insufficient space"))
return
}
}
events.Default.Log(events.ItemStarted, map[string]string{

View File

@@ -88,7 +88,6 @@ func (c *staticClient) Serve() {
l.Debugln(c, "joined", c.conn.RemoteAddr(), "via", c.conn.LocalAddr())
defer c.cleanup()
c.mut.Lock()
c.connected = true
c.mut.Unlock()
@@ -110,10 +109,10 @@ func (c *staticClient) Serve() {
case protocol.Ping:
if err := protocol.WriteMessage(c.conn, protocol.Pong{}); err != nil {
l.Infoln("Relay write:", err)
return
c.disconnect()
} else {
l.Debugln(c, "sent pong")
}
l.Debugln(c, "sent pong")
case protocol.SessionInvitation:
ip := net.IP(msg.Address)
@@ -124,24 +123,38 @@ func (c *staticClient) Serve() {
case protocol.RelayFull:
l.Infoln("Disconnected from relay due to it becoming full.")
return
c.disconnect()
default:
l.Infoln("Relay: protocol error: unexpected message %v", msg)
return
c.disconnect()
}
case <-c.stop:
l.Debugln(c, "stopping")
return
c.disconnect()
// We always exit via this branch of the select, to make sure the
// the reader routine exits.
case err := <-errors:
l.Infoln("Relay received:", err)
close(errors)
close(messages)
c.mut.Lock()
if c.connected {
c.conn.Close()
c.connected = false
l.Infoln("Relay received:", err)
}
if c.closeInvitationsOnFinish {
close(c.invitations)
c.invitations = make(chan protocol.SessionInvitation)
}
c.mut.Unlock()
return
case <-timeout.C:
l.Debugln(c, "timed out")
return
c.disconnect()
}
}
}
@@ -200,9 +213,6 @@ func (c *staticClient) connect() error {
c.mut.Unlock()
conn := tls.Client(tcpConn, c.config)
if err = conn.Handshake(); err != nil {
return err
}
if err := conn.SetDeadline(time.Now().Add(c.connectTimeout)); err != nil {
conn.Close()
@@ -218,13 +228,9 @@ func (c *staticClient) connect() error {
return nil
}
func (c *staticClient) cleanup() {
l.Debugln(c, "cleaning up")
func (c *staticClient) disconnect() {
l.Debugln(c, "disconnecting")
c.mut.Lock()
if c.closeInvitationsOnFinish {
close(c.invitations)
c.invitations = make(chan protocol.SessionInvitation)
}
c.connected = false
c.mut.Unlock()

View File

@@ -531,9 +531,8 @@ func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, ex
if err != nil && timeout > 0 {
// Try to repair error code 725 - OnlyPermanentLeasesSupported
envelope := &soapErrorResponse{}
err = xml.Unmarshal(response, envelope)
if err != nil {
return err
if unmarshalErr := xml.Unmarshal(response, envelope); unmarshalErr != nil {
return unmarshalErr
}
if envelope.ErrorCode == 725 {
return s.AddPortMapping(localIPAddress, protocol, externalPort, internalPort, description, 0)

View File

@@ -85,16 +85,18 @@ func (v Simple) Archive(filePath string) error {
}
// Glob according to the new file~timestamp.ext pattern.
newVersions, err := osutil.Glob(filepath.Join(dir, taggedFilename(file, TimeGlob)))
pattern := filepath.Join(dir, taggedFilename(file, TimeGlob))
newVersions, err := osutil.Glob(pattern)
if err != nil {
l.Warnln("globbing:", err)
l.Warnln("globbing:", err, "for", pattern)
return nil
}
// Also according to the old file.ext~timestamp pattern.
oldVersions, err := osutil.Glob(filepath.Join(dir, file+"~"+TimeGlob))
pattern = filepath.Join(dir, file+"~"+TimeGlob)
oldVersions, err := osutil.Glob(pattern)
if err != nil {
l.Warnln("globbing:", err)
l.Warnln("globbing:", err, "for", pattern)
return nil
}

View File

@@ -258,16 +258,18 @@ func (v Staggered) Archive(filePath string) error {
}
// Glob according to the new file~timestamp.ext pattern.
newVersions, err := osutil.Glob(filepath.Join(dir, taggedFilename(file, TimeGlob)))
pattern := filepath.Join(dir, taggedFilename(file, TimeGlob))
newVersions, err := osutil.Glob(pattern)
if err != nil {
l.Warnln("globbing:", err)
l.Warnln("globbing:", err, "for", pattern)
return nil
}
// Also according to the old file.ext~timestamp pattern.
oldVersions, err := osutil.Glob(filepath.Join(dir, file+"~"+TimeGlob))
pattern = filepath.Join(dir, file+"~"+TimeGlob)
oldVersions, err := osutil.Glob(pattern)
if err != nil {
l.Warnln("globbing:", err)
l.Warnln("globbing:", err, "for", pattern)
return nil
}

View File

@@ -15,6 +15,7 @@ pages=(
syncthing-bep.7
syncthing-localdisco.7
syncthing-globaldisco.7
syncthing-relay.7
)
for page in "${pages[@]}" ; do

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-BEP" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-BEP" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.
@@ -1003,8 +1003,8 @@ directions.
.UNINDENT
.SS Read Only
.sp
In read only mode, a device does not synchronize the local folder to the
cluster, but publishes changes to its local folder contents as usual.
In read only mode, a device does not apply any updates from the cluster,
but publishes changes of its local folder to the cluster as usual.
The local folder can be seen as a "master copy" that is never affected
by the actions of other cluster devices.
.INDENT 0.0

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-CONFIG" "5" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-DEVICE-IDS" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-EVENT-API" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-FAQ" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-GLOBALDISCO" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-LOCALDISCO" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v3
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-NETWORKING" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.

699
man/syncthing-relay.7 Normal file
View File

@@ -0,0 +1,699 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-RELAY" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.
.nr rst2man-indent-level 0
.
.de1 rstReportMargin
\\$1 \\n[an-margin]
level \\n[rst2man-indent-level]
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
-
\\n[rst2man-indent0]
\\n[rst2man-indent1]
\\n[rst2man-indent2]
..
.de1 INDENT
.\" .rstReportMargin pre:
. RS \\$1
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
. nr rst2man-indent-level +1
.\" .rstReportMargin post:
..
.de UNINDENT
. RE
.\" indent \\n[an-margin]
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
.nr rst2man-indent-level -1
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.SH WHAT IS A RELAY?
.sp
Relay is a service which relays data between two \fIdevices\fP which are not able to
connect to each other directly otherwise. This is usually due to both devices
being behind a NAT and neither side being able to open a port which would
be directly accessible from the internet.
.sp
A relay was designed to relay BEP protocol, hence the reliance on device ID\(aqs
in the protocol spec, but at the same time it is general enough that could be
reused by other protocols or applications, as the data transferred between two
devices which use a relay is completely obscure and does not affect the
relaying.
.SH OPERATION MODES
.sp
Relay listens on a single TCP socket, but has two different connection modes,
where a connection mode is a predefined set of messages which the relay and
the device are expecting to exchange.
.sp
The first mode is the \fIprotocol\fP mode which allows a client to interact
with the relay, for example join the relay, or request to connect to a device,
given it is available on the relay. Similarly to BEP, protocol mode requires
the device to connect via TLS using a strong suite of ciphers (same as BEP),
which allows the relay to verify and derive the identity (Device ID) of the
device.
.sp
The second mode is the \fIsession\fP mode which after a few initial messages
connects two devices directly to each other via the relay, and is a plain\-text
protocol, which for every byte written by one device, sends the same set of
bytes to the other device and vica versa.
.SH IDENTIFYING THE CONNECTION MODE
.sp
Because both connection modes operate over the same single socket, a method
of detecting the connection mode is required.
.sp
When a new client connects to the relay, the relay checks the first byte
that the client has sent, and if that matches 0x16, that implies to us that
the connection is a protocol mode connection, due to 0x16 being the first byte
in the TLS handshake, and only protocol mode connections use TLS.
.sp
If the first byte is not 0x16, then we assume that the connection is a session
mode connection.
.SH PROTOCOL MODE
.sp
Protocol mode uses TLS and protocol name defined by the TLS header should be
\fIbep\-relay\fP\&.
.sp
Protocol mode has two submodes:
1. Permanent protocol submode \- Joining the relay, and waiting for messages from
the relay asking to connect to some device which is interested in having a
session with you.
2. Temporary protocol submode \- Only used to request a session with a device
which is connected to the relay using the permanent protocol submode.
.SS Permanent protocol submode
.sp
A permanent protocol submode begins with the client sending a JoinRelayRequest
message, which the relay responds to with either a ResponseSuccess or
ResponseAlreadyConnected message if a client with the same device ID already
exists.
.sp
After the client has joined, no more messages are exchanged apart from
Ping/Pong messages for general connection keep alive checking.
.sp
From this point onwards, the client stand\-by\(aqs and waits for SessionInvitation
messages from the relay, which implies that some other device is trying to
connect with you. SessionInvitation message contains the unique session key
which then can be used to establish a connection in session mode.
.sp
If the client fails to send a JoinRelayRequest message within the first ping
interval, the connection is terminated.
If the client fails to send a message (even if its a ping message) every minute
(by default), the connection is terminated.
.SS Temporary protocol submode
.sp
A temporary protocol submode begins with ConnectRequest message, to which the
relay responds with either ResponseNotFound if the device the client it is after
is not available, or with a SessionInvitation, which contains the unique session
key which then can be used to establish a connection in session mode.
.sp
The connection is terminated immediately after that.
.SS Example Exchange
.sp
Client A \- Permanent protocol submode
Client B \- Temporary protocol submode
.TS
center;
|l|l|l|l|.
_
T{
#
T} T{
Client (A)
T} T{
Relay
T} T{
Client (B)
T}
_
T{
1
T} T{
JoinRelayRequest\->
T} T{
T} T{
T}
_
T{
2
T} T{
T} T{
<\-ResponseSuccess
T} T{
T}
_
T{
3
T} T{
Ping\->
T} T{
T} T{
T}
_
T{
4
T} T{
T} T{
<\-Pong
T} T{
T}
_
T{
5
T} T{
T} T{
T} T{
<\-ConnectRequest(A)
T}
_
T{
6
T} T{
T} T{
SessionInvitation(A)\->
T} T{
T}
_
T{
7
T} T{
T} T{
<\-SessionInvitation(B)
T} T{
T}
_
T{
8
T} T{
T} T{
T} T{
(Disconnects)
T}
_
T{
9
T} T{
Ping\->
T} T{
T} T{
T}
_
T{
10
T} T{
T} T{
<\-Pong
T} T{
T}
_
T{
11
T} T{
Ping\->
T} T{
T} T{
T}
_
T{
12
T} T{
T} T{
<\-Pong
T} T{
T}
_
.TE
.SH SESSION MODE
.sp
The first and only message the client sends in the session mode is the
SessionInvitation message which contains the session key identifying which
session you are trying to join. The relay responds with one of the following
Response messages:
.INDENT 0.0
.IP 1. 3
ResponseNotFound \- Session key is invalid
.IP 2. 3
ResponseAlreadyConnected \- Session is full (both sides already connected)
.IP 3. 3
ResponseSuccess \- You have successfully joined the session
.UNINDENT
.sp
After the successful response, all the bytes written and received will be
relayed between the two devices in the session directly.
.SS Example Exchange
.sp
Client A \- Permanent protocol mode
Client B \- Temporary protocol mode
.TS
center;
|l|l|l|l|.
_
T{
#
T} T{
Client (A)
T} T{
Relay
T} T{
Client (B)
T}
_
T{
1
T} T{
SessionInvitation(A)\->
T} T{
T} T{
T}
_
T{
2
T} T{
T} T{
<\-ResponseSuccess
T} T{
T}
_
T{
3
T} T{
Data\->
T} T{
(Buffers data)
T} T{
T}
_
T{
4
T} T{
Data\->
T} T{
(Buffers data)
T} T{
T}
_
T{
5
T} T{
T} T{
T} T{
<\-SessionInvitation(B)
T}
_
T{
6
T} T{
T} T{
ResponseSuccess\->
T} T{
T}
_
T{
7
T} T{
T} T{
Relays data \->
T} T{
T}
_
T{
8
T} T{
T} T{
Relays data \->
T} T{
T}
_
T{
9
T} T{
T} T{
<\-Relays data
T} T{
<\-Data
T}
_
.TE
.SH MESSAGES
.sp
All messages are preceeded by a header message. Header message contains the
magic value 0x9E79BC40, message type integer, and message length.
.sp
\fBWARNING:\fP
.INDENT 0.0
.INDENT 3.5
Some messages have no content, apart from the implied header which allows
us to identify what type of message it is.
.UNINDENT
.UNINDENT
.SS Header structure
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Magic |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Message Type |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Message Length |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
struct Header {
unsigned int Magic;
int MessageType;
int MessageLength;
}
.ft P
.fi
.UNINDENT
.UNINDENT
.SS Ping message (Type = 0)
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
struct Ping {
}
.ft P
.fi
.UNINDENT
.UNINDENT
.SS Pong message (Type = 1)
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
struct Pong {
}
.ft P
.fi
.UNINDENT
.UNINDENT
.SS JoinRelayRequest message (Type = 2)
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
struct JoinRelayRequest {
}
.ft P
.fi
.UNINDENT
.UNINDENT
.SS JoinSessionRequest message (Type = 3)
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of Key |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e Key (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
struct JoinSessionRequest {
opaque Key<32>;
}
.ft P
.fi
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B : Key
This is a unique random session key generated by the relay server. It is
used to identify which session you are trying to connect to.
.UNINDENT
.SS Response message (Type = 4)
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Code |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of Message |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e Message (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
struct Response {
int Code;
string Message<>;
}
.ft P
.fi
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B : Code
An integer representing the status code.
.TP
.B : Message
Message associated with the code.
.UNINDENT
.SS ConnectRequest message (Type = 5)
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of ID |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e ID (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
struct ConnectRequest {
opaque ID<32>;
}
.ft P
.fi
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B : ID
Device ID to which the client would like to connect.
.UNINDENT
.SS SessionInvitation message (Type = 6)
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of From |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e From (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of Key |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e Key (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of Address |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e Address (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| 0x0000 | Port |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Server Socket (V=0 or 1) |V|
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
struct SessionInvitation {
opaque From<32>;
opaque Key<32>;
opaque Address<32>;
unsigned int Port;
bool ServerSocket;
}
.ft P
.fi
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B : From
Device ID identifying who you will be connecting with.
.TP
.B : Key
A unique random session key generated by the relay server. It is used to
identify which session you are trying to connect to.
.TP
.B : Address
An optional IP address on which the relay server is expecting you to
connect, in order to start a connection in session mode.
Empty/all zero IP should be replaced with the relay\(aqs public IP address that
was used when establishing the protocol mode connection.
.TP
.B : Port
An optional port on which the relay server is expecting you to connect,
in order to start a connection in session mode.
.TP
.B : Server Socket
Because both sides connecting to the relay use the client side of the socket,
and some protocols behave differently depending if the connection starts on
the server side or the client side, this boolean indicates which side of the
connection this client should assume it\(aqs getting. The value is inverted in
the invitation which is sent to the other device, so that there is always
one client socket, and one server socket.
.UNINDENT
.SH HOW SYNCTHING USES RELAYS, AND GENERAL SECURITY
.sp
In the case of Syncthing and BEP, when two devices connect via relay, they
start their standard TLS connection encapsulated within the relay\(aqs plain\-text
session connection, effectively upgrading the plain\-text connection to a TLS
connection.
.sp
Even though the relay could be used for man\-in\-the\-middle attack, using TLS
at the application/BEP level ensures that all the traffic is safely encrypted,
and is completely meaningless to the relay. Furthermore, the secure suite of
ciphers used by BEP provides forward secrecy, meaning that even if the relay
did capture all the traffic, and even if the attacker did get their hands on the
device keys, they would still not be able to recover/decrypt any traffic which
was transported via the relay.
.sp
After establishing a relay session, syncthing looks at the SessionInvitation
message, and depending which side it has received, wraps the raw socket in
either a TLS client socket or a TLS server socket depending on the ServerSocket
boolean value in the SessionInvitation, and starts the TLS handshake.
.sp
From that point onwards it functions exactly the same way as if syncthing was
establishing a direct connection with the other device over the internet,
performing device ID validation, and full TLS encryption, and provides the same
security properties as it would provide when connecting over the internet.
.SH EXAMPLES OF STRONG CIPHER SUITES
.TS
center;
|l|l|l|.
_
T{
ID
T} T{
Name
T} T{
Description
T}
_
T{
0x009F
T} T{
DHE\-RSA\-AES256\-GCM\-SHA384
T} T{
TLSv1.2 DH RSA AESGCM(256) AEAD
T}
_
T{
0x006B
T} T{
DHE\-RSA\-AES256\-SHA256
T} T{
TLSv1.2 DH RSA AES(256) SHA256
T}
_
T{
0xC030
T} T{
ECDHE\-RSA\-AES256\-GCM\-SHA384
T} T{
TLSv1.2 ECDH RSA AESGCM(256) AEAD
T}
_
T{
0xC028
T} T{
ECDHE\-RSA\-AES256\-SHA384
T} T{
TLSv1.2 ECDH RSA AES(256) SHA384
T}
_
T{
0x009E
T} T{
DHE\-RSA\-AES128\-GCM\-SHA256
T} T{
TLSv1.2 DH RSA AESGCM(128) AEAD
T}
_
T{
0x0067
T} T{
DHE\-RSA\-AES128\-SHA256
T} T{
TLSv1.2 DH RSA AES(128) SHA256
T}
_
T{
0xC02F
T} T{
ECDHE\-RSA\-AES128\-GCM\-SHA256
T} T{
TLSv1.2 ECDH RSA AESGCM(128) AEAD
T}
_
T{
0xC027
T} T{
ECDHE\-RSA\-AES128\-SHA256
T} T{
TLSv1.2 ECDH RSA AES(128) SHA256
T}
_
.TE
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2015, The Syncthing Authors
.\" Generated by docutils manpage writer.
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-REST-API" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-SECURITY" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.
@@ -115,7 +115,7 @@ certificates are sent in clear text (as in HTTPS etc), meaning that the
certificate Common Name (by default \fBsyncthing\fP) is visible.
.sp
An eavesdropper can deduce that this is a Syncthing connection and calculate the
device ID:s involved based on the hashes of the sent certificates.
device IDs involved based on the hashes of the sent certificates.
.sp
Likewise, if the sync port (default 22000) is accessible from the internet, a
port scanner may discover it, attempt a TLS negotiation and thus obtain the
@@ -129,7 +129,7 @@ web GUI defaults to being reachable from the \fBlocal host only\fP\&.
.sp
Parties doing surveillance on your network (whether that be corporate IT, the
NSA or someone else) will be able to see that you use Syncthing, and your device
ID\(aqs \fI\%are OK to share anyway\fP <\fBhttp://docs.syncthing.net/users/faq.html#should-i-keep-my-device-ids-secret\fP>,
IDs \fI\%are OK to share anyway\fP <\fBhttp://docs.syncthing.net/users/faq.html#should-i-keep-my-device-ids-secret\fP>,
but the actual transmitted data is protected as well as we can. Knowing your
device ID can expose your IP address, using global discovery.
.SH PROTECTING YOUR SYNCTHING KEYS AND IDENTITY
@@ -141,8 +141,6 @@ synced files. Here are some general principles to protect your files:
.IP 1. 3
If a device of yours is lost, make sure to revoke its access from your other
devices.
.UNINDENT
.INDENT 0.0
.IP 2. 3
If you\(aqre syncing confidential data on an encrypted disk to guard against
device theft, put the Syncthing config folder on the same encrypted disk to

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-STIGNORE" "5" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "TODO" "7" "December 01, 2015" "v0.12" "Syncthing"
.TH "TODO" "7" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
Todo \- Keep automatic backups of deleted files by other nodes
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING" "1" "December 01, 2015" "v0.12" "Syncthing"
.TH "SYNCTHING" "1" "December 18, 2015" "v0.12" "Syncthing"
.SH NAME
syncthing \- Syncthing
.