#!/usr/bin/env bash main() { # Set default options local cmd="${CMD:-lxc}" local backup_dir="${BACKUP_DIR:-$PWD}" local compression="${COMPRESSION:-zstd}" local vm_list local retention=0 while getopts ":l:d:t:x:c:h" opt; do case "${opt}" in l) IFS="," read -r -a vm_list <<< "$OPTARG";; d) backup_dir="${OPTARG%/}";; t) cmd="$OPTARG";; x) compression="$OPTARG";; c) retention="${OPTARG}";; h|*) usage;; esac done lxc() { command "$cmd" "$@"; } list_vms() { lxc list -c n -f csv; } if ((${#vm_list[@]} == 0)); then mapfile -t vm_list < <(list_vms) fi for vm in "${vm_list[@]}"; do start_backup "$vm" "$backup_dir" "$compression" if (( retention > 0 )); then prune_backups "$vm" "$backup_dir" "$retention" fi done exit 0 } usage() { cat <] [-t lxc|incus] [-x ] [-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 and keep last (default: keep all) -h Show this message EOF exit 1 } start_backup() { local vm=$1 local backup_dir="$2" local compression="$3" local ext=".${compression}" if [[ "$compression" == 'none' ]]; then ext="" else command -v "$compression" >/dev/null 2>&1 || { echo "$compression command not found"; exit 1; } fi local vm_dir="${backup_dir}/${vm}" local backup_name backup_name="${vm}_$(date +"%Y-%m-%d_%H-%M-%S").tar${ext}" [[ -d "$backup_dir" ]] || { echo "${backup_dir} is not a directory"; exit 1; } [[ -d "$vm_dir" ]] || mkdir "$vm_dir" || { echo "Cannont create $vm_dir"; exit 1; } [[ -f "${vm_dir}/${backup_name}" ]] && { echo "Backup already exists"; return 2; } echo "starting $vm" lxc export "$vm" --instance-only --compression "${compression}" "${vm_dir}/${backup_name}" || { echo "Error exporting $vm"; return 1; } } prune_backups() { local vm="$1" local backup_dir="$2" local retention="$3" mapfile -t backup_files < <(find "${backup_dir}/${vm}" -maxdepth 1 -type f -printf '%T@ %p\n' | sort -n | head -n "-${retention}" | cut -d' ' -f2-) if [[ ${#backup_files[@]} -gt 0 ]]; then echo "Removing ${#backup_files[@]} old backup files for $vm (keeping last $retention files)" printf '%s\0' "${backup_files[@]}" | xargs -0 rm -fv > /dev/null else echo "No old backups to remove for $vm" fi } main "$@"