python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / cluster / patroni / default.nix
blob83b372f59497cc5e98f80b83117a84bd5b4cae66
1 { config, lib, pkgs, ... }:
2 with lib;
3 let
4   cfg = config.services.patroni;
5   defaultUser = "patroni";
6   defaultGroup = "patroni";
7   format = pkgs.formats.yaml { };
9   #boto doesn't support python 3.10 yet
10   patroni = pkgs.patroni.override { pythonPackages = pkgs.python39Packages; };
12   configFileName = "patroni-${cfg.scope}-${cfg.name}.yaml";
13   configFile = format.generate configFileName cfg.settings;
16   options.services.patroni = {
18     enable = mkEnableOption (lib.mdDoc "Patroni");
20     postgresqlPackage = mkOption {
21       type = types.package;
22       example = literalExpression "pkgs.postgresql_14";
23       description = mdDoc ''
24         PostgreSQL package to use.
25         Plugins can be enabled like this `pkgs.postgresql_14.withPackages (p: [ p.pg_safeupdate p.postgis ])`.
26       '';
27     };
29     postgresqlDataDir = mkOption {
30       type = types.path;
31       defaultText = literalExpression ''"/var/lib/postgresql/''${config.services.patroni.postgresqlPackage.psqlSchema}"'';
32       example = "/var/lib/postgresql/14";
33       default = "/var/lib/postgresql/${cfg.postgresqlPackage.psqlSchema}";
34       description = mdDoc ''
35         The data directory for PostgreSQL. If left as the default value
36         this directory will automatically be created before the PostgreSQL server starts, otherwise
37         the sysadmin is responsible for ensuring the directory exists with appropriate ownership
38         and permissions.
39       '';
40     };
42     postgresqlPort = mkOption {
43       type = types.port;
44       default = 5432;
45       description = mdDoc ''
46         The port on which PostgreSQL listens.
47       '';
48     };
50     user = mkOption {
51       type = types.str;
52       default = defaultUser;
53       example = "postgres";
54       description = mdDoc ''
55         The user for the service. If left as the default value this user will automatically be created,
56         otherwise the sysadmin is responsible for ensuring the user exists.
57       '';
58     };
60     group = mkOption {
61       type = types.str;
62       default = defaultGroup;
63       example = "postgres";
64       description = mdDoc ''
65         The group for the service. If left as the default value this group will automatically be created,
66         otherwise the sysadmin is responsible for ensuring the group exists.
67       '';
68     };
70     dataDir = mkOption {
71       type = types.path;
72       default = "/var/lib/patroni";
73       description = mdDoc ''
74         Folder where Patroni data will be written, used by Raft as well if enabled.
75       '';
76     };
78     scope = mkOption {
79       type = types.str;
80       example = "cluster1";
81       description = mdDoc ''
82         Cluster name.
83       '';
84     };
86     name = mkOption {
87       type = types.str;
88       example = "node1";
89       description = mdDoc ''
90         The name of the host. Must be unique for the cluster.
91       '';
92     };
94     namespace = mkOption {
95       type = types.str;
96       default = "/service";
97       description = mdDoc ''
98         Path within the configuration store where Patroni will keep information about the cluster.
99       '';
100     };
102     nodeIp = mkOption {
103       type = types.str;
104       example = "192.168.1.1";
105       description = mdDoc ''
106         IP address of this node.
107       '';
108     };
110     otherNodesIps = mkOption {
111       type = types.listOf types.string;
112       example = [ "192.168.1.2" "192.168.1.3" ];
113       description = mdDoc ''
114         IP addresses of the other nodes.
115       '';
116     };
118     restApiPort = mkOption {
119       type = types.port;
120       default = 8008;
121       description = mdDoc ''
122         The port on Patroni's REST api listens.
123       '';
124     };
126     raft = mkOption {
127       type = types.bool;
128       default = false;
129       description = mdDoc ''
130         This will configure Patroni to use its own RAFT implementation instead of using a dedicated DCS.
131       '';
132     };
134     raftPort = mkOption {
135       type = types.port;
136       default = 5010;
137       description = mdDoc ''
138         The port on which RAFT listens.
139       '';
140     };
142     softwareWatchdog = mkOption {
143       type = types.bool;
144       default = false;
145       description = mdDoc ''
146         This will configure Patroni to use the software watchdog built into the Linux kernel
147         as described in the [documentation](https://patroni.readthedocs.io/en/latest/watchdog.html#setting-up-software-watchdog-on-linux).
148       '';
149     };
151     settings = mkOption {
152       type = format.type;
153       default = { };
154       description = mdDoc ''
155         The primary patroni configuration. See the [documentation](https://patroni.readthedocs.io/en/latest/SETTINGS.html)
156         for possible values.
157         Secrets should be passed in by using the `environmentFiles` option.
158       '';
159     };
161     environmentFiles = mkOption {
162       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
163       default = { };
164       example = {
165         PATRONI_REPLICATION_PASSWORD = "/secret/file";
166         PATRONI_SUPERUSER_PASSWORD = "/secret/file";
167       };
168       description = mdDoc "Environment variables made available to Patroni as files content, useful for providing secrets from files.";
169     };
170   };
172   config = mkIf cfg.enable {
174     services.patroni.settings = {
175       scope = cfg.scope;
176       name = cfg.name;
177       namespace = cfg.namespace;
179       restapi = {
180         listen = "${cfg.nodeIp}:${toString cfg.restApiPort}";
181         connect_address = "${cfg.nodeIp}:${toString cfg.restApiPort}";
182       };
184       raft = mkIf cfg.raft {
185         data_dir = "${cfg.dataDir}/raft";
186         self_addr = "${cfg.nodeIp}:5010";
187         partner_addrs = map (ip: ip + ":5010") cfg.otherNodesIps;
188       };
190       postgresql = {
191         listen = "${cfg.nodeIp}:${toString cfg.postgresqlPort}";
192         connect_address = "${cfg.nodeIp}:${toString cfg.postgresqlPort}";
193         data_dir = cfg.postgresqlDataDir;
194         bin_dir = "${cfg.postgresqlPackage}/bin";
195         pgpass = "${cfg.dataDir}/pgpass";
196       };
198       watchdog = mkIf cfg.softwareWatchdog {
199         mode = "required";
200         device = "/dev/watchdog";
201         safety_margin = 5;
202       };
203     };
206     users = {
207       users = mkIf (cfg.user == defaultUser) {
208         patroni = {
209           group = cfg.group;
210           isSystemUser = true;
211         };
212       };
213       groups = mkIf (cfg.group == defaultGroup) {
214         patroni = { };
215       };
216     };
218     systemd.services = {
219       patroni = {
220         description = "Runners to orchestrate a high-availability PostgreSQL";
222         wantedBy = [ "multi-user.target" ];
223         after = [ "network.target" ];
225         script = ''
226           ${concatStringsSep "\n" (attrValues (mapAttrs (name: path: ''export ${name}="$(< ${escapeShellArg path})"'') cfg.environmentFiles))}
227           exec ${patroni}/bin/patroni ${configFile}
228         '';
230         serviceConfig = mkMerge [
231           {
232             User = cfg.user;
233             Group = cfg.group;
234             Type = "simple";
235             Restart = "on-failure";
236             TimeoutSec = 30;
237             ExecReload = "${pkgs.coreutils}/bin/kill -s HUP $MAINPID";
238             KillMode = "process";
239           }
240           (mkIf (cfg.postgresqlDataDir == "/var/lib/postgresql/${cfg.postgresqlPackage.psqlSchema}" && cfg.dataDir == "/var/lib/patroni") {
241             StateDirectory = "patroni patroni/raft postgresql postgresql/${cfg.postgresqlPackage.psqlSchema}";
242             StateDirectoryMode = "0750";
243           })
244         ];
245       };
246     };
248     boot.kernelModules = mkIf cfg.softwareWatchdog [ "softdog" ];
250     services.udev.extraRules = mkIf cfg.softwareWatchdog ''
251       KERNEL=="watchdog", OWNER="${cfg.user}", GROUP="${cfg.group}", MODE="0600"
252     '';
254     environment.systemPackages = [
255       patroni
256       cfg.postgresqlPackage
257       (mkIf cfg.raft pkgs.python310Packages.pysyncobj)
258     ];
260     environment.etc."${configFileName}".source = configFile;
262     environment.sessionVariables = {
263       PATRONICTL_CONFIG_FILE = "/etc/${configFileName}";
264     };
265   };
267   meta.maintainers = [ maintainers.phfroidmont ];