at master 7.8 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.services.libvirtd.autoSnapshot; 10 11 # Function to get VM config with defaults 12 getVMConfig = 13 vm: 14 if lib.isString vm then 15 { 16 name = vm; 17 inherit (cfg) snapshotType keep; 18 } 19 else 20 { 21 inherit (vm) name; 22 snapshotType = if vm.snapshotType != null then vm.snapshotType else cfg.snapshotType; 23 keep = if vm.keep != null then vm.keep else cfg.keep; 24 }; 25 26 # Main backup script combining all VM scripts 27 backupScript = '' 28 set -eo pipefail 29 30 # Initialize failure tracking 31 failed="" 32 33 # Define the VM snapshot function 34 function snap_vm() { 35 local vmName="$1" 36 local snapshotType="$2" 37 local keep="$3" 38 39 # Add validation for VM name 40 if ! echo "$vmName" | ${pkgs.gnugrep}/bin/grep -qE '^[a-zA-Z0-9_.-]+$'; then 41 echo "Invalid VM name: '$vmName'" 42 failed="$failed $vmName" 43 return 44 fi 45 46 echo "Processing VM: $vmName" 47 48 # Check if VM exists 49 if ! ${pkgs.libvirt}/bin/virsh dominfo "$vmName" >/dev/null 2>&1; then 50 echo "VM '$vmName' does not exist, skipping" 51 return 52 fi 53 54 # Create new snapshot 55 local snapshot_name 56 snapshot_name="${cfg.prefix}_$(date +%Y-%m-%d_%H%M%S)" 57 local snapshot_opts="" 58 [[ "$snapshotType" == "external" ]] && snapshot_opts="--disk-only" 59 if ! ${pkgs.libvirt}/bin/virsh snapshot-create-as \ 60 "$vmName" \ 61 "$snapshot_name" \ 62 "Automatic backup snapshot" \ 63 $snapshot_opts \ 64 --atomic; then 65 echo "Failed to create snapshot for $vmName" 66 failed="$failed $vmName" 67 return 68 fi 69 70 # List all automatic snapshots for this VM 71 readarray -t SNAPSHOTS < <(${pkgs.libvirt}/bin/virsh snapshot-list "$vmName" --name | ${pkgs.gnugrep}/bin/grep "^${cfg.prefix}_") 72 73 # Count snapshots 74 local snapshot_count=''${#SNAPSHOTS[@]} 75 76 # Delete old snapshots if we have more than the keep limit 77 if [[ $snapshot_count -gt $keep ]]; then 78 # Sort snapshots by date (they're named with date prefix) 79 readarray -t TO_DELETE < <(printf '%s\n' "''${SNAPSHOTS[@]}" | ${pkgs.coreutils}/bin/sort | ${pkgs.coreutils}/bin/head -n -$keep) 80 for snap in "''${TO_DELETE[@]}"; do 81 echo "Removing old snapshot $snap from $vmName" 82 83 # Check if snapshot is internal or external 84 local snapshot_location 85 snapshot_location=$(${pkgs.libvirt}/bin/virsh snapshot-info "$vmName" --snapshotname "$snap" | ${pkgs.gnugrep}/bin/grep "Location:" | ${pkgs.gawk}/bin/awk '{print $2}') 86 87 local delete_opts="" 88 [[ "$snapshot_location" == "internal" ]] && delete_opts="--metadata" 89 90 if ! ${pkgs.libvirt}/bin/virsh snapshot-delete "$vmName" "$snap" $delete_opts; then 91 echo "Failed to remove snapshot $snap from $vmName" 92 failed="$failed $vmName(cleanup)" 93 fi 94 done 95 fi 96 } 97 98 ${ 99 if cfg.vms == null then 100 '' 101 # Process all VMs 102 ${pkgs.libvirt}/bin/virsh list --all --name | while read -r vm; do 103 # Skip empty lines 104 [ -z "$vm" ] && continue 105 106 # Call snap_vm function with default settings 107 snap_vm "$vm" ${cfg.snapshotType} ${toString cfg.keep} 108 done 109 '' 110 else 111 '' 112 # Process specific VMs from the list 113 ${lib.concatMapStrings ( 114 vm: with getVMConfig vm; "snap_vm '${name}' ${snapshotType} ${toString keep}\n" 115 ) cfg.vms} 116 '' 117 } 118 119 # Report any failures 120 if [ -n "$failed" ]; then 121 echo "Snapshot operation failed for:$failed" 122 exit 1 123 fi 124 125 exit 0 126 ''; 127in 128{ 129 options = { 130 services.libvirtd.autoSnapshot = { 131 enable = lib.mkEnableOption "LibVirt VM snapshots"; 132 133 calendar = lib.mkOption { 134 type = lib.types.str; 135 default = "04:15:00"; 136 description = '' 137 When to create snapshots (systemd calendar format). 138 Default is 4:15 AM. 139 ''; 140 }; 141 142 prefix = lib.mkOption { 143 type = lib.types.str; 144 default = "autosnap"; 145 description = '' 146 Prefix for automatic snapshot names. 147 This is used to identify and manage automatic snapshots 148 separately from manual ones. 149 ''; 150 }; 151 152 keep = lib.mkOption { 153 type = lib.types.int; 154 default = 2; 155 description = "Default number of snapshots to keep for VMs that don't specify a keep value."; 156 }; 157 158 snapshotType = lib.mkOption { 159 type = lib.types.enum [ 160 "internal" 161 "external" 162 ]; 163 default = "internal"; 164 description = "Type of snapshot to create (internal or external)."; 165 }; 166 167 vms = lib.mkOption { 168 type = lib.types.nullOr ( 169 lib.types.listOf ( 170 lib.types.oneOf [ 171 lib.types.str 172 (lib.types.submodule { 173 options = { 174 name = lib.mkOption { 175 type = lib.types.str; 176 description = "Name of the VM"; 177 }; 178 snapshotType = lib.mkOption { 179 type = lib.types.nullOr ( 180 lib.types.enum [ 181 "internal" 182 "external" 183 ] 184 ); 185 default = null; 186 description = '' 187 Type of snapshot to create (internal or external). 188 If not specified, uses global snapshotType (${toString cfg.snapshotType}). 189 ''; 190 }; 191 keep = lib.mkOption { 192 type = lib.types.nullOr lib.types.int; 193 default = null; 194 description = '' 195 Number of snapshots to keep for this VM. 196 If not specified, uses global keep (${toString cfg.keep}). 197 ''; 198 }; 199 }; 200 }) 201 ] 202 ) 203 ); 204 default = null; 205 description = '' 206 If specified only the list of VMs will be snapshotted else all existing one. Each entry can be either: 207 - A string (VM name, uses default settings) 208 - An attribute set with VM configuration 209 ''; 210 example = lib.literalExpression '' 211 [ 212 "myvm1" # Uses defaults 213 { 214 name = "myvm2"; 215 keep = 30; # Override retention 216 } 217 ] 218 ''; 219 }; 220 }; 221 }; 222 223 config = lib.mkIf cfg.enable { 224 assertions = [ 225 { 226 assertion = (cfg.vms == null) || (lib.isList cfg.vms && cfg.vms != [ ]); 227 message = "'services.libvirtd.autoSnapshot.vms' must either be null for all VMs or a non-empty list of VM configurations"; 228 } 229 { 230 assertion = config.virtualisation.libvirtd.enable; 231 message = "virtualisation.libvirtd must be enabled to use services.libvirtd.autoSnapshot"; 232 } 233 ]; 234 235 systemd = { 236 timers.libvirtd-autosnapshot = { 237 description = "LibVirt VM snapshot timer"; 238 wantedBy = [ "timers.target" ]; 239 timerConfig = { 240 OnCalendar = cfg.calendar; 241 AccuracySec = "5m"; 242 Unit = "libvirtd-autosnapshot.service"; 243 }; 244 }; 245 246 services.libvirtd-autosnapshot = { 247 description = "LibVirt VM snapshot service"; 248 after = [ "libvirtd.service" ]; 249 requires = [ "libvirtd.service" ]; 250 serviceConfig = { 251 Type = "oneshot"; 252 User = "root"; 253 }; 254 script = backupScript; 255 }; 256 }; 257 }; 258 259 meta.maintainers = [ lib.maintainers._6543 ]; 260}