Compare commits

...
6 Commits
Author SHA1 Message Date
adam b8fe5a19fc Add new condition for when not removing any backups
Add a check for when retention equals 0. When retentions is set to 0 but no backups exist the message would still say "No old backups to remove for $vm".

This change changes the message as no backups would be removed even if they were found.
2026-02-02 15:28:40 +00:00
adam f859b776a2 change -c description 2026-02-02 15:06:36 +00:00
adam 087983bd4a less than 100 lines :) 2026-01-14 14:49:18 +00:00
adam fcf6f6008d improve defaults and argument processing.
Loop over VMs inside of main function rather than having 2 backup functions
2026-01-14 14:43:41 +00:00
adam 4a0a1ff9a7 fix some incorrect variable names 2026-01-14 09:45:58 +00:00
adam 5e07f58248 add flag to select compression algorithm 2026-01-14 00:35:59 +00:00
+55 -60
View File
@@ -1,102 +1,97 @@
#!/usr/bin/env bash
main() {
while getopts ":l:d:t:c:h" opt; do
# 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)
unset VM_FILTER
IFS="," read -r -a VM_FILTER <<< "$OPTARG"
;;
IFS="," read -r -a vm_list <<< "$OPTARG";;
d)
local backup_location="${OPTARG}"
backup_location="${backup_location%/}"
;;
backup_dir="${OPTARG%/}";;
t)
declare -g TARGET="$OPTARG"
;;
cmd="$OPTARG";;
x)
compression="$OPTARG";;
c)
declare -g run_cleanup=y
declare -g files_to_keep="${OPTARG}"
;;
h)
usage
;;
*)
usage
;;
retention="${OPTARG}";;
h|*)
usage;;
esac
done
backup_location=${backup_location:-$PWD}
lxc() { command "$cmd" "$@"; }
list_vms() { lxc list -c n -f csv; }
if [[ "${#VM_FILTER[*]}" -gt 0 ]]; then
backup_vms "$backup_location" "${VM_FILTER[@]}"
else
backup_vms "$backup_location"
if ((${#vm_list[@]} == 0)); then
mapfile -t vm_list < <(list_vms)
fi
}
lxc() {
local lxc_cmd="${TARGET:-lxc}"
command "$lxc_cmd" "$@"
}
for vm in "${vm_list[@]}"; do
start_backup "$vm" "$backup_dir" "$compression"
list_vms() {
lxc list -c n -f csv
if (( retention > 0 )); then
prune_backups "$vm" "$backup_dir" "$retention"
fi
done
exit 0
}
usage() {
cat <<EOF
Usage: ${0##*/} [-l vm[,vm...]] [-d backup_dir] [-t lxc|incus] [-c <keep n backups>] [-h]
Usage: ${0##*/} [-l vm[,vm...]] [-d <backup directory>] [-t lxc|incus] [-x <compression alogorithm>] [-c <keep last n backups>] [-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)
-c Cleanup old backups keeping a set number (default: 2)
-x Compression algorithm (e.g. gzip, zstd, none)
-c Cleanup old backups and keep last <n> (default: keep all)
-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 destination="${backup_base}/${vm}"
local backup_name="${vm}_$(date +"%Y-%m-%d_%H-%M-%S").tar"
local backup_dir="$2"
local compression="$3"
local ext=".${compression}"
[[ -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; }
if [[ "$compression" == 'none' ]]; then
ext=""
else
command -v "$compression" >/dev/null 2>&1 || { echo "$compression command not found"; exit 1; }
fi
lxc export "$vm" --instance-only --compression none "${destination}/${backup_name}" || { echo "Error exporting $vm"; return 1; }
local vm_dir="${backup_dir}/${vm}"
local backup_name
backup_name="${vm}_$(date +"%Y-%m-%d_%H-%M-%S").tar${ext}"
[[ -n "$run_cleanup" ]] && prune_backups "$destination"
[[ -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 backup_directory="$1"
mapfile -t backup_files < <(find "$backup_directory" -maxdepth 1 -name "*.tar" -printf '%T@ %p\n' | sort -n | head -n "-${files_to_keep}" | cut -d' ' -f2-)
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 ${#qcow2_files[@]} old backup files for $vm (keeping last $total_files_to_keep files)"
printf '%s\0' "${backup_files[@]}" | xargs -0 rm -fv
echo "Removing ${#backup_files[@]} old backup files for $vm (keeping last $retention files)"
printf '%s\0' "${backup_files[@]}" | xargs -0 rm -fv > /dev/null
elif [[ "${retention}" -eq 0 ]]; then
echo "Keeping all existing backups for $vm"
else
echo "No old backups to remove for $vm"
fi