services.libvirtd.autoSnapshot: init

6543 92d5ef97 74876446

+265 -1
+2 -1
ci/OWNERS
··· 120 120 /nixos/lib/test-driver @tfc 121 121 122 122 # NixOS QEMU virtualisation 123 - /nixos/modules/virtualisation/qemu-vm.nix @raitobezarius 123 + /nixos/modules/virtualisation/qemu-vm.nix @raitobezarius 124 + /nixos/modules/services/backup/libvirtd-autosnapshot.nix @6543 124 125 125 126 # ACME 126 127 /nixos/modules/security/acme @NixOS/acme
+2
nixos/doc/manual/release-notes/rl-2511.section.md
··· 116 116 developers to build scalable applications without sacrificing productivity or 117 117 reliability. Available as [services.temporal](#opt-services.temporal.enable). 118 118 119 + - `services.libvirtd.autoSnapshot`, a backup service for libvirt managed vms. 120 + 119 121 ## Backward Incompatibilities {#sec-release-25.11-incompatibilities} 120 122 121 123 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+1
nixos/modules/module-list.nix
··· 449 449 ./services/backup/btrbk.nix 450 450 ./services/backup/duplicati.nix 451 451 ./services/backup/duplicity.nix 452 + ./services/backup/libvirtd-autosnapshot.nix 452 453 ./services/backup/mysql-backup.nix 453 454 ./services/backup/pgbackrest.nix 454 455 ./services/backup/postgresql-backup.nix
+260
nixos/modules/services/backup/libvirtd-autosnapshot.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 7 + 8 + let 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 + ''; 127 + in 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 + }