414 lines
13 KiB
Bash
414 lines
13 KiB
Bash
|
#!/bin/bash
|
||
|
|
||
|
# Configuration
|
||
|
backupPath='/mnt/backups'
|
||
|
password_file="/etc/restic-password"
|
||
|
LOG_DIR="$HOME/mb-backups/logs"
|
||
|
LOG_FILE="${LOG_DIR}/backup_sessions.log"
|
||
|
ERROR_LOG_FILE="${LOG_DIR}/backup_sessions_error.log"
|
||
|
|
||
|
# Ensure log directory exists
|
||
|
mkdir -p "$LOG_DIR"
|
||
|
|
||
|
# Logging function
|
||
|
log_message() {
|
||
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||
|
}
|
||
|
|
||
|
# Error logging function
|
||
|
log_error() {
|
||
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$ERROR_LOG_FILE"
|
||
|
}
|
||
|
|
||
|
# Function: Display usage
|
||
|
display_usage() {
|
||
|
echo "Usage: $0 [options]"
|
||
|
echo ""
|
||
|
echo "Options:"
|
||
|
echo " sessions - Show all complete backup sessions"
|
||
|
echo " incomplete - Show incomplete backup sessions"
|
||
|
echo " all - Show all snapshots grouped by session"
|
||
|
echo " validate <tag> - Validate a specific backup session"
|
||
|
echo ""
|
||
|
echo "Examples:"
|
||
|
echo " $0 sessions"
|
||
|
echo " $0 validate manual-backup-2024-01-15_10-30-45"
|
||
|
exit 1
|
||
|
}
|
||
|
|
||
|
# Function: Validate repository access
|
||
|
validate_repository() {
|
||
|
log_message "Validating repository access..."
|
||
|
|
||
|
if [ ! -d "$backupPath" ]; then
|
||
|
log_error "Backup path '$backupPath' does not exist or is not accessible."
|
||
|
echo "Restic repository path not found. Please verify that the path exists and is mounted."
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
if ! RESTIC_PASSWORD=$(cat "$password_file") restic -r "$backupPath" snapshots &>/dev/null; then
|
||
|
log_message "Attempting to fix permissions for '$backupPath'..."
|
||
|
sudo chown -R "$(whoami)":"$(whoami)" "$backupPath" 2>/dev/null || true
|
||
|
sudo chmod -R u+rwX "$backupPath" 2>/dev/null || true
|
||
|
|
||
|
if ! RESTIC_PASSWORD=$(cat "$password_file") restic -r "$backupPath" snapshots &>/dev/null; then
|
||
|
log_error "Unable to access Restic repository at '$backupPath'."
|
||
|
echo "Restic repository is inaccessible. Please check the path, permissions, or mount status."
|
||
|
exit 1
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
log_message "Repository access validated successfully."
|
||
|
}
|
||
|
|
||
|
# Function: Get all snapshots with session analysis
|
||
|
get_all_snapshots() {
|
||
|
log_message "Retrieving all snapshots for session analysis..."
|
||
|
|
||
|
local snapshots_json
|
||
|
snapshots_json=$(RESTIC_PASSWORD=$(cat "$password_file") restic -r "$backupPath" snapshots --json 2>/dev/null)
|
||
|
|
||
|
if [ $? -ne 0 ] || [ -z "$snapshots_json" ]; then
|
||
|
log_error "Failed to retrieve snapshots from repository."
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
echo "$snapshots_json"
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
# Function: Extract backup session tags
|
||
|
extract_session_tags() {
|
||
|
local snapshots_json="$1"
|
||
|
|
||
|
# Extract all session tags (manual-backup-* and automated-backup-*)
|
||
|
echo "$snapshots_json" | jq -r '.[].tags[]' | grep -E '^(manual|automated)-backup-[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$' | sort | uniq
|
||
|
}
|
||
|
|
||
|
# Function: Analyze backup session completeness
|
||
|
analyze_session_completeness() {
|
||
|
local snapshots_json="$1"
|
||
|
local session_tag="$2"
|
||
|
|
||
|
# Get all snapshots for this session
|
||
|
local session_snapshots
|
||
|
session_snapshots=$(echo "$snapshots_json" | jq -r ".[] | select(.tags[] | contains(\"$session_tag\"))")
|
||
|
|
||
|
# Count snapshots in session
|
||
|
local snapshot_count
|
||
|
snapshot_count=$(echo "$session_snapshots" | jq -s '. | length')
|
||
|
|
||
|
# Extract component tags
|
||
|
local has_core=false
|
||
|
local has_media=false
|
||
|
local has_db=false
|
||
|
|
||
|
if echo "$session_snapshots" | jq -r '.tags[]' | grep -q "^core_files$"; then
|
||
|
has_core=true
|
||
|
fi
|
||
|
|
||
|
if echo "$session_snapshots" | jq -r '.tags[]' | grep -q "^media_themes$"; then
|
||
|
has_media=true
|
||
|
fi
|
||
|
|
||
|
if echo "$session_snapshots" | jq -r '.tags[]' | grep -q "^wordpress_db$"; then
|
||
|
has_db=true
|
||
|
fi
|
||
|
|
||
|
# Determine session status
|
||
|
local status="INCOMPLETE"
|
||
|
local missing_components=()
|
||
|
|
||
|
if [ "$has_core" = true ] && [ "$has_media" = true ] && [ "$has_db" = true ] && [ "$snapshot_count" -eq 3 ]; then
|
||
|
status="COMPLETE"
|
||
|
else
|
||
|
if [ "$has_core" = false ]; then
|
||
|
missing_components+=("core_files")
|
||
|
fi
|
||
|
if [ "$has_media" = false ]; then
|
||
|
missing_components+=("media_themes")
|
||
|
fi
|
||
|
if [ "$has_db" = false ]; then
|
||
|
missing_components+=("wordpress_db")
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
# Get session timestamp
|
||
|
local session_time
|
||
|
session_time=$(echo "$session_snapshots" | jq -r '.time' | head -n1)
|
||
|
|
||
|
# Output session info
|
||
|
echo "$session_tag|$status|$snapshot_count|$session_time|$(IFS=,; echo "${missing_components[*]}")"
|
||
|
}
|
||
|
|
||
|
# Function: Display complete backup sessions
|
||
|
display_complete_sessions() {
|
||
|
log_message "Displaying complete backup sessions..."
|
||
|
|
||
|
local snapshots_json
|
||
|
snapshots_json=$(get_all_snapshots)
|
||
|
|
||
|
if [ $? -ne 0 ]; then
|
||
|
echo "Failed to retrieve snapshots."
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
local session_tags
|
||
|
session_tags=$(extract_session_tags "$snapshots_json")
|
||
|
|
||
|
if [ -z "$session_tags" ]; then
|
||
|
echo "No backup sessions found."
|
||
|
return 0
|
||
|
fi
|
||
|
|
||
|
echo "=== COMPLETE BACKUP SESSIONS ==="
|
||
|
echo "Format: [Session Tag] [Date/Time] [Status]"
|
||
|
echo "========================================"
|
||
|
|
||
|
local complete_sessions=0
|
||
|
|
||
|
while IFS= read -r session_tag; do
|
||
|
if [ -n "$session_tag" ]; then
|
||
|
local session_info
|
||
|
session_info=$(analyze_session_completeness "$snapshots_json" "$session_tag")
|
||
|
|
||
|
local status
|
||
|
status=$(echo "$session_info" | cut -d'|' -f2)
|
||
|
|
||
|
if [ "$status" = "COMPLETE" ]; then
|
||
|
local session_time
|
||
|
session_time=$(echo "$session_info" | cut -d'|' -f4)
|
||
|
|
||
|
printf "%-45s %s [%s]\n" "$session_tag" "$session_time" "$status"
|
||
|
((complete_sessions++))
|
||
|
fi
|
||
|
fi
|
||
|
done <<< "$session_tags"
|
||
|
|
||
|
echo "========================================"
|
||
|
echo "Total complete backup sessions: $complete_sessions"
|
||
|
echo ""
|
||
|
echo "To restore a complete session, use:"
|
||
|
echo " ./restore_full_backup_session.sh <session_tag>"
|
||
|
}
|
||
|
|
||
|
# Function: Display incomplete backup sessions
|
||
|
display_incomplete_sessions() {
|
||
|
log_message "Displaying incomplete backup sessions..."
|
||
|
|
||
|
local snapshots_json
|
||
|
snapshots_json=$(get_all_snapshots)
|
||
|
|
||
|
if [ $? -ne 0 ]; then
|
||
|
echo "Failed to retrieve snapshots."
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
local session_tags
|
||
|
session_tags=$(extract_session_tags "$snapshots_json")
|
||
|
|
||
|
if [ -z "$session_tags" ]; then
|
||
|
echo "No backup sessions found."
|
||
|
return 0
|
||
|
fi
|
||
|
|
||
|
echo "=== INCOMPLETE BACKUP SESSIONS ==="
|
||
|
echo "Format: [Session Tag] [Date/Time] [Status] [Missing Components]"
|
||
|
echo "================================================================="
|
||
|
|
||
|
local incomplete_sessions=0
|
||
|
|
||
|
while IFS= read -r session_tag; do
|
||
|
if [ -n "$session_tag" ]; then
|
||
|
local session_info
|
||
|
session_info=$(analyze_session_completeness "$snapshots_json" "$session_tag")
|
||
|
|
||
|
local status
|
||
|
status=$(echo "$session_info" | cut -d'|' -f2)
|
||
|
|
||
|
if [ "$status" = "INCOMPLETE" ]; then
|
||
|
local session_time missing_components
|
||
|
session_time=$(echo "$session_info" | cut -d'|' -f4)
|
||
|
missing_components=$(echo "$session_info" | cut -d'|' -f5)
|
||
|
|
||
|
printf "%-45s %s [%s] Missing: %s\n" "$session_tag" "$session_time" "$status" "$missing_components"
|
||
|
((incomplete_sessions++))
|
||
|
fi
|
||
|
fi
|
||
|
done <<< "$session_tags"
|
||
|
|
||
|
echo "================================================================="
|
||
|
echo "Total incomplete backup sessions: $incomplete_sessions"
|
||
|
echo ""
|
||
|
echo "WARNING: Incomplete sessions cannot be used for full restoration."
|
||
|
}
|
||
|
|
||
|
# Function: Display all sessions with details
|
||
|
display_all_sessions() {
|
||
|
log_message "Displaying all backup sessions with details..."
|
||
|
|
||
|
local snapshots_json
|
||
|
snapshots_json=$(get_all_snapshots)
|
||
|
|
||
|
if [ $? -ne 0 ]; then
|
||
|
echo "Failed to retrieve snapshots."
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
local session_tags
|
||
|
session_tags=$(extract_session_tags "$snapshots_json")
|
||
|
|
||
|
if [ -z "$session_tags" ]; then
|
||
|
echo "No backup sessions found."
|
||
|
return 0
|
||
|
fi
|
||
|
|
||
|
echo "=== ALL BACKUP SESSIONS ==="
|
||
|
echo "Format: [Session Tag] [Date/Time] [Status] [Snapshot Count] [Missing Components]"
|
||
|
echo "============================================================================="
|
||
|
|
||
|
local total_sessions=0
|
||
|
local complete_sessions=0
|
||
|
|
||
|
while IFS= read -r session_tag; do
|
||
|
if [ -n "$session_tag" ]; then
|
||
|
local session_info
|
||
|
session_info=$(analyze_session_completeness "$snapshots_json" "$session_tag")
|
||
|
|
||
|
local status session_time snapshot_count missing_components
|
||
|
status=$(echo "$session_info" | cut -d'|' -f2)
|
||
|
snapshot_count=$(echo "$session_info" | cut -d'|' -f3)
|
||
|
session_time=$(echo "$session_info" | cut -d'|' -f4)
|
||
|
missing_components=$(echo "$session_info" | cut -d'|' -f5)
|
||
|
|
||
|
if [ "$status" = "COMPLETE" ]; then
|
||
|
printf "%-45s %s [%s] (%d snapshots)\n" "$session_tag" "$session_time" "$status" "$snapshot_count"
|
||
|
((complete_sessions++))
|
||
|
else
|
||
|
printf "%-45s %s [%s] (%d snapshots) Missing: %s\n" "$session_tag" "$session_time" "$status" "$snapshot_count" "$missing_components"
|
||
|
fi
|
||
|
|
||
|
((total_sessions++))
|
||
|
fi
|
||
|
done <<< "$session_tags"
|
||
|
|
||
|
echo "============================================================================="
|
||
|
echo "Total sessions: $total_sessions (Complete: $complete_sessions, Incomplete: $((total_sessions - complete_sessions)))"
|
||
|
}
|
||
|
|
||
|
# Function: Validate specific backup session
|
||
|
validate_backup_session() {
|
||
|
local session_tag="$1"
|
||
|
|
||
|
log_message "Validating backup session: $session_tag"
|
||
|
|
||
|
# Validate session tag format
|
||
|
if [[ ! "$session_tag" =~ ^(manual|automated)-backup-[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$ ]]; then
|
||
|
echo "ERROR: Invalid backup session tag format."
|
||
|
echo "Expected format: manual-backup-YYYY-MM-DD_HH-MM-SS or automated-backup-YYYY-MM-DD_HH-MM-SS"
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
local snapshots_json
|
||
|
snapshots_json=$(get_all_snapshots)
|
||
|
|
||
|
if [ $? -ne 0 ]; then
|
||
|
echo "Failed to retrieve snapshots."
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
# Check if session exists
|
||
|
local session_snapshots
|
||
|
session_snapshots=$(echo "$snapshots_json" | jq -r ".[] | select(.tags[] | contains(\"$session_tag\"))")
|
||
|
|
||
|
if [ -z "$session_snapshots" ]; then
|
||
|
echo "ERROR: No snapshots found for session tag '$session_tag'."
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
# Analyze session
|
||
|
local session_info
|
||
|
session_info=$(analyze_session_completeness "$snapshots_json" "$session_tag")
|
||
|
|
||
|
local status snapshot_count session_time missing_components
|
||
|
status=$(echo "$session_info" | cut -d'|' -f2)
|
||
|
snapshot_count=$(echo "$session_info" | cut -d'|' -f3)
|
||
|
session_time=$(echo "$session_info" | cut -d'|' -f4)
|
||
|
missing_components=$(echo "$session_info" | cut -d'|' -f5)
|
||
|
|
||
|
echo "=== BACKUP SESSION VALIDATION ==="
|
||
|
echo "Session Tag: $session_tag"
|
||
|
echo "Session Time: $session_time"
|
||
|
echo "Status: $status"
|
||
|
echo "Snapshot Count: $snapshot_count"
|
||
|
|
||
|
if [ "$status" = "COMPLETE" ]; then
|
||
|
echo "✓ All required components present:"
|
||
|
echo " ✓ Core Files (core_files)"
|
||
|
echo " ✓ Media Files (media_themes)"
|
||
|
echo " ✓ Database (wordpress_db)"
|
||
|
echo ""
|
||
|
echo "This session is ready for full restoration using:"
|
||
|
echo " ./restore_full_backup_session.sh $session_tag"
|
||
|
else
|
||
|
echo "✗ Session is incomplete. Missing components:"
|
||
|
IFS=',' read -ra MISSING <<< "$missing_components"
|
||
|
for component in "${MISSING[@]}"; do
|
||
|
echo " ✗ $component"
|
||
|
done
|
||
|
echo ""
|
||
|
echo "WARNING: This session cannot be used for full restoration."
|
||
|
fi
|
||
|
|
||
|
echo "================================="
|
||
|
|
||
|
# Show individual snapshots in session
|
||
|
echo ""
|
||
|
echo "Individual snapshots in this session:"
|
||
|
echo "$session_snapshots" | jq -r '" " + .short_id + " - " + .time + " - Tags: " + (.tags | join(", "))'
|
||
|
}
|
||
|
|
||
|
# Function: Validate environment
|
||
|
validate_environment() {
|
||
|
if [ ! -f "$password_file" ]; then
|
||
|
log_error "Password file not found at $password_file"
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
validate_repository
|
||
|
}
|
||
|
|
||
|
# Main script logic
|
||
|
main() {
|
||
|
local action="${1:-sessions}"
|
||
|
|
||
|
case "$action" in
|
||
|
"sessions")
|
||
|
validate_environment
|
||
|
display_complete_sessions
|
||
|
;;
|
||
|
"incomplete")
|
||
|
validate_environment
|
||
|
display_incomplete_sessions
|
||
|
;;
|
||
|
"all")
|
||
|
validate_environment
|
||
|
display_all_sessions
|
||
|
;;
|
||
|
"validate")
|
||
|
if [ -z "$2" ]; then
|
||
|
echo "ERROR: Session tag required for validation."
|
||
|
echo "Usage: $0 validate <session_tag>"
|
||
|
exit 1
|
||
|
fi
|
||
|
validate_environment
|
||
|
validate_backup_session "$2"
|
||
|
;;
|
||
|
*)
|
||
|
display_usage
|
||
|
;;
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
# Run main function
|
||
|
main "$@"
|