From b758ee3a74ed8f51c2719868b8b978c65557712c Mon Sep 17 00:00:00 2001 From: Adam Dinwoodie Date: Fri, 25 Sep 2026 11:51:47 +0200 Subject: [PATCH 1/3] nixosTests.syncthing-folders: avoid IFD Avoid import-from-derivation and non-reproducible derivations by generating node configurations for Syncthing tests in advance, rather than generating them at eval time. The latter requires import-from-derivation and also means every time the node configurations are generated, syncthing will generate unique certificates, meaning those derivations are different on every build. While we're rewriting things, convert the IFD part to a script that can be run to generate new Syncthing certificates and node IDs, and which was used to generate the certificates and IDs in this commit. --- nixos/tests/syncthing/folders.nix | 65 ++++++++++++++------- nixos/tests/syncthing/test-nodes/a/cert.pem | 11 ++++ nixos/tests/syncthing/test-nodes/a/id | 1 + nixos/tests/syncthing/test-nodes/a/key.pem | 3 + nixos/tests/syncthing/test-nodes/b/cert.pem | 11 ++++ nixos/tests/syncthing/test-nodes/b/id | 1 + nixos/tests/syncthing/test-nodes/b/key.pem | 3 + nixos/tests/syncthing/test-nodes/c/cert.pem | 11 ++++ nixos/tests/syncthing/test-nodes/c/id | 1 + nixos/tests/syncthing/test-nodes/c/key.pem | 3 + 10 files changed, 88 insertions(+), 22 deletions(-) create mode 100644 nixos/tests/syncthing/test-nodes/a/cert.pem create mode 100644 nixos/tests/syncthing/test-nodes/a/id create mode 100644 nixos/tests/syncthing/test-nodes/a/key.pem create mode 100644 nixos/tests/syncthing/test-nodes/b/cert.pem create mode 100644 nixos/tests/syncthing/test-nodes/b/id create mode 100644 nixos/tests/syncthing/test-nodes/b/key.pem create mode 100644 nixos/tests/syncthing/test-nodes/c/cert.pem create mode 100644 nixos/tests/syncthing/test-nodes/c/id create mode 100644 nixos/tests/syncthing/test-nodes/c/key.pem diff --git a/nixos/tests/syncthing/folders.nix b/nixos/tests/syncthing/folders.nix index 1a182da4c845..267926135ee3 100644 --- a/nixos/tests/syncthing/folders.nix +++ b/nixos/tests/syncthing/folders.nix @@ -1,15 +1,8 @@ { lib, pkgs, ... }: let - genNodeId = - name: - pkgs.runCommand "syncthing-test-certs-${name}" { } '' - mkdir -p $out - ${pkgs.syncthing}/bin/syncthing generate --home=$out - ${pkgs.libxml2}/bin/xmllint --xpath 'string(configuration/device/@id)' $out/config.xml > $out/id - ''; - idA = genNodeId "a"; - idB = genNodeId "b"; - idC = genNodeId "c"; + nodeA = ./test-nodes/a; + nodeB = ./test-nodes/b; + nodeC = ./test-nodes/c; testPassword = "it's a secret"; in { @@ -24,12 +17,12 @@ in services.syncthing = { enable = true; openDefaultPorts = true; - cert = "${idA}/cert.pem"; - key = "${idA}/key.pem"; + cert = "${nodeA}/cert.pem"; + key = "${nodeA}/key.pem"; guiAddress = "unix:///run/syncthing/syncthing.sock"; settings = { - devices.b.id = lib.fileContents "${idB}/id"; - devices.c.id = lib.fileContents "${idC}/id"; + devices.b.id = lib.fileContents "${nodeB}/id"; + devices.c.id = lib.fileContents "${nodeC}/id"; folders.foo = { path = "/var/lib/syncthing/foo"; devices = [ "b" ]; @@ -67,11 +60,11 @@ in services.syncthing = { enable = true; openDefaultPorts = true; - cert = "${idB}/cert.pem"; - key = "${idB}/key.pem"; + cert = "${nodeB}/cert.pem"; + key = "${nodeB}/key.pem"; settings = { - devices.a.id = lib.fileContents "${idA}/id"; - devices.c.id = lib.fileContents "${idC}/id"; + devices.a.id = lib.fileContents "${nodeA}/id"; + devices.c.id = lib.fileContents "${nodeC}/id"; folders.foo = { path = "/var/lib/syncthing/foo"; devices = [ "a" ]; @@ -115,11 +108,11 @@ in services.syncthing = { enable = true; openDefaultPorts = true; - cert = "${idC}/cert.pem"; - key = "${idC}/key.pem"; + cert = "${nodeC}/cert.pem"; + key = "${nodeC}/key.pem"; settings = { - devices.a.id = lib.fileContents "${idA}/id"; - devices.b.id = lib.fileContents "${idB}/id"; + devices.a.id = lib.fileContents "${nodeA}/id"; + devices.b.id = lib.fileContents "${nodeB}/id"; folders.bar = { path = "/var/lib/syncthing/bar"; devices = [ @@ -143,6 +136,34 @@ in }; }; + # Run from the root of the nixpkgs repository with + # + # nix-build -A nixosTests.syncthing-folders.genNodeData && + # ./result/bin/genNodeData.sh + # + # This generates new keys, certificates, and overall Syncthing config, and + # updates the certificate and key files and the ID file extracted from the + # overall Syncthing config file. + passthru.genNodeData = pkgs.writeShellApplication { + name = "genNodeData.sh"; + runtimeInputs = with pkgs; [ + syncthing + libxml2 + ]; + text = '' + rm -r nixos/tests/syncthing/test-nodes + mkdir nixos/tests/syncthing/test-nodes + cd nixos/tests/syncthing/test-nodes + + for d in a b c; do + mkdir -- "$d" + syncthing generate --home="$d" + xmllint --xpath 'string(configuration/device/@id)' "$d"/config.xml >"$d"/id + rm -f -- "$d"/.syncthing.tmp.* "$d"/config.xml + done + ''; + }; + testScript = '' start_all() diff --git a/nixos/tests/syncthing/test-nodes/a/cert.pem b/nixos/tests/syncthing/test-nodes/a/cert.pem new file mode 100644 index 000000000000..e4a948a25727 --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/a/cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBoDCCAVKgAwIBAgIJAJ7l/z0JF6aOMAUGAytlcDBKMRIwEAYDVQQKEwlTeW5j +dGhpbmcxIDAeBgNVBAsTF0F1dG9tYXRpY2FsbHkgR2VuZXJhdGVkMRIwEAYDVQQD +EwlzeW5jdGhpbmcwHhcNMjYwNDAyMDAwMDAwWhcNNDYwMzI4MDAwMDAwWjBKMRIw +EAYDVQQKEwlTeW5jdGhpbmcxIDAeBgNVBAsTF0F1dG9tYXRpY2FsbHkgR2VuZXJh +dGVkMRIwEAYDVQQDEwlzeW5jdGhpbmcwKjAFBgMrZXADIQBCsJ1O6QrxxFP/YKKj +WAjdZp07AiTIIC0/p/mHGbmyraNVMFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQW +MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC +CXN5bmN0aGluZzAFBgMrZXADQQBqGFXpwvEoAc7N6mT9evXI3++STiTE6Lu3Z2z3 +ecp5vU3fS5U+b0fO4BEUrNoaDdXurfOal6+LoydAnJcFamwN +-----END CERTIFICATE----- diff --git a/nixos/tests/syncthing/test-nodes/a/id b/nixos/tests/syncthing/test-nodes/a/id new file mode 100644 index 000000000000..97a37907b52c --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/a/id @@ -0,0 +1 @@ +F2ACIUG-FML5RHY-ATV55ZN-5KGVUIV-RWFG3Y6-QPLTKZB-NWUZAGD-7QRCWAP diff --git a/nixos/tests/syncthing/test-nodes/a/key.pem b/nixos/tests/syncthing/test-nodes/a/key.pem new file mode 100644 index 000000000000..bb6111462cfc --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/a/key.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIE2ls259KcQgtizG7hwP3aBhlYBNuPJwSBG8d4uVBFOh +-----END PRIVATE KEY----- diff --git a/nixos/tests/syncthing/test-nodes/b/cert.pem b/nixos/tests/syncthing/test-nodes/b/cert.pem new file mode 100644 index 000000000000..a3fbcd34dc46 --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/b/cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBnzCCAVGgAwIBAgIIKyLKAcqLWn4wBQYDK2VwMEoxEjAQBgNVBAoTCVN5bmN0 +aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0ZWQxEjAQBgNVBAMT +CXN5bmN0aGluZzAeFw0yNjA0MDIwMDAwMDBaFw00NjAzMjgwMDAwMDBaMEoxEjAQ +BgNVBAoTCVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0 +ZWQxEjAQBgNVBAMTCXN5bmN0aGluZzAqMAUGAytlcAMhABoahydRmwpbCII6mz9i +E8FeGH7YdHqgMeAmLiFZu2wMo1UwUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJ +c3luY3RoaW5nMAUGAytlcANBADFVKB2vF16j0gc/h71x3vUi042FbmXTPk9kMb2O +9O0NnWSoMuOGPFDEoHWBFuUuixQ/3cw45zw+fea28m95gQo= +-----END CERTIFICATE----- diff --git a/nixos/tests/syncthing/test-nodes/b/id b/nixos/tests/syncthing/test-nodes/b/id new file mode 100644 index 000000000000..a96022726527 --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/b/id @@ -0,0 +1 @@ +LOB4D2H-OYG64QZ-NDX43LJ-NYR4HQU-BRNXCNI-ERACWFP-OVURLBI-63MR5QP diff --git a/nixos/tests/syncthing/test-nodes/b/key.pem b/nixos/tests/syncthing/test-nodes/b/key.pem new file mode 100644 index 000000000000..4253bf0f9255 --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/b/key.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIJtOML1Tshk03rB41IX5ajEeem50CdWzHDimotheTyZi +-----END PRIVATE KEY----- diff --git a/nixos/tests/syncthing/test-nodes/c/cert.pem b/nixos/tests/syncthing/test-nodes/c/cert.pem new file mode 100644 index 000000000000..1561c4ac3465 --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/c/cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBoDCCAVKgAwIBAgIJAIZk5+sv3EbTMAUGAytlcDBKMRIwEAYDVQQKEwlTeW5j +dGhpbmcxIDAeBgNVBAsTF0F1dG9tYXRpY2FsbHkgR2VuZXJhdGVkMRIwEAYDVQQD +EwlzeW5jdGhpbmcwHhcNMjYwNDAyMDAwMDAwWhcNNDYwMzI4MDAwMDAwWjBKMRIw +EAYDVQQKEwlTeW5jdGhpbmcxIDAeBgNVBAsTF0F1dG9tYXRpY2FsbHkgR2VuZXJh +dGVkMRIwEAYDVQQDEwlzeW5jdGhpbmcwKjAFBgMrZXADIQB0QK+PM7oLutgCKoIo +UOh8/XiSmGJWbkN175hALTIaYKNVMFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQW +MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC +CXN5bmN0aGluZzAFBgMrZXADQQAbedm895vhgYizMXc38IwhquYv2S4ORHZac0Bw +ZYKJKze7EhKzqvdxcU5uVIUaMPSGi86wtmmsiijfhY8rnD0E +-----END CERTIFICATE----- diff --git a/nixos/tests/syncthing/test-nodes/c/id b/nixos/tests/syncthing/test-nodes/c/id new file mode 100644 index 000000000000..492e50839af2 --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/c/id @@ -0,0 +1 @@ +MPGAF2L-AUIF5DE-UCI3AMX-PTGF3ZI-XDS6UJI-YUU4ZWT-UUWY7X6-N2DAAQG diff --git a/nixos/tests/syncthing/test-nodes/c/key.pem b/nixos/tests/syncthing/test-nodes/c/key.pem new file mode 100644 index 000000000000..2577c5f60064 --- /dev/null +++ b/nixos/tests/syncthing/test-nodes/c/key.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIMSmcIlXQTKkaMaOLh+zsgU1ULCvCz949E56OzQ1dsMK +-----END PRIVATE KEY----- From 6455637cda21eb0dc3cd3516ad1593752d71674c Mon Sep 17 00:00:00 2001 From: Adam Dinwoodie Date: Mon, 28 Sep 2026 11:59:34 +0200 Subject: [PATCH 2/3] nixosTests.syncthing-{folders,no-settings}: clarify names Change test names to match the name of the test, so different syncthing tests can be distinguished. --- nixos/tests/syncthing/folders.nix | 2 +- nixos/tests/syncthing/no-settings.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/tests/syncthing/folders.nix b/nixos/tests/syncthing/folders.nix index 267926135ee3..d0d889e7d6d5 100644 --- a/nixos/tests/syncthing/folders.nix +++ b/nixos/tests/syncthing/folders.nix @@ -6,7 +6,7 @@ let testPassword = "it's a secret"; in { - name = "syncthing"; + name = "syncthing-folders"; meta.maintainers = with pkgs.lib.maintainers; [ zarelit ]; nodes = { diff --git a/nixos/tests/syncthing/no-settings.nix b/nixos/tests/syncthing/no-settings.nix index ee79d389e92a..c389d95ac7fa 100644 --- a/nixos/tests/syncthing/no-settings.nix +++ b/nixos/tests/syncthing/no-settings.nix @@ -1,6 +1,6 @@ { lib, pkgs, ... }: { - name = "syncthing"; + name = "syncthing-no-settings"; meta.maintainers = with pkgs.lib.maintainers; [ chkno ]; nodes = { From 0db3be3aca444a5c29a5b635d8b60eeb0e773d32 Mon Sep 17 00:00:00 2001 From: Adam Dinwoodie Date: Fri, 25 Sep 2026 11:51:47 +0200 Subject: [PATCH 3/3] nixosTests.syncthing-folders: refactor for clarity Rewrite the test node configuration to use multiple modules and group config according to the Syncthing folder it's being used for, rather than by node, to make the different test cases clearer. --- nixos/tests/syncthing/folders.nix | 290 ++++++++++++++++-------------- 1 file changed, 159 insertions(+), 131 deletions(-) diff --git a/nixos/tests/syncthing/folders.nix b/nixos/tests/syncthing/folders.nix index d0d889e7d6d5..06ee16e1c3a6 100644 --- a/nixos/tests/syncthing/folders.nix +++ b/nixos/tests/syncthing/folders.nix @@ -1,141 +1,161 @@ { lib, pkgs, ... }: let - nodeA = ./test-nodes/a; - nodeB = ./test-nodes/b; - nodeC = ./test-nodes/c; + nodeNames = [ + "a" + "b" + "c" + ]; + nodeDirs = lib.genAttrs nodeNames (n: ./test-nodes + "/${n}"); + nodeData = lib.mapAttrs (n: v: { + cert = "${v}/cert.pem"; + key = "${v}/key.pem"; + id = lib.fileContents (v + "/id"); + }) nodeDirs; + testPassword = "it's a secret"; + + commonNodeConfigModule = { + services.syncthing = { + enable = true; + openDefaultPorts = true; + settings.devices = lib.mapAttrs (n: v: { inherit (v) id; }) nodeData; + }; + }; + + nodeConfigModules = { + a = { + services.syncthing = { + inherit (nodeData.a) cert key; + guiAddress = "unix:///run/syncthing/syncthing.sock"; + }; + }; + b = { + services.syncthing = { inherit (nodeData.b) cert key; }; + }; + c = { + services.syncthing = { inherit (nodeData.c) cert key; }; + }; + }; + + nodeFolderConfigModules = [ + # "foo" is a folder that is synchronised only between nodes a and b. + rec { + a = { + services.syncthing.settings.folders.foo = { + path = "/var/lib/syncthing/foo"; + devices = [ + "a" + "b" + ]; + }; + }; + + b = a; + + c = { }; + } + + # "bar" is synchronised between a and c, and between b and c, but c only + # gets an encrypted copy, and a and b never synchronise directly to each + # other. + rec { + a = + { config, ... }: + { + environment.etc.bar-encryption-password.text = testPassword; + + services.syncthing.settings.folders.bar = { + path = "/var/lib/syncthing/bar"; + devices = [ + { + name = "c"; + encryptionPasswordFile = "/etc/${config.environment.etc.bar-encryption-password.target}"; + } + ]; + }; + }; + + b = a; + + c = { + services.syncthing.settings.folders.bar = { + path = "/var/lib/syncthing/bar"; + devices = [ + "a" + "b" + ]; + type = "receiveencrypted"; + }; + }; + } + + # "baz" is synchronised between all three nodes, but has filters on b and c + # that mean they shouldn't receive certain files. + { + a = { + services.syncthing.settings.folders.baz = { + path = "/var/lib/syncthing/baz"; + devices = [ + "b" + "c" + ]; + ignorePatterns = [ ]; + }; + }; + + b = { + services.syncthing.settings.folders.baz = { + path = "/var/lib/syncthing/baz"; + devices = [ + "a" + "c" + ]; + ignorePatterns = [ + "notB" + # Just test that an apostrophe doesn't break the curl config + # commands. See: https://github.com/NixOS/nixpkgs/issues/554744 + "apostrophe'" + ]; + }; + }; + + c = { + services.syncthing.settings.folders.baz = { + path = "/var/lib/syncthing/baz"; + devices = [ + "a" + "b" + ]; + ignorePatterns = [ "notC" ]; + }; + }; + } + + # "foo bar" tests handling whitespace in folder IDs. + { + a = { + services.syncthing.settings.folders."foo bar" = { + path = "/var/lib/syncthing/foo-bar"; + devices = [ "b" ]; + }; + }; + + b = { + services.syncthing.settings.folders."foo bar" = { + path = "/var/lib/syncthing/foo-bar"; + devices = [ "a" ]; + ignorePatterns = [ "notB" ]; + }; + }; + + c = { }; + } + ]; in { name = "syncthing-folders"; meta.maintainers = with pkgs.lib.maintainers; [ zarelit ]; - nodes = { - a = - { config, ... }: - { - environment.etc.bar-encryption-password.text = testPassword; - services.syncthing = { - enable = true; - openDefaultPorts = true; - cert = "${nodeA}/cert.pem"; - key = "${nodeA}/key.pem"; - guiAddress = "unix:///run/syncthing/syncthing.sock"; - settings = { - devices.b.id = lib.fileContents "${nodeB}/id"; - devices.c.id = lib.fileContents "${nodeC}/id"; - folders.foo = { - path = "/var/lib/syncthing/foo"; - devices = [ "b" ]; - }; - folders.bar = { - path = "/var/lib/syncthing/bar"; - devices = [ - { - name = "c"; - encryptionPasswordFile = "/etc/${config.environment.etc.bar-encryption-password.target}"; - } - ]; - }; - folders.baz = { - path = "/var/lib/syncthing/baz"; - devices = [ - "b" - "c" - ]; - ignorePatterns = [ ]; - }; - folders."foo bar" = { - path = "/var/lib/syncthing/foo-bar"; - devices = [ - "b" - ]; - }; - }; - }; - }; - b = - { config, ... }: - { - environment.etc.bar-encryption-password.text = testPassword; - services.syncthing = { - enable = true; - openDefaultPorts = true; - cert = "${nodeB}/cert.pem"; - key = "${nodeB}/key.pem"; - settings = { - devices.a.id = lib.fileContents "${nodeA}/id"; - devices.c.id = lib.fileContents "${nodeC}/id"; - folders.foo = { - path = "/var/lib/syncthing/foo"; - devices = [ "a" ]; - }; - folders.bar = { - path = "/var/lib/syncthing/bar"; - devices = [ - { - name = "c"; - encryptionPasswordFile = "/etc/${config.environment.etc.bar-encryption-password.target}"; - } - ]; - }; - folders.baz = { - path = "/var/lib/syncthing/baz"; - devices = [ - "a" - "c" - ]; - ignorePatterns = [ - "notB" - ]; - }; - # Test how we handle white spaces in folder IDs - folders."foo bar" = { - path = "/var/lib/syncthing/foo-bar"; - devices = [ - "a" - ]; - ignorePatterns = [ - "notB" - # Just test that an apostrophe doesn't break the curl config - # commands. See: https://github.com/NixOS/nixpkgs/issues/554744 - "apostrophe'" - ]; - }; - }; - }; - }; - c = { - services.syncthing = { - enable = true; - openDefaultPorts = true; - cert = "${nodeC}/cert.pem"; - key = "${nodeC}/key.pem"; - settings = { - devices.a.id = lib.fileContents "${nodeA}/id"; - devices.b.id = lib.fileContents "${nodeB}/id"; - folders.bar = { - path = "/var/lib/syncthing/bar"; - devices = [ - "a" - "b" - ]; - type = "receiveencrypted"; - }; - folders.baz = { - path = "/var/lib/syncthing/baz"; - devices = [ - "a" - "b" - ]; - ignorePatterns = [ - "notC" - ]; - }; - }; - }; - }; - }; - # Run from the root of the nixpkgs repository with # # nix-build -A nixosTests.syncthing-folders.genNodeData && @@ -155,7 +175,7 @@ in mkdir nixos/tests/syncthing/test-nodes cd nixos/tests/syncthing/test-nodes - for d in a b c; do + for d in ${lib.escapeShellArgs nodeNames}; do mkdir -- "$d" syncthing generate --home="$d" xmllint --xpath 'string(configuration/device/@id)' "$d"/config.xml >"$d"/id @@ -164,6 +184,14 @@ in ''; }; + nodes = lib.genAttrs nodeNames (n: { + imports = [ + commonNodeConfigModule + nodeConfigModules."${n}" + ] + ++ map (builtins.getAttr n) nodeFolderConfigModules; + }); + testScript = '' start_all()