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.service" ];
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
128 if `config.services.postgresqlBackup.backupAll` is enabled.
129 Note that config.services.postgresqlBackup.backupAll is also active,
130 when no databases where specified.
131 '';
132 };
133
134 compression = lib.mkOption {
135 type = lib.types.enum [
136 "none"
137 "gzip"
138 "zstd"
139 ];
140 default = "gzip";
141 description = ''
142 The type of compression to use on the generated database dump.
143 '';
144 };
145
146 compressionLevel = lib.mkOption {
147 type = lib.types.ints.between 1 19;
148 default = 6;
149 description = ''
150 The compression level used when compression is enabled.
151 gzip accepts levels 1 to 9. zstd accepts levels 1 to 19.
152 '';
153 };
154 };
155
156 };
157
158 config = lib.mkMerge [
159 {
160 assertions = [
161 {
162 assertion = cfg.backupAll -> cfg.databases == [ ];
163 message = "config.services.postgresqlBackup.backupAll cannot be used together with config.services.postgresqlBackup.databases";
164 }
165 {
166 assertion =
167 cfg.compression == "none"
168 || (cfg.compression == "gzip" && cfg.compressionLevel >= 1 && cfg.compressionLevel <= 9)
169 || (cfg.compression == "zstd" && cfg.compressionLevel >= 1 && cfg.compressionLevel <= 19);
170 message = "config.services.postgresqlBackup.compressionLevel must be set between 1 and 9 for gzip and 1 and 19 for zstd";
171 }
172 ];
173 }
174 (lib.mkIf cfg.enable {
175 systemd.tmpfiles.rules = [
176 "d '${cfg.location}' 0700 postgres - - -"
177 ];
178 })
179 (lib.mkIf (cfg.enable && cfg.backupAll) {
180 systemd.services.postgresqlBackup = postgresqlBackupService "all" "pg_dumpall";
181 })
182 (lib.mkIf (cfg.enable && !cfg.backupAll) {
183 systemd.services = lib.listToAttrs (
184 map (
185 db:
186 let
187 cmd = "pg_dump ${cfg.pgdumpOptions} ${db}";
188 in
189 {
190 name = "postgresqlBackup-${db}";
191 value = postgresqlBackupService db cmd;
192 }
193 ) cfg.databases
194 );
195 })
196 ];
197
198 meta.maintainers = with lib.maintainers; [ Scrumplex ];
199}