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