Backup Verification
Verify backup completion, file integrity, and send alerts on failures
Table of Contents
Automatically verify backup completion, check file integrity, and alert on failures.
Overview
This script validates that backups completed successfully by checking for backup files, verifying they’re recent, ensuring non-zero file sizes, and optionally testing sample file restoration. Alerts are sent if any verification step fails.
Use Case: Automated backup verification, disaster recovery preparedness, compliance validation, and early detection of backup failures before data loss occurs.
Platform: Linux (Bash) and Windows (PowerShell) Requirements: Read access to backup location, optional write access for restore testing Execution Time: 30 seconds to 5 minutes depending on file count and restore tests
The Script (Bash)
1#!/bin/bash
2
3#############################################################################
4# Backup Verification Script (Bash)
5#
6# Description: Verify backup completion, file integrity, and send alerts
7# Author: glyph.sh
8# Reference: https://glyph.sh/scripts/backup-verification/
9#
10# Features:
11# - Check if backup completed successfully
12# - Verify backup file exists and is recent
13# - Check backup file size (not 0 bytes)
14# - Test restore of sample files (optional)
15# - Send alert if backup failed
16# - Multiple notification methods (email, syslog, webhook)
17#############################################################################
18
19# Configuration
20BACKUP_BASE_PATH=${BACKUP_BASE_PATH:-"/backup"}
21BACKUP_NAME=${BACKUP_NAME:-"daily"}
22MAX_AGE_HOURS=${MAX_AGE_HOURS:-24} # Maximum age in hours
23MIN_SIZE_MB=${MIN_SIZE_MB:-10} # Minimum backup size in MB
24TEST_RESTORE=${TEST_RESTORE:-false} # Test restore functionality
25RESTORE_TEST_PATH=${RESTORE_TEST_PATH:-"/tmp/restore_test"}
26ALERT_EMAIL=${ALERT_EMAIL:-"admin@example.com"}
27SEND_EMAIL=${SEND_EMAIL:-false}
28USE_SYSLOG=${USE_SYSLOG:-true}
29WEBHOOK_URL=${WEBHOOK_URL:-""}
30BACKUP_PATTERN=${BACKUP_PATTERN:-"*.tar.gz"} # File pattern to search
31
32# Colors for output
33RED='\033[0;31m'
34GREEN='\033[0;32m'
35YELLOW='\033[1;33m'
36BLUE='\033[0;34m'
37NC='\033[0m' # No Color
38
39# Exit codes
40EXIT_SUCCESS=0
41EXIT_NO_BACKUP_FOUND=1
42EXIT_BACKUP_TOO_OLD=2
43EXIT_BACKUP_TOO_SMALL=3
44EXIT_RESTORE_FAILED=4
45EXIT_VERIFICATION_FAILED=5
46
47#############################################################################
48# Functions
49#############################################################################
50
51log_message() {
52 local level="$1"
53 local message="$2"
54 local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
55
56 # Log to syslog
57 if [ "$USE_SYSLOG" = true ]; then
58 logger -t "backup-verification" -p "user.${level}" "$message"
59 fi
60
61 # Print to console
62 case "$level" in
63 "error")
64 echo -e "${RED}[ERROR]${NC} [$timestamp] $message"
65 ;;
66 "warning")
67 echo -e "${YELLOW}[WARNING]${NC} [$timestamp] $message"
68 ;;
69 "info")
70 echo -e "${GREEN}[INFO]${NC} [$timestamp] $message"
71 ;;
72 *)
73 echo "[$timestamp] $message"
74 ;;
75 esac
76}
77
78send_alert() {
79 local subject="$1"
80 local message="$2"
81 local severity="$3" # success, warning, error
82
83 # Email alert
84 if [ "$SEND_EMAIL" = true ] && command -v mail >/dev/null 2>&1; then
85 echo -e "$message" | mail -s "$subject" "$ALERT_EMAIL"
86 log_message "info" "Email alert sent to $ALERT_EMAIL"
87 fi
88
89 # Webhook alert (Slack/Discord/Teams)
90 if [ -n "$WEBHOOK_URL" ] && command -v curl >/dev/null 2>&1; then
91 local emoji="✅"
92 [ "$severity" = "warning" ] && emoji="⚠️"
93 [ "$severity" = "error" ] && emoji="❌"
94
95 curl -X POST "$WEBHOOK_URL" \
96 -H 'Content-Type: application/json' \
97 -d "{\"text\":\"${emoji} ${subject}\n\n${message}\"}" \
98 >/dev/null 2>&1
99 log_message "info" "Webhook alert sent"
100 fi
101}
102
103find_latest_backup() {
104 local backup_path="$1"
105 local pattern="$2"
106
107 # Find the most recent backup file matching pattern
108 find "$backup_path" -name "$pattern" -type f -printf '%T@ %p\n' 2>/dev/null | \
109 sort -rn | \
110 head -1 | \
111 awk '{print $2}'
112}
113
114verify_backup_exists() {
115 log_message "info" "Checking for backup files in $BACKUP_BASE_PATH"
116
117 LATEST_BACKUP=$(find_latest_backup "$BACKUP_BASE_PATH" "$BACKUP_PATTERN")
118
119 if [ -z "$LATEST_BACKUP" ]; then
120 log_message "error" "No backup files found matching pattern: $BACKUP_PATTERN"
121 send_alert "[CRITICAL] Backup Verification Failed" \
122 "No backup files found in $BACKUP_BASE_PATH\nPattern: $BACKUP_PATTERN\nHostname: $(hostname)\nDate: $(date)" \
123 "error"
124 return $EXIT_NO_BACKUP_FOUND
125 fi
126
127 log_message "info" "Found backup: $LATEST_BACKUP"
128 return 0
129}
130
131verify_backup_age() {
132 local backup_file="$1"
133 local max_age="$2"
134
135 log_message "info" "Verifying backup age (max: ${max_age}h)"
136
137 # Get file modification time in seconds since epoch
138 local file_time=$(stat -c %Y "$backup_file" 2>/dev/null || stat -f %m "$backup_file" 2>/dev/null)
139 local current_time=$(date +%s)
140 local age_seconds=$((current_time - file_time))
141 local age_hours=$((age_seconds / 3600))
142 local max_age_seconds=$((max_age * 3600))
143
144 log_message "info" "Backup age: ${age_hours}h"
145
146 if [ "$age_seconds" -gt "$max_age_seconds" ]; then
147 log_message "error" "Backup is too old: ${age_hours}h (max: ${max_age}h)"
148 send_alert "[CRITICAL] Backup Too Old" \
149 "Backup file: $backup_file\nAge: ${age_hours} hours\nMaximum allowed: ${max_age} hours\nHostname: $(hostname)\nDate: $(date)" \
150 "error"
151 return $EXIT_BACKUP_TOO_OLD
152 fi
153
154 log_message "info" "Backup age verification passed"
155 return 0
156}
157
158verify_backup_size() {
159 local backup_file="$1"
160 local min_size_mb="$2"
161
162 log_message "info" "Verifying backup size (min: ${min_size_mb}MB)"
163
164 # Get file size in bytes
165 local size_bytes=$(stat -c %s "$backup_file" 2>/dev/null || stat -f %z "$backup_file" 2>/dev/null)
166 local size_mb=$((size_bytes / 1024 / 1024))
167
168 log_message "info" "Backup size: ${size_mb}MB"
169
170 if [ "$size_bytes" -eq 0 ]; then
171 log_message "error" "Backup file is empty (0 bytes)"
172 send_alert "[CRITICAL] Empty Backup File" \
173 "Backup file: $backup_file\nSize: 0 bytes\nHostname: $(hostname)\nDate: $(date)" \
174 "error"
175 return $EXIT_BACKUP_TOO_SMALL
176 fi
177
178 if [ "$size_mb" -lt "$min_size_mb" ]; then
179 log_message "error" "Backup file is too small: ${size_mb}MB (min: ${min_size_mb}MB)"
180 send_alert "[WARNING] Backup Size Below Threshold" \
181 "Backup file: $backup_file\nSize: ${size_mb}MB\nMinimum expected: ${min_size_mb}MB\nHostname: $(hostname)\nDate: $(date)" \
182 "warning"
183 return $EXIT_BACKUP_TOO_SMALL
184 fi
185
186 log_message "info" "Backup size verification passed"
187 return 0
188}
189
190verify_backup_integrity() {
191 local backup_file="$1"
192
193 log_message "info" "Verifying backup file integrity"
194
195 # Determine file type and verify
196 case "$backup_file" in
197 *.tar.gz|*.tgz)
198 if ! tar -tzf "$backup_file" >/dev/null 2>&1; then
199 log_message "error" "Backup archive is corrupted (tar verification failed)"
200 send_alert "[CRITICAL] Backup Corrupted" \
201 "Backup file: $backup_file\nIntegrity check failed: tar verification failed\nHostname: $(hostname)\nDate: $(date)" \
202 "error"
203 return $EXIT_VERIFICATION_FAILED
204 fi
205 ;;
206 *.tar)
207 if ! tar -tf "$backup_file" >/dev/null 2>&1; then
208 log_message "error" "Backup archive is corrupted (tar verification failed)"
209 send_alert "[CRITICAL] Backup Corrupted" \
210 "Backup file: $backup_file\nIntegrity check failed: tar verification failed\nHostname: $(hostname)\nDate: $(date)" \
211 "error"
212 return $EXIT_VERIFICATION_FAILED
213 fi
214 ;;
215 *.zip)
216 if command -v unzip >/dev/null 2>&1; then
217 if ! unzip -t "$backup_file" >/dev/null 2>&1; then
218 log_message "error" "Backup archive is corrupted (zip verification failed)"
219 send_alert "[CRITICAL] Backup Corrupted" \
220 "Backup file: $backup_file\nIntegrity check failed: zip verification failed\nHostname: $(hostname)\nDate: $(date)" \
221 "error"
222 return $EXIT_VERIFICATION_FAILED
223 fi
224 else
225 log_message "warning" "unzip not available, skipping integrity check"
226 fi
227 ;;
228 *)
229 log_message "info" "File type not recognized for integrity check, skipping"
230 return 0
231 ;;
232 esac
233
234 log_message "info" "Backup integrity verification passed"
235 return 0
236}
237
238test_restore_sample() {
239 local backup_file="$1"
240 local restore_path="$2"
241
242 log_message "info" "Testing sample file restoration"
243
244 # Create restore test directory
245 mkdir -p "$restore_path" 2>/dev/null
246
247 if [ ! -d "$restore_path" ]; then
248 log_message "warning" "Cannot create restore test directory: $restore_path"
249 return 0 # Non-critical, continue
250 fi
251
252 # Extract a few files for testing
253 case "$backup_file" in
254 *.tar.gz|*.tgz)
255 if ! tar -xzf "$backup_file" -C "$restore_path" --strip-components=1 2>/dev/null; then
256 log_message "error" "Sample restore test failed (tar extraction failed)"
257 send_alert "[WARNING] Restore Test Failed" \
258 "Backup file: $backup_file\nRestore test failed during extraction\nHostname: $(hostname)\nDate: $(date)" \
259 "warning"
260 return $EXIT_RESTORE_FAILED
261 fi
262 ;;
263 *.tar)
264 if ! tar -xf "$backup_file" -C "$restore_path" --strip-components=1 2>/dev/null; then
265 log_message "error" "Sample restore test failed (tar extraction failed)"
266 send_alert "[WARNING] Restore Test Failed" \
267 "Backup file: $backup_file\nRestore test failed during extraction\nHostname: $(hostname)\nDate: $(date)" \
268 "warning"
269 return $EXIT_RESTORE_FAILED
270 fi
271 ;;
272 *.zip)
273 if command -v unzip >/dev/null 2>&1; then
274 if ! unzip -q "$backup_file" -d "$restore_path" 2>/dev/null; then
275 log_message "error" "Sample restore test failed (zip extraction failed)"
276 send_alert "[WARNING] Restore Test Failed" \
277 "Backup file: $backup_file\nRestore test failed during extraction\nHostname: $(hostname)\nDate: $(date)" \
278 "warning"
279 return $EXIT_RESTORE_FAILED
280 fi
281 fi
282 ;;
283 esac
284
285 # Verify files were restored
286 local restored_count=$(find "$restore_path" -type f | wc -l)
287 if [ "$restored_count" -eq 0 ]; then
288 log_message "error" "No files were restored during test"
289 send_alert "[WARNING] Restore Test Failed" \
290 "Backup file: $backup_file\nNo files extracted during restore test\nHostname: $(hostname)\nDate: $(date)" \
291 "warning"
292 # Clean up
293 rm -rf "$restore_path"
294 return $EXIT_RESTORE_FAILED
295 fi
296
297 log_message "info" "Restore test passed: $restored_count file(s) extracted"
298
299 # Clean up test restore
300 rm -rf "$restore_path"
301
302 return 0
303}
304
305print_summary() {
306 local status="$1"
307 local backup_file="$2"
308
309 echo ""
310 echo -e "${BLUE}========================================${NC}"
311 echo -e "${BLUE} Backup Verification Summary${NC}"
312 echo -e "${BLUE}========================================${NC}"
313 echo ""
314 echo "Hostname: $(hostname)"
315 echo "Date: $(date)"
316 echo "Backup Path: $BACKUP_BASE_PATH"
317 echo "Latest Backup: $backup_file"
318 echo ""
319
320 if [ "$status" = "success" ]; then
321 echo -e "${GREEN}✓ All verification checks passed${NC}"
322 echo -e "${GREEN}✓ Backup is valid and ready for restore${NC}"
323 else
324 echo -e "${RED}✗ Verification failed - check logs above${NC}"
325 echo -e "${RED}✗ Backup may not be usable for restore${NC}"
326 fi
327 echo ""
328}
329
330show_help() {
331 cat << EOF
332Usage: $0 [OPTIONS]
333
334Backup Verification Script
335
336OPTIONS:
337 -p, --path PATH Backup base path (default: /backup)
338 -n, --name NAME Backup name/identifier
339 -a, --max-age HOURS Maximum backup age in hours (default: 24)
340 -s, --min-size MB Minimum backup size in MB (default: 10)
341 -t, --test-restore Enable restore test
342 -r, --restore-path PATH Restore test path (default: /tmp/restore_test)
343 -e, --email ADDRESS Email for alerts
344 -m, --mail Enable email alerts
345 -w, --webhook URL Webhook URL for alerts
346 --pattern PATTERN File pattern to search (default: *.tar.gz)
347 -h, --help Display this help message
348
349EXAMPLES:
350 # Basic verification
351 $0 --path /backup
352
353 # With email alerts
354 $0 --path /backup --email admin@example.com --mail
355
356 # With restore test
357 $0 --path /backup --test-restore
358
359 # Custom thresholds
360 $0 --path /backup --max-age 12 --min-size 100
361
362ENVIRONMENT VARIABLES:
363 BACKUP_BASE_PATH Backup directory path
364 MAX_AGE_HOURS Maximum backup age in hours
365 MIN_SIZE_MB Minimum backup size in MB
366 TEST_RESTORE Enable restore test (true/false)
367 ALERT_EMAIL Email address for alerts
368 SEND_EMAIL Enable email alerts (true/false)
369 WEBHOOK_URL Webhook URL for alerts
370
371EOF
372 exit 0
373}
374
375#############################################################################
376# Main Script
377#############################################################################
378
379# Parse command line arguments
380while [[ $# -gt 0 ]]; do
381 case $1 in
382 -p|--path)
383 BACKUP_BASE_PATH="$2"
384 shift 2
385 ;;
386 -n|--name)
387 BACKUP_NAME="$2"
388 shift 2
389 ;;
390 -a|--max-age)
391 MAX_AGE_HOURS="$2"
392 shift 2
393 ;;
394 -s|--min-size)
395 MIN_SIZE_MB="$2"
396 shift 2
397 ;;
398 -t|--test-restore)
399 TEST_RESTORE=true
400 shift
401 ;;
402 -r|--restore-path)
403 RESTORE_TEST_PATH="$2"
404 shift 2
405 ;;
406 -e|--email)
407 ALERT_EMAIL="$2"
408 SEND_EMAIL=true
409 shift 2
410 ;;
411 -m|--mail)
412 SEND_EMAIL=true
413 shift
414 ;;
415 -w|--webhook)
416 WEBHOOK_URL="$2"
417 shift 2
418 ;;
419 --pattern)
420 BACKUP_PATTERN="$2"
421 shift 2
422 ;;
423 -h|--help)
424 show_help
425 ;;
426 *)
427 echo "Unknown option: $1"
428 echo "Use -h or --help for usage information"
429 exit 1
430 ;;
431 esac
432done
433
434# Main execution
435echo -e "${BLUE}========================================${NC}"
436echo -e "${BLUE} Backup Verification${NC}"
437echo -e "${BLUE}========================================${NC}"
438echo ""
439
440VERIFICATION_STATUS="success"
441EXIT_CODE=$EXIT_SUCCESS
442
443# Step 1: Check backup exists
444if ! verify_backup_exists; then
445 VERIFICATION_STATUS="failed"
446 EXIT_CODE=$EXIT_NO_BACKUP_FOUND
447 print_summary "$VERIFICATION_STATUS" "N/A"
448 exit $EXIT_CODE
449fi
450
451# Step 2: Verify backup age
452if ! verify_backup_age "$LATEST_BACKUP" "$MAX_AGE_HOURS"; then
453 VERIFICATION_STATUS="failed"
454 EXIT_CODE=$EXIT_BACKUP_TOO_OLD
455fi
456
457# Step 3: Verify backup size
458if ! verify_backup_size "$LATEST_BACKUP" "$MIN_SIZE_MB"; then
459 VERIFICATION_STATUS="failed"
460 EXIT_CODE=$EXIT_BACKUP_TOO_SMALL
461fi
462
463# Step 4: Verify backup integrity
464if ! verify_backup_integrity "$LATEST_BACKUP"; then
465 VERIFICATION_STATUS="failed"
466 EXIT_CODE=$EXIT_VERIFICATION_FAILED
467fi
468
469# Step 5: Optional restore test
470if [ "$TEST_RESTORE" = true ]; then
471 if ! test_restore_sample "$LATEST_BACKUP" "$RESTORE_TEST_PATH"; then
472 VERIFICATION_STATUS="failed"
473 EXIT_CODE=$EXIT_RESTORE_FAILED
474 fi
475fi
476
477# Print summary
478print_summary "$VERIFICATION_STATUS" "$LATEST_BACKUP"
479
480# Send success notification if all checks passed
481if [ "$VERIFICATION_STATUS" = "success" ]; then
482 send_alert "[SUCCESS] Backup Verification Passed" \
483 "Backup file: $LATEST_BACKUP\nAll verification checks passed\nHostname: $(hostname)\nDate: $(date)" \
484 "success"
485fi
486
487log_message "info" "Backup verification completed with status: $VERIFICATION_STATUS"
488
489exit $EXIT_CODEThe Script (PowerShell)
1<#
2.SYNOPSIS
3 Backup Verification Script for Windows
4
5.DESCRIPTION
6 Verifies backup completion, file integrity, and sends alerts on failures.
7
8 Features:
9 - Check if backup completed successfully
10 - Verify backup file exists and is recent
11 - Check backup file size (not 0 bytes)
12 - Test restore of sample files (optional)
13 - Send alert if backup failed
14
15.PARAMETER BackupPath
16 Path to backup directory
17
18.PARAMETER MaxAgeHours
19 Maximum backup age in hours (default: 24)
20
21.PARAMETER MinSizeMB
22 Minimum backup size in MB (default: 10)
23
24.PARAMETER TestRestore
25 Enable restore test
26
27.PARAMETER RestoreTestPath
28 Path for restore testing
29
30.PARAMETER EmailTo
31 Email address for alerts
32
33.PARAMETER SmtpServer
34 SMTP server for email alerts
35
36.PARAMETER WebhookUrl
37 Webhook URL for alerts
38
39.PARAMETER BackupPattern
40 File pattern to search (default: *.zip)
41
42.EXAMPLE
43 .\Backup-Verification.ps1 -BackupPath "D:\Backups"
44
45.EXAMPLE
46 .\Backup-Verification.ps1 -BackupPath "D:\Backups" -TestRestore -EmailTo "admin@example.com"
47
48.NOTES
49 Author: glyph.sh
50 Reference: https://glyph.sh/scripts/backup-verification/
51#>
52
53[CmdletBinding()]
54param(
55 [Parameter(Mandatory=$false)]
56 [string]$BackupPath = "C:\Backups",
57
58 [Parameter(Mandatory=$false)]
59 [int]$MaxAgeHours = 24,
60
61 [Parameter(Mandatory=$false)]
62 [int]$MinSizeMB = 10,
63
64 [Parameter(Mandatory=$false)]
65 [switch]$TestRestore,
66
67 [Parameter(Mandatory=$false)]
68 [string]$RestoreTestPath = "$env:TEMP\restore_test",
69
70 [Parameter(Mandatory=$false)]
71 [string]$EmailTo,
72
73 [Parameter(Mandatory=$false)]
74 [string]$SmtpServer,
75
76 [Parameter(Mandatory=$false)]
77 [string]$WebhookUrl,
78
79 [Parameter(Mandatory=$false)]
80 [string]$BackupPattern = "*.zip"
81)
82
83# Exit codes
84$ExitSuccess = 0
85$ExitNoBackupFound = 1
86$ExitBackupTooOld = 2
87$ExitBackupTooSmall = 3
88$ExitRestoreFailed = 4
89$ExitVerificationFailed = 5
90
91# Initialize status
92$VerificationStatus = "success"
93$ExitCode = $ExitSuccess
94$LatestBackup = $null
95
96#############################################################################
97# Functions
98#############################################################################
99
100function Write-Log {
101 param(
102 [Parameter(Mandatory=$true)]
103 [ValidateSet('Info', 'Warning', 'Error')]
104 [string]$Level,
105
106 [Parameter(Mandatory=$true)]
107 [string]$Message
108 )
109
110 $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
111 $color = switch ($Level) {
112 'Info' { 'Green' }
113 'Warning' { 'Yellow' }
114 'Error' { 'Red' }
115 }
116
117 Write-Host "[$Level] [$timestamp] $Message" -ForegroundColor $color
118
119 # Write to Windows Event Log
120 try {
121 $eventSource = "BackupVerification"
122 if (-not [System.Diagnostics.EventLog]::SourceExists($eventSource)) {
123 New-EventLog -LogName Application -Source $eventSource
124 }
125
126 $eventType = switch ($Level) {
127 'Info' { 'Information' }
128 'Warning' { 'Warning' }
129 'Error' { 'Error' }
130 }
131
132 Write-EventLog -LogName Application -Source $eventSource -EntryType $eventType -EventId 1000 -Message $Message
133 } catch {
134 # Silently continue if event log write fails
135 }
136}
137
138function Send-Alert {
139 param(
140 [Parameter(Mandatory=$true)]
141 [string]$Subject,
142
143 [Parameter(Mandatory=$true)]
144 [string]$Message,
145
146 [Parameter(Mandatory=$true)]
147 [ValidateSet('success', 'warning', 'error')]
148 [string]$Severity
149 )
150
151 # Email alert
152 if ($EmailTo -and $SmtpServer) {
153 try {
154 Send-MailMessage -To $EmailTo `
155 -From "backup-verification@$env:COMPUTERNAME" `
156 -Subject $Subject `
157 -Body $Message `
158 -SmtpServer $SmtpServer `
159 -ErrorAction Stop
160 Write-Log -Level Info -Message "Email alert sent to $EmailTo"
161 } catch {
162 Write-Log -Level Warning -Message "Failed to send email: $_"
163 }
164 }
165
166 # Webhook alert
167 if ($WebhookUrl) {
168 try {
169 $emoji = switch ($Severity) {
170 'success' { '✅' }
171 'warning' { '⚠️' }
172 'error' { '❌' }
173 }
174
175 $body = @{
176 text = "$emoji $Subject`n`n$Message"
177 } | ConvertTo-Json
178
179 Invoke-RestMethod -Uri $WebhookUrl -Method Post -Body $body -ContentType 'application/json' -ErrorAction Stop
180 Write-Log -Level Info -Message "Webhook alert sent"
181 } catch {
182 Write-Log -Level Warning -Message "Failed to send webhook: $_"
183 }
184 }
185}
186
187function Find-LatestBackup {
188 param(
189 [Parameter(Mandatory=$true)]
190 [string]$Path,
191
192 [Parameter(Mandatory=$true)]
193 [string]$Pattern
194 )
195
196 if (-not (Test-Path $Path)) {
197 Write-Log -Level Error -Message "Backup path does not exist: $Path"
198 return $null
199 }
200
201 $backupFiles = Get-ChildItem -Path $Path -Filter $Pattern -File -ErrorAction SilentlyContinue |
202 Sort-Object LastWriteTime -Descending |
203 Select-Object -First 1
204
205 return $backupFiles
206}
207
208function Test-BackupAge {
209 param(
210 [Parameter(Mandatory=$true)]
211 [System.IO.FileInfo]$BackupFile,
212
213 [Parameter(Mandatory=$true)]
214 [int]$MaxAgeHours
215 )
216
217 Write-Log -Level Info -Message "Verifying backup age (max: ${MaxAgeHours}h)"
218
219 $fileAge = (Get-Date) - $BackupFile.LastWriteTime
220 $ageHours = [math]::Round($fileAge.TotalHours, 1)
221
222 Write-Log -Level Info -Message "Backup age: ${ageHours}h"
223
224 if ($fileAge.TotalHours -gt $MaxAgeHours) {
225 Write-Log -Level Error -Message "Backup is too old: ${ageHours}h (max: ${MaxAgeHours}h)"
226 Send-Alert -Subject "[CRITICAL] Backup Too Old" `
227 -Message "Backup file: $($BackupFile.FullName)`nAge: $ageHours hours`nMaximum allowed: $MaxAgeHours hours`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
228 -Severity "error"
229 return $false
230 }
231
232 Write-Log -Level Info -Message "Backup age verification passed"
233 return $true
234}
235
236function Test-BackupSize {
237 param(
238 [Parameter(Mandatory=$true)]
239 [System.IO.FileInfo]$BackupFile,
240
241 [Parameter(Mandatory=$true)]
242 [int]$MinSizeMB
243 )
244
245 Write-Log -Level Info -Message "Verifying backup size (min: ${MinSizeMB}MB)"
246
247 $sizeMB = [math]::Round($BackupFile.Length / 1MB, 2)
248
249 Write-Log -Level Info -Message "Backup size: ${sizeMB}MB"
250
251 if ($BackupFile.Length -eq 0) {
252 Write-Log -Level Error -Message "Backup file is empty (0 bytes)"
253 Send-Alert -Subject "[CRITICAL] Empty Backup File" `
254 -Message "Backup file: $($BackupFile.FullName)`nSize: 0 bytes`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
255 -Severity "error"
256 return $false
257 }
258
259 if ($sizeMB -lt $MinSizeMB) {
260 Write-Log -Level Error -Message "Backup file is too small: ${sizeMB}MB (min: ${MinSizeMB}MB)"
261 Send-Alert -Subject "[WARNING] Backup Size Below Threshold" `
262 -Message "Backup file: $($BackupFile.FullName)`nSize: ${sizeMB}MB`nMinimum expected: ${MinSizeMB}MB`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
263 -Severity "warning"
264 return $false
265 }
266
267 Write-Log -Level Info -Message "Backup size verification passed"
268 return $true
269}
270
271function Test-BackupIntegrity {
272 param(
273 [Parameter(Mandatory=$true)]
274 [System.IO.FileInfo]$BackupFile
275 )
276
277 Write-Log -Level Info -Message "Verifying backup file integrity"
278
279 # Test ZIP file integrity
280 if ($BackupFile.Extension -eq '.zip') {
281 try {
282 Add-Type -AssemblyName System.IO.Compression.FileSystem
283 $zip = [System.IO.Compression.ZipFile]::OpenRead($BackupFile.FullName)
284 $entryCount = $zip.Entries.Count
285 $zip.Dispose()
286
287 if ($entryCount -eq 0) {
288 Write-Log -Level Error -Message "Backup archive is empty (0 entries)"
289 Send-Alert -Subject "[CRITICAL] Backup Corrupted" `
290 -Message "Backup file: $($BackupFile.FullName)`nIntegrity check failed: archive contains no entries`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
291 -Severity "error"
292 return $false
293 }
294
295 Write-Log -Level Info -Message "Backup integrity verification passed ($entryCount entries)"
296 return $true
297 } catch {
298 Write-Log -Level Error -Message "Backup archive is corrupted: $_"
299 Send-Alert -Subject "[CRITICAL] Backup Corrupted" `
300 -Message "Backup file: $($BackupFile.FullName)`nIntegrity check failed: $_`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
301 -Severity "error"
302 return $false
303 }
304 }
305
306 Write-Log -Level Info -Message "File type not recognized for integrity check, skipping"
307 return $true
308}
309
310function Test-RestoreSample {
311 param(
312 [Parameter(Mandatory=$true)]
313 [System.IO.FileInfo]$BackupFile,
314
315 [Parameter(Mandatory=$true)]
316 [string]$RestorePath
317 )
318
319 Write-Log -Level Info -Message "Testing sample file restoration"
320
321 # Create restore test directory
322 if (Test-Path $RestorePath) {
323 Remove-Item -Path $RestorePath -Recurse -Force -ErrorAction SilentlyContinue
324 }
325 New-Item -Path $RestorePath -ItemType Directory -Force | Out-Null
326
327 try {
328 if ($BackupFile.Extension -eq '.zip') {
329 Add-Type -AssemblyName System.IO.Compression.FileSystem
330
331 # Extract first 5 files as a sample
332 $zip = [System.IO.Compression.ZipFile]::OpenRead($BackupFile.FullName)
333 $sampleEntries = $zip.Entries | Select-Object -First 5
334
335 foreach ($entry in $sampleEntries) {
336 if ($entry.FullName -notlike "*/") { # Skip directories
337 $destinationPath = Join-Path $RestorePath $entry.Name
338 [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destinationPath, $true)
339 }
340 }
341
342 $zip.Dispose()
343 } else {
344 Write-Log -Level Warning -Message "Restore test not supported for this file type"
345 return $true
346 }
347
348 # Verify files were restored
349 $restoredCount = (Get-ChildItem -Path $RestorePath -File -Recurse).Count
350
351 if ($restoredCount -eq 0) {
352 Write-Log -Level Error -Message "No files were restored during test"
353 Send-Alert -Subject "[WARNING] Restore Test Failed" `
354 -Message "Backup file: $($BackupFile.FullName)`nNo files extracted during restore test`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
355 -Severity "warning"
356 return $false
357 }
358
359 Write-Log -Level Info -Message "Restore test passed: $restoredCount file(s) extracted"
360 return $true
361 } catch {
362 Write-Log -Level Error -Message "Restore test failed: $_"
363 Send-Alert -Subject "[WARNING] Restore Test Failed" `
364 -Message "Backup file: $($BackupFile.FullName)`nRestore test failed: $_`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
365 -Severity "warning"
366 return $false
367 } finally {
368 # Clean up
369 if (Test-Path $RestorePath) {
370 Remove-Item -Path $RestorePath -Recurse -Force -ErrorAction SilentlyContinue
371 }
372 }
373}
374
375function Show-Summary {
376 param(
377 [Parameter(Mandatory=$true)]
378 [string]$Status,
379
380 [Parameter(Mandatory=$false)]
381 [System.IO.FileInfo]$BackupFile
382 )
383
384 Write-Host "`n========================================" -ForegroundColor Cyan
385 Write-Host " Backup Verification Summary" -ForegroundColor Cyan
386 Write-Host "========================================`n" -ForegroundColor Cyan
387
388 Write-Host "Hostname: $env:COMPUTERNAME"
389 Write-Host "Date: $(Get-Date)"
390 Write-Host "Backup Path: $BackupPath"
391 if ($BackupFile) {
392 Write-Host "Latest Backup: $($BackupFile.FullName)"
393 } else {
394 Write-Host "Latest Backup: N/A"
395 }
396 Write-Host ""
397
398 if ($Status -eq "success") {
399 Write-Host "✓ All verification checks passed" -ForegroundColor Green
400 Write-Host "✓ Backup is valid and ready for restore" -ForegroundColor Green
401 } else {
402 Write-Host "✗ Verification failed - check logs above" -ForegroundColor Red
403 Write-Host "✗ Backup may not be usable for restore" -ForegroundColor Red
404 }
405 Write-Host ""
406}
407
408#############################################################################
409# Main Script
410#############################################################################
411
412Write-Host "========================================" -ForegroundColor Cyan
413Write-Host " Backup Verification" -ForegroundColor Cyan
414Write-Host "========================================`n" -ForegroundColor Cyan
415
416# Step 1: Find latest backup
417Write-Log -Level Info -Message "Checking for backup files in $BackupPath"
418$LatestBackup = Find-LatestBackup -Path $BackupPath -Pattern $BackupPattern
419
420if (-not $LatestBackup) {
421 Write-Log -Level Error -Message "No backup files found matching pattern: $BackupPattern"
422 Send-Alert -Subject "[CRITICAL] Backup Verification Failed" `
423 -Message "No backup files found in $BackupPath`nPattern: $BackupPattern`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
424 -Severity "error"
425 $VerificationStatus = "failed"
426 $ExitCode = $ExitNoBackupFound
427 Show-Summary -Status $VerificationStatus -BackupFile $null
428 exit $ExitCode
429}
430
431Write-Log -Level Info -Message "Found backup: $($LatestBackup.FullName)"
432
433# Step 2: Verify backup age
434if (-not (Test-BackupAge -BackupFile $LatestBackup -MaxAgeHours $MaxAgeHours)) {
435 $VerificationStatus = "failed"
436 $ExitCode = $ExitBackupTooOld
437}
438
439# Step 3: Verify backup size
440if (-not (Test-BackupSize -BackupFile $LatestBackup -MinSizeMB $MinSizeMB)) {
441 $VerificationStatus = "failed"
442 $ExitCode = $ExitBackupTooSmall
443}
444
445# Step 4: Verify backup integrity
446if (-not (Test-BackupIntegrity -BackupFile $LatestBackup)) {
447 $VerificationStatus = "failed"
448 $ExitCode = $ExitVerificationFailed
449}
450
451# Step 5: Optional restore test
452if ($TestRestore) {
453 if (-not (Test-RestoreSample -BackupFile $LatestBackup -RestorePath $RestoreTestPath)) {
454 $VerificationStatus = "failed"
455 $ExitCode = $ExitRestoreFailed
456 }
457}
458
459# Show summary
460Show-Summary -Status $VerificationStatus -BackupFile $LatestBackup
461
462# Send success notification
463if ($VerificationStatus -eq "success") {
464 Send-Alert -Subject "[SUCCESS] Backup Verification Passed" `
465 -Message "Backup file: $($LatestBackup.FullName)`nAll verification checks passed`nHostname: $env:COMPUTERNAME`nDate: $(Get-Date)" `
466 -Severity "success"
467}
468
469Write-Log -Level Info -Message "Backup verification completed with status: $VerificationStatus"
470
471exit $ExitCodeUsage
Bash Usage
Basic Verification
1chmod +x backup-verification.sh
2./backup-verification.sh --path /backupWith Email Alerts
1./backup-verification.sh \
2 --path /backup \
3 --email admin@example.com \
4 --mailWith Restore Test
1./backup-verification.sh \
2 --path /backup \
3 --test-restore \
4 --restore-path /tmp/restore_testCustom Thresholds
1./backup-verification.sh \
2 --path /backup \
3 --max-age 12 \
4 --min-size 100 \
5 --pattern "*.tar.gz"With Webhook (Slack/Discord)
1./backup-verification.sh \
2 --path /backup \
3 --webhook "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"PowerShell Usage
Basic Verification
1.\Backup-Verification.ps1 -BackupPath "D:\Backups"With Email Alerts
1.\Backup-Verification.ps1 `
2 -BackupPath "D:\Backups" `
3 -EmailTo "admin@example.com" `
4 -SmtpServer "smtp.example.com"With Restore Test
1.\Backup-Verification.ps1 `
2 -BackupPath "D:\Backups" `
3 -TestRestore `
4 -RestoreTestPath "C:\Temp\restore_test"Custom Thresholds
1.\Backup-Verification.ps1 `
2 -BackupPath "D:\Backups" `
3 -MaxAgeHours 12 `
4 -MinSizeMB 100 `
5 -BackupPattern "*.zip"What It Does
- Finds Latest Backup: Locates the most recent backup file matching the specified pattern
- Verifies Age: Ensures backup was created within the allowed time window
- Checks Size: Validates backup is not empty and meets minimum size requirements
- Tests Integrity: Verifies archive can be read and is not corrupted
- Optional Restore Test: Extracts sample files to verify backup is restorable
- Sends Alerts: Notifies administrators via email, webhook, or syslog on failures
Automated Verification
Linux Cron Job
1# Edit crontab
2crontab -e
3
4# Verify backups daily at 6 AM
50 6 * * * /usr/local/bin/backup-verification.sh --path /backup --email admin@example.com --mail
6
7# Verify with restore test weekly
80 6 * * 0 /usr/local/bin/backup-verification.sh --path /backup --test-restore --email admin@example.com --mailWindows Task Scheduler
1# Create scheduled task for daily verification
2$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
3 -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Backup-Verification.ps1 -BackupPath D:\Backups -EmailTo admin@example.com -SmtpServer smtp.example.com"
4
5$trigger = New-ScheduledTaskTrigger -Daily -At 6:00AM
6
7$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
8
9Register-ScheduledTask -TaskName "Backup Verification" `
10 -Action $action `
11 -Trigger $trigger `
12 -Principal $principal `
13 -Description "Daily backup verification"Verification Checks Explained
Age Check
Ensures backups are running on schedule. A backup older than expected indicates:
- Backup job failed to run
- Backup schedule needs updating
- Backup system is offline
Size Check
Validates backup contains data. Zero-byte or unexpectedly small files indicate:
- Backup process crashed mid-execution
- Source data is missing or inaccessible
- Backup compression failed
Integrity Check
Confirms backup file is not corrupted and can be read:
- Tests archive can be opened
- Validates file structure
- Prevents restore failures
Restore Test
Most comprehensive check - actually extracts files:
- Confirms data can be recovered
- Tests extraction process
- Validates backup usability
Common Issues Prevented
- Discovering backup failures before data loss occurs
- Identifying corrupted backups before they’re needed
- Detecting storage space issues affecting backups
- Finding scheduling problems early
- Validating backup processes are working correctly
- Meeting compliance requirements for backup verification
Integration Examples
Slack Webhook
1# Get webhook URL from Slack (Incoming Webhooks app)
2WEBHOOK="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXX"
3
4./backup-verification.sh \
5 --path /backup \
6 --webhook "$WEBHOOK"Email with SMTP
1# Configure system mail first
2# Edit /etc/postfix/main.cf or use msmtp
3
4./backup-verification.sh \
5 --path /backup \
6 --email admin@example.com \
7 --mailMonitoring Integration
1# Export metrics for Prometheus/Grafana
2./backup-verification.sh --path /backup > /var/lib/node_exporter/backup_status.prom
3
4# Or send to monitoring system
5if ./backup-verification.sh --path /backup; then
6 curl -X POST "http://monitoring.example.com/api/metrics" \
7 -d '{"backup_status": "success", "timestamp": "'$(date -u +%s)'"}'
8fiExit Codes
| Code | Meaning |
|---|---|
| 0 | Success - all checks passed |
| 1 | No backup found |
| 2 | Backup too old |
| 3 | Backup too small or empty |
| 4 | Restore test failed |
| 5 | Integrity verification failed |
Use exit codes for automation and monitoring:
1#!/bin/bash
2./backup-verification.sh --path /backup
3EXIT_CODE=$?
4
5if [ $EXIT_CODE -eq 0 ]; then
6 echo "Backup verified successfully"
7elif [ $EXIT_CODE -eq 1 ]; then
8 echo "CRITICAL: No backup found!"
9 # Trigger emergency backup
10else
11 echo "WARNING: Backup verification failed with code $EXIT_CODE"
12fiSee Also
Download
Bash Version
1curl -O https://glyph.sh/scripts/backup-verification.sh
2chmod +x backup-verification.sh
3./backup-verification.sh --helpPowerShell Version
1Invoke-WebRequest -Uri "https://glyph.sh/scripts/Backup-Verification.ps1" -OutFile "Backup-Verification.ps1"
2Get-Help .\Backup-Verification.ps1 -Full