Sfoglia il codice sorgente

all: Refactor preparing configuration (#7127)

Simon Frei 4 anni fa
parent
commit
24af89c8e2

+ 1 - 1
cmd/strelaysrv/main.go

@@ -185,7 +185,7 @@ func main() {
 		log.Println("ID:", id)
 	}
 
-	wrapper := config.Wrap("config", config.New(id), events.NoopLogger)
+	wrapper := config.Wrap("config", config.New(id), id, events.NoopLogger)
 	wrapper.SetOptions(config.OptionsConfiguration{
 		NATLeaseM:   natLease,
 		NATRenewalM: natRenewal,

+ 2 - 2
lib/api/api_test.go

@@ -113,7 +113,7 @@ func TestStopAfterBrokenConfig(t *testing.T) {
 			RawUseTLS:  false,
 		},
 	}
-	w := config.Wrap("/dev/null", cfg, events.NoopLogger)
+	w := config.Wrap("/dev/null", cfg, protocol.LocalDeviceID, events.NoopLogger)
 
 	srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, events.NoopLogger, nil, nil, nil, nil, nil, nil, false).(*service)
 	defer os.Remove(token)
@@ -1251,7 +1251,7 @@ func TestConfigChanges(t *testing.T) {
 		panic(err)
 	}
 	defer os.Remove(tmpFile.Name())
-	w := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger)
+	w := config.Wrap(tmpFile.Name(), cfg, protocol.LocalDeviceID, events.NoopLogger)
 	tmpFile.Close()
 	baseURL, cancel, err := startHTTP(w)
 	if err != nil {

+ 4 - 0
lib/api/mocked_config_test.go

@@ -142,6 +142,10 @@ func (c *mockedConfig) StunServers() []string {
 	return nil
 }
 
+func (c *mockedConfig) MyID() protocol.DeviceID {
+	return protocol.DeviceID{}
+}
+
 type noopWaiter struct{}
 
 func (noopWaiter) Wait() {}

+ 1 - 1
lib/config/commit_test.go

@@ -44,7 +44,7 @@ func (validationError) String() string {
 func TestReplaceCommit(t *testing.T) {
 	t.Skip("broken, fails randomly, #3834")
 
-	w := wrap("/dev/null", Configuration{Version: 0})
+	w := wrap("/dev/null", Configuration{Version: 0}, device1)
 	if w.RawCopy().Version != 0 {
 		t.Fatal("Config incorrect")
 	}

+ 77 - 101
lib/config/config.go

@@ -25,7 +25,6 @@ import (
 
 	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/protocol"
-	"github.com/syncthing/syncthing/lib/rand"
 	"github.com/syncthing/syncthing/lib/util"
 )
 
@@ -105,8 +104,6 @@ func New(myID protocol.DeviceID) Configuration {
 	cfg.Options.UnackedNotificationIDs = []string{"authenticationUserAndPassword"}
 
 	util.SetDefaults(&cfg)
-	util.SetDefaults(&cfg.Options)
-	util.SetDefaults(&cfg.GUI)
 
 	// Can't happen.
 	if err := cfg.prepare(myID); err != nil {
@@ -152,8 +149,6 @@ func ReadXML(r io.Reader, myID protocol.DeviceID) (Configuration, int, error) {
 	var cfg xmlConfiguration
 
 	util.SetDefaults(&cfg)
-	util.SetDefaults(&cfg.Options)
-	util.SetDefaults(&cfg.GUI)
 
 	if err := xml.NewDecoder(r).Decode(&cfg); err != nil {
 		return Configuration{}, 0, err
@@ -171,8 +166,6 @@ func ReadJSON(r io.Reader, myID protocol.DeviceID) (Configuration, error) {
 	var cfg Configuration
 
 	util.SetDefaults(&cfg)
-	util.SetDefaults(&cfg.Options)
-	util.SetDefaults(&cfg.GUI)
 
 	bs, err := ioutil.ReadAll(r)
 	if err != nil {
@@ -230,38 +223,61 @@ func (cfg *Configuration) WriteXML(w io.Writer) error {
 }
 
 func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
-	var myName string
+	cfg.ensureMyDevice(myID)
 
+	existingDevices, err := cfg.prepareFoldersAndDevices(myID)
+	if err != nil {
+		return err
+	}
+
+	cfg.GUI.prepare()
+
+	guiPWIsSet := cfg.GUI.User != "" && cfg.GUI.Password != ""
+	cfg.Options.prepare(guiPWIsSet)
+
+	ignoredDevices := cfg.prepareIgnoredDevices(existingDevices)
+
+	cfg.preparePendingDevices(existingDevices, ignoredDevices)
+
+	cfg.removeDeprecatedProtocols()
+
+	util.FillNilExceptDeprecated(cfg)
+
+	// TestIssue1750 relies on migrations happening after preparing options.
+	cfg.applyMigrations()
+
+	return nil
+}
+
+func (cfg *Configuration) ensureMyDevice(myID protocol.DeviceID) {
 	// Ensure this device is present in the config
 	for _, device := range cfg.Devices {
 		if device.DeviceID == myID {
-			goto found
+			return
 		}
 	}
 
-	myName, _ = os.Hostname()
+	myName, _ := os.Hostname()
 	cfg.Devices = append(cfg.Devices, DeviceConfiguration{
 		DeviceID: myID,
 		Name:     myName,
 	})
+}
 
-found:
+func (cfg *Configuration) prepareFoldersAndDevices(myID protocol.DeviceID) (map[protocol.DeviceID]bool, error) {
+	existingDevices := cfg.prepareDeviceList()
 
-	if err := cfg.clean(); err != nil {
-		return err
+	sharedFolders, err := cfg.prepareFolders(myID, existingDevices)
+	if err != nil {
+		return nil, err
 	}
 
-	// Ensure that we are part of the devices
-	for i := range cfg.Folders {
-		cfg.Folders[i].Devices = ensureDevicePresent(cfg.Folders[i].Devices, myID)
-	}
+	cfg.prepareDevices(sharedFolders)
 
-	return nil
+	return existingDevices, nil
 }
 
-func (cfg *Configuration) clean() error {
-	util.FillNilSlices(&cfg.Options)
-
+func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]bool {
 	// Ensure that the device list is
 	// - free from duplicates
 	// - no devices with empty ID
@@ -272,89 +288,62 @@ func (cfg *Configuration) clean() error {
 		return cfg.Devices[a].DeviceID.Compare(cfg.Devices[b].DeviceID) == -1
 	})
 
+	// Build a list of available devices
+	existingDevices := make(map[protocol.DeviceID]bool, len(cfg.Devices))
+	for _, device := range cfg.Devices {
+		existingDevices[device.DeviceID] = true
+	}
+	return existingDevices
+}
+
+func (cfg *Configuration) prepareFolders(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) (map[protocol.DeviceID][]string, error) {
 	// Prepare folders and check for duplicates. Duplicates are bad and
 	// dangerous, can't currently be resolved in the GUI, and shouldn't
 	// happen when configured by the GUI. We return with an error in that
 	// situation.
-	existingFolders := make(map[string]*FolderConfiguration)
+	sharedFolders := make(map[protocol.DeviceID][]string, len(cfg.Devices))
+	existingFolders := make(map[string]*FolderConfiguration, len(cfg.Folders))
 	for i := range cfg.Folders {
 		folder := &cfg.Folders[i]
-		folder.prepare()
 
 		if folder.ID == "" {
-			return errFolderIDEmpty
+			return nil, errFolderIDEmpty
 		}
 
 		if folder.Path == "" {
-			return fmt.Errorf("folder %q: %w", folder.ID, errFolderPathEmpty)
+			return nil, fmt.Errorf("folder %q: %w", folder.ID, errFolderPathEmpty)
 		}
 
 		if _, ok := existingFolders[folder.ID]; ok {
-			return fmt.Errorf("folder %q: %w", folder.ID, errFolderIDDuplicate)
+			return nil, fmt.Errorf("folder %q: %w", folder.ID, errFolderIDDuplicate)
 		}
 
-		existingFolders[folder.ID] = folder
-	}
-
-	cfg.Options.RawListenAddresses = util.UniqueTrimmedStrings(cfg.Options.RawListenAddresses)
-	cfg.Options.RawGlobalAnnServers = util.UniqueTrimmedStrings(cfg.Options.RawGlobalAnnServers)
+		folder.prepare(myID, existingDevices)
 
-	if cfg.Version > 0 && cfg.Version < OldestHandledVersion {
-		l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
-	}
-
-	// Upgrade configuration versions as appropriate
-	migrationsMut.Lock()
-	migrations.apply(cfg)
-	migrationsMut.Unlock()
+		existingFolders[folder.ID] = folder
 
-	// Build a list of available devices
-	existingDevices := make(map[protocol.DeviceID]bool)
-	for _, device := range cfg.Devices {
-		existingDevices[device.DeviceID] = true
+		for _, dev := range folder.Devices {
+			sharedFolders[dev.DeviceID] = append(sharedFolders[dev.DeviceID], folder.ID)
+		}
 	}
-
 	// Ensure that the folder list is sorted by ID
 	sort.Slice(cfg.Folders, func(a, b int) bool {
 		return cfg.Folders[a].ID < cfg.Folders[b].ID
 	})
+	return sharedFolders, nil
+}
 
-	// Ensure that in all folder configs
-	// - any loose devices are not present in the wrong places
-	// - there are no duplicate devices
-	// - the versioning configuration parameter map is not nil
-	sharedFolders := make(map[protocol.DeviceID][]string, len(cfg.Devices))
-	for i := range cfg.Folders {
-		cfg.Folders[i].Devices = ensureExistingDevices(cfg.Folders[i].Devices, existingDevices)
-		cfg.Folders[i].Devices = ensureNoDuplicateFolderDevices(cfg.Folders[i].Devices)
-		if cfg.Folders[i].Versioning.Params == nil {
-			cfg.Folders[i].Versioning.Params = map[string]string{}
-		}
-		sort.Slice(cfg.Folders[i].Devices, func(a, b int) bool {
-			return cfg.Folders[i].Devices[a].DeviceID.Compare(cfg.Folders[i].Devices[b].DeviceID) == -1
-		})
-		for _, dev := range cfg.Folders[i].Devices {
-			sharedFolders[dev.DeviceID] = append(sharedFolders[dev.DeviceID], cfg.Folders[i].ID)
-		}
-	}
-
+func (cfg *Configuration) prepareDevices(sharedFolders map[protocol.DeviceID][]string) {
 	for i := range cfg.Devices {
 		cfg.Devices[i].prepare(sharedFolders[cfg.Devices[i].DeviceID])
 	}
+}
 
-	// Very short reconnection intervals are annoying
-	if cfg.Options.ReconnectIntervalS < 5 {
-		cfg.Options.ReconnectIntervalS = 5
-	}
-
-	if cfg.GUI.APIKey == "" {
-		cfg.GUI.APIKey = rand.String(32)
-	}
-
+func (cfg *Configuration) prepareIgnoredDevices(existingDevices map[protocol.DeviceID]bool) map[protocol.DeviceID]bool {
 	// The list of ignored devices should not contain any devices that have
 	// been manually added to the config.
-	var newIgnoredDevices []ObservedDevice
-	ignoredDevices := make(map[protocol.DeviceID]bool)
+	newIgnoredDevices := cfg.IgnoredDevices[:0]
+	ignoredDevices := make(map[protocol.DeviceID]bool, len(cfg.IgnoredDevices))
 	for _, dev := range cfg.IgnoredDevices {
 		if !existingDevices[dev.ID] {
 			ignoredDevices[dev.ID] = true
@@ -362,7 +351,10 @@ func (cfg *Configuration) clean() error {
 		}
 	}
 	cfg.IgnoredDevices = newIgnoredDevices
+	return ignoredDevices
+}
 
+func (cfg *Configuration) preparePendingDevices(existingDevices, ignoredDevices map[protocol.DeviceID]bool) {
 	// The list of pending devices should not contain devices that were added manually, nor should it contain
 	// ignored devices.
 
@@ -371,7 +363,7 @@ func (cfg *Configuration) clean() error {
 		return cfg.PendingDevices[i].Time.Before(cfg.PendingDevices[j].Time)
 	})
 
-	var newPendingDevices []ObservedDevice
+	newPendingDevices := cfg.PendingDevices[:0]
 nextPendingDevice:
 	for _, pendingDevice := range cfg.PendingDevices {
 		if !existingDevices[pendingDevice.ID] && !ignoredDevices[pendingDevice.ID] {
@@ -385,7 +377,9 @@ nextPendingDevice:
 		}
 	}
 	cfg.PendingDevices = newPendingDevices
+}
 
+func (cfg *Configuration) removeDeprecatedProtocols() {
 	// Deprecated protocols are removed from the list of listeners and
 	// device addresses. So far just kcp*.
 	for _, prefix := range []string{"kcp"} {
@@ -395,35 +389,17 @@ nextPendingDevice:
 			dev.Addresses = filterURLSchemePrefix(dev.Addresses, prefix)
 		}
 	}
+}
 
-	// Initialize any empty slices
-	if cfg.Folders == nil {
-		cfg.Folders = []FolderConfiguration{}
-	}
-	if cfg.IgnoredDevices == nil {
-		cfg.IgnoredDevices = []ObservedDevice{}
-	}
-	if cfg.PendingDevices == nil {
-		cfg.PendingDevices = []ObservedDevice{}
-	}
-	if cfg.Options.AlwaysLocalNets == nil {
-		cfg.Options.AlwaysLocalNets = []string{}
-	}
-	if cfg.Options.UnackedNotificationIDs == nil {
-		cfg.Options.UnackedNotificationIDs = []string{}
-	} else if cfg.GUI.User != "" && cfg.GUI.Password != "" {
-		for i, key := range cfg.Options.UnackedNotificationIDs {
-			if key == "authenticationUserAndPassword" {
-				cfg.Options.UnackedNotificationIDs = append(cfg.Options.UnackedNotificationIDs[:i], cfg.Options.UnackedNotificationIDs[i+1:]...)
-				break
-			}
-		}
-	}
-	if cfg.Options.FeatureFlags == nil {
-		cfg.Options.FeatureFlags = []string{}
+func (cfg *Configuration) applyMigrations() {
+	if cfg.Version > 0 && cfg.Version < OldestHandledVersion {
+		l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
 	}
 
-	return nil
+	// Upgrade configuration versions as appropriate
+	migrationsMut.Lock()
+	migrations.apply(cfg)
+	migrationsMut.Unlock()
 }
 
 // DeviceMap returns a map of device ID to device configuration for the given configuration.

+ 14 - 9
lib/config/config_test.go

@@ -526,7 +526,7 @@ func TestNewSaveLoad(t *testing.T) {
 	}
 
 	intCfg := New(device1)
-	cfg := wrap(path, intCfg)
+	cfg := wrap(path, intCfg, device1)
 
 	if exists(path) {
 		t.Error(path, "exists")
@@ -646,7 +646,7 @@ func TestPullOrder(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	wrapper = wrap("testdata/pullorder.xml", cfg)
+	wrapper = wrap("testdata/pullorder.xml", cfg, device1)
 	folders = wrapper.Folders()
 
 	for _, tc := range expected {
@@ -941,7 +941,8 @@ func TestIssue4219(t *testing.T) {
 		]
 	}`))
 
-	cfg, err := ReadJSON(r, protocol.LocalDeviceID)
+	myID := protocol.LocalDeviceID
+	cfg, err := ReadJSON(r, myID)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -959,7 +960,7 @@ func TestIssue4219(t *testing.T) {
 		t.Errorf("There should be three ignored folders, not %d", ignoredFolders)
 	}
 
-	w := wrap("/tmp/cfg", cfg)
+	w := wrap("/tmp/cfg", cfg, myID)
 	if !w.IgnoredFolder(device2, "t1") {
 		t.Error("Folder device2 t1 should be ignored")
 	}
@@ -1119,12 +1120,16 @@ func TestRemoveDeviceWithEmptyID(t *testing.T) {
 		},
 	}
 
-	cfg.clean()
+	cfg.prepare(device1)
 
-	if len(cfg.Devices) != 0 {
+	if len(cfg.Devices) != 1 {
+		t.Error("Expected one device")
+	} else if cfg.Devices[0].DeviceID != device1 {
 		t.Error("Expected device with empty ID to be removed from config:", cfg.Devices)
 	}
-	if len(cfg.Folders[0].Devices) != 0 {
+	if len(cfg.Folders[0].Devices) != 1 {
+		t.Error("Expected one device in folder")
+	} else if cfg.Folders[0].Devices[0].DeviceID != device1 {
 		t.Error("Expected device with empty ID to be removed from folder")
 	}
 }
@@ -1175,8 +1180,8 @@ func load(path string, myID protocol.DeviceID) (Wrapper, error) {
 	return cfg, err
 }
 
-func wrap(path string, cfg Configuration) Wrapper {
-	return Wrap(path, cfg, events.NoopLogger)
+func wrap(path string, cfg Configuration, myID protocol.DeviceID) Wrapper {
+	return Wrap(path, cfg, myID, events.NoopLogger)
 }
 
 func TestInternalVersioningConfiguration(t *testing.T) {

+ 15 - 5
lib/config/folderconfiguration.go

@@ -10,6 +10,7 @@ import (
 	"errors"
 	"fmt"
 	"runtime"
+	"sort"
 	"strings"
 	"time"
 
@@ -43,7 +44,7 @@ func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.
 
 	util.SetDefaults(&f)
 
-	f.prepare()
+	f.prepare(myID, nil)
 	return f
 }
 
@@ -182,7 +183,19 @@ func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
 	return deviceIDs
 }
 
-func (f *FolderConfiguration) prepare() {
+func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
+	// Ensure that
+	// - any loose devices are not present in the wrong places
+	// - there are no duplicate devices
+	// - we are part of the devices
+	f.Devices = ensureExistingDevices(f.Devices, existingDevices)
+	f.Devices = ensureNoDuplicateFolderDevices(f.Devices)
+	f.Devices = ensureDevicePresent(f.Devices, myID)
+
+	sort.Slice(f.Devices, func(a, b int) bool {
+		return f.Devices[a].DeviceID.Compare(f.Devices[b].DeviceID) == -1
+	})
+
 	if f.RescanIntervalS > MaxRescanIntervalS {
 		f.RescanIntervalS = MaxRescanIntervalS
 	} else if f.RescanIntervalS < 0 {
@@ -194,9 +207,6 @@ func (f *FolderConfiguration) prepare() {
 		f.FSWatcherDelayS = 10
 	}
 
-	if f.Versioning.Params == nil {
-		f.Versioning.Params = make(map[string]string)
-	}
 	if f.Versioning.CleanupIntervalS > MaxRescanIntervalS {
 		f.Versioning.CleanupIntervalS = MaxRescanIntervalS
 	} else if f.Versioning.CleanupIntervalS < 0 {

+ 8 - 0
lib/config/guiconfiguration.go

@@ -11,6 +11,8 @@ import (
 	"os"
 	"strconv"
 	"strings"
+
+	"github.com/syncthing/syncthing/lib/rand"
 )
 
 func (c GUIConfiguration) IsAuthEnabled() bool {
@@ -126,6 +128,12 @@ func (c GUIConfiguration) IsValidAPIKey(apiKey string) bool {
 	}
 }
 
+func (c *GUIConfiguration) prepare() {
+	if c.APIKey == "" {
+		c.APIKey = rand.String(32)
+	}
+}
+
 func (c GUIConfiguration) Copy() GUIConfiguration {
 	return c
 }

+ 21 - 0
lib/config/optionsconfiguration.go

@@ -28,6 +28,27 @@ func (opts OptionsConfiguration) Copy() OptionsConfiguration {
 	return optsCopy
 }
 
+func (opts *OptionsConfiguration) prepare(guiPWIsSet bool) {
+	util.FillNilSlices(opts)
+
+	opts.RawListenAddresses = util.UniqueTrimmedStrings(opts.RawListenAddresses)
+	opts.RawGlobalAnnServers = util.UniqueTrimmedStrings(opts.RawGlobalAnnServers)
+
+	// Very short reconnection intervals are annoying
+	if opts.ReconnectIntervalS < 5 {
+		opts.ReconnectIntervalS = 5
+	}
+
+	if guiPWIsSet && len(opts.UnackedNotificationIDs) > 0 {
+		for i, key := range opts.UnackedNotificationIDs {
+			if key == "authenticationUserAndPassword" {
+				opts.UnackedNotificationIDs = append(opts.UnackedNotificationIDs[:i], opts.UnackedNotificationIDs[i+1:]...)
+				break
+			}
+		}
+	}
+}
+
 // RequiresRestartOnly returns a copy with only the attributes that require
 // restart on change.
 func (opts OptionsConfiguration) RequiresRestartOnly() OptionsConfiguration {

+ 10 - 3
lib/config/wrapper.go

@@ -55,6 +55,7 @@ func (noopWaiter) Wait() {}
 // notifications of changes to registered Handlers
 type Wrapper interface {
 	ConfigPath() string
+	MyID() protocol.DeviceID
 
 	RawCopy() Configuration
 	Replace(cfg Configuration) (Waiter, error)
@@ -97,6 +98,7 @@ type wrapper struct {
 	cfg      Configuration
 	path     string
 	evLogger events.Logger
+	myID     protocol.DeviceID
 
 	waiter Waiter // Latest ongoing config change
 	subs   []Committer
@@ -107,11 +109,12 @@ type wrapper struct {
 
 // Wrap wraps an existing Configuration structure and ties it to a file on
 // disk.
-func Wrap(path string, cfg Configuration, evLogger events.Logger) Wrapper {
+func Wrap(path string, cfg Configuration, myID protocol.DeviceID, evLogger events.Logger) Wrapper {
 	w := &wrapper{
 		cfg:      cfg,
 		path:     path,
 		evLogger: evLogger,
+		myID:     myID,
 		waiter:   noopWaiter{}, // Noop until first config change
 		mut:      sync.NewMutex(),
 	}
@@ -132,13 +135,17 @@ func Load(path string, myID protocol.DeviceID, evLogger events.Logger) (Wrapper,
 		return nil, 0, err
 	}
 
-	return Wrap(path, cfg, evLogger), originalVersion, nil
+	return Wrap(path, cfg, myID, evLogger), originalVersion, nil
 }
 
 func (w *wrapper) ConfigPath() string {
 	return w.path
 }
 
+func (w *wrapper) MyID() protocol.DeviceID {
+	return w.myID
+}
+
 // Subscribe registers the given handler to be called on any future
 // configuration changes.
 func (w *wrapper) Subscribe(c Committer) {
@@ -184,7 +191,7 @@ func (w *wrapper) Replace(cfg Configuration) (Waiter, error) {
 func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
 	from := w.cfg
 
-	if err := to.clean(); err != nil {
+	if err := to.prepare(w.myID); err != nil {
 		return noopWaiter{}, err
 	}
 

+ 2 - 1
lib/connections/lan_test.go

@@ -11,6 +11,7 @@ import (
 
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/events"
+	"github.com/syncthing/syncthing/lib/protocol"
 )
 
 func TestIsLANHost(t *testing.T) {
@@ -36,7 +37,7 @@ func TestIsLANHost(t *testing.T) {
 		Options: config.OptionsConfiguration{
 			AlwaysLocalNets: []string{"10.20.30.0/24"},
 		},
-	}, events.NoopLogger)
+	}, protocol.LocalDeviceID, events.NoopLogger)
 	s := &service{cfg: cfg}
 
 	for _, tc := range cases {

+ 1 - 1
lib/connections/limiter_test.go

@@ -30,7 +30,7 @@ func init() {
 }
 
 func initConfig() config.Wrapper {
-	cfg := config.Wrap("/dev/null", config.New(device1), events.NoopLogger)
+	cfg := config.Wrap("/dev/null", config.New(device1), device1, events.NoopLogger)
 	dev1Conf = config.NewDeviceConfiguration(device1, "device1")
 	dev2Conf = config.NewDeviceConfiguration(device2, "device2")
 	dev3Conf = config.NewDeviceConfiguration(device3, "device3")

+ 1 - 1
lib/discover/cache_test.go

@@ -23,7 +23,7 @@ func setupCache() *manager {
 	cfg.Options.LocalAnnEnabled = false
 	cfg.Options.GlobalAnnEnabled = false
 
-	return NewManager(protocol.LocalDeviceID, config.Wrap("", cfg, events.NoopLogger), tls.Certificate{}, events.NoopLogger, nil).(*manager)
+	return NewManager(protocol.LocalDeviceID, config.Wrap("", cfg, protocol.LocalDeviceID, events.NoopLogger), tls.Certificate{}, events.NoopLogger, nil).(*manager)
 }
 
 func TestCacheUnique(t *testing.T) {

+ 2 - 2
lib/model/model_test.go

@@ -115,7 +115,7 @@ func createTmpWrapper(cfg config.Configuration) config.Wrapper {
 	if err != nil {
 		panic(err)
 	}
-	wrapper := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger)
+	wrapper := config.Wrap(tmpFile.Name(), cfg, myID, events.NoopLogger)
 	tmpFile.Close()
 	return wrapper
 }
@@ -331,7 +331,7 @@ func TestDeviceRename(t *testing.T) {
 			DeviceID: device1,
 		},
 	}
-	cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg, events.NoopLogger)
+	cfg := config.Wrap("testdata/tmpconfig.xml", rawCfg, device1, events.NoopLogger)
 
 	db := db.NewLowlevel(backend.OpenMemory())
 	m := newModel(cfg, myID, "syncthing", "dev", db, nil)

+ 1 - 1
lib/nat/structs_test.go

@@ -64,7 +64,7 @@ func TestMappingClearAddresses(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	w := config.Wrap(tmpFile.Name(), config.Configuration{}, events.NoopLogger)
+	w := config.Wrap(tmpFile.Name(), config.Configuration{}, protocol.LocalDeviceID, events.NoopLogger)
 	defer os.RemoveAll(tmpFile.Name())
 	tmpFile.Close()
 

+ 3 - 3
lib/syncthing/syncthing_test.go

@@ -37,7 +37,7 @@ func TestShortIDCheck(t *testing.T) {
 			{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 0, 0}},
 			{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 1, 1}}, // first 56 bits same, differ in the first 64 bits
 		},
-	}, events.NoopLogger)
+	}, protocol.LocalDeviceID, events.NoopLogger)
 	defer os.Remove(cfg.ConfigPath())
 
 	if err := checkShortIDs(cfg); err != nil {
@@ -49,7 +49,7 @@ func TestShortIDCheck(t *testing.T) {
 			{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 0}},
 			{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 1}}, // first 64 bits same
 		},
-	}, events.NoopLogger)
+	}, protocol.LocalDeviceID, events.NoopLogger)
 
 	if err := checkShortIDs(cfg); err == nil {
 		t.Error("Should have gotten an error")
@@ -76,7 +76,7 @@ func TestStartupFail(t *testing.T) {
 			{DeviceID: id},
 			{DeviceID: conflID},
 		},
-	}, events.NoopLogger)
+	}, protocol.LocalDeviceID, events.NoopLogger)
 	defer os.Remove(cfg.ConfigPath())
 
 	db := backend.OpenMemory()

+ 2 - 2
lib/syncthing/utils.go

@@ -49,12 +49,12 @@ func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger,
 
 	if noDefaultFolder {
 		l.Infoln("We will skip creation of a default folder on first start")
-		return config.Wrap(path, newCfg, evLogger), nil
+		return config.Wrap(path, newCfg, myID, evLogger), nil
 	}
 
 	newCfg.Folders = append(newCfg.Folders, config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations.Get(locations.DefFolder)))
 	l.Infoln("Default folder created and/or linked to new config")
-	return config.Wrap(path, newCfg, evLogger), nil
+	return config.Wrap(path, newCfg, myID, evLogger), nil
 }
 
 // LoadConfigAtStartup loads an existing config. If it doesn't yet exist, it

+ 30 - 3
lib/util/utils.go

@@ -83,6 +83,10 @@ func SetDefaults(data interface{}) {
 			default:
 				panic(f.Type())
 			}
+		} else if f.CanSet() && f.Kind() == reflect.Struct && f.CanAddr() {
+			if addr := f.Addr(); addr.CanInterface() {
+				SetDefaults(addr.Interface())
+			}
 		}
 	}
 }
@@ -137,9 +141,22 @@ func UniqueTrimmedStrings(ss []string) []string {
 	return us
 }
 
+func FillNilExceptDeprecated(data interface{}) {
+	fillNil(data, true)
+}
+
 func FillNil(data interface{}) {
+	fillNil(data, false)
+}
+
+func fillNil(data interface{}, skipDeprecated bool) {
 	s := reflect.ValueOf(data).Elem()
+	t := s.Type()
 	for i := 0; i < s.NumField(); i++ {
+		if skipDeprecated && strings.HasPrefix(t.Field(i).Name, "Deprecated") {
+			continue
+		}
+
 		f := s.Field(i)
 
 		for f.Kind() == reflect.Ptr && f.IsZero() && f.CanSet() {
@@ -160,9 +177,19 @@ func FillNil(data interface{}) {
 				}
 			}
 
-			if f.Kind() == reflect.Struct && f.CanAddr() {
-				if addr := f.Addr(); addr.CanInterface() {
-					FillNil(addr.Interface())
+			switch f.Kind() {
+			case reflect.Slice:
+				if f.Type().Elem().Kind() != reflect.Struct {
+					continue
+				}
+				for i := 0; i < f.Len(); i++ {
+					fillNil(f.Index(i).Addr().Interface(), skipDeprecated)
+				}
+			case reflect.Struct:
+				if f.CanAddr() {
+					if addr := f.Addr(); addr.CanInterface() {
+						fillNil(addr.Interface(), skipDeprecated)
+					}
 				}
 			}
 		}

+ 2 - 1
lib/watchaggregator/aggregator_test.go

@@ -18,6 +18,7 @@ import (
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/fs"
+	"github.com/syncthing/syncthing/lib/protocol"
 )
 
 func TestMain(m *testing.M) {
@@ -47,7 +48,7 @@ var (
 	}
 	defaultCfg = config.Wrap("", config.Configuration{
 		Folders: []config.FolderConfiguration{defaultFolderCfg},
-	}, events.NoopLogger)
+	}, protocol.LocalDeviceID, events.NoopLogger)
 )
 
 // Represents possibly multiple (different event types) expected paths from