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")
'';