diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index c84a3e3b01938..3d887f80f352d 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -79,7 +79,32 @@
Other Notable Changes - - + + + + The option + services.redis.servers + was added to support per-application + redis-server which is more secure since + Redis databases are only mere key prefixes without any + configuration or ACL of their own. Backward-compatibility is + preserved by mapping old + services.redis.settings to + services.redis.servers."".settings, + but you are strongly encouraged to name each + redis-server instance after the application + using it, instead of keeping that nameless one. Except for the + nameless + services.redis.servers."" still + accessible at 127.0.0.1:6379, and to the + members of the Unix group redis through the + Unix socket /run/redis/redis.sock, all + other services.redis.servers.${serverName} + are only accessible by default to the members of the Unix + group redis-${serverName} through the Unix + socket /run/redis-${serverName}/redis.sock. + + +
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index 45ed69cf1b031..2b5ed3795df97 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -33,3 +33,19 @@ In addition to numerous new and upgraded packages, this release has the followin Please switch to `claws-mail`, which is Claws Mail's latest release based on GTK+3 and Python 3. ## Other Notable Changes {#sec-release-22.05-notable-changes} + +- The option [services.redis.servers](#opt-services.redis.servers) was added + to support per-application `redis-server` which is more secure since Redis databases + are only mere key prefixes without any configuration or ACL of their own. + Backward-compatibility is preserved by mapping old `services.redis.settings` + to `services.redis.servers."".settings`, but you are strongly encouraged + to name each `redis-server` instance after the application using it, + instead of keeping that nameless one. + Except for the nameless `services.redis.servers.""` + still accessible at `127.0.0.1:6379`, + and to the members of the Unix group `redis` + through the Unix socket `/run/redis/redis.sock`, + all other `services.redis.servers.${serverName}` + are only accessible by default + to the members of the Unix group `redis-${serverName}` + through the Unix socket `/run/redis-${serverName}/redis.sock`. diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix index 578d9d9ec8d78..c5513635392cd 100644 --- a/nixos/modules/services/databases/redis.nix +++ b/nixos/modules/services/databases/redis.nix @@ -5,17 +5,18 @@ with lib; let cfg = config.services.redis; - ulimitNofile = cfg.maxclients + 32; - mkValueString = value: if value == true then "yes" else if value == false then "no" else generators.mkValueStringDefault { } value; - redisConfig = pkgs.writeText "redis.conf" (generators.toKeyValue { + redisConfig = settings: pkgs.writeText "redis.conf" (generators.toKeyValue { listsAsDuplicateKeys = true; mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " "; - } cfg.settings); + } settings); + + redisName = name: "redis" + optionalString (name != "") ("-"+name); + enabledServers = filterAttrs (name: conf: conf.enable) config.services.redis.servers; in { imports = [ @@ -24,7 +25,28 @@ in { (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.") (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.") (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.") - (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.settings instead.") + (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.servers.*.settings instead.") + (mkRenamedOptionModule [ "services" "redis" "enable"] [ "services" "redis" "servers" "" "enable" ]) + (mkRenamedOptionModule [ "services" "redis" "port"] [ "services" "redis" "servers" "" "port" ]) + (mkRenamedOptionModule [ "services" "redis" "openFirewall"] [ "services" "redis" "servers" "" "openFirewall" ]) + (mkRenamedOptionModule [ "services" "redis" "bind"] [ "services" "redis" "servers" "" "bind" ]) + (mkRenamedOptionModule [ "services" "redis" "unixSocket"] [ "services" "redis" "servers" "" "unixSocket" ]) + (mkRenamedOptionModule [ "services" "redis" "unixSocketPerm"] [ "services" "redis" "servers" "" "unixSocketPerm" ]) + (mkRenamedOptionModule [ "services" "redis" "logLevel"] [ "services" "redis" "servers" "" "logLevel" ]) + (mkRenamedOptionModule [ "services" "redis" "logfile"] [ "services" "redis" "servers" "" "logfile" ]) + (mkRenamedOptionModule [ "services" "redis" "syslog"] [ "services" "redis" "servers" "" "syslog" ]) + (mkRenamedOptionModule [ "services" "redis" "databases"] [ "services" "redis" "servers" "" "databases" ]) + (mkRenamedOptionModule [ "services" "redis" "maxclients"] [ "services" "redis" "servers" "" "maxclients" ]) + (mkRenamedOptionModule [ "services" "redis" "save"] [ "services" "redis" "servers" "" "save" ]) + (mkRenamedOptionModule [ "services" "redis" "slaveOf"] [ "services" "redis" "servers" "" "slaveOf" ]) + (mkRenamedOptionModule [ "services" "redis" "masterAuth"] [ "services" "redis" "servers" "" "masterAuth" ]) + (mkRenamedOptionModule [ "services" "redis" "requirePass"] [ "services" "redis" "servers" "" "requirePass" ]) + (mkRenamedOptionModule [ "services" "redis" "requirePassFile"] [ "services" "redis" "servers" "" "requirePassFile" ]) + (mkRenamedOptionModule [ "services" "redis" "appendOnly"] [ "services" "redis" "servers" "" "appendOnly" ]) + (mkRenamedOptionModule [ "services" "redis" "appendFsync"] [ "services" "redis" "servers" "" "appendFsync" ]) + (mkRenamedOptionModule [ "services" "redis" "slowLogLogSlowerThan"] [ "services" "redis" "servers" "" "slowLogLogSlowerThan" ]) + (mkRenamedOptionModule [ "services" "redis" "slowLogMaxLen"] [ "services" "redis" "servers" "" "slowLogMaxLen" ]) + (mkRenamedOptionModule [ "services" "redis" "settings"] [ "services" "redis" "servers" "" "settings" ]) ]; ###### interface @@ -32,18 +54,6 @@ in { options = { services.redis = { - - enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable the Redis server. Note that the NixOS module for - Redis disables kernel support for Transparent Huge Pages (THP), - because this features causes major performance problems for Redis, - e.g. (https://redis.io/topics/latency). - ''; - }; - package = mkOption { type = types.package; default = pkgs.redis; @@ -51,176 +61,226 @@ in { description = "Which Redis derivation to use."; }; - port = mkOption { - type = types.port; - default = 6379; - description = "The port for Redis to listen to."; - }; - - vmOverCommit = mkOption { - type = types.bool; - default = false; - description = '' - Set vm.overcommit_memory to 1 (Suggested for Background Saving: http://redis.io/topics/faq) - ''; - }; + vmOverCommit = mkEnableOption '' + setting of vm.overcommit_memory to 1 + (Suggested for Background Saving: http://redis.io/topics/faq) + ''; - openFirewall = mkOption { - type = types.bool; - default = false; - description = '' - Whether to open ports in the firewall for the server. - ''; - }; + servers = mkOption { + type = with types; attrsOf (submodule ({config, name, ...}@args: { + options = { + enable = mkEnableOption '' + Redis server. + + Note that the NixOS module for Redis disables kernel support + for Transparent Huge Pages (THP), + because this features causes major performance problems for Redis, + e.g. (https://redis.io/topics/latency). + ''; + + user = mkOption { + type = types.str; + default = redisName name; + defaultText = "\"redis\" or \"redis-\${name}\" if name != \"\""; + description = "The username and groupname for redis-server."; + }; - bind = mkOption { - type = with types; nullOr str; - default = "127.0.0.1"; - description = '' - The IP interface to bind to. - null means "all interfaces". - ''; - example = "192.0.2.1"; - }; + port = mkOption { + type = types.port; + default = 6379; + description = "The port for Redis to listen to."; + }; - unixSocket = mkOption { - type = with types; nullOr path; - default = null; - description = "The path to the socket to bind to."; - example = "/run/redis/redis.sock"; - }; + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Whether to open ports in the firewall for the server. + ''; + }; - unixSocketPerm = mkOption { - type = types.int; - default = 750; - description = "Change permissions for the socket"; - example = 700; - }; + bind = mkOption { + type = with types; nullOr str; + default = if name == "" then "127.0.0.1" else null; + defaultText = "127.0.0.1 or null if name != \"\""; + description = '' + The IP interface to bind to. + null means "all interfaces". + ''; + example = "192.0.2.1"; + }; - logLevel = mkOption { - type = types.str; - default = "notice"; # debug, verbose, notice, warning - example = "debug"; - description = "Specify the server verbosity level, options: debug, verbose, notice, warning."; - }; + unixSocket = mkOption { + type = with types; nullOr path; + default = "/run/${redisName name}/redis.sock"; + defaultText = "\"/run/redis/redis.sock\" or \"/run/redis-\${name}/redis.sock\" if name != \"\""; + description = "The path to the socket to bind to."; + }; - logfile = mkOption { - type = types.str; - default = "/dev/null"; - description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output."; - example = "/var/log/redis.log"; - }; + unixSocketPerm = mkOption { + type = types.int; + default = 660; + description = "Change permissions for the socket"; + example = 600; + }; - syslog = mkOption { - type = types.bool; - default = true; - description = "Enable logging to the system logger."; - }; + logLevel = mkOption { + type = types.str; + default = "notice"; # debug, verbose, notice, warning + example = "debug"; + description = "Specify the server verbosity level, options: debug, verbose, notice, warning."; + }; - databases = mkOption { - type = types.int; - default = 16; - description = "Set the number of databases."; - }; + logfile = mkOption { + type = types.str; + default = "/dev/null"; + description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output."; + example = "/var/log/redis.log"; + }; - maxclients = mkOption { - type = types.int; - default = 10000; - description = "Set the max number of connected clients at the same time."; - }; + syslog = mkOption { + type = types.bool; + default = true; + description = "Enable logging to the system logger."; + }; - save = mkOption { - type = with types; listOf (listOf int); - default = [ [900 1] [300 10] [60 10000] ]; - description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes."; - }; + databases = mkOption { + type = types.int; + default = 16; + description = "Set the number of databases."; + }; - slaveOf = mkOption { - type = with types; nullOr (submodule ({ ... }: { - options = { - ip = mkOption { - type = str; - description = "IP of the Redis master"; - example = "192.168.1.100"; + maxclients = mkOption { + type = types.int; + default = 10000; + description = "Set the max number of connected clients at the same time."; }; - port = mkOption { - type = port; - description = "port of the Redis master"; - default = 6379; + save = mkOption { + type = with types; listOf (listOf int); + default = [ [900 1] [300 10] [60 10000] ]; + description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes."; }; - }; - })); - default = null; - description = "IP and port to which this redis instance acts as a slave."; - example = { ip = "192.168.1.100"; port = 6379; }; - }; + slaveOf = mkOption { + type = with types; nullOr (submodule ({ ... }: { + options = { + ip = mkOption { + type = str; + description = "IP of the Redis master"; + example = "192.168.1.100"; + }; + + port = mkOption { + type = port; + description = "port of the Redis master"; + default = 6379; + }; + }; + })); + + default = null; + description = "IP and port to which this redis instance acts as a slave."; + example = { ip = "192.168.1.100"; port = 6379; }; + }; - masterAuth = mkOption { - type = with types; nullOr str; - default = null; - description = ''If the master is password protected (using the requirePass configuration) - it is possible to tell the slave to authenticate before starting the replication synchronization - process, otherwise the master will refuse the slave request. - (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)''; - }; + masterAuth = mkOption { + type = with types; nullOr str; + default = null; + description = ''If the master is password protected (using the requirePass configuration) + it is possible to tell the slave to authenticate before starting the replication synchronization + process, otherwise the master will refuse the slave request. + (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)''; + }; - requirePass = mkOption { - type = with types; nullOr str; - default = null; - description = '' - Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE). - Use requirePassFile to store it outside of the nix store in a dedicated file. - ''; - example = "letmein!"; - }; + requirePass = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE). + Use requirePassFile to store it outside of the nix store in a dedicated file. + ''; + example = "letmein!"; + }; - requirePassFile = mkOption { - type = with types; nullOr path; - default = null; - description = "File with password for the database."; - example = "/run/keys/redis-password"; - }; + requirePassFile = mkOption { + type = with types; nullOr path; + default = null; + description = "File with password for the database."; + example = "/run/keys/redis-password"; + }; - appendOnly = mkOption { - type = types.bool; - default = false; - description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence."; - }; + appendOnly = mkOption { + type = types.bool; + default = false; + description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence."; + }; - appendFsync = mkOption { - type = types.str; - default = "everysec"; # no, always, everysec - description = "How often to fsync the append-only log, options: no, always, everysec."; - }; + appendFsync = mkOption { + type = types.str; + default = "everysec"; # no, always, everysec + description = "How often to fsync the append-only log, options: no, always, everysec."; + }; - slowLogLogSlowerThan = mkOption { - type = types.int; - default = 10000; - description = "Log queries whose execution take longer than X in milliseconds."; - example = 1000; - }; + slowLogLogSlowerThan = mkOption { + type = types.int; + default = 10000; + description = "Log queries whose execution take longer than X in milliseconds."; + example = 1000; + }; - slowLogMaxLen = mkOption { - type = types.int; - default = 128; - description = "Maximum number of items to keep in slow log."; - }; + slowLogMaxLen = mkOption { + type = types.int; + default = 128; + description = "Maximum number of items to keep in slow log."; + }; - settings = mkOption { - type = with types; attrsOf (oneOf [ bool int str (listOf str) ]); + settings = mkOption { + # TODO: this should be converted to freeformType + type = with types; attrsOf (oneOf [ bool int str (listOf str) ]); + default = {}; + description = '' + Redis configuration. Refer to + + for details on supported values. + ''; + example = literalExpression '' + { + loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ]; + } + ''; + }; + }; + config.settings = mkMerge [ + { + port = if config.bind == null then 0 else config.port; + daemonize = false; + supervised = "systemd"; + loglevel = config.logLevel; + logfile = config.logfile; + syslog-enabled = config.syslog; + databases = config.databases; + maxclients = config.maxclients; + save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save; + dbfilename = "dump.rdb"; + dir = "/var/lib/${redisName name}"; + appendOnly = config.appendOnly; + appendfsync = config.appendFsync; + slowlog-log-slower-than = config.slowLogLogSlowerThan; + slowlog-max-len = config.slowLogMaxLen; + } + (mkIf (config.bind != null) { bind = config.bind; }) + (mkIf (config.unixSocket != null) { + unixsocket = config.unixSocket; + unixsocketperm = toString config.unixSocketPerm; + }) + (mkIf (config.slaveOf != null) { slaveof = "${config.slaveOf.ip} ${toString config.slaveOf.port}"; }) + (mkIf (config.masterAuth != null) { masterauth = config.masterAuth; }) + (mkIf (config.requirePass != null) { requirepass = config.requirePass; }) + ]; + })); + description = "Configuration of multiple redis-server instances."; default = {}; - description = '' - Redis configuration. Refer to - - for details on supported values. - ''; - example = literalExpression '' - { - loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ]; - } - ''; }; }; @@ -229,78 +289,61 @@ in { ###### implementation - config = mkIf config.services.redis.enable { - assertions = [{ - assertion = cfg.requirePass != null -> cfg.requirePassFile == null; - message = "You can only set one services.redis.requirePass or services.redis.requirePassFile"; - }]; - boot.kernel.sysctl = (mkMerge [ + config = mkIf (enabledServers != {}) { + + assertions = attrValues (mapAttrs (name: conf: { + assertion = conf.requirePass != null -> conf.requirePassFile == null; + message = '' + You can only set one services.redis.servers.${name}.requirePass + or services.redis.servers.${name}.requirePassFile + ''; + }) enabledServers); + + boot.kernel.sysctl = mkMerge [ { "vm.nr_hugepages" = "0"; } ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } ) - ]); + ]; - networking.firewall = mkIf cfg.openFirewall { - allowedTCPPorts = [ cfg.port ]; - }; - - users.users.redis = { - description = "Redis database user"; - group = "redis"; - isSystemUser = true; - }; - users.groups.redis = {}; + networking.firewall.allowedTCPPorts = concatMap (conf: + optional conf.openFirewall conf.port + ) (attrValues enabledServers); environment.systemPackages = [ cfg.package ]; - services.redis.settings = mkMerge [ - { - port = cfg.port; - daemonize = false; - supervised = "systemd"; - loglevel = cfg.logLevel; - logfile = cfg.logfile; - syslog-enabled = cfg.syslog; - databases = cfg.databases; - maxclients = cfg.maxclients; - save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") cfg.save; - dbfilename = "dump.rdb"; - dir = "/var/lib/redis"; - appendOnly = cfg.appendOnly; - appendfsync = cfg.appendFsync; - slowlog-log-slower-than = cfg.slowLogLogSlowerThan; - slowlog-max-len = cfg.slowLogMaxLen; - } - (mkIf (cfg.bind != null) { bind = cfg.bind; }) - (mkIf (cfg.unixSocket != null) { unixsocket = cfg.unixSocket; unixsocketperm = "${toString cfg.unixSocketPerm}"; }) - (mkIf (cfg.slaveOf != null) { slaveof = "${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}"; }) - (mkIf (cfg.masterAuth != null) { masterauth = cfg.masterAuth; }) - (mkIf (cfg.requirePass != null) { requirepass = cfg.requirePass; }) - ]; + users.users = mapAttrs' (name: conf: nameValuePair (redisName name) { + description = "System user for the redis-server instance ${name}"; + isSystemUser = true; + group = redisName name; + }) enabledServers; + users.groups = mapAttrs' (name: conf: nameValuePair (redisName name) { + }) enabledServers; - systemd.services.redis = { - description = "Redis Server"; + systemd.services = mapAttrs' (name: conf: nameValuePair (redisName name) { + description = "Redis Server - ${redisName name}"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - preStart = '' - install -m 600 ${redisConfig} /run/redis/redis.conf - '' + optionalString (cfg.requirePassFile != null) '' - password=$(cat ${escapeShellArg cfg.requirePassFile}) - echo "requirePass $password" >> /run/redis/redis.conf - ''; - serviceConfig = { - ExecStart = "${cfg.package}/bin/redis-server /run/redis/redis.conf"; + ExecStart = "${cfg.package}/bin/redis-server /run/${redisName name}/redis.conf"; + ExecStartPre = [("+"+pkgs.writeShellScript "${redisName name}-credentials" ('' + install -o '${conf.user}' -m 600 ${redisConfig conf.settings} /run/${redisName name}/redis.conf + '' + optionalString (conf.requirePassFile != null) '' + { + printf requirePass' ' + cat ${escapeShellArg conf.requirePassFile} + } >>/run/${redisName name}/redis.conf + '') + )]; Type = "notify"; # User and group - User = "redis"; - Group = "redis"; + User = conf.user; + Group = conf.user; # Runtime directory and mode - RuntimeDirectory = "redis"; + RuntimeDirectory = redisName name; RuntimeDirectoryMode = "0750"; # State directory and mode - StateDirectory = "redis"; + StateDirectory = redisName name; StateDirectoryMode = "0700"; # Access write directories UMask = "0077"; @@ -309,7 +352,7 @@ in { # Security NoNewPrivileges = true; # Process Properties - LimitNOFILE = "${toString ulimitNofile}"; + LimitNOFILE = mkDefault "${toString (conf.maxclients + 32)}"; # Sandboxing ProtectSystem = "strict"; ProtectHome = true; @@ -322,7 +365,9 @@ in { ProtectKernelModules = true; ProtectKernelTunables = true; ProtectControlGroups = true; - RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + RestrictAddressFamilies = + optionals (conf.bind != null) ["AF_INET" "AF_INET6"] ++ + optional (conf.unixSocket != null) "AF_UNIX"; RestrictNamespaces = true; LockPersonality = true; MemoryDenyWriteExecute = true; @@ -333,6 +378,7 @@ in { SystemCallArchitectures = "native"; SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid"; }; - }; + }) enabledServers; + }; } diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index 62deb38649514..008a5edd071d0 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -939,7 +939,7 @@ let exporterConfig = { enable = true; }; - metricProvider.services.redis.enable = true; + metricProvider.services.redis.servers."".enable = true; exporterTest = '' wait_for_unit("redis.service") wait_for_unit("prometheus-redis-exporter.service") diff --git a/nixos/tests/redis.nix b/nixos/tests/redis.nix index 28b6058c2c026..7b70c239ad6ed 100644 --- a/nixos/tests/redis.nix +++ b/nixos/tests/redis.nix @@ -1,7 +1,4 @@ import ./make-test-python.nix ({ pkgs, ... }: -let - redisSocket = "/run/redis/redis.sock"; -in { name = "redis"; meta = with pkgs.lib.maintainers; { @@ -10,35 +7,40 @@ in nodes = { machine = - { pkgs, ... }: + { pkgs, lib, ... }: with lib; { - services.redis.enable = true; - services.redis.unixSocket = redisSocket; + services.redis.servers."".enable = true; + services.redis.servers."test".enable = true; - # Allow access to the unix socket for the "redis" group. - services.redis.unixSocketPerm = 770; - - users.users."member" = { + users.users = listToAttrs (map (suffix: nameValuePair "member${suffix}" { createHome = false; - description = "A member of the redis group"; + description = "A member of the redis${suffix} group"; isNormalUser = true; - extraGroups = [ - "redis" - ]; - }; + extraGroups = [ "redis${suffix}" ]; + }) ["" "-test"]); }; }; - testScript = '' + testScript = { nodes, ... }: let + inherit (nodes.machine.config.services) redis; + in '' start_all() machine.wait_for_unit("redis") + machine.wait_for_unit("redis-test") + + # The unnamed Redis server still opens a port for backward-compatibility machine.wait_for_open_port("6379") + machine.wait_for_file("${redis.servers."".unixSocket}") + machine.wait_for_file("${redis.servers."test".unixSocket}") + # The unix socket is accessible to the redis group machine.succeed('su member -c "redis-cli ping | grep PONG"') + machine.succeed('su member-test -c "redis-cli ping | grep PONG"') machine.succeed("redis-cli ping | grep PONG") - machine.succeed("redis-cli -s ${redisSocket} ping | grep PONG") + machine.succeed("redis-cli -s ${redis.servers."".unixSocket} ping | grep PONG") + machine.succeed("redis-cli -s ${redis.servers."test".unixSocket} ping | grep PONG") ''; }) diff --git a/nixos/tests/txredisapi.nix b/nixos/tests/txredisapi.nix index bc3814a713750..7c6b36a5c47d5 100644 --- a/nixos/tests/txredisapi.nix +++ b/nixos/tests/txredisapi.nix @@ -10,17 +10,19 @@ import ./make-test-python.nix ({ pkgs, ... }: { pkgs, ... }: { - services.redis.enable = true; - services.redis.unixSocket = "/run/redis/redis.sock"; + services.redis.servers."".enable = true; environment.systemPackages = with pkgs; [ (python38.withPackages (ps: [ ps.twisted ps.txredisapi ps.mock ]))]; }; }; - testScript = '' + testScript = { nodes, ... }: let + inherit (nodes.machine.config.services) redis; + in '' start_all() machine.wait_for_unit("redis") - machine.wait_for_open_port("6379") + machine.wait_for_file("${redis.servers."".unixSocket}") + machine.succeed("ln -s ${redis.servers."".unixSocket} /tmp/redis.sock") tests = machine.succeed("PYTHONPATH=\"${pkgs.python3Packages.txredisapi.src}\" python -m twisted.trial ${pkgs.python3Packages.txredisapi.src}/tests") '';