Compare commits

..
Author SHA1 Message Date
Jakob Borg e4d08b336e fix(config): remove extraneous defaults setting while unmarshalling folder options (fixes #10746, fixes #10389) (#10763)
This broke PATCH on folders. Any place that needs the defaults should
set them prior to unmarshal.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-23 07:57:36 +02:00
Jakob Borg 97cb72a608 chore(versioner): attempt to prevent blatantly unsafe external versioner commands (fixes #10721) (#10722)
While preparing the command, attempt to verify that the template
expansion happens in a way that will result in a non-shell-injection
command. I don't presume to say that this is a 100% prevention, and the
script itself can always do dumb shit with the file path later.
Nonetheless, we should make a best-effort attempt.

Equally, this could generate false positives for commands that are
strangely written but in fact safe. I think this is acceptable; external
versioning is currently used by approximately 0.02% of users, and
presumably most of them have a setup that is sane.

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-23 07:48:53 +02:00
Simon Frei c9236b1adc chore: stop treating dirs as having size 128 (#10750)
Resp. directories for the database, and both dirs and symlinks for
`FileInfo.FileSize`. Instead handle it in the UI progress percentage. We
even already have special cases there for deletions, might as well
handle directories just like any other zero-sized needed item there.

This went through the very thorough testing of running it on my laptop,
the migration was applied and it seemed to be working fine after.

---------

Signed-off-by: Simon Frei <freisim93@gmail.com>
2026-06-22 20:32:03 +02:00
Jakob Borg 5313c75eba chore(model): increase default value for num hashers (#10761) 2026-06-22 12:53:03 +02:00
Syncthing Release Automation 40febaa811 chore(gui, man, authors): update docs, translations, and contributors 2026-06-22 05:29:45 +00:00
Jakob Borg 92ff9ac078 chore(osutil): fixup test for symlinked folder root (#10758)
This test was incorrectly rewritten to use fakefs, which doesn't really
test what it's supposed to test.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-21 19:48:15 +02:00
Henrik Bråthen f7f2e7c70b fix(folder): check if context canceled when scanning (fixes #10363) (#10757)
During initial folder scan and the scan is interrupted with a
context.Canceled error, it was previously logged as a "Failed initial
scan" error, which can be misleading

The change adds a explicit check for context.Canceled and silently
ignores it (fixes #10363)

Signed-off-by: Henrik Bråthen <henrikbs@proton.me>
2026-06-21 18:23:37 +02:00
Syncthing Release Automation 3d168d6e44 chore(gui, man, authors): update docs, translations, and contributors 2026-06-15 05:28:39 +00:00
Jakob Borg 8cc4c4ffb3 build: use Go 1.26 for Windows as well (#10744)
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-13 14:01:46 +02:00
39 changed files with 231 additions and 140 deletions
+1 -1
View File
@@ -192,7 +192,7 @@ jobs:
- uses: actions/setup-go@v6
with:
go-version: "~1.25.7" # temporarily stay on Go 1.25 due to linker error on Go 1.26
go-version: ${{ needs.facts.outputs.go-version }}
cache: false
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
+1
View File
@@ -149,6 +149,7 @@ HansK-p <42314815+HansK-p@users.noreply.github.com>
Harrison Jones (harrisonhjones) <harrisonhjones@users.noreply.github.com>
Hazem Krimi <me@hazemkrimi.tech>
Heiko Zuerker (Smiley73) <heiko@zuerker.org>
Henrik Bråthen <henrikbs@proton.me>
Hireworks <129852174+hireworksltd@users.noreply.github.com>
Hugo Locurcio <hugo.locurcio@hugo.pro>
Iain Barnett <iainspeed@gmail.com>
+2 -2
View File
@@ -124,7 +124,7 @@
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Negatiboa ez den zenbaki bat hauta ezazu (\"2.35\" adib.) bai eta unitate bat. Disko osoaren ehuneko espazioa",
"Enter a non-privileged port number (1024 - 65535).": "Abantailatua ez den portu zenbalki bat sar ezazu (1024 - 65535)",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Sartu komaz bereizitako helbideak (\"tcp://ip:port\", \"tcp://host:port\"), edo \"dynamic\" helbidea automatikoki bilatzeko.",
"Enter ignore patterns, one per line.": "Ezkluzio filtroak sar, lerro bakoitzean bat bakarrik.",
"Enter ignore patterns, one per line.": "Sartu baztertze-patroiak, bakarra lerro bakoitzeko.",
"Enter up to three octal digits.": "Sartu 3 digitu bitarte",
"Error": "Hutsa",
"External File Versioning": "Fitxategi bertsioen kanpoko kudeaketa",
@@ -229,7 +229,7 @@
"Optional descriptive label for the folder. Can be different on each device.": "Partekatzearen izen hautuzkoa eta atsegina, zure gisa. Tresna bakotxean desberdina izaiten ahal da.",
"Options": "Hautuzkoak",
"Out of Sync": "Ez sinkronizatua",
"Out of Sync Items": "Ez sinkronizatu elementuak",
"Out of Sync Items": "Sinkronizatu gabeko elementuak",
"Outgoing Rate Limit (KiB/s)": "Bidaltze emari gehienekoa (KiB/s)",
"Override": "Gainidatzi",
"Override Changes": "Aldaketak desegin",
@@ -30,7 +30,7 @@
<h4 class="text-center" translate>The Syncthing Authors</h4>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, bt90, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, greatroar, Lars K.W. Gohlke, Lode Hoste, Marcus B Spencer, Michael Ploujnikov, Ross Smith II, Stefan Tatschner, Tommy van der Vorst, Wulf Weich, Adam Piggott, Adel Qalieh, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Andreas Sommer, andresvia, Andrew Rabert, Andrey D, andyleap, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, ardevd, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Norcombe, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benno Fünfstück, Benny Ng, boomsquared, Boqin Qin, Boris Rybalkin, Brendan Long, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Christian Kujau, Christian Prescott, chucic, cjc7373, Colin Kennedy, Cromefire_, cui, Cyprien Devillez, d-volution, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Daniil Gentili, Darshil Chanpura, dashangcun, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, derekriemer, DerRockWolf, desbma, Devon G. Redekopp, digital, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, domain, Domenic Horner, Dominik Heidler, Elias, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, entity0xfe, Epifeny, epifeny, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, georgespatton, ghjklw, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, guangwu, gudvinr, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, ignacy123, Iskander Sharipov, Jaakko Hannikainen, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jaya Chithra, Jaya Kumar, Jeffery To, jelle van der Waa, Jens Diemer, Jochen Voss, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jose Manuel Delicado, JRNitre, jtagcat, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., klemens, Kurt Fitzner, kylosus, Lars Lehtonen, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, LSmithx2, Luiz Angelo Daros de Luca, Lukas Lihotzki, Luke Hamburg, luzpaz, Majed Abdulaziz, Marc Laporte, Marcel Meyer, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, mathias4833, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, mattn, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maximilian, Maxwell G, Michael Jephcote, Michael Rienstra, Michael Wang 汪東陽, MichaIng, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, mv1005, Nate Morrison, nf, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, orangekame3, otbutz, overkill, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, perewa, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Philippe Schommers, Phill Luby, Piotr Bejda, polyfloyd, Prathik P Kulkarni, pullmerge, Quentin Hibon, Rahmi Pruitt, RealCharlesChia, red_led, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, rubenbe, Ruslan Yevdokymov, Ryan Qian, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, sec65, Sergey Mishin, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Shablone, Shivam Kumar, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Tao, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tully Robinson, Tyler Brazier, Tyler Kropp, Umer-Azaz, Unrud, Val Markovic, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, vvaswani, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., yparitcher, 佛跳墙, 落心
Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, bt90, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, greatroar, Lars K.W. Gohlke, Lode Hoste, Marcus B Spencer, Michael Ploujnikov, Ross Smith II, Stefan Tatschner, Tommy van der Vorst, Wulf Weich, Adam Piggott, Adel Qalieh, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Andreas Sommer, andresvia, Andrew Rabert, Andrey D, andyleap, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, ardevd, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Norcombe, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benno Fünfstück, Benny Ng, boomsquared, Boqin Qin, Boris Rybalkin, Brendan Long, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Christian Kujau, Christian Prescott, chucic, cjc7373, Colin Kennedy, Cromefire_, cui, Cyprien Devillez, d-volution, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Daniil Gentili, Darshil Chanpura, dashangcun, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, derekriemer, DerRockWolf, desbma, Devon G. Redekopp, digital, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, domain, Domenic Horner, Dominik Heidler, Elias, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, entity0xfe, Epifeny, epifeny, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, georgespatton, ghjklw, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, guangwu, gudvinr, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Henrik Bråthen, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, ignacy123, Iskander Sharipov, Jaakko Hannikainen, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jaya Chithra, Jaya Kumar, Jeffery To, jelle van der Waa, Jens Diemer, Jochen Voss, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jose Manuel Delicado, JRNitre, jtagcat, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., klemens, Kurt Fitzner, kylosus, Lars Lehtonen, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, LSmithx2, Luiz Angelo Daros de Luca, Lukas Lihotzki, Luke Hamburg, luzpaz, Majed Abdulaziz, Marc Laporte, Marcel Meyer, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, mathias4833, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, mattn, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maximilian, Maxwell G, Michael Jephcote, Michael Rienstra, Michael Wang 汪東陽, MichaIng, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, mv1005, Nate Morrison, nf, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, orangekame3, otbutz, overkill, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, perewa, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Philippe Schommers, Phill Luby, Piotr Bejda, polyfloyd, Prathik P Kulkarni, pullmerge, Quentin Hibon, Rahmi Pruitt, RealCharlesChia, red_led, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, rubenbe, Ruslan Yevdokymov, Ryan Qian, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, sec65, Sergey Mishin, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Shablone, Shivam Kumar, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Tao, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tully Robinson, Tyler Brazier, Tyler Kropp, Umer-Azaz, Unrud, Val Markovic, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, vvaswani, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., yparitcher, 佛跳墙, 落心
</div>
</div>
</div>
@@ -1139,20 +1139,20 @@ angular.module('syncthing.core')
};
$scope.syncPercentage = function (folder) {
if (typeof $scope.model[folder] === 'undefined') {
var model = $scope.model[folder];
if (typeof model === 'undefined') {
return 100;
}
if ($scope.model[folder].needTotalItems === 0) {
if (model.needTotalItems === 0) {
return 100;
}
if (($scope.model[folder].needBytes == 0 && $scope.model[folder].needDeletes > 0) || $scope.model[folder].globalBytes == 0) {
// We don't need any data, but we have deletes that we need
// to do. Drop down the completion percentage to indicate
// that we have stuff to do.
// Do the same thing in case we only have zero byte files to sync.
if (model.needBytes == 0 && model.needTotalItems > 0) {
// We don't need any data, but we have deletes, directories,
// symlinks or zero byte files that we need to do. Drop down the
// completion percentage to indicate that we have stuff to do.
return 95;
}
return progressIntegerPercentage($scope.model[folder].inSyncBytes, $scope.model[folder].globalBytes);
return progressIntegerPercentage(model.inSyncBytes, model.globalBytes);
};
$scope.scanPercentage = function (folder) {
+3 -4
View File
@@ -29,7 +29,6 @@ import (
const (
folderID = "test"
blockSize = 128 << 10
dirSize = 128
)
func TestBasics(t *testing.T) {
@@ -72,11 +71,11 @@ func TestBasics(t *testing.T) {
t.Fatal(err)
}
const (
localSize = (1+2+3)*blockSize + dirSize
localSize = (1+2+3)*blockSize
remoteSize = (3 + 4 + 5) * blockSize
globalSize = (2+3+3+4+5)*blockSize + dirSize
globalSize = (2+3+3+4+5)*blockSize
needSizeLocal = remoteSize
needSizeRemote = (2+3)*blockSize + dirSize
needSizeRemote = (2+3)*blockSize
)
t.Run("SchemaVersion", func(t *testing.T) {
-4
View File
@@ -111,10 +111,6 @@ func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo, opti
f.BlocksHash = nil
}
if f.Type == protocol.FileInfoTypeDirectory {
f.Size = 128 // synthetic directory size
}
// Insert the file.
//
// If it is a remote file, set remote_sequence otherwise leave it at
@@ -0,0 +1,12 @@
-- Copyright (C) 2026 The Syncthing Authors.
--
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this file,
-- You can obtain one at https://mozilla.org/MPL/2.0/.
-- All non-file entries should have size zero.
-- Directories were previously stored with a "synthetic" size of 128.
UPDATE files
SET size = 0
WHERE type != 0
;
+4
View File
@@ -106,6 +106,10 @@ func (i indirectFI) FileInfo() (protocol.FileInfo, error) {
fi.Blocks = bl.Blocks
}
fi.Name = osutil.NativeFilename(fi.Name)
// Undo earlier behaviour of setting a synthetic size on dirs.
if fi.Type != protocol.FileInfoTypeFile && fi.Size > 0 {
fi.Size = 0
}
return protocol.FileInfoFromDB(&fi), nil
}
+1 -1
View File
@@ -1774,7 +1774,7 @@ func fileIntfJSONMap(f protocol.FileInfo) map[string]interface{} {
out := map[string]interface{}{
"name": f.FileName(),
"type": f.FileType().String(),
"size": f.FileSize(),
"size": f.Size,
"deleted": f.IsDeleted(),
"invalid": f.IsInvalid(),
"ignored": f.IsIgnored(),
+9 -3
View File
@@ -1762,9 +1762,10 @@ func TestConfigChanges(t *testing.T) {
folder2Path := "/rest/config/folders/folder2"
// Create a folder and add another
// Create a folder and add another. Give folder2 a non-default
// RescanIntervalS so we can verify it survives a later partial PATCH.
mod(http.MethodPut, "/rest/config/folders", []config.FolderConfiguration{{ID: "folder1", Path: "folder1"}})
mod(http.MethodPut, folder2Path, config.FolderConfiguration{ID: "folder2", Path: "folder2"})
mod(http.MethodPut, folder2Path, config.FolderConfiguration{ID: "folder2", Path: "folder2", RescanIntervalS: 1234})
// Check they are there
get("/rest/config/folders/folder1").Body.Close()
@@ -1779,9 +1780,14 @@ func TestConfigChanges(t *testing.T) {
if err := unmarshalTo(resp.Body, &folder); err != nil {
t.Fatal(err)
}
if !dev.Paused {
if !folder.Paused {
t.Error("Expected folder to be paused")
}
// A partial PATCH must not reset other (default-tagged) attributes to
// their default values.
if folder.RescanIntervalS != 1234 {
t.Error("Expected RescanIntervalS to be preserved as 1234, got", folder.RescanIntervalS)
}
// Delete folder2
req, _ := http.NewRequest(http.MethodDelete, baseURL+folder2Path, nil)
-11
View File
@@ -9,7 +9,6 @@ package config
import (
"bytes"
"crypto/sha256"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
@@ -395,16 +394,6 @@ func (f XattrFilter) GetMaxTotalSize() int {
return f.MaxTotalSize
}
func (f *FolderConfiguration) UnmarshalJSON(data []byte) error {
structutil.SetDefaults(f)
// avoid recursing into this method
type noCustomUnmarshal FolderConfiguration
ptr := (*noCustomUnmarshal)(f)
return json.Unmarshal(data, ptr)
}
func (f *FolderConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
structutil.SetDefaults(f)
+6 -3
View File
@@ -975,10 +975,13 @@ func (f *folder) scanTimerFired(ctx context.Context) error {
select {
case <-f.initialScanFinished:
default:
if err != nil {
f.sl.ErrorContext(ctx, "Failed initial scan", slogutil.Error(err))
} else {
switch {
case err == nil:
f.sl.InfoContext(ctx, "Completed initial scan")
case errors.Is(err, context.Canceled):
// Suppressed: context was canceled (e.g. by user)
default:
f.sl.ErrorContext(ctx, "Failed initial scan", slogutil.Error(err))
}
close(f.initialScanFinished)
}
+4 -5
View File
@@ -163,12 +163,11 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// We now have a newer file than the rest of the cluster. Global state should reflect this.
size = mustV(m.GlobalSize("ro"))
const sizeOfDir = 128
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
if size.Files != 1 || size.Bytes != int64(len(oldData)) {
t.Fatalf("Global: expected no change due to the new file: %+v", size)
}
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
if size.Files != 1 || size.Bytes != int64(len(newData)) {
t.Fatalf("Local: expected the new file to be reflected: %+v", size)
}
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
@@ -185,11 +184,11 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
m.Revert("ro")
size = mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
if size.Files != 1 || size.Bytes != int64(len(oldData)) {
t.Fatalf("Global: expected the global size to revert: %+v", size)
}
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
if size.Files != 1 || size.Bytes != int64(len(newData)) {
t.Fatalf("Local: expected the local size to remain: %+v", size)
}
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
+8 -21
View File
@@ -2548,13 +2548,6 @@ func (m *model) DelayScan(folder string, next time.Duration) {
func (m *model) numHashers(folder string) int {
m.mut.RLock()
folderCfg := m.folderCfgs[folder]
numFolders := max(1, len(m.folderCfgs))
// MaxFolderConcurrency already limits the number of scanned folders, so
// prefer it over the overall number of folders to avoid limiting performance
// further for no reason.
if concurrency := m.cfg.Options().MaxFolderConcurrency(); concurrency > 0 {
numFolders = min(numFolders, concurrency)
}
m.mut.RUnlock()
if folderCfg.Hashers > 0 {
@@ -2562,21 +2555,15 @@ func (m *model) numHashers(folder string) int {
return folderCfg.Hashers
}
numCpus := runtime.GOMAXPROCS(-1)
if build.IsWindows || build.IsDarwin || build.IsIOS || build.IsAndroid {
// Interactive operating systems; don't load the system too heavily by
// default.
numCpus = max(1, numCpus/4)
numCPUs := runtime.GOMAXPROCS(-1)
switch {
case build.IsWindows || build.IsIOS || build.IsAndroid:
// Use a quarter of the CPU cores on interactive or constrained OSes
return max(1, numCPUs/4)
default:
// Otherwise use up to half
return max(1, numCPUs/2)
}
// For other operating systems and architectures, lets try to get some
// work done... Divide the available CPU cores among the configured
// folders.
if perFolder := numCpus / numFolders; perFolder > 0 {
return perFolder
}
return 1
}
// generateClusterConfig returns a ClusterConfigMessage that is correct and the
+4 -2
View File
@@ -1787,10 +1787,12 @@ func TestGlobalDirectoryTree(t *testing.T) {
b := func(isfile bool, path ...string) protocol.FileInfo {
typ := protocol.FileInfoTypeDirectory
var blocks []protocol.BlockInfo
var size int64
if isfile {
typ = protocol.FileInfoTypeFile
blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
size = 0xa
}
seq++
return protocol.FileInfo{
@@ -1798,7 +1800,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
Type: typ,
ModifiedS: 0x666,
Blocks: blocks,
Size: 0xa,
Size: size,
Sequence: seq,
}
}
@@ -1814,7 +1816,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
return &TreeEntry{
Name: name,
ModTime: time.Unix(0x666, 0),
Size: 128,
Size: 0,
Type: protocol.FileInfoTypeDirectory.String(),
Children: entries,
}
+23 -5
View File
@@ -8,8 +8,10 @@ package osutil_test
import (
"path/filepath"
"slices"
"testing"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/rand"
@@ -59,11 +61,16 @@ func TestTraversesSymlink(t *testing.T) {
}
}
func TestIssue4875(t *testing.T) {
testFsPath := rand.String(32)
testFs := fs.NewFilesystem(fs.FilesystemTypeFake, testFsPath)
func TestSymlinkedFSRootTraversal(t *testing.T) {
// Normal FS operations should work inside a folder root that is a symlink
if build.IsWindows {
t.Skip("symlinks unavailable")
}
testFsPath := t.TempDir()
testFs := fs.NewFilesystem(fs.FilesystemTypeBasic, testFsPath)
testFs.MkdirAll(filepath.Join("a", "b", "c"), 0o755)
if err := testFs.CreateSymlink(filepath.Join("a", "b"), filepath.Join("a", "l")); err != nil {
if err := testFs.CreateSymlink("b", filepath.Join("a", "l")); err != nil {
t.Fatal(err)
}
@@ -76,10 +83,21 @@ func TestIssue4875(t *testing.T) {
t.Fatal("error in setup, a/l/c should be a directory")
}
testFs = fs.NewFilesystem(fs.FilesystemTypeFake, filepath.Join(testFsPath, "a/l"))
testFs = fs.NewFilesystem(fs.FilesystemTypeBasic, filepath.Join(testFsPath, "a/l"))
if err := osutil.TraversesSymlink(testFs, "."); err != nil {
t.Error(`TraversesSymlink on filesystem with symlink at root returned error for ".":`, err)
}
// We should be able to create and list files within the symlinked root
if _, err := testFs.Create("test"); err != nil {
t.Fatal(err)
}
if names, err := testFs.DirNames("."); err != nil {
t.Fatal(err)
} else if !slices.Contains(names, "test") {
t.Log(names)
t.Error("missing test file in listing")
}
}
var traversesSymlinkResult error
-10
View File
@@ -389,16 +389,6 @@ func (f FileInfo) HasPermissionBits() bool {
return !f.NoPermissions
}
func (f FileInfo) FileSize() int64 {
if f.Deleted {
return 0
}
if f.IsDirectory() || f.IsSymlink() {
return SyntheticDirectorySize
}
return f.Size
}
func (f FileInfo) BlockSize() int {
if f.RawBlockSize < MinBlockSize {
return MinBlockSize
+3 -2
View File
@@ -56,7 +56,8 @@ const (
// DesiredPerFileBlocks is the number of blocks we aim for per file
DesiredPerFileBlocks = 2000
SyntheticDirectorySize = 128
// We used to send this size for directories and still accept that for compat.
deprecatedSyntheticDirectorySize = 128
// don't bother compressing messages smaller than this many bytes
compressionThreshold = 128
@@ -640,7 +641,7 @@ func checkFileInfoConsistency(f FileInfo) error {
// Only files should have blocks
return errNonFileHasBlocks
case f.IsDirectory() && f.Size != 0 && f.Size != SyntheticDirectorySize:
case f.IsDirectory() && f.Size != 0 && f.Size != deprecatedSyntheticDirectorySize:
// Directories should be size zero or the synthetic directory size
return errNonFileHasSize
+2 -2
View File
@@ -464,7 +464,7 @@ func TestCheckConsistency(t *testing.T) {
fi: FileInfo{
Name: "foo",
Type: FileInfoTypeDirectory,
Size: SyntheticDirectorySize,
Size: deprecatedSyntheticDirectorySize,
},
ok: true,
},
@@ -492,7 +492,7 @@ func TestCheckConsistency(t *testing.T) {
Name: "foo",
Type: FileInfoTypeSymlink,
SymlinkTarget: []byte("bar"),
Size: SyntheticDirectorySize,
Size: deprecatedSyntheticDirectorySize,
},
ok: false,
},
+3 -3
View File
@@ -41,9 +41,9 @@ type testfileList []testfile
var testdata = testfileList{
{"afile", 4, "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
{"dir1", 128, ""},
{"dir1", 0, ""},
{filepath.Join("dir1", "dfile"), 5, "49ae93732fcf8d63fe1cce759664982dbd5b23161f007dba8561862adc96d063"},
{"dir2", 128, ""},
{"dir2", 0, ""},
{filepath.Join("dir2", "cfile"), 4, "bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c"},
{"excludes", 37, "df90b52f0c55dba7a7a940affe482571563b1ac57bd5be4d8a0291e7de928e06"},
{"further-excludes", 5, "7eb0a548094fa6295f7fd9200d69973e5f5ec5c04f2a86d998080ac43ecf89f1"},
@@ -601,7 +601,7 @@ func (l fileList) testfiles() testfileList {
if len(f.Blocks) > 1 {
panic("simple test case stuff only supports a single block per file")
}
testfiles[i] = testfile{name: f.Name, length: f.FileSize()}
testfiles[i] = testfile{name: f.Name, length: f.Size}
if len(f.Blocks) == 1 {
testfiles[i].hash = fmt.Sprintf("%x", f.Blocks[0].Hash)
}
+55 -27
View File
@@ -27,6 +27,8 @@ func init() {
factories["external"] = newExternal
}
const unixSpecialChars = "`" + `"'<>;!#$&*? `
type external struct {
command string
filesystem fs.Filesystem
@@ -68,36 +70,11 @@ func (v external) Archive(filePath string) error {
return errors.New("command is empty, please enter a valid command")
}
words, err := shellquote.Split(v.command)
cmd, err := v.prepareCommand(filePath)
if err != nil {
return fmt.Errorf("command is invalid: %w", err)
return err
}
context := map[string]string{
"%FOLDER_FILESYSTEM%": string(v.filesystem.Type()),
"%FOLDER_PATH%": v.filesystem.URI(),
"%FILE_PATH%": filePath,
}
for i, word := range words {
for key, val := range context {
word = strings.ReplaceAll(word, key, val)
}
words[i] = word
}
cmd := exec.Command(words[0], words[1:]...)
env := os.Environ()
// filter STGUIAUTH and STGUIAPIKEY from environment variables
var filteredEnv []string
for _, x := range env {
if !strings.HasPrefix(x, "STGUIAUTH=") && !strings.HasPrefix(x, "STGUIAPIKEY=") {
filteredEnv = append(filteredEnv, x)
}
}
cmd.Env = filteredEnv
combinedOutput, err := cmd.CombinedOutput()
l.Debugln("external command output:", string(combinedOutput))
if err != nil {
@@ -126,3 +103,54 @@ func (external) Restore(_ string, _ time.Time) error {
func (external) Clean(_ context.Context) error {
return nil
}
// prepareCommand returns the command with environment for the given file
// path.
func (v external) prepareCommand(filePath string) (*exec.Cmd, error) {
words, err := shellquote.Split(v.command)
if err != nil {
return nil, fmt.Errorf("command is invalid: %w", err)
}
context := map[string]string{
"%FOLDER_FILESYSTEM%": string(v.filesystem.Type()),
"%FOLDER_PATH%": v.filesystem.URI(),
"%FILE_PATH%": filePath,
}
for i, word := range words {
unsafe := strings.ContainsAny(word, unixSpecialChars)
for key, val := range context {
// If the parameter contains both an unsafe character and a
// template placeholder, we consider it unsafe and reject it.
// Note that the shell splitting will have already removed outer
// quotes, so that a command like `foo "%FILE_PATH%"` is fine
// here, despite the double quote being one of our unsafe
// characters.
if unsafe && strings.Contains(word, key) {
return nil, errors.New("unsafe external versioning command; see https://docs.syncthing.net/users/versioning.html#external-file-versioning")
}
word = strings.ReplaceAll(word, key, val)
}
words[i] = word
}
// filter STGUIAUTH and STGUIAPIKEY from environment variables, and add
// our folder info.
env := os.Environ()
var filteredEnv []string
for _, x := range env {
if !strings.HasPrefix(x, "STGUIAUTH=") && !strings.HasPrefix(x, "STGUIAPIKEY=") {
filteredEnv = append(filteredEnv, x)
}
}
for k, v := range context {
k = strings.Trim(k, "%")
filteredEnv = append(filteredEnv, fmt.Sprintf("%s=%s", k, v))
}
cmd := exec.Command(words[0], words[1:]...) //nolint:gosec // execution with user tainted data, by design
cmd.Env = filteredEnv
return cmd, nil
}
+33
View File
@@ -76,6 +76,39 @@ func TestExternal(t *testing.T) {
}
}
func TestExternalCommandSplit(t *testing.T) {
e := external{
filesystem: fs.NewFilesystem(fs.FilesystemTypeFake, "TestExternalCommandSplit"),
}
cases := []struct {
cmd string
safe bool
}{
{`echo %FOLDER_PATH% %FILE_PATH%`, true},
{`echo "%FOLDER_PATH% %FILE_PATH%"`, false},
{`echo %FOLDER_PATH%/%FILE_PATH%`, true},
{`echo "%FOLDER_PATH%/%FILE_PATH%"`, true},
{`echo '%FOLDER_PATH%/%FILE_PATH%'`, true},
{`echo "'%FOLDER_PATH%/%FILE_PATH%'"`, false},
{`sh -c "echo '%FOLDER_PATH%/%FILE_PATH%'"`, false},
{`sh -c "echo %FOLDER_PATH%/%FILE_PATH%"`, false},
}
for _, tc := range cases {
e.command = tc.cmd
res, err := e.prepareCommand("evil file name")
if tc.safe && err != nil {
t.Fatal(err)
}
if !tc.safe && err == nil {
t.Logf("%q", res.Path)
t.Logf("%q", res.Args)
t.Errorf("should be unsafe: %q", tc.cmd)
}
}
}
func prepForRemoval(t *testing.T, file string) {
if err := os.RemoveAll("testdata"); err != nil {
t.Fatal(err)
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "STDISCOSRV" "1" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "STDISCOSRV" "1" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "STRELAYSRV" "1" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "STRELAYSRV" "1" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.SH SYNOPSIS
+1 -1
View File
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-BEP" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-BEP" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.SH INTRODUCTION AND DEFINITIONS
+5 -6
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-CONFIG" "5" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.SH OVERVIEW
@@ -1528,11 +1528,10 @@ addresses to global discovery.
.INDENT 0.0
.TP
.B sendFullIndexOnUpgrade
Controls whether all index data is resent when an upgrade has happened,
equivalent to starting Syncthing with \fB\-\-reset\-deltas\fP\&. This used
to be the default behavior in older versions, but is mainly useful as a
troubleshooting step and causes high database churn. The default is now
\fBfalse\fP\&.
Controls whether all index data is resent when an upgrade has happened.
This used to be the default behavior in older versions, but is mainly useful
as a troubleshooting step and causes high database churn. The default is
now \fBfalse\fP\&.
.UNINDENT
.INDENT 0.0
.TP
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-DEVICE-IDS" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-EVENT-API" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.SH DESCRIPTION
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-FAQ" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.INDENT 0.0
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-GLOBALDISCO" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.SH ANNOUNCEMENTS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-LOCALDISCO" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.SH MODE OF OPERATION
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-NETWORKING" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.SH ROUTER SETUP
+1 -1
View File
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-RELAY" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-RELAY" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.SH WHAT IS A RELAY?
+5 -3
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-REST-API" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.sp
@@ -665,8 +665,10 @@ curl \-X POST \-H \(dqX\-API\-Key: abc123\(dq http://localhost:8384/rest/system/
.UNINDENT
.UNINDENT
.sp
\fBCaution\fP: See \fB\-\-reset\-database\fP for \fB\&.stfolder\fP creation
side\-effect and caution regarding mountpoints.
Creates \fB\&.stfolder\fP folders in each sync folder if they do not already exist.
\fBCaution\fP: Ensure that all sync folders which are mountpoints are already
mounted. Inconsistent versions may result if the mountpoint is later mounted
and contains older versions.
.SS POST /rest/system/restart
.sp
Post with empty body to immediately restart Syncthing.
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-SECURITY" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-STIGNORE" "5" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-VERSIONING" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-VERSIONING" "7" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.sp
+25 -3
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING" "1" "Jun 07, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING" "1" "Jun 08, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing \- Syncthing
.SH SYNOPSIS
@@ -404,14 +404,36 @@ acknowledge (clear) them.
.TP
.B debug
Various tools to aid in diagnosing problems or collection information for
bug reports. Some of these commands access the database directly and can
therefore only work when Syncthing is not running.
bug reports.
.TP
.B \fB\-\fP (a single dash)
Reads subsequent commands from the standard input stream, without needing to
call the \fBsyncthing cli\fP command over and over. Exits on any invalid
command or when EOF (end\-of\-file) is received.
.UNINDENT
.sp
For troubleshooting issues, the \fBdebug\fP subcommand provides some commonly
needed actions for data analysis and repair. Some of these commands access the
database directly and can therefore only work when Syncthing is not running.
Not to be confused with the \fBcli debug\fP subcommand group, which works via the
REST API.
.INDENT 0.0
.TP
.B debug reset\-database
Reset the database, forcing a full rescan and resync. \fBMust only be used
when Syncthing is not running.\fP More granular reset operations are
available on the REST API while Syncthing is running:
\fI\%POST /rest/system/reset\fP
.TP
.B debug database\-statistics
Display database size statistics.
.TP
.B debug database\-counts
Display database folder counts.
.TP
.B debug database\-file
Display database file metadata.
.UNINDENT
.SH PROXIES
.sp
Syncthing can use a SOCKS, HTTP, or HTTPS proxy to talk to the outside