Backup scripts for kernal virtual machines using libvirt.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
6.2 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. #!/bin/bash
  2. # Copyright (c) 2022 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
  3. # This is for backing up Rados Block Device (Ceph) storage.
  4. # A file to prevent overlapping runs.
  5. PIDFILE="/tmp/backup-image.pid"
  6. # If the pid file exists and process is running, exit.
  7. if [[ -f "$PIDFILE" ]]; then
  8. PID=$(cat "$PIDFILE")
  9. if ps -p "$PID" >/dev/null; then
  10. echo "Backup process already running, exiting."
  11. exit 1
  12. fi
  13. fi
  14. # Create a new pid file for this process.
  15. echo $BASHPID >"$PIDFILE"
  16. # The borg repository we're backing up to.
  17. export BORG_REPO='/media/Storage/Backup/kvm'
  18. # If you have a passphrase for your repository,
  19. # set it here or you can use bash to retrieve it.
  20. # export BORG_PASSPHRASE=''
  21. # Set answers for automation.
  22. export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
  23. export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes
  24. export BORG_CHECK_I_KNOW_WHAT_I_AM_DOING=NO
  25. export BORG_DELETE_I_KNOW_WHAT_I_AM_DOING=NO
  26. # Set to empty string to disable pruning.
  27. PRUNE_OPTIONS="--keep-daily 7 --keep-weekly 4 --keep-monthly 6"
  28. # Remove PID file on exit.
  29. cleanup() {
  30. rm "$PIDFILE"
  31. }
  32. trap cleanup EXIT
  33. # Name the snapshot today's date.
  34. SNAPSHOT_NAME=$(date '+%Y-%m-%dT%H-%M-%S')
  35. # Keep number of snapshots in RBD.
  36. SNAPSHOTS_KEEP=0
  37. # Allows providing an argument of a domain to specifically backup.
  38. BACKUP_DOMAIN="$1"
  39. # Failures should remove pid file and exit with status code 1.
  40. fail() {
  41. echo "$1"
  42. exit 1
  43. }
  44. # If the domain is running, commit the changes saved to the snapshot to the image to finish the backup.
  45. cleanupSnapshots() {
  46. IMAGE="$1"
  47. snapshots=()
  48. # Read list of snapshots for the provided image.
  49. SNAPLIST_STATUS_TMP="/tmp/backup-snap-tmp"
  50. while read -r _ NAME _; do
  51. snapshots+=("$NAME")
  52. done < <(
  53. rbd snap list "$IMAGE" | tail -n +2
  54. echo "${PIPESTATUS[0]}" >"$SNAPLIST_STATUS_TMP"
  55. )
  56. # Get status from the snapshot listing.
  57. status=1
  58. if [[ -f $SNAPLIST_STATUS_TMP ]]; then
  59. status=$(cat "$SNAPLIST_STATUS_TMP")
  60. rm "$SNAPLIST_STATUS_TMP"
  61. fi
  62. # If status has an error, exit.
  63. if ((status!=0)); then
  64. fail "Snapshot listing failed"
  65. fi
  66. # If the snapshot count is more than the number to keep,
  67. # remove snapshots until count matches.
  68. # The snapshots are listed from oldest to newest, so this
  69. # should keep the newer snapshots.
  70. snpashot_count=${#snapshots[@]}
  71. if ((snpashot_count>=SNAPSHOTS_KEEP)); then
  72. # Loop through snapshots until we removed enough to equal keep count.
  73. for ((i = 0; snpashot_count-i > SNAPSHOTS_KEEP; i++)); do
  74. NAME=${snapshots[$i]}
  75. echo "Removing snapshot: $IMAGE@$NAME"
  76. # Remove snapshot.
  77. rbd snap remove "$IMAGE@$NAME"
  78. done
  79. fi
  80. }
  81. # I save the status in a temporary file so I can error out and exit if a failure occurs.
  82. DOMLIST_STATUS_TMP="/tmp/backup-image-domlist-tmp"
  83. while read -r _ DOMAIN _; do
  84. # If the domain is empty, its not needed.
  85. if [[ -z "$DOMAIN" ]]; then
  86. continue
  87. fi
  88. # If a backup domain was provided, we're only going to backup that domain.
  89. if [[ -n "$BACKUP_DOMAIN" ]] && [[ "$BACKUP_DOMAIN" != "$DOMAIN" ]]; then
  90. continue
  91. fi
  92. # Get the images that need backing up.
  93. DEVS=()
  94. IMAGES=()
  95. BLKLIST_STATUS_TMP="/tmp/backup-image-blklist-tmp"
  96. while read -r DEV IMAGE; do
  97. # Ignore empty line or no image.
  98. if [[ -z "$IMAGE" ]] || [[ "$IMAGE" == "-" ]]; then
  99. continue
  100. fi
  101. # Ignore iso files.
  102. if [[ "$IMAGE" =~ \.iso$ ]]; then
  103. continue
  104. fi
  105. # Ignore non-rbd files.
  106. if [[ "$IMAGE" =~ ^\/ ]]; then
  107. continue
  108. fi
  109. # This image needs backing up.
  110. DEVS+=("$DEV")
  111. IMAGES+=("$IMAGE")
  112. done < <(
  113. virsh domblklist "$DOMAIN" | tail -n +3
  114. echo "${PIPESTATUS[0]}" >"$BLKLIST_STATUS_TMP"
  115. )
  116. # Get status from the block listing.
  117. status=1
  118. if [[ -f $BLKLIST_STATUS_TMP ]]; then
  119. status=$(cat "$BLKLIST_STATUS_TMP")
  120. rm "$BLKLIST_STATUS_TMP"
  121. fi
  122. # If status has an error, exit.
  123. if ((status!=0)); then
  124. fail "Domain block listing failed"
  125. fi
  126. # For each image we can backup, back it up.
  127. for ((i = 0; i < ${#DEVS[@]}; i++)); do
  128. DEV=${DEVS[$i]}
  129. IMAGE=${IMAGES[$i]}
  130. # RBD_POOL=${IMAGE%/*}
  131. # RBD_IMAGE=${IMAGE##*/}
  132. # BACKUP_NAME="${RBD_POOL}_${RBD_IMAGE}"
  133. # Create a snapshot.
  134. rbd snap create "$IMAGE@$SNAPSHOT_NAME"
  135. # Export volume to borg backup.
  136. echo "Creating backup for $IMAGE"
  137. if ! rbd export "$IMAGE@$SNAPSHOT_NAME" - | pv | borg create \
  138. --verbose \
  139. --stats \
  140. --show-rc \
  141. "::$DOMAIN-$DEV-{now}" -; then
  142. fail "Failed to backup $IMAGE"
  143. fi
  144. # Prune if options are configured.
  145. if [[ -n "$PRUNE_OPTIONS" ]]; then
  146. echo "Pruning backups for $IMAGE"
  147. if ! eval borg prune --list \
  148. --show-rc \
  149. --glob-archives "'$DOMAIN-$DEV-*'" \
  150. "$PRUNE_OPTIONS"; then
  151. fail "Failed to prune $DOMAIN"
  152. fi
  153. fi
  154. # Cleanup snapshots.
  155. cleanupSnapshots "$IMAGE"
  156. done
  157. # Backup the domain info.
  158. echo "Backing up $DOMAIN xml"
  159. if ! virsh dumpxml "$DOMAIN" | borg create \
  160. --verbose \
  161. --stats \
  162. --show-rc \
  163. "::$DOMAIN-xml-{now}" -; then
  164. fail "Failed to backup $DOMAIN"
  165. fi
  166. # Prune if options are configured.
  167. if [[ -n "$PRUNE_OPTIONS" ]]; then
  168. echo "Pruning backups for $IMAGE"
  169. if ! eval borg prune --list \
  170. --show-rc \
  171. --glob-archives "'$DOMAIN-xml-*'" \
  172. "$PRUNE_OPTIONS"; then
  173. fail "Failed to prune $DOMAIN"
  174. fi
  175. fi
  176. done < <(
  177. virsh list --all | tail -n +3
  178. echo "${PIPESTATUS[0]}" >"$DOMLIST_STATUS_TMP"
  179. )
  180. # Get status from the domain listing.
  181. status=1
  182. if [[ -f $DOMLIST_STATUS_TMP ]]; then
  183. status=$(cat "$DOMLIST_STATUS_TMP")
  184. rm "$DOMLIST_STATUS_TMP"
  185. fi
  186. # If status has an error, exit.
  187. if ((status!=0)); then
  188. fail "Domain listing failed"
  189. fi
  190. # Shrink repo.
  191. borg compact