1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.redis;
7 redisBool = b: if b then "yes" else "no";
8 condOption = name: value: if value != null then "${name} ${toString value}" else "";
9
10 redisConfig = pkgs.writeText "redis.conf" ''
11 pidfile ${cfg.pidFile}
12 port ${toString cfg.port}
13 ${condOption "bind" cfg.bind}
14 ${condOption "unixsocket" cfg.unixSocket}
15 loglevel ${cfg.logLevel}
16 logfile ${cfg.logfile}
17 syslog-enabled ${redisBool cfg.syslog}
18 databases ${toString cfg.databases}
19 ${concatMapStrings (d: "save ${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}\n") cfg.save}
20 dbfilename ${cfg.dbFilename}
21 dir ${toString cfg.dbpath}
22 ${if cfg.slaveOf != null then "slaveof ${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}" else ""}
23 ${condOption "masterauth" cfg.masterAuth}
24 ${condOption "requirepass" cfg.requirePass}
25 appendOnly ${redisBool cfg.appendOnly}
26 appendfsync ${cfg.appendFsync}
27 slowlog-log-slower-than ${toString cfg.slowLogLogSlowerThan}
28 slowlog-max-len ${toString cfg.slowLogMaxLen}
29 ${cfg.extraConfig}
30 '';
31in
32{
33
34 ###### interface
35
36 options = {
37
38 services.redis = {
39
40 enable = mkOption {
41 type = types.bool;
42 default = false;
43 description = "Whether to enable the Redis server.";
44 };
45
46 package = mkOption {
47 type = types.package;
48 default = pkgs.redis;
49 defaultText = "pkgs.redis";
50 description = "Which Redis derivation to use.";
51 };
52
53 user = mkOption {
54 type = types.str;
55 default = "redis";
56 description = "User account under which Redis runs.";
57 };
58
59 pidFile = mkOption {
60 type = types.path;
61 default = "/var/lib/redis/redis.pid";
62 description = "";
63 };
64
65 port = mkOption {
66 type = types.int;
67 default = 6379;
68 description = "The port for Redis to listen to.";
69 };
70
71 vmOverCommit = mkOption {
72 type = types.bool;
73 default = false;
74 description = ''
75 Set vm.overcommit_memory to 1 (Suggested for Background Saving: http://redis.io/topics/faq)
76 '';
77 };
78
79 openFirewall = mkOption {
80 type = types.bool;
81 default = false;
82 description = ''
83 Whether to open ports in the firewall for the server.
84 '';
85 };
86
87 bind = mkOption {
88 type = with types; nullOr str;
89 default = null; # All interfaces
90 description = "The IP interface to bind to.";
91 example = "127.0.0.1";
92 };
93
94 unixSocket = mkOption {
95 type = with types; nullOr path;
96 default = null;
97 description = "The path to the socket to bind to.";
98 example = "/var/run/redis.sock";
99 };
100
101 logLevel = mkOption {
102 type = types.str;
103 default = "notice"; # debug, verbose, notice, warning
104 example = "debug";
105 description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
106 };
107
108 logfile = mkOption {
109 type = types.str;
110 default = "/dev/null";
111 description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
112 example = "/var/log/redis.log";
113 };
114
115 syslog = mkOption {
116 type = types.bool;
117 default = true;
118 description = "Enable logging to the system logger.";
119 };
120
121 databases = mkOption {
122 type = types.int;
123 default = 16;
124 description = "Set the number of databases.";
125 };
126
127 save = mkOption {
128 type = with types; listOf (listOf int);
129 default = [ [900 1] [300 10] [60 10000] ];
130 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.";
131 example = [ [900 1] [300 10] [60 10000] ];
132 };
133
134 dbFilename = mkOption {
135 type = types.str;
136 default = "dump.rdb";
137 description = "The filename where to dump the DB.";
138 };
139
140 dbpath = mkOption {
141 type = types.path;
142 default = "/var/lib/redis";
143 description = "The DB will be written inside this directory, with the filename specified using the 'dbFilename' configuration.";
144 };
145
146 slaveOf = mkOption {
147 default = null; # { ip, port }
148 description = "An attribute set with two attributes: ip and port to which this redis instance acts as a slave.";
149 example = { ip = "192.168.1.100"; port = 6379; };
150 };
151
152 masterAuth = mkOption {
153 default = null;
154 description = ''If the master is password protected (using the requirePass configuration)
155 it is possible to tell the slave to authenticate before starting the replication synchronization
156 process, otherwise the master will refuse the slave request.
157 (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
158 };
159
160 requirePass = mkOption {
161 type = with types; nullOr str;
162 default = null;
163 description = "Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)";
164 example = "letmein!";
165 };
166
167 appendOnly = mkOption {
168 type = types.bool;
169 default = false;
170 description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
171 };
172
173 appendOnlyFilename = mkOption {
174 type = types.str;
175 default = "appendonly.aof";
176 description = "Filename for the append-only file (stored inside of dbpath)";
177 };
178
179 appendFsync = mkOption {
180 type = types.str;
181 default = "everysec"; # no, always, everysec
182 description = "How often to fsync the append-only log, options: no, always, everysec.";
183 };
184
185 slowLogLogSlowerThan = mkOption {
186 type = types.int;
187 default = 10000;
188 description = "Log queries whose execution take longer than X in milliseconds.";
189 example = 1000;
190 };
191
192 slowLogMaxLen = mkOption {
193 type = types.int;
194 default = 128;
195 description = "Maximum number of items to keep in slow log.";
196 };
197
198 extraConfig = mkOption {
199 type = types.lines;
200 default = "";
201 description = "Extra configuration options for redis.conf.";
202 };
203 };
204
205 };
206
207
208 ###### implementation
209
210 config = mkIf config.services.redis.enable {
211
212 boot.kernel.sysctl = mkIf cfg.vmOverCommit {
213 "vm.overcommit_memory" = "1";
214 };
215
216 networking.firewall = mkIf cfg.openFirewall {
217 allowedTCPPorts = [ cfg.port ];
218 };
219
220 users.extraUsers.redis =
221 { name = cfg.user;
222 uid = config.ids.uids.redis;
223 description = "Redis database user";
224 };
225
226 environment.systemPackages = [ cfg.package ];
227
228 systemd.services.redis_init =
229 { description = "Redis Server Initialisation";
230
231 wantedBy = [ "redis.service" ];
232 before = [ "redis.service" ];
233
234 serviceConfig.Type = "oneshot";
235
236 script = ''
237 install -d -m0700 -o ${cfg.user} ${cfg.dbpath}
238 chown -R ${cfg.user} ${cfg.dbpath}
239 '';
240 };
241
242 systemd.services.redis =
243 { description = "Redis Server";
244
245 wantedBy = [ "multi-user.target" ];
246 after = [ "network.target" ];
247
248 serviceConfig = {
249 ExecStart = "${cfg.package}/bin/redis-server ${redisConfig}";
250 User = cfg.user;
251 };
252 };
253
254 };
255
256}