mirror of
https://github.com/CHN-beta/nixpkgs.git
synced 2026-01-13 11:30:35 +08:00
services.libvirtd.autoSnapshot: init
This commit is contained in:
@@ -120,7 +120,8 @@ nixos/modules/installer/tools/nix-fallback-paths.nix @NixOS/nix-team @raitobeza
|
||||
/nixos/lib/test-driver @tfc
|
||||
|
||||
# NixOS QEMU virtualisation
|
||||
/nixos/modules/virtualisation/qemu-vm.nix @raitobezarius
|
||||
/nixos/modules/virtualisation/qemu-vm.nix @raitobezarius
|
||||
/nixos/modules/services/backup/libvirtd-autosnapshot.nix @6543
|
||||
|
||||
# ACME
|
||||
/nixos/modules/security/acme @NixOS/acme
|
||||
|
||||
@@ -116,6 +116,8 @@
|
||||
developers to build scalable applications without sacrificing productivity or
|
||||
reliability. Available as [services.temporal](#opt-services.temporal.enable).
|
||||
|
||||
- `services.libvirtd.autoSnapshot`, a backup service for libvirt managed vms.
|
||||
|
||||
## Backward Incompatibilities {#sec-release-25.11-incompatibilities}
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
||||
@@ -449,6 +449,7 @@
|
||||
./services/backup/btrbk.nix
|
||||
./services/backup/duplicati.nix
|
||||
./services/backup/duplicity.nix
|
||||
./services/backup/libvirtd-autosnapshot.nix
|
||||
./services/backup/mysql-backup.nix
|
||||
./services/backup/pgbackrest.nix
|
||||
./services/backup/postgresql-backup.nix
|
||||
|
||||
260
nixos/modules/services/backup/libvirtd-autosnapshot.nix
Normal file
260
nixos/modules/services/backup/libvirtd-autosnapshot.nix
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.libvirtd.autoSnapshot;
|
||||
|
||||
# Function to get VM config with defaults
|
||||
getVMConfig =
|
||||
vm:
|
||||
if lib.isString vm then
|
||||
{
|
||||
name = vm;
|
||||
inherit (cfg) snapshotType keep;
|
||||
}
|
||||
else
|
||||
{
|
||||
inherit (vm) name;
|
||||
snapshotType = if vm.snapshotType != null then vm.snapshotType else cfg.snapshotType;
|
||||
keep = if vm.keep != null then vm.keep else cfg.keep;
|
||||
};
|
||||
|
||||
# Main backup script combining all VM scripts
|
||||
backupScript = ''
|
||||
set -eo pipefail
|
||||
|
||||
# Initialize failure tracking
|
||||
failed=""
|
||||
|
||||
# Define the VM snapshot function
|
||||
function snap_vm() {
|
||||
local vmName="$1"
|
||||
local snapshotType="$2"
|
||||
local keep="$3"
|
||||
|
||||
# Add validation for VM name
|
||||
if ! echo "$vmName" | ${pkgs.gnugrep}/bin/grep -qE '^[a-zA-Z0-9_.-]+$'; then
|
||||
echo "Invalid VM name: '$vmName'"
|
||||
failed="$failed $vmName"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Processing VM: $vmName"
|
||||
|
||||
# Check if VM exists
|
||||
if ! ${pkgs.libvirt}/bin/virsh dominfo "$vmName" >/dev/null 2>&1; then
|
||||
echo "VM '$vmName' does not exist, skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
# Create new snapshot
|
||||
local snapshot_name
|
||||
snapshot_name="${cfg.prefix}_$(date +%Y-%m-%d_%H%M%S)"
|
||||
local snapshot_opts=""
|
||||
[[ "$snapshotType" == "external" ]] && snapshot_opts="--disk-only"
|
||||
if ! ${pkgs.libvirt}/bin/virsh snapshot-create-as \
|
||||
"$vmName" \
|
||||
"$snapshot_name" \
|
||||
"Automatic backup snapshot" \
|
||||
$snapshot_opts \
|
||||
--atomic; then
|
||||
echo "Failed to create snapshot for $vmName"
|
||||
failed="$failed $vmName"
|
||||
return
|
||||
fi
|
||||
|
||||
# List all automatic snapshots for this VM
|
||||
readarray -t SNAPSHOTS < <(${pkgs.libvirt}/bin/virsh snapshot-list "$vmName" --name | ${pkgs.gnugrep}/bin/grep "^${cfg.prefix}_")
|
||||
|
||||
# Count snapshots
|
||||
local snapshot_count=''${#SNAPSHOTS[@]}
|
||||
|
||||
# Delete old snapshots if we have more than the keep limit
|
||||
if [[ $snapshot_count -gt $keep ]]; then
|
||||
# Sort snapshots by date (they're named with date prefix)
|
||||
readarray -t TO_DELETE < <(printf '%s\n' "''${SNAPSHOTS[@]}" | ${pkgs.coreutils}/bin/sort | ${pkgs.coreutils}/bin/head -n -$keep)
|
||||
for snap in "''${TO_DELETE[@]}"; do
|
||||
echo "Removing old snapshot $snap from $vmName"
|
||||
|
||||
# Check if snapshot is internal or external
|
||||
local snapshot_location
|
||||
snapshot_location=$(${pkgs.libvirt}/bin/virsh snapshot-info "$vmName" --snapshotname "$snap" | ${pkgs.gnugrep}/bin/grep "Location:" | ${pkgs.gawk}/bin/awk '{print $2}')
|
||||
|
||||
local delete_opts=""
|
||||
[[ "$snapshot_location" == "internal" ]] && delete_opts="--metadata"
|
||||
|
||||
if ! ${pkgs.libvirt}/bin/virsh snapshot-delete "$vmName" "$snap" $delete_opts; then
|
||||
echo "Failed to remove snapshot $snap from $vmName"
|
||||
failed="$failed $vmName(cleanup)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
${
|
||||
if cfg.vms == null then
|
||||
''
|
||||
# Process all VMs
|
||||
${pkgs.libvirt}/bin/virsh list --all --name | while read -r vm; do
|
||||
# Skip empty lines
|
||||
[ -z "$vm" ] && continue
|
||||
|
||||
# Call snap_vm function with default settings
|
||||
snap_vm "$vm" ${cfg.snapshotType} ${toString cfg.keep}
|
||||
done
|
||||
''
|
||||
else
|
||||
''
|
||||
# Process specific VMs from the list
|
||||
${lib.concatMapStrings (
|
||||
vm: with getVMConfig vm; "snap_vm '${name}' ${snapshotType} ${toString keep}\n"
|
||||
) cfg.vms}
|
||||
''
|
||||
}
|
||||
|
||||
# Report any failures
|
||||
if [ -n "$failed" ]; then
|
||||
echo "Snapshot operation failed for:$failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
'';
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.libvirtd.autoSnapshot = {
|
||||
enable = lib.mkEnableOption "LibVirt VM snapshots";
|
||||
|
||||
calendar = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "04:15:00";
|
||||
description = ''
|
||||
When to create snapshots (systemd calendar format).
|
||||
Default is 4:15 AM.
|
||||
'';
|
||||
};
|
||||
|
||||
prefix = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "autosnap";
|
||||
description = ''
|
||||
Prefix for automatic snapshot names.
|
||||
This is used to identify and manage automatic snapshots
|
||||
separately from manual ones.
|
||||
'';
|
||||
};
|
||||
|
||||
keep = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 2;
|
||||
description = "Default number of snapshots to keep for VMs that don't specify a keep value.";
|
||||
};
|
||||
|
||||
snapshotType = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"internal"
|
||||
"external"
|
||||
];
|
||||
default = "internal";
|
||||
description = "Type of snapshot to create (internal or external).";
|
||||
};
|
||||
|
||||
vms = lib.mkOption {
|
||||
type = lib.types.nullOr (
|
||||
lib.types.listOf (
|
||||
lib.types.oneOf [
|
||||
lib.types.str
|
||||
(lib.types.submodule {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name of the VM";
|
||||
};
|
||||
snapshotType = lib.mkOption {
|
||||
type = lib.types.nullOr (
|
||||
lib.types.enum [
|
||||
"internal"
|
||||
"external"
|
||||
]
|
||||
);
|
||||
default = null;
|
||||
description = ''
|
||||
Type of snapshot to create (internal or external).
|
||||
If not specified, uses global snapshotType (${toString cfg.snapshotType}).
|
||||
'';
|
||||
};
|
||||
keep = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Number of snapshots to keep for this VM.
|
||||
If not specified, uses global keep (${toString cfg.keep}).
|
||||
'';
|
||||
};
|
||||
};
|
||||
})
|
||||
]
|
||||
)
|
||||
);
|
||||
default = null;
|
||||
description = ''
|
||||
If specified only the list of VMs will be snapshotted else all existing one. Each entry can be either:
|
||||
- A string (VM name, uses default settings)
|
||||
- An attribute set with VM configuration
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
[
|
||||
"myvm1" # Uses defaults
|
||||
{
|
||||
name = "myvm2";
|
||||
keep = 30; # Override retention
|
||||
}
|
||||
]
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = (cfg.vms == null) || (lib.isList cfg.vms && cfg.vms != [ ]);
|
||||
message = "'services.libvirtd.autoSnapshot.vms' must either be null for all VMs or a non-empty list of VM configurations";
|
||||
}
|
||||
{
|
||||
assertion = config.virtualisation.libvirtd.enable;
|
||||
message = "virtualisation.libvirtd must be enabled to use services.libvirtd.autoSnapshot";
|
||||
}
|
||||
];
|
||||
|
||||
systemd = {
|
||||
timers.libvirtd-autosnapshot = {
|
||||
description = "LibVirt VM snapshot timer";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = cfg.calendar;
|
||||
AccuracySec = "5m";
|
||||
Unit = "libvirtd-autosnapshot.service";
|
||||
};
|
||||
};
|
||||
|
||||
services.libvirtd-autosnapshot = {
|
||||
description = "LibVirt VM snapshot service";
|
||||
after = [ "libvirtd.service" ];
|
||||
requires = [ "libvirtd.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
};
|
||||
script = backupScript;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = [ lib.maintainers._6543 ];
|
||||
}
|
||||
Reference in New Issue
Block a user