1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 cfg = config.services.postgresqlBackup;
10
11 postgresqlBackupService =
12 db: dumpCmd:
13 let
14 compressSuffixes = {
15 "none" = "";
16 "gzip" = ".gz";
17 "zstd" = ".zstd";
18 };
19 compressSuffix = lib.getAttr cfg.compression compressSuffixes;
20
21 compressCmd = lib.getAttr cfg.compression {
22 "none" = "cat";
23 "gzip" = "${pkgs.gzip}/bin/gzip -c -${toString cfg.compressionLevel} --rsyncable";
24 "zstd" = "${pkgs.zstd}/bin/zstd -c -${toString cfg.compressionLevel} --rsyncable";
25 };
26
27 mkSqlPath = prefix: suffix: "${cfg.location}/${db}${prefix}.sql${suffix}";
28 curFile = mkSqlPath "" compressSuffix;
29 prevFile = mkSqlPath ".prev" compressSuffix;
30 prevFiles = map (mkSqlPath ".prev") (lib.attrValues compressSuffixes);
31 inProgressFile = mkSqlPath ".in-progress" compressSuffix;
32 in
33 {
34 enable = true;
35
36 description = "Backup of ${db} database(s)";
37
38 requires = [ "postgresql.target" ];
39
40 path = [
41 pkgs.coreutils
42 config.services.postgresql.package
43 ];
44
45 script = ''
46 set -e -o pipefail
47
48 umask 0077 # ensure backup is only readable by postgres user
49
50 if [ -e ${curFile} ]; then
51 rm -f ${toString prevFiles}
52 mv ${curFile} ${prevFile}
53 fi
54
55 ${dumpCmd} \
56 | ${compressCmd} \
57 > ${inProgressFile}
58
59 mv ${inProgressFile} ${curFile}
60 '';
61
62 serviceConfig = {
63 Type = "oneshot";
64 User = "postgres";
65 };
66
67 startAt = cfg.startAt;
68 };
69
70in
71{
72
73 imports = [
74 (lib.mkRemovedOptionModule [ "services" "postgresqlBackup" "period" ] ''
75 A systemd timer is now used instead of cron.
76 The starting time can be configured via <literal>services.postgresqlBackup.startAt</literal>.
77 '')
78 ];
79
80 options = {
81 services.postgresqlBackup = {
82 enable = lib.mkEnableOption "PostgreSQL dumps";
83
84 startAt = lib.mkOption {
85 default = "*-*-* 01:15:00";
86 type = with lib.types; either (listOf str) str;
87 description = ''
88 This option defines (see `systemd.time` for format) when the
89 databases should be dumped.
90 The default is to update at 01:15 (at night) every day.
91 '';
92 };
93
94 backupAll = lib.mkOption {
95 default = cfg.databases == [ ];
96 defaultText = lib.literalExpression "services.postgresqlBackup.databases == []";
97 type = lib.types.bool;
98 description = ''
99 Backup all databases using pg_dumpall.
100 This option is mutual exclusive to
101 `services.postgresqlBackup.databases`.
102 The resulting backup dump will have the name all.sql.gz.
103 This option is the default if no databases are specified.
104 '';
105 };
106
107 databases = lib.mkOption {
108 default = [ ];
109 type = lib.types.listOf lib.types.str;
110 description = ''
111 List of database names to dump.
112 '';
113 };
114
115 location = lib.mkOption {
116 default = "/var/backup/postgresql";
117 type = lib.types.path;
118 description = ''
119 Path of directory where the PostgreSQL database dumps will be placed.
120 '';
121 };
122
123 pgdumpOptions = lib.mkOption {
124 type = lib.types.separatedString " ";
125 default = "-C";
126 description = ''
127 Command line options for pg_dump. This options is not used if
128 `config.services.postgresqlBackup.backupAll` is enabled. Note that
129 config.services.postgresqlBackup.backupAll is also active, when no
130 databases where specified.
131 '';
132 };
133
134 pgdumpAllOptions = lib.mkOption {
135 type = lib.types.separatedString " ";
136 default = "";
137 description = ''
138 Command line options for pg_dumpall. This options is not used if
139 `config.services.postgresqlBackup.backupAll` is disabled.
140 '';
141 };
142
143 compression = lib.mkOption {
144 type = lib.types.enum [
145 "none"
146 "gzip"
147 "zstd"
148 ];
149 default = "gzip";
150 description = ''
151 The type of compression to use on the generated database dump.
152 '';
153 };
154
155 compressionLevel = lib.mkOption {
156 type = lib.types.ints.between 1 19;
157 default = 6;
158 description = ''
159 The compression level used when compression is enabled.
160 gzip accepts levels 1 to 9. zstd accepts levels 1 to 19.
161 '';
162 };
163 };
164
165 };
166
167 config = lib.mkIf cfg.enable (
168 lib.mkMerge [
169 {
170 assertions = [
171 {
172 assertion = cfg.backupAll -> cfg.databases == [ ];
173 message = "config.services.postgresqlBackup.backupAll cannot be used together with config.services.postgresqlBackup.databases";
174 }
175 {
176 assertion =
177 cfg.compression == "none"
178 || (cfg.compression == "gzip" && cfg.compressionLevel >= 1 && cfg.compressionLevel <= 9)
179 || (cfg.compression == "zstd" && cfg.compressionLevel >= 1 && cfg.compressionLevel <= 19);
180 message = "config.services.postgresqlBackup.compressionLevel must be set between 1 and 9 for gzip and 1 and 19 for zstd";
181 }
182 ];
183
184 systemd.tmpfiles.rules = [
185 "d '${cfg.location}' 0700 postgres - - -"
186 ];
187 }
188
189 (lib.mkIf cfg.backupAll {
190 systemd.services.postgresqlBackup = postgresqlBackupService "all" "pg_dumpall ${cfg.pgdumpAllOptions}";
191 })
192
193 (lib.mkIf (!cfg.backupAll) {
194 systemd.services = lib.listToAttrs (
195 map (
196 db:
197 let
198 cmd = "pg_dump ${cfg.pgdumpOptions} ${db}";
199 in
200 {
201 name = "postgresqlBackup-${db}";
202 value = postgresqlBackupService db cmd;
203 }
204 ) cfg.databases
205 );
206 })
207 ]
208 );
209
210 meta.maintainers = with lib.maintainers; [ Scrumplex ];
211}