at 25.11-pre 6.9 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.mysqlBackup; 9 defaultUser = "mysqlbackup"; 10 11 # Newer mariadb versions warn of the usage of 'mysqldump' and recommend 'mariadb-dump' (https://mariadb.com/kb/en/mysqldump/) 12 dumpBinary = 13 if 14 ( 15 lib.getName config.services.mysql.package == lib.getName pkgs.mariadb 16 && lib.versionAtLeast config.services.mysql.package.version "11.0.0" 17 ) 18 then 19 "${config.services.mysql.package}/bin/mariadb-dump" 20 else 21 "${config.services.mysql.package}/bin/mysqldump"; 22 23 compressionAlgs = { 24 gzip = rec { 25 pkg = pkgs.gzip; 26 ext = ".gz"; 27 minLevel = 1; 28 maxLevel = 9; 29 cmd = compressionLevelFlag: "${pkg}/bin/gzip -c ${cfg.gzipOptions} ${compressionLevelFlag}"; 30 }; 31 xz = rec { 32 pkg = pkgs.xz; 33 ext = ".xz"; 34 minLevel = 0; 35 maxLevel = 9; 36 cmd = compressionLevelFlag: "${pkg}/bin/xz -z -c ${compressionLevelFlag} -"; 37 }; 38 zstd = rec { 39 pkg = pkgs.zstd; 40 ext = ".zst"; 41 minLevel = 1; 42 maxLevel = 19; 43 cmd = compressionLevelFlag: "${pkg}/bin/zstd ${compressionLevelFlag} -"; 44 }; 45 }; 46 47 compressionLevelFlag = lib.optionalString (cfg.compressionLevel != null) ( 48 "-" + toString cfg.compressionLevel 49 ); 50 51 selectedAlg = compressionAlgs.${cfg.compressionAlg}; 52 compressionCmd = selectedAlg.cmd compressionLevelFlag; 53 54 shouldUseSingleTransaction = 55 db: 56 if lib.isBool cfg.singleTransaction then 57 cfg.singleTransaction 58 else 59 lib.elem db cfg.singleTransaction; 60 61 backupScript = '' 62 set -o pipefail 63 failed="" 64 ${lib.concatMapStringsSep "\n" backupDatabaseScript cfg.databases} 65 if [ -n "$failed" ]; then 66 echo "Backup of database(s) failed:$failed" 67 exit 1 68 fi 69 ''; 70 71 backupDatabaseScript = db: '' 72 dest="${cfg.location}/${db}${selectedAlg.ext}" 73 if ${dumpBinary} ${lib.optionalString (shouldUseSingleTransaction db) "--single-transaction"} ${db} | ${compressionCmd} > $dest.tmp; then 74 mv $dest.tmp $dest 75 echo "Backed up to $dest" 76 else 77 echo "Failed to back up to $dest" 78 rm -f $dest.tmp 79 failed="$failed ${db}" 80 fi 81 ''; 82 83in 84{ 85 options = { 86 services.mysqlBackup = { 87 enable = lib.mkEnableOption "MySQL backups"; 88 89 calendar = lib.mkOption { 90 type = lib.types.str; 91 default = "01:15:00"; 92 description = '' 93 Configured when to run the backup service systemd unit (DayOfWeek Year-Month-Day Hour:Minute:Second). 94 ''; 95 }; 96 97 compressionAlg = lib.mkOption { 98 type = lib.types.enum (lib.attrNames compressionAlgs); 99 default = "gzip"; 100 description = '' 101 Compression algorithm to use for database dumps. 102 ''; 103 }; 104 105 compressionLevel = lib.mkOption { 106 type = lib.types.nullOr lib.types.int; 107 default = null; 108 description = '' 109 Compression level to use for ${lib.concatStringsSep ", " (lib.init (lib.attrNames compressionAlgs))} or ${lib.last (lib.attrNames compressionAlgs)}. 110 ${lib.concatStringsSep "\n" ( 111 lib.mapAttrsToList ( 112 name: algo: "- For ${name}: ${toString algo.minLevel}-${toString algo.maxLevel}" 113 ) compressionAlgs 114 )} 115 116 :::{.note} 117 If compression level is also specified in gzipOptions, the gzipOptions value will be overwritten 118 ::: 119 ''; 120 }; 121 122 user = lib.mkOption { 123 type = lib.types.str; 124 default = defaultUser; 125 description = '' 126 User to be used to perform backup. 127 ''; 128 }; 129 130 databases = lib.mkOption { 131 default = [ ]; 132 type = lib.types.listOf lib.types.str; 133 description = '' 134 List of database names to dump. 135 ''; 136 }; 137 138 location = lib.mkOption { 139 type = lib.types.path; 140 default = "/var/backup/mysql"; 141 description = '' 142 Location to put the compressed MySQL database dumps. 143 ''; 144 }; 145 146 singleTransaction = lib.mkOption { 147 default = false; 148 type = lib.types.oneOf [ 149 lib.types.bool 150 (lib.types.listOf lib.types.str) 151 ]; 152 description = '' 153 Whether to create database dump in a single transaction. 154 Can be either a boolean for all databases or a list of database names. 155 ''; 156 }; 157 158 gzipOptions = lib.mkOption { 159 default = "--no-name --rsyncable"; 160 type = lib.types.str; 161 description = '' 162 Command line options to use when invoking `gzip`. 163 Only used when compression is set to "gzip". 164 If compression level is specified both here and in compressionLevel, the compressionLevel value will take precedence. 165 ''; 166 }; 167 }; 168 }; 169 170 config = lib.mkIf cfg.enable { 171 # assert config to be correct 172 assertions = [ 173 { 174 assertion = 175 cfg.compressionLevel == null 176 || selectedAlg.minLevel <= cfg.compressionLevel && cfg.compressionLevel <= selectedAlg.maxLevel; 177 message = "${cfg.compressionAlg} compression level must be between ${toString selectedAlg.minLevel} and ${toString selectedAlg.maxLevel}"; 178 } 179 { 180 assertion = 181 !(lib.isList cfg.singleTransaction) 182 || lib.all (db: lib.elem db cfg.databases) cfg.singleTransaction; 183 message = "All databases in singleTransaction must be included in the databases option"; 184 } 185 ]; 186 187 # ensure unix user to be used to perform backup exist. 188 users.users = lib.optionalAttrs (cfg.user == defaultUser) { 189 ${defaultUser} = { 190 isSystemUser = true; 191 createHome = false; 192 home = cfg.location; 193 group = "nogroup"; 194 }; 195 }; 196 197 # add the compression tool to the system environment. 198 environment.systemPackages = [ selectedAlg.pkg ]; 199 200 # ensure database user to be used to perform backup exist. 201 services.mysql.ensureUsers = [ 202 { 203 name = cfg.user; 204 ensurePermissions = 205 let 206 privs = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES"; 207 grant = db: lib.nameValuePair "\\`${db}\\`.*" privs; 208 in 209 lib.listToAttrs (map grant cfg.databases); 210 } 211 ]; 212 213 systemd = { 214 timers.mysql-backup = { 215 description = "Mysql backup timer"; 216 wantedBy = [ "timers.target" ]; 217 timerConfig = { 218 OnCalendar = cfg.calendar; 219 AccuracySec = "5m"; 220 Unit = "mysql-backup.service"; 221 }; 222 }; 223 services.mysql-backup = { 224 description = "MySQL backup service"; 225 enable = true; 226 serviceConfig = { 227 Type = "oneshot"; 228 User = cfg.user; 229 }; 230 script = backupScript; 231 }; 232 tmpfiles.rules = [ 233 "d ${cfg.location} 0700 ${cfg.user} - - -" 234 ]; 235 }; 236 }; 237 238 meta.maintainers = [ lib.maintainers._6543 ]; 239}