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 {
14 listsAsDuplicateKeys = true;
15 mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " ";
18 redisName = name: "redis" + optionalString (name != "") ("-"+name);
19 enabledServers = filterAttrs (name: conf: conf.enable) config.services.redis.servers;
23 (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.")
24 (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.")
25 (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.")
26 (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.")
27 (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.")
28 (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.servers.*.settings instead.")
29 (mkRenamedOptionModule [ "services" "redis" "enable"] [ "services" "redis" "servers" "" "enable" ])
30 (mkRenamedOptionModule [ "services" "redis" "port"] [ "services" "redis" "servers" "" "port" ])
31 (mkRenamedOptionModule [ "services" "redis" "openFirewall"] [ "services" "redis" "servers" "" "openFirewall" ])
32 (mkRenamedOptionModule [ "services" "redis" "bind"] [ "services" "redis" "servers" "" "bind" ])
33 (mkRenamedOptionModule [ "services" "redis" "unixSocket"] [ "services" "redis" "servers" "" "unixSocket" ])
34 (mkRenamedOptionModule [ "services" "redis" "unixSocketPerm"] [ "services" "redis" "servers" "" "unixSocketPerm" ])
35 (mkRenamedOptionModule [ "services" "redis" "logLevel"] [ "services" "redis" "servers" "" "logLevel" ])
36 (mkRenamedOptionModule [ "services" "redis" "logfile"] [ "services" "redis" "servers" "" "logfile" ])
37 (mkRenamedOptionModule [ "services" "redis" "syslog"] [ "services" "redis" "servers" "" "syslog" ])
38 (mkRenamedOptionModule [ "services" "redis" "databases"] [ "services" "redis" "servers" "" "databases" ])
39 (mkRenamedOptionModule [ "services" "redis" "maxclients"] [ "services" "redis" "servers" "" "maxclients" ])
40 (mkRenamedOptionModule [ "services" "redis" "save"] [ "services" "redis" "servers" "" "save" ])
41 (mkRenamedOptionModule [ "services" "redis" "slaveOf"] [ "services" "redis" "servers" "" "slaveOf" ])
42 (mkRenamedOptionModule [ "services" "redis" "masterAuth"] [ "services" "redis" "servers" "" "masterAuth" ])
43 (mkRenamedOptionModule [ "services" "redis" "requirePass"] [ "services" "redis" "servers" "" "requirePass" ])
44 (mkRenamedOptionModule [ "services" "redis" "requirePassFile"] [ "services" "redis" "servers" "" "requirePassFile" ])
45 (mkRenamedOptionModule [ "services" "redis" "appendOnly"] [ "services" "redis" "servers" "" "appendOnly" ])
46 (mkRenamedOptionModule [ "services" "redis" "appendFsync"] [ "services" "redis" "servers" "" "appendFsync" ])
47 (mkRenamedOptionModule [ "services" "redis" "slowLogLogSlowerThan"] [ "services" "redis" "servers" "" "slowLogLogSlowerThan" ])
48 (mkRenamedOptionModule [ "services" "redis" "slowLogMaxLen"] [ "services" "redis" "servers" "" "slowLogMaxLen" ])
49 (mkRenamedOptionModule [ "services" "redis" "settings"] [ "services" "redis" "servers" "" "settings" ])
57 package = mkPackageOption pkgs "redis" { };
59 vmOverCommit = mkEnableOption ''
60 set `vm.overcommit_memory` sysctl to 1
61 (Suggested for Background Saving: <https://redis.io/docs/get-started/faq/>)
62 '' // { default = true; };
65 type = with types; attrsOf (submodule ({ config, name, ... }: {
67 enable = mkEnableOption "Redis server";
71 default = redisName name;
72 defaultText = literalExpression ''
73 if name == "" then "redis" else "redis-''${name}"
75 description = "The username and groupname for redis-server.";
80 default = if name == "" then 6379 else 0;
81 defaultText = literalExpression ''if name == "" then 6379 else 0'';
83 The TCP port to accept connections.
84 If port 0 is specified Redis will not listen on a TCP socket.
88 openFirewall = mkOption {
92 Whether to open ports in the firewall for the server.
96 extraParams = mkOption {
97 type = with types; listOf str;
99 description = "Extra parameters to append to redis-server invocation";
100 example = [ "--sentinel" ];
104 type = with types; nullOr str;
105 default = "127.0.0.1";
107 The IP interface to bind to.
108 `null` means "all interfaces".
110 example = "192.0.2.1";
113 unixSocket = mkOption {
114 type = with types; nullOr path;
115 default = "/run/${redisName name}/redis.sock";
116 defaultText = literalExpression ''
117 if name == "" then "/run/redis/redis.sock" else "/run/redis-''${name}/redis.sock"
119 description = "The path to the socket to bind to.";
122 unixSocketPerm = mkOption {
125 description = "Change permissions for the socket";
129 logLevel = mkOption {
131 default = "notice"; # debug, verbose, notice, warning
133 description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
138 default = "/dev/null";
139 description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
140 example = "/var/log/redis.log";
146 description = "Enable logging to the system logger.";
149 databases = mkOption {
152 description = "Set the number of databases.";
155 maxclients = mkOption {
158 description = "Set the max number of connected clients at the same time.";
162 type = with types; listOf (listOf int);
163 default = [ [900 1] [300 10] [60 10000] ];
165 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.
167 If set to the empty list (`[]`) then RDB persistence will be disabled (useful if you are using AOF or don't want any persistence).
172 type = with types; nullOr (submodule ({ ... }: {
176 description = "IP of the Redis master";
177 example = "192.168.1.100";
182 description = "port of the Redis master";
189 description = "IP and port to which this redis instance acts as a slave.";
190 example = { ip = "192.168.1.100"; port = 6379; };
193 masterAuth = mkOption {
194 type = with types; nullOr str;
196 description = ''If the master is password protected (using the requirePass configuration)
197 it is possible to tell the slave to authenticate before starting the replication synchronization
198 process, otherwise the master will refuse the slave request.
199 (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
202 requirePass = mkOption {
203 type = with types; nullOr str;
206 Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
207 Use requirePassFile to store it outside of the nix store in a dedicated file.
209 example = "letmein!";
212 requirePassFile = mkOption {
213 type = with types; nullOr path;
215 description = "File with password for the database.";
216 example = "/run/keys/redis-password";
219 appendOnly = mkOption {
222 description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
225 appendFsync = mkOption {
227 default = "everysec"; # no, always, everysec
228 description = "How often to fsync the append-only log, options: no, always, everysec.";
231 slowLogLogSlowerThan = mkOption {
234 description = "Log queries whose execution take longer than X in milliseconds.";
238 slowLogMaxLen = mkOption {
241 description = "Maximum number of items to keep in slow log.";
244 settings = mkOption {
245 # TODO: this should be converted to freeformType
246 type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
249 Redis configuration. Refer to
250 <https://redis.io/topics/config>
251 for details on supported values.
253 example = literalExpression ''
255 loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
260 config.settings = mkMerge [
262 inherit (config) port logfile databases maxclients appendOnly;
264 supervised = "systemd";
265 loglevel = config.logLevel;
266 syslog-enabled = config.syslog;
267 save = if config.save == []
268 then ''""'' # Disable saving with `save = ""`
270 (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}")
272 dbfilename = "dump.rdb";
273 dir = "/var/lib/${redisName name}";
274 appendfsync = config.appendFsync;
275 slowlog-log-slower-than = config.slowLogLogSlowerThan;
276 slowlog-max-len = config.slowLogMaxLen;
278 (mkIf (config.bind != null) { inherit (config) bind; })
279 (mkIf (config.unixSocket != null) {
280 unixsocket = config.unixSocket;
281 unixsocketperm = toString config.unixSocketPerm;
283 (mkIf (config.slaveOf != null) { slaveof = "${config.slaveOf.ip} ${toString config.slaveOf.port}"; })
284 (mkIf (config.masterAuth != null) { masterauth = config.masterAuth; })
285 (mkIf (config.requirePass != null) { requirepass = config.requirePass; })
288 description = "Configuration of multiple `redis-server` instances.";
296 ###### implementation
298 config = mkIf (enabledServers != {}) {
300 assertions = attrValues (mapAttrs (name: conf: {
301 assertion = conf.requirePass != null -> conf.requirePassFile == null;
303 You can only set one services.redis.servers.${name}.requirePass
304 or services.redis.servers.${name}.requirePassFile
308 boot.kernel.sysctl = mkIf cfg.vmOverCommit {
309 "vm.overcommit_memory" = "1";
312 networking.firewall.allowedTCPPorts = concatMap (conf:
313 optional conf.openFirewall conf.port
314 ) (attrValues enabledServers);
316 environment.systemPackages = [ cfg.package ];
318 users.users = mapAttrs' (name: conf: nameValuePair (redisName name) {
319 description = "System user for the redis-server instance ${name}";
321 group = redisName name;
323 users.groups = mapAttrs' (name: conf: nameValuePair (redisName name) {
326 systemd.services = mapAttrs' (name: conf: nameValuePair (redisName name) {
327 description = "Redis Server - ${redisName name}";
329 wantedBy = [ "multi-user.target" ];
330 after = [ "network.target" ];
333 ExecStart = "${cfg.package}/bin/${cfg.package.serverBin or "redis-server"} /var/lib/${redisName name}/redis.conf ${escapeShellArgs conf.extraParams}";
334 ExecStartPre = "+"+pkgs.writeShellScript "${redisName name}-prep-conf" (let
335 redisConfVar = "/var/lib/${redisName name}/redis.conf";
336 redisConfRun = "/run/${redisName name}/nixos.conf";
337 redisConfStore = redisConfig conf.settings;
339 touch "${redisConfVar}" "${redisConfRun}"
340 chown '${conf.user}' "${redisConfVar}" "${redisConfRun}"
341 chmod 0600 "${redisConfVar}" "${redisConfRun}"
342 if [ ! -s ${redisConfVar} ]; then
343 echo 'include "${redisConfRun}"' > "${redisConfVar}"
345 echo 'include "${redisConfStore}"' > "${redisConfRun}"
346 ${optionalString (conf.requirePassFile != null) ''
348 echo -n "requirepass "
349 cat ${escapeShellArg conf.requirePassFile}
350 } >> "${redisConfRun}"
357 # Runtime directory and mode
358 RuntimeDirectory = redisName name;
359 RuntimeDirectoryMode = "0750";
360 # State directory and mode
361 StateDirectory = redisName name;
362 StateDirectoryMode = "0700";
363 # Access write directories
366 CapabilityBoundingSet = "";
368 NoNewPrivileges = true;
370 LimitNOFILE = mkDefault "${toString (conf.maxclients + 32)}";
372 ProtectSystem = "strict";
375 PrivateDevices = true;
378 ProtectHostname = true;
379 ProtectKernelLogs = true;
380 ProtectKernelModules = true;
381 ProtectKernelTunables = true;
382 ProtectControlGroups = true;
383 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
384 RestrictNamespaces = true;
385 LockPersonality = true;
386 # we need to disable MemoryDenyWriteExecute for keydb
387 MemoryDenyWriteExecute = cfg.package.pname != "keydb";
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";