grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / databases / foundationdb.nix
blob8fc35474b39c559aafafc019ef5281f454c0957f
1 { config, lib, pkgs, ... }:
2 let
3   cfg = config.services.foundationdb;
4   pkg = cfg.package;
6   # used for initial cluster configuration
7   initialIpAddr = if (cfg.publicAddress != "auto") then cfg.publicAddress else "127.0.0.1";
9   fdbServers = n:
10     lib.concatStringsSep "\n" (map (x: "[fdbserver.${toString (x+cfg.listenPortStart)}]") (lib.range 0 (n - 1)));
12   backupAgents = n:
13     lib.concatStringsSep "\n" (map (x: "[backup_agent.${toString x}]") (lib.range 1 n));
15   configFile = pkgs.writeText "foundationdb.conf" ''
16     [general]
17     cluster_file  = /etc/foundationdb/fdb.cluster
19     [fdbmonitor]
20     restart_delay = ${toString cfg.restartDelay}
21     user          = ${cfg.user}
22     group         = ${cfg.group}
24     [fdbserver]
25     command        = ${pkg}/bin/fdbserver
26     public_address = ${cfg.publicAddress}:$ID
27     listen_address = ${cfg.listenAddress}
28     datadir        = ${cfg.dataDir}/$ID
29     logdir         = ${cfg.logDir}
30     logsize        = ${cfg.logSize}
31     maxlogssize    = ${cfg.maxLogSize}
32     ${lib.optionalString (cfg.class != null) "class = ${cfg.class}"}
33     memory         = ${cfg.memory}
34     storage_memory = ${cfg.storageMemory}
36     ${lib.optionalString (lib.versionAtLeast cfg.package.version "6.1") ''
37     trace_format   = ${cfg.traceFormat}
38     ''}
40     ${lib.optionalString (cfg.tls != null) ''
41       tls_plugin           = ${pkg}/libexec/plugins/FDBLibTLS.so
42       tls_certificate_file = ${cfg.tls.certificate}
43       tls_key_file         = ${cfg.tls.key}
44       tls_verify_peers     = ${cfg.tls.allowedPeers}
45     ''}
47     ${lib.optionalString (cfg.locality.machineId    != null) "locality_machineid=${cfg.locality.machineId}"}
48     ${lib.optionalString (cfg.locality.zoneId       != null) "locality_zoneid=${cfg.locality.zoneId}"}
49     ${lib.optionalString (cfg.locality.datacenterId != null) "locality_dcid=${cfg.locality.datacenterId}"}
50     ${lib.optionalString (cfg.locality.dataHall     != null) "locality_data_hall=${cfg.locality.dataHall}"}
52     ${fdbServers cfg.serverProcesses}
54     [backup_agent]
55     command = ${pkg}/libexec/backup_agent
56     ${backupAgents cfg.backupProcesses}
57   '';
60   options.services.foundationdb = {
62     enable = lib.mkEnableOption "FoundationDB Server";
64     package = lib.mkOption {
65       type        = lib.types.package;
66       description = ''
67         The FoundationDB package to use for this server. This must be specified by the user
68         in order to ensure migrations and upgrades are controlled appropriately.
69       '';
70     };
72     publicAddress = lib.mkOption {
73       type        = lib.types.str;
74       default     = "auto";
75       description = "Publicly visible IP address of the process. Port is determined by process ID";
76     };
78     listenAddress = lib.mkOption {
79       type        = lib.types.str;
80       default     = "public";
81       description = "Publicly visible IP address of the process. Port is determined by process ID";
82     };
84     listenPortStart = lib.mkOption {
85       type          = lib.types.int;
86       default       = 4500;
87       description   = ''
88         Starting port number for database listening sockets. Every FDB process binds to a
89         subsequent port, to this number reflects the start of the overall range. e.g. having
90         8 server processes will use all ports between 4500 and 4507.
91       '';
92     };
94     openFirewall = lib.mkOption {
95       type        = lib.types.bool;
96       default     = false;
97       description = ''
98         Open the firewall ports corresponding to FoundationDB processes and coordinators
99         using {option}`config.networking.firewall.*`.
100       '';
101     };
103     dataDir = lib.mkOption {
104       type        = lib.types.path;
105       default     = "/var/lib/foundationdb";
106       description = "Data directory. All cluster data will be put under here.";
107     };
109     logDir = lib.mkOption {
110       type        = lib.types.path;
111       default     = "/var/log/foundationdb";
112       description = "Log directory.";
113     };
115     user = lib.mkOption {
116       type        = lib.types.str;
117       default     = "foundationdb";
118       description = "User account under which FoundationDB runs.";
119     };
121     group = lib.mkOption {
122       type        = lib.types.str;
123       default     = "foundationdb";
124       description = "Group account under which FoundationDB runs.";
125     };
127     class = lib.mkOption {
128       type        = lib.types.nullOr (lib.types.enum [ "storage" "transaction" "stateless" ]);
129       default     = null;
130       description = "Process class";
131     };
133     restartDelay = lib.mkOption {
134       type = lib.types.int;
135       default = 10;
136       description = "Number of seconds to wait before restarting servers.";
137     };
139     logSize = lib.mkOption {
140       type        = lib.types.str;
141       default     = "10MiB";
142       description = ''
143         Roll over to a new log file after the current log file
144         reaches the specified size.
145       '';
146     };
148     maxLogSize = lib.mkOption {
149       type        = lib.types.str;
150       default     = "100MiB";
151       description = ''
152         Delete the oldest log file when the total size of all log
153         files exceeds the specified size. If set to 0, old log files
154         will not be deleted.
155       '';
156     };
158     serverProcesses = lib.mkOption {
159       type = lib.types.int;
160       default = 1;
161       description = "Number of fdbserver processes to run.";
162     };
164     backupProcesses = lib.mkOption {
165       type = lib.types.int;
166       default = 1;
167       description = "Number of backup_agent processes to run for snapshots.";
168     };
170     memory = lib.mkOption {
171       type        = lib.types.str;
172       default     = "8GiB";
173       description = ''
174         Maximum memory used by the process. The default value is
175         `8GiB`. When specified without a unit,
176         `MiB` is assumed. This parameter does not
177         change the memory allocation of the program. Rather, it sets
178         a hard limit beyond which the process will kill itself and
179         be restarted. The default value of `8GiB`
180         is double the intended memory usage in the default
181         configuration (providing an emergency buffer to deal with
182         memory leaks or similar problems). It is not recommended to
183         decrease the value of this parameter below its default
184         value. It may be increased if you wish to allocate a very
185         large amount of storage engine memory or cache. In
186         particular, when the `storageMemory`
187         parameter is increased, the `memory`
188         parameter should be increased by an equal amount.
189       '';
190     };
192     storageMemory = lib.mkOption {
193       type        = lib.types.str;
194       default     = "1GiB";
195       description = ''
196         Maximum memory used for data storage. The default value is
197         `1GiB`. When specified without a unit,
198         `MB` is assumed. Clusters using the memory
199         storage engine will be restricted to using this amount of
200         memory per process for purposes of data storage. Memory
201         overhead associated with storing the data is counted against
202         this total. If you increase the
203         `storageMemory`, you should also increase
204         the `memory` parameter by the same amount.
205       '';
206     };
208     tls = lib.mkOption {
209       default = null;
210       description = ''
211         FoundationDB Transport Security Layer (TLS) settings.
212       '';
214       type = lib.types.nullOr (lib.types.submodule ({
215         options = {
216           certificate = lib.mkOption {
217             type = lib.types.str;
218             description = ''
219               Path to the TLS certificate file. This certificate will
220               be offered to, and may be verified by, clients.
221             '';
222           };
224           key = lib.mkOption {
225             type = lib.types.str;
226             description = "Private key file for the certificate.";
227           };
229           allowedPeers = lib.mkOption {
230             type = lib.types.str;
231             default = "Check.Valid=1,Check.Unexpired=1";
232             description = ''
233               "Peer verification string". This may be used to adjust which TLS
234               client certificates a server will accept, as a form of user
235               authorization; for example, it may only accept TLS clients who
236               offer a certificate abiding by some locality or organization name.
238               For more information, please see the FoundationDB documentation.
239             '';
240           };
241         };
242       }));
243     };
245     locality = lib.mkOption {
246       default = {
247         machineId    = null;
248         zoneId       = null;
249         datacenterId = null;
250         dataHall     = null;
251       };
253       description = ''
254         FoundationDB locality settings.
255       '';
257       type = lib.types.submodule ({
258         options = {
259           machineId = lib.mkOption {
260             default = null;
261             type = lib.types.nullOr lib.types.str;
262             description = ''
263               Machine identifier key. All processes on a machine should share a
264               unique id. By default, processes on a machine determine a unique id to share.
265               This does not generally need to be set.
266             '';
267           };
269           zoneId = lib.mkOption {
270             default = null;
271             type = lib.types.nullOr lib.types.str;
272             description = ''
273               Zone identifier key. Processes that share a zone id are
274               considered non-unique for the purposes of data replication.
275               If unset, defaults to machine id.
276             '';
277           };
279           datacenterId = lib.mkOption {
280             default = null;
281             type = lib.types.nullOr lib.types.str;
282             description = ''
283               Data center identifier key. All processes physically located in a
284               data center should share the id. If you are depending on data
285               center based replication this must be set on all processes.
286             '';
287           };
289           dataHall = lib.mkOption {
290             default = null;
291             type = lib.types.nullOr lib.types.str;
292             description = ''
293               Data hall identifier key. All processes physically located in a
294               data hall should share the id. If you are depending on data
295               hall based replication this must be set on all processes.
296             '';
297           };
298         };
299       });
300     };
302     extraReadWritePaths = lib.mkOption {
303       default = [ ];
304       type = lib.types.listOf lib.types.path;
305       description = ''
306         An extra set of filesystem paths that FoundationDB can read to
307         and write from. By default, FoundationDB runs under a heavily
308         namespaced systemd environment without write access to most of
309         the filesystem outside of its data and log directories. By
310         adding paths to this list, the set of writeable paths will be
311         expanded. This is useful for allowing e.g. backups to local files,
312         which must be performed on behalf of the foundationdb service.
313       '';
314     };
316     pidfile = lib.mkOption {
317       type        = lib.types.path;
318       default     = "/run/foundationdb.pid";
319       description = "Path to pidfile for fdbmonitor.";
320     };
322     traceFormat = lib.mkOption {
323       type = lib.types.enum [ "xml" "json" ];
324       default = "xml";
325       description = "Trace logging format.";
326     };
327   };
329   config = lib.mkIf cfg.enable {
330     assertions = [
331       { assertion = lib.versionOlder cfg.package.version "6.1" -> cfg.traceFormat == "xml";
332         message = ''
333           Versions of FoundationDB before 6.1 do not support configurable trace formats (only XML is supported).
334           This option has no effect for version '' + cfg.package.version + '', and enabling it is an error.
335         '';
336       }
337     ];
339     environment.systemPackages = [ pkg ];
341     users.users = lib.optionalAttrs (cfg.user == "foundationdb") {
342       foundationdb = {
343         description = "FoundationDB User";
344         uid         = config.ids.uids.foundationdb;
345         group       = cfg.group;
346       };
347     };
349     users.groups = lib.optionalAttrs (cfg.group == "foundationdb") {
350       foundationdb.gid = config.ids.gids.foundationdb;
351     };
353     networking.firewall.allowedTCPPortRanges = lib.mkIf cfg.openFirewall
354       [ { from = cfg.listenPortStart;
355           to = (cfg.listenPortStart + cfg.serverProcesses) - 1;
356         }
357       ];
359     systemd.tmpfiles.rules = [
360       "d /etc/foundationdb 0755 ${cfg.user} ${cfg.group} - -"
361       "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
362       "d '${cfg.logDir}' 0770 ${cfg.user} ${cfg.group} - -"
363       "F '${cfg.pidfile}' - ${cfg.user} ${cfg.group} - -"
364     ];
366     systemd.services.foundationdb = {
367       description             = "FoundationDB Service";
369       after                   = [ "network.target" ];
370       wantedBy                = [ "multi-user.target" ];
371       unitConfig =
372         { RequiresMountsFor = "${cfg.dataDir} ${cfg.logDir}";
373         };
375       serviceConfig =
376         let rwpaths = [ cfg.dataDir cfg.logDir cfg.pidfile "/etc/foundationdb" ]
377                    ++ cfg.extraReadWritePaths;
378         in
379         { Type       = "simple";
380           Restart    = "always";
381           RestartSec = 5;
382           User       = cfg.user;
383           Group      = cfg.group;
384           PIDFile    = "${cfg.pidfile}";
386           PermissionsStartOnly = true;  # setup needs root perms
387           TimeoutSec           = 120;   # give reasonable time to shut down
389           # Security options
390           NoNewPrivileges       = true;
391           ProtectHome           = true;
392           ProtectSystem         = "strict";
393           ProtectKernelTunables = true;
394           ProtectControlGroups  = true;
395           PrivateTmp            = true;
396           PrivateDevices        = true;
397           ReadWritePaths        = lib.concatStringsSep " " (map (x: "-" + x) rwpaths);
398         };
400       path = [ pkg pkgs.coreutils ];
402       preStart = ''
403         if [ ! -f /etc/foundationdb/fdb.cluster ]; then
404             cf=/etc/foundationdb/fdb.cluster
405             desc=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
406             rand=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
407             echo ''${desc}:''${rand}@${initialIpAddr}:${builtins.toString cfg.listenPortStart} > $cf
408             chmod 0664 $cf
409             touch "${cfg.dataDir}/.first_startup"
410         fi
411       '';
413       script = "exec fdbmonitor --lockfile ${cfg.pidfile} --conffile ${configFile}";
415       postStart = ''
416         if [ -e "${cfg.dataDir}/.first_startup" ]; then
417           fdbcli --exec "configure new single ssd"
418           rm -f "${cfg.dataDir}/.first_startup";
419         fi
420       '';
421     };
422   };
424   meta.doc         = ./foundationdb.md;
425   meta.maintainers = with lib.maintainers; [ thoughtpolice ];