at 21.11-pre 11 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.redis; 7 8 ulimitNofile = cfg.maxclients + 32; 9 10 mkValueString = value: 11 if value == true then "yes" 12 else if value == false then "no" 13 else generators.mkValueStringDefault { } value; 14 15 redisConfig = pkgs.writeText "redis.conf" (generators.toKeyValue { 16 listsAsDuplicateKeys = true; 17 mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " "; 18 } cfg.settings); 19 20in { 21 imports = [ 22 (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.") 23 (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.") 24 (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.") 25 (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.") 26 (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.") 27 (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.settings instead.") 28 ]; 29 30 ###### interface 31 32 options = { 33 34 services.redis = { 35 36 enable = mkOption { 37 type = types.bool; 38 default = false; 39 description = '' 40 Whether to enable the Redis server. Note that the NixOS module for 41 Redis disables kernel support for Transparent Huge Pages (THP), 42 because this features causes major performance problems for Redis, 43 e.g. (https://redis.io/topics/latency). 44 ''; 45 }; 46 47 package = mkOption { 48 type = types.package; 49 default = pkgs.redis; 50 defaultText = "pkgs.redis"; 51 description = "Which Redis derivation to use."; 52 }; 53 54 port = mkOption { 55 type = types.int; 56 default = 6379; 57 description = "The port for Redis to listen to."; 58 }; 59 60 vmOverCommit = mkOption { 61 type = types.bool; 62 default = false; 63 description = '' 64 Set vm.overcommit_memory to 1 (Suggested for Background Saving: http://redis.io/topics/faq) 65 ''; 66 }; 67 68 openFirewall = mkOption { 69 type = types.bool; 70 default = false; 71 description = '' 72 Whether to open ports in the firewall for the server. 73 ''; 74 }; 75 76 bind = mkOption { 77 type = with types; nullOr str; 78 default = "127.0.0.1"; 79 description = '' 80 The IP interface to bind to. 81 <literal>null</literal> means "all interfaces". 82 ''; 83 example = "192.0.2.1"; 84 }; 85 86 unixSocket = mkOption { 87 type = with types; nullOr path; 88 default = null; 89 description = "The path to the socket to bind to."; 90 example = "/run/redis/redis.sock"; 91 }; 92 93 unixSocketPerm = mkOption { 94 type = types.int; 95 default = 750; 96 description = "Change permissions for the socket"; 97 example = 700; 98 }; 99 100 logLevel = mkOption { 101 type = types.str; 102 default = "notice"; # debug, verbose, notice, warning 103 example = "debug"; 104 description = "Specify the server verbosity level, options: debug, verbose, notice, warning."; 105 }; 106 107 logfile = mkOption { 108 type = types.str; 109 default = "/dev/null"; 110 description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output."; 111 example = "/var/log/redis.log"; 112 }; 113 114 syslog = mkOption { 115 type = types.bool; 116 default = true; 117 description = "Enable logging to the system logger."; 118 }; 119 120 databases = mkOption { 121 type = types.int; 122 default = 16; 123 description = "Set the number of databases."; 124 }; 125 126 maxclients = mkOption { 127 type = types.int; 128 default = 10000; 129 description = "Set the max number of connected clients at the same time."; 130 }; 131 132 save = mkOption { 133 type = with types; listOf (listOf int); 134 default = [ [900 1] [300 10] [60 10000] ]; 135 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."; 136 example = [ [900 1] [300 10] [60 10000] ]; 137 }; 138 139 slaveOf = mkOption { 140 type = with types; nullOr (submodule ({ ... }: { 141 options = { 142 ip = mkOption { 143 type = str; 144 description = "IP of the Redis master"; 145 example = "192.168.1.100"; 146 }; 147 148 port = mkOption { 149 type = port; 150 description = "port of the Redis master"; 151 default = 6379; 152 }; 153 }; 154 })); 155 156 default = null; 157 description = "IP and port to which this redis instance acts as a slave."; 158 example = { ip = "192.168.1.100"; port = 6379; }; 159 }; 160 161 masterAuth = mkOption { 162 type = with types; nullOr str; 163 default = null; 164 description = ''If the master is password protected (using the requirePass configuration) 165 it is possible to tell the slave to authenticate before starting the replication synchronization 166 process, otherwise the master will refuse the slave request. 167 (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)''; 168 }; 169 170 requirePass = mkOption { 171 type = with types; nullOr str; 172 default = null; 173 description = '' 174 Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE). 175 Use requirePassFile to store it outside of the nix store in a dedicated file. 176 ''; 177 example = "letmein!"; 178 }; 179 180 requirePassFile = mkOption { 181 type = with types; nullOr path; 182 default = null; 183 description = "File with password for the database."; 184 example = "/run/keys/redis-password"; 185 }; 186 187 appendOnly = mkOption { 188 type = types.bool; 189 default = false; 190 description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence."; 191 }; 192 193 appendFsync = mkOption { 194 type = types.str; 195 default = "everysec"; # no, always, everysec 196 description = "How often to fsync the append-only log, options: no, always, everysec."; 197 }; 198 199 slowLogLogSlowerThan = mkOption { 200 type = types.int; 201 default = 10000; 202 description = "Log queries whose execution take longer than X in milliseconds."; 203 example = 1000; 204 }; 205 206 slowLogMaxLen = mkOption { 207 type = types.int; 208 default = 128; 209 description = "Maximum number of items to keep in slow log."; 210 }; 211 212 settings = mkOption { 213 type = with types; attrsOf (oneOf [ bool int str (listOf str) ]); 214 default = {}; 215 description = '' 216 Redis configuration. Refer to 217 <link xlink:href="https://redis.io/topics/config"/> 218 for details on supported values. 219 ''; 220 example = literalExample '' 221 { 222 loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ]; 223 } 224 ''; 225 }; 226 }; 227 228 }; 229 230 231 ###### implementation 232 233 config = mkIf config.services.redis.enable { 234 assertions = [{ 235 assertion = cfg.requirePass != null -> cfg.requirePassFile == null; 236 message = "You can only set one services.redis.requirePass or services.redis.requirePassFile"; 237 }]; 238 boot.kernel.sysctl = (mkMerge [ 239 { "vm.nr_hugepages" = "0"; } 240 ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } ) 241 ]); 242 243 networking.firewall = mkIf cfg.openFirewall { 244 allowedTCPPorts = [ cfg.port ]; 245 }; 246 247 users.users.redis = { 248 description = "Redis database user"; 249 isSystemUser = true; 250 }; 251 users.groups.redis = {}; 252 253 environment.systemPackages = [ cfg.package ]; 254 255 services.redis.settings = mkMerge [ 256 { 257 port = cfg.port; 258 daemonize = false; 259 supervised = "systemd"; 260 loglevel = cfg.logLevel; 261 logfile = cfg.logfile; 262 syslog-enabled = cfg.syslog; 263 databases = cfg.databases; 264 maxclients = cfg.maxclients; 265 save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") cfg.save; 266 dbfilename = "dump.rdb"; 267 dir = "/var/lib/redis"; 268 appendOnly = cfg.appendOnly; 269 appendfsync = cfg.appendFsync; 270 slowlog-log-slower-than = cfg.slowLogLogSlowerThan; 271 slowlog-max-len = cfg.slowLogMaxLen; 272 } 273 (mkIf (cfg.bind != null) { bind = cfg.bind; }) 274 (mkIf (cfg.unixSocket != null) { unixsocket = cfg.unixSocket; unixsocketperm = "${toString cfg.unixSocketPerm}"; }) 275 (mkIf (cfg.slaveOf != null) { slaveof = "${cfg.slaveOf.ip} ${cfg.slaveOf.port}"; }) 276 (mkIf (cfg.masterAuth != null) { masterauth = cfg.masterAuth; }) 277 (mkIf (cfg.requirePass != null) { requirepass = cfg.requirePass; }) 278 ]; 279 280 systemd.services.redis = { 281 description = "Redis Server"; 282 283 wantedBy = [ "multi-user.target" ]; 284 after = [ "network.target" ]; 285 286 preStart = '' 287 install -m 600 ${redisConfig} /run/redis/redis.conf 288 '' + optionalString (cfg.requirePassFile != null) '' 289 password=$(cat ${escapeShellArg cfg.requirePassFile}) 290 echo "requirePass $password" >> /run/redis/redis.conf 291 ''; 292 293 serviceConfig = { 294 ExecStart = "${cfg.package}/bin/redis-server /run/redis/redis.conf"; 295 Type = "notify"; 296 # User and group 297 User = "redis"; 298 Group = "redis"; 299 # Runtime directory and mode 300 RuntimeDirectory = "redis"; 301 RuntimeDirectoryMode = "0750"; 302 # State directory and mode 303 StateDirectory = "redis"; 304 StateDirectoryMode = "0700"; 305 # Access write directories 306 UMask = "0077"; 307 # Capabilities 308 CapabilityBoundingSet = ""; 309 # Security 310 NoNewPrivileges = true; 311 # Process Properties 312 LimitNOFILE = "${toString ulimitNofile}"; 313 # Sandboxing 314 ProtectSystem = "strict"; 315 ProtectHome = true; 316 PrivateTmp = true; 317 PrivateDevices = true; 318 PrivateUsers = true; 319 ProtectClock = true; 320 ProtectHostname = true; 321 ProtectKernelLogs = true; 322 ProtectKernelModules = true; 323 ProtectKernelTunables = true; 324 ProtectControlGroups = true; 325 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; 326 RestrictNamespaces = true; 327 LockPersonality = true; 328 MemoryDenyWriteExecute = true; 329 RestrictRealtime = true; 330 RestrictSUIDSGID = true; 331 PrivateMounts = true; 332 # System Call Filtering 333 SystemCallArchitectures = "native"; 334 SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid"; 335 }; 336 }; 337 }; 338}