5757 exit 1
5858}
5959
60- # start new log.
61- starting_log () {
62- mkdir -p " $BACKUP_DIR / $ACTIVEVM "
60+ init_log () {
61+ (( LOG_INITIALIZED )) && return
62+ mkdir -p " $( dirname " $LOGFILE " ) "
6363 exec >> >( tee -a " $LOGFILE " ) 2>&1
64- echo " $( date + ' %Y-%m-%d %H:%M:%S ' ) Starting backup of $ACTIVEVM "
64+ LOG_INITIALIZED=1
6565}
6666
67+ start_log () {
68+ mkdir -p " $BACKUP_DIR /$ACTIVEVM "
69+ echo " $( date +' %Y-%m-%d %H:%M:%S' ) Starting backup of $ACTIVEVM "
70+ }
6771# backup config of VM.
6872backup_vm_config () {
6973 virsh dumpxml " $ACTIVEVM " > " $BACKUP_DIR /$ACTIVEVM /$ACTIVEVM " .xml
7074 echo " $( date ' +%Y-%m-%d %H:%M:%S' ) Saved $ACTIVEVM domain XML"
7175}
7276
77+ # double‑checks the path we are about to delete.
78+ safe_rm () {
79+ local target=" $1 "
80+ [[ -z " $target " || " $target " == " /" ]] && fatal " Refusing to remove empty or root path"
81+ [[ ! -e " $target " ]] && fatal " safe_rm: '$target ' does not exist"
82+ rm -rf --one-file-system -- " $target "
83+ }
84+
7385# Getting a list and a path of disk images.
7486vm_disks_get () {
7587 # robust domblklist parsing using separator
76- mapfile -t DISK_INFO < <( virsh domblklist --details --type disk --noheadings --separator ' |' " $ACTIVEVM " )
88+ mapfile -t DISK_INFO < <( virsh domblklist --details --type disk --noheadings --separator ' |' " $ACTIVEVM " 2> /dev/null )
7789 DISK_LIST=()
7890 DISK_PATH=()
7991 for line in " ${DISK_INFO[@]} " ; do
80- IFS=' |' read -r _dev _type target source <<< " $line"
92+ IFS=' |' read -r target source _type _dev <<< " $line"
8193 DISK_LIST+=(" $target " )
8294 DISK_PATH+=(" $source " )
8395 done
84- echo " $( date ' +%Y-%m-%d %H:%M:%S' ) Disk targets: ${DISK_LIST[*]} "
85- echo " $( date ' +%Y-%m-%d %H:%M:%S' ) Disk paths : ${DISK_PATH[*]} "
96+ echo " $( date ' +%Y-%m-%d %H:%M:%S' ) Disk targets: ${DISK_LIST[*]} " ;
97+ echo " $( date ' +%Y-%m-%d %H:%M:%S' ) Disk paths : ${DISK_PATH[*]} " ;
8698}
8799
88100# Getting a block device which is a snapshot.
89- get_vm_shapshots () {
101+ get_snapshots () {
90102 virsh snapshot-list --domain " $ACTIVEVM " --no-metadata --name 2> /dev/null || true
91103}
92104
105+
93106# Entry point.
94107set -euo pipefail
95108IFS=$' \n\t '
109+ shopt -s nocasematch
96110
97111[[ $# -lt 2 ]] && usage
98- COMMAND_USE=" $1 "
99- shift
112+ COMMAND_USE=" $1 " ; shift
100113
101114[[ $EUID -ne 0 ]] && fatal " Please run as root (e.g. sudo $0 ...)"
102115
103116case " $COMMAND_USE " in
104- --active | --stopped | --clean) ;;
105- * ) usage ;;
117+ --active| --stopped| --clean) ;;
118+ * ) usage ;;
106119esac
107120
121+ LOG_INITIALIZED=0
122+ init_log
123+
108124for ACTIVEVM in " $@ " ; do
109- SNAPSHOT_NAME=" snapshot-$( date +%s) "
125+ SNAPSHOT_NAME=" snapshot-${ACTIVEVM} - $ ( date +%s%N ) "
110126 start_log
111127 backup_vm_config
112128 vm_disks_get
113129
114130 if [[ $COMMAND_USE == " --active" ]]; then
115131 echo " Creating live snapshot $SNAPSHOT_NAME for $ACTIVEVM "
116- if ! get_snapshots | grep -q " $SNAPSHOT_NAME " ; then
132+ if ! get_snapshots | grep -Fxq " $SNAPSHOT_NAME " ; then
117133 virsh snapshot-create-as --domain " $ACTIVEVM " " $SNAPSHOT_NAME " --disk-only \
118134 --atomic --quiesce --no-metadata
119135 else
@@ -125,43 +141,39 @@ for ACTIVEVM in "$@"; do
125141
126142 for SRC in " ${DISK_PATH[@]} " ; do
127143 FILENAME=$( basename " $SRC " )
128- [[ " $SRC " == " -" || " $SRC " =~ \\ .iso$ || " $SRC " =~ \\ .ISO$ ]] && {
129- echo " Skip removable/media: $SRC "
130- continue
131- }
132- echo " Copying $SRC -> $BACKUP_DIR /$ACTIVEVM /$FILENAME "
144+ [[ " $SRC " == " -" || " $SRC " == * .iso ]] && { echo " Skip removable/media: $SRC " && continue ; }
145+ echo " Copying $SRC -> $BACKUP_DIR /$ACTIVEVM /$FILENAME " ;
133146 cp --reflink=auto --sparse=always " $SRC " " $BACKUP_DIR /$ACTIVEVM /$FILENAME "
134147 done
135148
136149 # commit + remove snapshot layer
137150 for disk in " ${DISK_LIST[@]} " ; do
138151 virsh blockcommit " $ACTIVEVM " " $disk " --active --verbose --pivot || echo " Nothing to commit for $disk "
139152 done
153+ vm_disks_get
140154 # remove snapshot file(s)
141- for snap in $( get_snapshots) ; do
142- echo " Removing snapshot file $snap "
143- safe_rm " $snap " || true
155+ for p in " ${DISK_PATH[@]} " ; do
156+ [[ $p == * .snapshot ]] || continue
157+ echo " Removing leftover snapshot layer $p "
158+ rm -f -- " $p "
144159 done
145160 echo " Backup of $ACTIVEVM finished"
146161
147162 elif [[ $COMMAND_USE == " --stopped" ]]; then
148163 echo " Shutting down $ACTIVEVM "
149164 virsh shutdown " $ACTIVEVM " || true
150165 COUNTER=40 # 40*3=120s
151- while virsh list | grep -q " $ACTIVEVM " && (( COUNTER-- > 0 )) ; do
166+ while virsh list --name | grep -Fxq " $ACTIVEVM " && (( COUNTER-- > 0 )) ; do
152167 sleep 3
153168 done
154- if virsh list | grep -q " $ACTIVEVM " ; then
169+ if virsh list --name | grep -Fxq " $ACTIVEVM " ; then
155170 echo " Force‑off $ACTIVEVM "
156171 virsh destroy " $ACTIVEVM "
157172 fi
158173
159174 for SRC in " ${DISK_PATH[@]} " ; do
160175 FILENAME=$( basename " $SRC " )
161- [[ " $SRC " == " -" || " $SRC " =~ \\ .iso$ || " $SRC " =~ \\ .ISO$ ]] && {
162- echo " Skip removable/media: $SRC "
163- continue
164- }
176+ [[ " $SRC " == " -" || " $SRC " == * .iso ]] && { echo " Skip removable/media: $SRC " && continue ; }
165177 cp --reflink=auto --sparse=always " $SRC " " $BACKUP_DIR /$ACTIVEVM /$FILENAME "
166178 done
167179
0 commit comments