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