1 { config, lib, pkgs, ... }:
6 cfg = config.services.redis;
9 if value == true then "yes"
10 else if value == false then "no"
11 else generators.mkValueStringDefault { } value;
13 redisConfig = settings: pkgs.writeText "redis.conf" (generators.toKeyValue
15 listsAsDuplicateKeys = true;
16 mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " ";
20 redisName = name: "redis" + optionalString (name != "") ("-" + name);
21 enabledServers = filterAttrs (_name: conf: conf.enable) config.services.redis.servers;
26 (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.")
27 (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.")
28 (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.")
29 (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.")
30 (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.")
31 (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.settings instead.")
32 (mkRenamedOptionModule [ "services" "redis" "enable" ] [ "services" "redis" "servers" "" "enable" ])
33 (mkRenamedOptionModule [ "services" "redis" "port" ] [ "services" "redis" "servers" "" "port" ])
34 (mkRenamedOptionModule [ "services" "redis" "openFirewall" ] [ "services" "redis" "servers" "" "openFirewall" ])
35 (mkRenamedOptionModule [ "services" "redis" "bind" ] [ "services" "redis" "servers" "" "bind" ])
36 (mkRenamedOptionModule [ "services" "redis" "unixSocket" ] [ "services" "redis" "servers" "" "unixSocket" ])
37 (mkRenamedOptionModule [ "services" "redis" "unixSocketPerm" ] [ "services" "redis" "servers" "" "unixSocketPerm" ])
38 (mkRenamedOptionModule [ "services" "redis" "logLevel" ] [ "services" "redis" "servers" "" "logLevel" ])
39 (mkRenamedOptionModule [ "services" "redis" "logfile" ] [ "services" "redis" "servers" "" "logfile" ])
40 (mkRenamedOptionModule [ "services" "redis" "syslog" ] [ "services" "redis" "servers" "" "syslog" ])
41 (mkRenamedOptionModule [ "services" "redis" "databases" ] [ "services" "redis" "servers" "" "databases" ])
42 (mkRenamedOptionModule [ "services" "redis" "maxclients" ] [ "services" "redis" "servers" "" "maxclients" ])
43 (mkRenamedOptionModule [ "services" "redis" "save" ] [ "services" "redis" "servers" "" "save" ])
44 (mkRenamedOptionModule [ "services" "redis" "slaveOf" ] [ "services" "redis" "servers" "" "slaveOf" ])
45 (mkRenamedOptionModule [ "services" "redis" "masterAuth" ] [ "services" "redis" "servers" "" "masterAuth" ])
46 (mkRenamedOptionModule [ "services" "redis" "requirePass" ] [ "services" "redis" "servers" "" "requirePass" ])
47 (mkRenamedOptionModule [ "services" "redis" "requirePassFile" ] [ "services" "redis" "servers" "" "requirePassFile" ])
48 (mkRenamedOptionModule [ "services" "redis" "appendOnly" ] [ "services" "redis" "servers" "" "appendOnly" ])
49 (mkRenamedOptionModule [ "services" "redis" "appendFsync" ] [ "services" "redis" "servers" "" "appendFsync" ])
50 (mkRenamedOptionModule [ "services" "redis" "slowLogLogSlowerThan" ] [ "services" "redis" "servers" "" "slowLogLogSlowerThan" ])
51 (mkRenamedOptionModule [ "services" "redis" "slowLogMaxLen" ] [ "services" "redis" "servers" "" "slowLogMaxLen" ])
52 (mkRenamedOptionModule [ "services" "redis" "settings" ] [ "services" "redis" "servers" "" "settings" ])
63 defaultText = "pkgs.redis";
64 description = "Which Redis derivation to use.";
67 vmOverCommit = mkEnableOption ''
68 setting of vm.overcommit_memory to 1
69 (Suggested for Background Saving: http://redis.io/topics/faq)
73 type = with types; attrsOf (submodule ({ config, name, ... }: {
75 enable = mkEnableOption ''
78 Note that the NixOS module for Redis disables kernel support
79 for Transparent Huge Pages (THP),
80 because this features causes major performance problems for Redis,
81 e.g. (https://redis.io/topics/latency).
86 default = redisName name;
87 defaultText = "\"redis\" or \"redis-\${name}\" if name != \"\"";
88 description = "The username and groupname for redis-server.";
94 description = "The port for Redis to listen to.";
97 openFirewall = mkOption {
101 Whether to open ports in the firewall for the server.
106 type = with types; nullOr str;
107 default = if name == "" then "127.0.0.1" else null;
108 defaultText = "127.0.0.1 or null if name != \"\"";
110 The IP interface to bind to.
111 <literal>null</literal> means "all interfaces".
113 example = "192.0.2.1";
116 unixSocket = mkOption {
117 type = with types; nullOr path;
118 default = "/run/${redisName name}/redis.sock";
119 defaultText = "\"/run/redis/redis.sock\" or \"/run/redis-\${name}/redis.sock\" if name != \"\"";
120 description = "The path to the socket to bind to.";
123 unixSocketPerm = mkOption {
126 description = "Change permissions for the socket";
130 logLevel = mkOption {
132 default = "notice"; # debug, verbose, notice, warning
134 description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
139 default = "/dev/null";
140 description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
141 example = "/var/log/redis.log";
147 description = "Enable logging to the system logger.";
150 databases = mkOption {
153 description = "Set the number of databases.";
156 maxclients = mkOption {
159 description = "Set the max number of connected clients at the same time.";
163 type = with types; listOf (listOf int);
164 default = [ [ 900 1 ] [ 300 10 ] [ 60 10000 ] ];
165 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.";
166 example = [ [ 900 1 ] [ 300 10 ] [ 60 10000 ] ];
170 type = with types; nullOr (submodule ({ ... }: {
174 description = "IP of the Redis master";
175 example = "192.168.1.100";
180 description = "port of the Redis master";
187 description = "IP and port to which this redis instance acts as a slave.";
188 example = { ip = "192.168.1.100"; port = 6379; };
191 masterAuth = mkOption {
192 type = with types; nullOr str;
194 description = ''If the master is password protected (using the requirePass configuration)
195 it is possible to tell the slave to authenticate before starting the replication synchronization
196 process, otherwise the master will refuse the slave request.
197 (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
200 requirePass = mkOption {
201 type = with types; nullOr str;
204 Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
205 Use requirePassFile to store it outside of the nix store in a dedicated file.
207 example = "letmein!";
210 requirePassFile = mkOption {
211 type = with types; nullOr path;
213 description = "File with password for the database.";
214 example = "/run/keys/redis-password";
217 appendOnly = mkOption {
220 description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
223 appendFsync = mkOption {
225 default = "everysec"; # no, always, everysec
226 description = "How often to fsync the append-only log, options: no, always, everysec.";
229 slowLogLogSlowerThan = mkOption {
232 description = "Log queries whose execution take longer than X in milliseconds.";
236 slowLogMaxLen = mkOption {
239 description = "Maximum number of items to keep in slow log.";
242 settings = mkOption {
243 # TODO: this should be converted to freeformType
244 type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
247 Redis configuration. Refer to
248 <link xlink:href="https://redis.io/topics/config"/>
249 for details on supported values.
251 example = literalExample ''
253 loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
258 config.settings = mkMerge [
260 port = if config.bind == null then 0 else config.port;
262 supervised = "systemd";
263 loglevel = config.logLevel;
264 logfile = config.logfile;
265 syslog-enabled = config.syslog;
266 databases = config.databases;
267 maxclients = config.maxclients;
268 save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save;
269 dbfilename = "dump.rdb";
270 dir = "/var/lib/${redisName name}";
271 appendOnly = config.appendOnly;
272 appendfsync = config.appendFsync;
273 slowlog-log-slower-than = config.slowLogLogSlowerThan;
274 slowlog-max-len = config.slowLogMaxLen;
276 (mkIf (config.bind != null) { bind = config.bind; })
277 (mkIf (config.unixSocket != null) {
278 unixsocket = config.unixSocket;
279 unixsocketperm = toString config.unixSocketPerm;
281 (mkIf (config.slaveOf != null) { slaveof = "${config.slaveOf.ip} ${toString config.slaveOf.port}"; })
282 (mkIf (config.masterAuth != null) { masterauth = config.masterAuth; })
283 (mkIf (config.requirePass != null) { requirepass = config.requirePass; })
286 description = "Configuration of multiple <literal>redis-server</literal> instances.";
294 ###### implementation
296 config = mkIf (enabledServers != { }) {
298 assertions = attrValues (mapAttrs
300 assertion = conf.requirePass != null -> conf.requirePassFile == null;
302 You can only set one services.redis.servers.${name}.requirePass
303 or services.redis.servers.${name}.requirePassFile
308 boot.kernel.sysctl = mkMerge [
309 { "vm.nr_hugepages" = "0"; }
310 (mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; })
313 networking.firewall.allowedTCPPorts = concatMap
315 optional conf.openFirewall conf.port
317 (attrValues enabledServers);
319 environment.systemPackages = [ cfg.package ];
321 users.users = mapAttrs'
322 (name: _conf: nameValuePair (redisName name) {
323 description = "System user for the redis-server instance ${name}";
325 group = redisName name;
328 users.groups = mapAttrs'
329 (name: _conf: nameValuePair (redisName name) { })
332 systemd.services = mapAttrs'
333 (name: conf: nameValuePair (redisName name) {
334 description = "Redis Server - ${redisName name}";
336 wantedBy = [ "multi-user.target" ];
337 after = [ "network.target" ];
340 ExecStart = "${cfg.package}/bin/redis-server /run/${redisName name}/redis.conf";
342 ("+" + pkgs.writeShellScript "${redisName name}-credentials" (''
343 install -o '${conf.user}' -m 600 ${redisConfig conf.settings} /run/${redisName name}/redis.conf
344 '' + optionalString (conf.requirePassFile != null) ''
346 printf requirePass' '
347 cat ${escapeShellArg conf.requirePassFile}
348 } >>/run/${redisName name}/redis.conf
356 # Runtime directory and mode
357 RuntimeDirectory = redisName name;
358 RuntimeDirectoryMode = "0750";
359 # State directory and mode
360 StateDirectory = redisName name;
361 StateDirectoryMode = "0700";
362 # Access write directories
365 CapabilityBoundingSet = "";
367 NoNewPrivileges = true;
369 LimitNOFILE = mkDefault "${toString (conf.maxclients + 32)}";
371 ProtectSystem = "strict";
374 PrivateDevices = true;
377 ProtectHostname = true;
378 ProtectKernelLogs = true;
379 ProtectKernelModules = true;
380 ProtectKernelTunables = true;
381 ProtectControlGroups = true;
382 RestrictAddressFamilies =
383 optionals (conf.bind != null) [ "AF_INET" "AF_INET6" ] ++
384 optional (conf.unixSocket != null) "AF_UNIX";
385 RestrictNamespaces = true;
386 LockPersonality = true;
387 MemoryDenyWriteExecute = true;
388 RestrictRealtime = true;
389 RestrictSUIDSGID = true;
390 PrivateMounts = true;
391 # System Call Filtering
392 SystemCallArchitectures = "native";
393 SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid";