Loading nixos/modules/services/networking/syncthing.nix +22 −1 Original line number Diff line number Diff line Loading @@ -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 ${ Loading @@ -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"} | Loading Loading @@ -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; Loading Loading @@ -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 { Loading nixos/tests/all-tests.nix +10 −6 Original line number Diff line number Diff line Loading @@ -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; Loading nixos/tests/syncthing-folders.nix→nixos/tests/syncthing/folders.nix +0 −0 File moved. View file nixos/tests/syncthing/guiPassword.nix 0 → 100644 +56 −0 Original line number Diff line number Diff line { 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 ''; } nixos/tests/syncthing/guiPasswordFile.nix 0 → 100644 +56 −0 Original line number Diff line number Diff line { 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 ''; } Loading
nixos/modules/services/networking/syncthing.nix +22 −1 Original line number Diff line number Diff line Loading @@ -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 ${ Loading @@ -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"} | Loading Loading @@ -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; Loading Loading @@ -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 { Loading
nixos/tests/all-tests.nix +10 −6 Original line number Diff line number Diff line Loading @@ -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; Loading
nixos/tests/syncthing/guiPassword.nix 0 → 100644 +56 −0 Original line number Diff line number Diff line { 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 ''; }
nixos/tests/syncthing/guiPasswordFile.nix 0 → 100644 +56 −0 Original line number Diff line number Diff line { 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 ''; }