#!/usr/bin/env bash main() { while getopts ":l:d:t:x:c:h" opt; do case "${opt}" in l) unset VM_FILTER IFS="," read -r -a VM_FILTER <<< "$OPTARG" ;; d) local backup_location="${OPTARG}" backup_location="${backup_location%/}" ;; t) declare -g TARGET="$OPTARG" ;; x) declare -g EXTENSION if [[ "${OPTARG,,}" == 'none' ]]; then EXTENTION="" else command -v "$OPTARG" >/dev/null 2>&1 || { echo "$OPTARG command not found"; exit 1; } EXTENTION="${OPTARG,,}" fi declare -g COMPRESSION="$OPTARG" ;; c) declare -g run_cleanup=y declare -g files_to_keep="${OPTARG}" ;; h) usage ;; *) usage ;; esac done backup_location=${backup_location:-$PWD} if [[ "${#VM_FILTER[*]}" -gt 0 ]]; then backup_vms "$backup_location" "${VM_FILTER[@]}" else backup_vms "$backup_location" fi } lxc() { local lxc_cmd="${TARGET:-lxc}" command "$lxc_cmd" "$@" } list_vms() { lxc list -c n -f csv } usage() { cat <] [-c ] [-h] Options: -l Comma-separated list of VMs to back up -d Directory where backups will be stored (default: current directory) -t Manager type: lxc or incus (default: lxc) -x Compression algorithm (e.g. gzip, zstd, none) -c Cleanup old backups keeping a set number (default: 2) -h Show this message EOF exit 1 } backup_vms() { local backup_location="$1"; shift local vms=("$@") # when no argument use list of all VMs if ((${#vms[@]} == 0)); then mapfile -t vms < <(list_vms) fi for vm in "${vms[@]}"; do [[ -n $vm ]] || continue start_backup "$vm" "$backup_location" done } start_backup() { local vm=$1 local backup_base="$2" local compression="${COMPRESSION:-none}" local ext="${EXTENTION}" ; [[ -n "$ext" ]] && ext=".${ext}" local destination="${backup_base}/${vm}" local backup_name="${vm}_$(date +"%Y-%m-%d_%H-%M-%S").tar${ext}" [[ -d "$backup_base" ]] || { echo "${backup_base} is not a directory"; exit 1; } [[ -d "$destination" ]] || mkdir "$destination" [[ -f "${destination}/${backup_name}" ]] && { echo "Backup already exists"; return 2; } lxc export "$vm" --instance-only --compression "${compression}" "${destination}/${backup_name}" || { echo "Error exporting $vm"; return 1; } [[ -n "$run_cleanup" ]] && prune_backups "$destination" } prune_backups() { local backup_directory="$1" mapfile -t backup_files < <(find "$backup_directory" -maxdepth 1 -type f -printf '%T@ %p\n' | sort -n | head -n "-${files_to_keep}" | cut -d' ' -f2-) if [[ ${#backup_files[@]} -gt 0 ]]; then echo "Removing ${#qcow2_files[@]} old backup files for $vm (keeping last $total_files_to_keep files)" printf '%s\0' "${backup_files[@]}" | xargs -0 rm -fv else echo "No old backups to remove for $vm" fi } main "$@"