summaryrefslogtreecommitdiff
path: root/modules/pgbackrest.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/pgbackrest.nix')
-rw-r--r--modules/pgbackrest.nix192
1 files changed, 192 insertions, 0 deletions
diff --git a/modules/pgbackrest.nix b/modules/pgbackrest.nix
new file mode 100644
index 00000000..41a7b381
--- /dev/null
+++ b/modules/pgbackrest.nix
@@ -0,0 +1,192 @@
1{ config, pkgs, lib, utils, ... }:
2
3with utils;
4with lib;
5
6let
7 cfg = config.services.pgbackrest;
8 settingsFormat = pkgs.formats.ini {
9 listsAsDuplicateKeys = true;
10 mkKeyValue = lib.generators.mkKeyValueDefault {
11 mkValueString = v: with builtins;
12 let err = t: v: abort
13 ("mkValueString: " +
14 "${t} not supported: ${toPretty {} v}");
15 in if isInt v then toString v
16 # convert derivations to store paths
17 else if lib.isDerivation v then toString v
18 # we default to not quoting strings
19 else if isString v then v
20 # isString returns "1", which is not a good default
21 else if true == v then "y"
22 # here it returns to "", which is even less of a good default
23 else if false == v then "n"
24 else if null == v then "null"
25 # if you have lists you probably want to replace this
26 else if isList v then err "lists" v
27 # same as for lists, might want to replace
28 else if isAttrs v then err "attrsets" v
29 # functions can’t be printed of course
30 else if isFunction v then err "functions" v
31 # Floats currently can't be converted to precise strings,
32 # condition warning on nix version once this isn't a problem anymore
33 # See https://github.com/NixOS/nix/pull/3480
34 else if isFloat v then libStr.floatToString v
35 else err "this value is" (toString v);
36 } "=";
37 };
38
39 loglevelType = types.enum ["off" "error" "warn" "info" "detail" "debug" "trace"];
40 inherit (utils.systemdUtils.unitOptions) unitOption;
41in {
42 options = {
43 services.pgbackrest = {
44 enable = mkEnableOption "pgBackRest";
45
46 package = mkPackageOption pkgs "pgbackrest" {};
47
48 configurePostgresql = {
49 enable = mkEnableOption "configuring PostgreSQL for sending WAL to pgBackRest" // {
50 default = config.services.postgresql.enable;
51 defaultText = literalExpression "config.systemd.services.postgresql.enable";
52 };
53
54 stanza = mkOption {
55 type = types.str;
56 default = config.networking.hostName;
57 };
58 };
59
60 settings = mkOption {
61 type = types.submodule {
62 freeformType = settingsFormat.type;
63
64 options = {
65 global.log-level-console = mkOption {
66 type = loglevelType;
67 default = "detail";
68 };
69 global.log-level-file = mkOption {
70 type = loglevelType;
71 default = "off";
72 };
73 global.log-level-stderr = mkOption {
74 type = loglevelType;
75 default = "warn";
76 };
77
78 global.log-subprocess = mkOption {
79 type = types.bool;
80 default = true;
81 };
82 global.log-timestamp = mkOption {
83 type = types.bool;
84 default = false;
85 };
86 };
87 };
88 default = {};
89 description = ''
90 Configuration for pgBackRest
91 '';
92 };
93
94 tlsServer = {
95 enable = mkEnableOption "pgBackRest TLS Server";
96
97 user = mkOption {
98 type = types.str;
99 default = "postgres";
100 };
101 group = mkOption {
102 type = types.str;
103 default = "postgres";
104 };
105 };
106
107 backups = mkOption {
108 type = types.attrsOf (types.submodule ({ name, ... }: {
109 options = {
110 type = mkOption {
111 type = types.enum ["full" "incr" "diff"];
112 default = "full";
113 };
114
115 stanza = mkOption {
116 type = types.str;
117 default = cfg.configurePostgresql.stanza;
118 };
119 repo = mkOption {
120 type = types.nullOr (types.strMatching "^[0-9]+$");
121 };
122
123 user = mkOption {
124 type = types.str;
125 default = "postgres";
126 };
127 group = mkOption {
128 type = types.str;
129 default = "postgres";
130 };
131
132 timerConfig = mkOption {
133 type = types.attrsOf unitOption;
134 };
135 };
136 }));
137 default = {};
138 };
139 };
140 };
141
142 config = mkIf cfg.enable {
143 environment.systemPackages = [ cfg.package ];
144
145 services.postgresql.settings = mkIf cfg.configurePostgresql.enable {
146 archive_command = "pgbackrest --stanza ${escapeSystemdExecArg cfg.configurePostgresql.stanza} archive-push %p";
147 archive_mode = true;
148 max_wal_senders = mkDefault 3;
149 wal_level = "replica";
150 };
151
152 systemd.services = {
153 postgresql.path = mkIf cfg.configurePostgresql.enable [ cfg.package ];
154 pgbackrest-tls-server = mkIf cfg.tlsServer.enable {
155 description = "pgBackRest TLS-Server";
156 wantedBy = [ "multi-user.target" ];
157 after = [ "network.target" ];
158
159 restartTriggers = [ config.environment.etc."pgbackrest/pgbackrest.conf".source ];
160
161 unitConfig = {
162 StartLimitIntervalSec = 0;
163 };
164
165 serviceConfig = {
166 Type = "simple";
167 Restart = "always";
168 RestartSec = 1;
169 User = cfg.tlsServer.user;
170 Group = cfg.tlsServer.group;
171 ExecStart = "${cfg.package}/bin/pgbackrest server";
172 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
173 };
174 };
175 } // mapAttrs' (name: backupCfg: nameValuePair "pgbackrest-backup@${escapeSystemdPath name}" {
176 description = "Perform pgBackRest Backup (${name}${optionalString (!(isNull backupCfg.repo)) " repo${backupCfg.repo}"})";
177 serviceConfig = {
178 Type = "oneshot";
179 ExecStart = "${cfg.package}/bin/pgbackrest --type ${escapeSystemdExecArg backupCfg.type} --stanza ${escapeSystemdExecArg backupCfg.stanza}${optionalString (!(isNull backupCfg.repo)) " --repo ${backupCfg.repo}"} backup";
180 User = backupCfg.user;
181 Group = backupCfg.group;
182 };
183 }) cfg.backups;
184
185 systemd.timers = mapAttrs' (name: backupCfg: nameValuePair "pgbackrest-backup@${escapeSystemdPath name}" {
186 wantedBy = [ "timers.target" ];
187 inherit (backupCfg) timerConfig;
188 }) cfg.backups;
189
190 environment.etc."pgbackrest/pgbackrest.conf".source = settingsFormat.generate "pgbackrest.conf" cfg.settings;
191 };
192}