dr_restore.sh

Download
bash 375 lines 9.8 KB
  1#!/usr/bin/env bash
  2#
  3# Disaster Recovery Restore Script
  4#
  5# This script restores from backups created by dr_backup.sh
  6#
  7# Features:
  8# - Interactive backup selection
  9# - Integrity verification (checksums)
 10# - Database restoration
 11# - Configuration file restoration
 12# - Dry-run mode
 13# - Comprehensive logging
 14
 15set -euo pipefail
 16
 17#------------------------------------------------------------------------------
 18# Configuration
 19#------------------------------------------------------------------------------
 20
 21readonly SCRIPT_NAME="$(basename "$0")"
 22readonly BACKUP_ROOT="/var/backups/dr"
 23readonly LOG_DIR="${BACKUP_ROOT}/logs"
 24readonly LOG_FILE="${LOG_DIR}/restore_$(date +%Y%m%d_%H%M%S).log"
 25
 26# Database configuration
 27readonly DB_HOST="localhost"
 28readonly DB_PORT="5432"
 29readonly DB_USER="backup_user"
 30
 31# Restore settings
 32readonly RESTORE_ROOT="/restore"
 33
 34#------------------------------------------------------------------------------
 35# Functions
 36#------------------------------------------------------------------------------
 37
 38usage() {
 39    cat <<EOF
 40Usage: $SCRIPT_NAME [OPTIONS]
 41
 42Restore from disaster recovery backups.
 43
 44OPTIONS:
 45    -h, --help              Show this help message
 46    -d, --date DATE         Restore from specific date (YYYYMMDD_HHMMSS)
 47    -t, --type TYPE         Restore type: all|filesystem|database|config
 48    -n, --dry-run           Show what would be done without executing
 49    -l, --list              List available backups
 50    -v, --verify            Verify backup integrity only
 51
 52EXAMPLES:
 53    $SCRIPT_NAME --list                     # List available backups
 54    $SCRIPT_NAME -d 20240215_120000         # Restore specific backup
 55    $SCRIPT_NAME -t database                # Restore databases only
 56    $SCRIPT_NAME -n -d 20240215_120000      # Dry-run mode
 57
 58EOF
 59}
 60
 61log() {
 62    local level="$1"
 63    shift
 64    local message="$*"
 65    local timestamp
 66    timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
 67
 68    echo "[${timestamp}] [${level}] ${message}" | tee -a "$LOG_FILE"
 69}
 70
 71die() {
 72    log "ERROR" "$*"
 73    exit 1
 74}
 75
 76list_backups() {
 77    log "INFO" "Available backups:"
 78
 79    echo ""
 80    echo "Full Filesystem Backups:"
 81    if [[ -d "${BACKUP_ROOT}/full" ]]; then
 82        find "${BACKUP_ROOT}/full" -maxdepth 1 -type d | \
 83            grep -E '[0-9]{8}_[0-9]{6}' | \
 84            sort -r | \
 85            sed 's/.*\//  - /'
 86    else
 87        echo "  No backups found"
 88    fi
 89
 90    echo ""
 91    echo "Database Backups:"
 92    if [[ -d "${BACKUP_ROOT}/database" ]]; then
 93        find "${BACKUP_ROOT}/database" -maxdepth 1 -type d | \
 94            grep -E '[0-9]{8}_[0-9]{6}' | \
 95            sort -r | \
 96            sed 's/.*\//  - /'
 97    else
 98        echo "  No backups found"
 99    fi
100
101    echo ""
102}
103
104verify_backup_integrity() {
105    local backup_date="$1"
106    local backup_path="${BACKUP_ROOT}/full/${backup_date}"
107
108    log "INFO" "Verifying backup integrity: $backup_date"
109
110    if [[ ! -f "${backup_path}/checksums.txt" ]]; then
111        log "WARN" "No checksum file found for backup: $backup_date"
112        return 1
113    fi
114
115    pushd "$backup_path" > /dev/null
116
117    if sha256sum -c checksums.txt >> "$LOG_FILE" 2>&1; then
118        log "INFO" "Backup integrity verified successfully"
119        popd > /dev/null
120        return 0
121    else
122        log "ERROR" "Backup integrity verification failed"
123        popd > /dev/null
124        return 1
125    fi
126}
127
128restore_filesystem() {
129    local backup_date="$1"
130    local dry_run="$2"
131    local backup_path="${BACKUP_ROOT}/full/${backup_date}"
132
133    log "INFO" "Restoring filesystem from: $backup_date"
134
135    if [[ ! -d "$backup_path" ]]; then
136        die "Backup not found: $backup_path"
137    fi
138
139    # Create restore directory
140    if [[ "$dry_run" != "true" ]]; then
141        mkdir -p "$RESTORE_ROOT"
142    fi
143
144    # Find tar archives
145    local archives
146    mapfile -t archives < <(find "$backup_path" -name "*.tar*" -type f)
147
148    if [[ ${#archives[@]} -eq 0 ]]; then
149        log "WARN" "No tar archives found in backup"
150        return 0
151    fi
152
153    for archive in "${archives[@]}"; do
154        log "INFO" "Processing archive: $(basename "$archive")"
155
156        if [[ "$dry_run" == "true" ]]; then
157            log "INFO" "[DRY-RUN] Would extract: $archive"
158            continue
159        fi
160
161        # Decrypt if needed
162        if [[ "$archive" == *.gpg ]]; then
163            log "INFO" "Decrypting archive"
164            local decrypted="${archive%.gpg}"
165            gpg --decrypt "$archive" > "$decrypted" || die "Decryption failed"
166            archive="$decrypted"
167        fi
168
169        # Decompress if needed
170        if [[ "$archive" == *.gz ]]; then
171            log "INFO" "Decompressing archive"
172            gunzip -k "$archive" || die "Decompression failed"
173            archive="${archive%.gz}"
174        fi
175
176        # Extract
177        log "INFO" "Extracting archive to: $RESTORE_ROOT"
178        tar -xpf "$archive" -C "$RESTORE_ROOT" || die "Extraction failed"
179    done
180
181    log "INFO" "Filesystem restore completed"
182}
183
184restore_databases() {
185    local backup_date="$1"
186    local dry_run="$2"
187    local db_backup_path="${BACKUP_ROOT}/database/${backup_date}"
188
189    log "INFO" "Restoring databases from: $backup_date"
190
191    if [[ ! -d "$db_backup_path" ]]; then
192        die "Database backup not found: $db_backup_path"
193    fi
194
195    if ! command -v pg_restore &>/dev/null; then
196        log "WARN" "pg_restore not found, skipping database restore"
197        return 0
198    fi
199
200    # Find database dumps
201    local dumps
202    mapfile -t dumps < <(find "$db_backup_path" -name "*.sql*" -type f)
203
204    if [[ ${#dumps[@]} -eq 0 ]]; then
205        log "WARN" "No database dumps found in backup"
206        return 0
207    fi
208
209    for dump in "${dumps[@]}"; do
210        local db_name
211        db_name="$(basename "$dump" | cut -d'_' -f1)"
212
213        log "INFO" "Restoring database: $db_name"
214
215        if [[ "$dry_run" == "true" ]]; then
216            log "INFO" "[DRY-RUN] Would restore database: $db_name from $dump"
217            continue
218        fi
219
220        # Decrypt if needed
221        if [[ "$dump" == *.gpg ]]; then
222            log "INFO" "Decrypting database dump"
223            local decrypted="${dump%.gpg}"
224            gpg --decrypt "$dump" > "$decrypted" || die "Decryption failed"
225            dump="$decrypted"
226        fi
227
228        # Decompress if needed
229        if [[ "$dump" == *.gz ]]; then
230            log "INFO" "Decompressing database dump"
231            gunzip -k "$dump" || die "Decompression failed"
232            dump="${dump%.gz}"
233        fi
234
235        # Restore database
236        PGPASSWORD="${DB_PASSWORD:-}" pg_restore \
237            -h "$DB_HOST" \
238            -p "$DB_PORT" \
239            -U "$DB_USER" \
240            -d "$db_name" \
241            -c \
242            "$dump" \
243            >> "$LOG_FILE" 2>&1 || log "WARN" "Database restore failed for $db_name"
244    done
245
246    log "INFO" "Database restore completed"
247}
248
249restore_config() {
250    local backup_date="$1"
251    local dry_run="$2"
252    local config_backup="${BACKUP_ROOT}/config/config_${backup_date}.tar.gz"
253
254    log "INFO" "Restoring configuration files"
255
256    if [[ ! -f "$config_backup" ]]; then
257        log "WARN" "Configuration backup not found: $config_backup"
258        return 0
259    fi
260
261    if [[ "$dry_run" == "true" ]]; then
262        log "INFO" "[DRY-RUN] Would restore config from: $config_backup"
263        return 0
264    fi
265
266    # Extract to temporary location first
267    local temp_dir
268    temp_dir="$(mktemp -d)"
269    trap 'rm -rf "$temp_dir"' EXIT
270
271    tar -xzf "$config_backup" -C "$temp_dir" || die "Config extraction failed"
272
273    log "INFO" "Configuration files extracted to: $temp_dir"
274    log "WARN" "Please manually review and copy config files as needed"
275
276    log "INFO" "Configuration restore completed"
277}
278
279#------------------------------------------------------------------------------
280# Main
281#------------------------------------------------------------------------------
282
283main() {
284    local backup_date=""
285    local restore_type="all"
286    local dry_run=false
287    local list_only=false
288    local verify_only=false
289
290    # Parse arguments
291    while [[ $# -gt 0 ]]; do
292        case "$1" in
293            -h|--help)
294                usage
295                exit 0
296                ;;
297            -d|--date)
298                backup_date="$2"
299                shift 2
300                ;;
301            -t|--type)
302                restore_type="$2"
303                shift 2
304                ;;
305            -n|--dry-run)
306                dry_run=true
307                shift
308                ;;
309            -l|--list)
310                list_only=true
311                shift
312                ;;
313            -v|--verify)
314                verify_only=true
315                shift
316                ;;
317            *)
318                echo "Unknown option: $1"
319                usage
320                exit 1
321                ;;
322        esac
323    done
324
325    # Setup logging
326    mkdir -p "$LOG_DIR"
327
328    if [[ "$list_only" == "true" ]]; then
329        list_backups
330        exit 0
331    fi
332
333    if [[ -z "$backup_date" ]]; then
334        die "Backup date required. Use --list to see available backups"
335    fi
336
337    log "INFO" "Starting disaster recovery restore"
338    log "INFO" "Backup date: $backup_date"
339    log "INFO" "Restore type: $restore_type"
340
341    if [[ "$verify_only" == "true" ]]; then
342        verify_backup_integrity "$backup_date"
343        exit 0
344    fi
345
346    # Verify integrity before restore
347    if ! verify_backup_integrity "$backup_date"; then
348        die "Backup integrity check failed. Aborting restore."
349    fi
350
351    case "$restore_type" in
352        all)
353            restore_filesystem "$backup_date" "$dry_run"
354            restore_databases "$backup_date" "$dry_run"
355            restore_config "$backup_date" "$dry_run"
356            ;;
357        filesystem)
358            restore_filesystem "$backup_date" "$dry_run"
359            ;;
360        database)
361            restore_databases "$backup_date" "$dry_run"
362            ;;
363        config)
364            restore_config "$backup_date" "$dry_run"
365            ;;
366        *)
367            die "Unknown restore type: $restore_type"
368            ;;
369    esac
370
371    log "INFO" "Restore completed successfully"
372}
373
374main "$@"