diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index aa4d08a7f74a..c3baf59db455 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -236,13 +236,14 @@ let + /* Now we update the other settings defined in cleanedConfig which are not - "folders" or "devices". + "folders", "devices", or "guiPasswordFile". */ (lib.pipe cleanedConfig [ builtins.attrNames (lib.subtractLists [ "folders" "devices" + "guiPasswordFile" ]) (map (subOption: '' curl -X PUT -d ${ @@ -251,6 +252,12 @@ let '')) (lib.concatStringsSep "\n") ]) + + + # Now we hash the contents of guiPasswordFile and use the result to update the gui password + (lib.optionalString (cfg.guiPasswordFile != null) '' + ${pkgs.mkpasswd}/bin/mkpasswd -m bcrypt --stdin <"${cfg.guiPasswordFile}" | tr -d "\n" > "$RUNTIME_DIRECTORY/password_bcrypt" + curl -X PATCH --variable "pw_bcrypt@$RUNTIME_DIRECTORY/password_bcrypt" --expand-json '{ "password": "{{pw_bcrypt}}" }' ${curlAddressArgs "/rest/config/gui"} + '') + '' # restart Syncthing if required if curl ${curlAddressArgs "/rest/config/restart-required"} | @@ -285,6 +292,14 @@ in ''; }; + guiPasswordFile = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to file containing the plaintext password for Syncthing's GUI. + ''; + }; + overrideDevices = mkOption { type = types.bool; default = true; @@ -837,6 +852,12 @@ in from the configuration, creating path conflicts. ''; } + { + assertion = (lib.hasAttrByPath [ "gui" "password" ] cfg.settings) -> cfg.guiPasswordFile == null; + message = '' + Please use only one of services.syncthing.settings.gui.password or services.syncthing.guiPasswordFile. + ''; + } ]; networking.firewall = mkIf cfg.openDefaultPorts { diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 8570caba92f2..55d1f2e788e3 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1402,12 +1402,16 @@ in switchTest = runTest ./switch-test.nix; sx = runTest ./sx.nix; sympa = runTest ./sympa.nix; - syncthing = runTest ./syncthing.nix; - syncthing-folders = runTest ./syncthing-folders.nix; - syncthing-init = runTest ./syncthing-init.nix; - syncthing-many-devices = runTest ./syncthing-many-devices.nix; - syncthing-no-settings = runTest ./syncthing-no-settings.nix; - syncthing-relay = runTest ./syncthing-relay.nix; + syncthing = runTest ./syncthing/main.nix; + syncthing-folders = runTest ./syncthing/folders.nix; + syncthing-guiPassword = runTest ./syncthing/guiPassword.nix; + syncthing-guiPasswordFile = runTest ./syncthing/guiPasswordFile.nix; + syncthing-init = runTest ./syncthing/init.nix; + # FIXME: Test has been failing since 2025-07-06: + # https://github.com/NixOS/nixpkgs/issues/447674 + # syncthing-many-devices = runTest ./syncthing/many-devices.nix; + syncthing-no-settings = runTest ./syncthing/no-settings.nix; + syncthing-relay = runTest ./syncthing/relay.nix; sysfs = runTest ./sysfs.nix; sysinit-reactivation = runTest ./sysinit-reactivation.nix; systemd = runTest ./systemd.nix; diff --git a/nixos/tests/syncthing-folders.nix b/nixos/tests/syncthing/folders.nix similarity index 100% rename from nixos/tests/syncthing-folders.nix rename to nixos/tests/syncthing/folders.nix diff --git a/nixos/tests/syncthing/guiPassword.nix b/nixos/tests/syncthing/guiPassword.nix new file mode 100644 index 000000000000..ab802b68f6c0 --- /dev/null +++ b/nixos/tests/syncthing/guiPassword.nix @@ -0,0 +1,56 @@ +{ lib, pkgs, ... }: +{ + name = "syncthing-guiPassword"; + meta.maintainers = with lib.maintainers; [ nullcube ]; + enableOCR = true; + + nodes.machine = { + imports = [ ../common/x11.nix ]; + environment.systemPackages = with pkgs; [ + syncthing + xdotool + ]; + + programs.firefox = { + enable = true; + preferences = { + # Prevent firefox from asking to save the password + "signon.rememberSignons" = false; + }; + }; + + services.syncthing = { + enable = true; + settings.options.urAccepted = -1; + settings.gui = { + insecureAdminAccess = false; + user = "alice"; + password = "alice_password"; + }; + }; + }; + + testScript = '' + machine.wait_for_unit("syncthing.service") + machine.wait_for_x() + machine.execute("xterm -e 'firefox 127.0.0.1:8384' >&2 &") + machine.wait_for_window("Syncthing") + machine.screenshot("pre-login") + + with subtest("Syncthing requests authentication"): + machine.wait_for_text("Authentication Required", 10) + + with subtest("Syncthing password is valid"): + machine.execute("xdotool type \"alice\"") + machine.execute("xdotool key Tab") + machine.execute("xdotool type \"alice_password\"") + machine.execute("xdotool key Enter") + machine.sleep(2) + machine.wait_for_text("This Device", 10) + machine.screenshot("post-login") + + with subtest("Plaintext Syncthing password is not in final config"): + config = machine.succeed("cat /var/lib/syncthing/.config/syncthing/config.xml") + assert "alice_password" not in config + ''; +} diff --git a/nixos/tests/syncthing/guiPasswordFile.nix b/nixos/tests/syncthing/guiPasswordFile.nix new file mode 100644 index 000000000000..fa25fe946c6a --- /dev/null +++ b/nixos/tests/syncthing/guiPasswordFile.nix @@ -0,0 +1,56 @@ +{ lib, pkgs, ... }: +{ + name = "syncthing-guiPasswordFile"; + meta.maintainers = with lib.maintainers; [ nullcube ]; + enableOCR = true; + + nodes.machine = { + imports = [ ../common/x11.nix ]; + environment.systemPackages = with pkgs; [ + syncthing + xdotool + ]; + + programs.firefox = { + enable = true; + preferences = { + # Prevent firefox from asking to save the password + "signon.rememberSignons" = false; + }; + }; + + services.syncthing = { + enable = true; + settings.options.urAccepted = -1; + settings.gui = { + insecureAdminAccess = false; + user = "alice"; + }; + guiPasswordFile = (pkgs.writeText "syncthing-password-file" ''alice_password'').outPath; + }; + }; + + testScript = '' + machine.wait_for_unit("syncthing.service") + machine.wait_for_x() + machine.execute("xterm -e 'firefox 127.0.0.1:8384' >&2 &") + machine.wait_for_window("Syncthing") + machine.screenshot("pre-login") + + with subtest("Syncthing requests authentication"): + machine.wait_for_text("Authentication Required", 10) + + with subtest("Syncthing password is valid"): + machine.execute("xdotool type \"alice\"") + machine.execute("xdotool key Tab") + machine.execute("xdotool type \"alice_password\"") + machine.execute("xdotool key Enter") + machine.sleep(2) + machine.wait_for_text("This Device", 10) + machine.screenshot("post-login") + + with subtest("Plaintext Syncthing password is not in final config"): + config = machine.succeed("cat /var/lib/syncthing/.config/syncthing/config.xml") + assert "alice_password" not in config + ''; +} diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing/init.nix similarity index 100% rename from nixos/tests/syncthing-init.nix rename to nixos/tests/syncthing/init.nix diff --git a/nixos/tests/syncthing.nix b/nixos/tests/syncthing/main.nix similarity index 100% rename from nixos/tests/syncthing.nix rename to nixos/tests/syncthing/main.nix diff --git a/nixos/tests/syncthing-many-devices.nix b/nixos/tests/syncthing/many-devices.nix similarity index 100% rename from nixos/tests/syncthing-many-devices.nix rename to nixos/tests/syncthing/many-devices.nix diff --git a/nixos/tests/syncthing-no-settings.nix b/nixos/tests/syncthing/no-settings.nix similarity index 100% rename from nixos/tests/syncthing-no-settings.nix rename to nixos/tests/syncthing/no-settings.nix diff --git a/nixos/tests/syncthing-relay.nix b/nixos/tests/syncthing/relay.nix similarity index 100% rename from nixos/tests/syncthing-relay.nix rename to nixos/tests/syncthing/relay.nix diff --git a/pkgs/applications/networking/syncthing/default.nix b/pkgs/applications/networking/syncthing/default.nix index 50506a77f795..97b84cd59dbc 100644 --- a/pkgs/applications/networking/syncthing/default.nix +++ b/pkgs/applications/networking/syncthing/default.nix @@ -67,8 +67,10 @@ let tests = { inherit (nixosTests) syncthing + syncthing-folders + syncthing-guiPassword + syncthing-guiPasswordFile syncthing-init - syncthing-many-devices syncthing-no-settings syncthing-relay ;