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 bind = mkOption {
72 type = with types; nullOr str;
73 default = null; # All interfaces
74 description = "The IP interface to bind to.";
75 example = "127.0.0.1";
76 };
77
78 unixSocket = mkOption {
79 type = with types; nullOr path;
80 default = null;
81 description = "The path to the socket to bind to.";
82 example = "/var/run/redis.sock";
83 };
84
85 logLevel = mkOption {
86 type = types.str;
87 default = "notice"; # debug, verbose, notice, warning
88 example = "debug";
89 description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
90 };
91
92 logfile = mkOption {
93 type = types.str;
94 default = "/dev/null";
95 description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
96 example = "/var/log/redis.log";
97 };
98
99 syslog = mkOption {
100 type = types.bool;
101 default = true;
102 description = "Enable logging to the system logger.";
103 };
104
105 databases = mkOption {
106 type = types.int;
107 default = 16;
108 description = "Set the number of databases.";
109 };
110
111 save = mkOption {
112 type = with types; listOf (listOf int);
113 default = [ [900 1] [300 10] [60 10000] ];
114 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.";
115 example = [ [900 1] [300 10] [60 10000] ];
116 };
117
118 dbFilename = mkOption {
119 type = types.str;
120 default = "dump.rdb";
121 description = "The filename where to dump the DB.";
122 };
123
124 dbpath = mkOption {
125 type = types.path;
126 default = "/var/lib/redis";
127 description = "The DB will be written inside this directory, with the filename specified using the 'dbFilename' configuration.";
128 };
129
130 slaveOf = mkOption {
131 default = null; # { ip, port }
132 description = "An attribute set with two attributes: ip and port to which this redis instance acts as a slave.";
133 example = { ip = "192.168.1.100"; port = 6379; };
134 };
135
136 masterAuth = mkOption {
137 default = null;
138 description = ''If the master is password protected (using the requirePass configuration)
139 it is possible to tell the slave to authenticate before starting the replication synchronization
140 process, otherwise the master will refuse the slave request.
141 (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
142 };
143
144 requirePass = mkOption {
145 type = with types; nullOr str;
146 default = null;
147 description = "Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)";
148 example = "letmein!";
149 };
150
151 appendOnly = mkOption {
152 type = types.bool;
153 default = false;
154 description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
155 };
156
157 appendOnlyFilename = mkOption {
158 type = types.str;
159 default = "appendonly.aof";
160 description = "Filename for the append-only file (stored inside of dbpath)";
161 };
162
163 appendFsync = mkOption {
164 type = types.str;
165 default = "everysec"; # no, always, everysec
166 description = "How often to fsync the append-only log, options: no, always, everysec.";
167 };
168
169 slowLogLogSlowerThan = mkOption {
170 type = types.int;
171 default = 10000;
172 description = "Log queries whose execution take longer than X in milliseconds.";
173 example = 1000;
174 };
175
176 slowLogMaxLen = mkOption {
177 type = types.int;
178 default = 128;
179 description = "Maximum number of items to keep in slow log.";
180 };
181
182 extraConfig = mkOption {
183 type = types.lines;
184 default = "";
185 description = "Extra configuration options for redis.conf.";
186 };
187 };
188
189 };
190
191
192 ###### implementation
193
194 config = mkIf config.services.redis.enable {
195
196 users.extraUsers.redis =
197 { name = cfg.user;
198 uid = config.ids.uids.redis;
199 description = "Redis database user";
200 };
201
202 environment.systemPackages = [ cfg.package ];
203
204 systemd.services.redis_init =
205 { description = "Redis Server Initialisation";
206
207 wantedBy = [ "redis.service" ];
208 before = [ "redis.service" ];
209
210 serviceConfig.Type = "oneshot";
211
212 script = ''
213 if ! test -e ${cfg.dbpath}; then
214 install -d -m0700 -o ${cfg.user} ${cfg.dbpath}
215 fi
216 '';
217 };
218
219 systemd.services.redis =
220 { description = "Redis Server";
221
222 wantedBy = [ "multi-user.target" ];
223 after = [ "network.target" ];
224
225 serviceConfig = {
226 ExecStart = "${cfg.package}/bin/redis-server ${redisConfig}";
227 User = cfg.user;
228 };
229 };
230
231 };
232
233}