nixos/syncthing: add guiPasswordFile option, add and move Syncthing tests (#446197)

This commit is contained in:
Doron Behar 2025-10-04 16:50:14 +00:00 committed by GitHub
commit 0fe515d371
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 147 additions and 8 deletions

View file

@ -236,13 +236,14 @@ let
+ +
/* /*
Now we update the other settings defined in cleanedConfig which are not Now we update the other settings defined in cleanedConfig which are not
"folders" or "devices". "folders", "devices", or "guiPasswordFile".
*/ */
(lib.pipe cleanedConfig [ (lib.pipe cleanedConfig [
builtins.attrNames builtins.attrNames
(lib.subtractLists [ (lib.subtractLists [
"folders" "folders"
"devices" "devices"
"guiPasswordFile"
]) ])
(map (subOption: '' (map (subOption: ''
curl -X PUT -d ${ curl -X PUT -d ${
@ -251,6 +252,12 @@ let
'')) ''))
(lib.concatStringsSep "\n") (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 # restart Syncthing if required
if curl ${curlAddressArgs "/rest/config/restart-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 { overrideDevices = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
@ -837,6 +852,12 @@ in
from the configuration, creating path conflicts. 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 { networking.firewall = mkIf cfg.openDefaultPorts {

View file

@ -1402,12 +1402,16 @@ in
switchTest = runTest ./switch-test.nix; switchTest = runTest ./switch-test.nix;
sx = runTest ./sx.nix; sx = runTest ./sx.nix;
sympa = runTest ./sympa.nix; sympa = runTest ./sympa.nix;
syncthing = runTest ./syncthing.nix; syncthing = runTest ./syncthing/main.nix;
syncthing-folders = runTest ./syncthing-folders.nix; syncthing-folders = runTest ./syncthing/folders.nix;
syncthing-init = runTest ./syncthing-init.nix; syncthing-guiPassword = runTest ./syncthing/guiPassword.nix;
syncthing-many-devices = runTest ./syncthing-many-devices.nix; syncthing-guiPasswordFile = runTest ./syncthing/guiPasswordFile.nix;
syncthing-no-settings = runTest ./syncthing-no-settings.nix; syncthing-init = runTest ./syncthing/init.nix;
syncthing-relay = runTest ./syncthing-relay.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; sysfs = runTest ./sysfs.nix;
sysinit-reactivation = runTest ./sysinit-reactivation.nix; sysinit-reactivation = runTest ./sysinit-reactivation.nix;
systemd = runTest ./systemd.nix; systemd = runTest ./systemd.nix;

View file

@ -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
'';
}

View file

@ -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
'';
}

View file

@ -67,8 +67,10 @@ let
tests = { tests = {
inherit (nixosTests) inherit (nixosTests)
syncthing syncthing
syncthing-folders
syncthing-guiPassword
syncthing-guiPasswordFile
syncthing-init syncthing-init
syncthing-many-devices
syncthing-no-settings syncthing-no-settings
syncthing-relay syncthing-relay
; ;