52c75d7a / scripts /utils /restic-restore.sh
autoface's picture
Add restic auto-recovery feature and backup service conflict checking
cf5db51
#!/bin/bash
set -e
# Restic Data Restore Script for HuggingFace Environment
# Simplified version: Focus on core restore functionality, directly use latest snapshot
# Logging functions
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [RESTIC-RESTORE] $*"
}
log_success() {
echo -e "\033[32m[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] $*\033[0m"
}
log_error() {
echo -e "\033[31m[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*\033[0m"
}
log_warn() {
echo -e "\033[33m[$(date '+%Y-%m-%d %H:%M:%S')] [WARNING] $*\033[0m"
}
log_info() {
echo -e "\033[36m[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*\033[0m"
}
# Display usage instructions
show_usage() {
cat << EOF
Restic Data Restore Script (Simplified - Only supports latest snapshot)
Usage: $0 [options]
Options:
-h, --help Show this help message
-l, --list List available snapshots
-c, --check Check repository integrity
-r, --repo PATH Specify repository path (default: \$RESTIC_REPOSITORY_PATH)
-p, --password-file FILE Specify password file (default: \$HOME/config/restic-password)
-t, --target PATH Specify restore target path (default: /tmp/restic_restore)
-m, --mode MODE Restore mode:
- safe: Safe mode, restore to specified directory (default)
- direct: Direct mode, restore directly to target location
- replace: Replace mode, clear target directory before restore
-i, --include PATTERN Only restore matching paths (can be used multiple times)
-e, --exclude PATTERN Exclude matching paths (can be used multiple times)
-v, --verbose Verbose output
-f, --force Force execution without confirmation
--dry-run Dry run, don't perform actual restore
Note: This version only supports restoring the latest snapshot, simplifying complex snapshot ID handling logic
Examples:
# List available snapshots
$0 --list
# Safe restore latest snapshot to default temp directory
$0
# Direct restore to specified location
$0 --mode direct --target /home/user
# Full replacement mode (clear target directory first)
$0 --mode replace --target /home/user
# Only restore config files
$0 --include "/home/user/config"
Environment Variables:
RESTIC_REPOSITORY_PATH Restic repository path
RESTIC_PASSWORD Restic password
EOF
}
# Check if restic is installed
check_restic() {
if ! command -v restic &> /dev/null; then
log_error "restic is not installed, please install restic first"
return 1
fi
return 0
}
# Check and set environment variables
setup_environment() {
# Set default values
USER_HOME="${HOME:-/home/user}"
REPO_PATH="${RESTIC_REPOSITORY_PATH:-$USER_HOME/data/restic-repo}"
PASSWORD_FILE="${PASSWORD_FILE:-$USER_HOME/config/restic-password}"
TARGET_PATH="${TARGET_PATH:-/tmp/restic_restore_$(date +%s)}"
RESTORE_MODE="${RESTORE_MODE:-safe}"
# Check repository path (skip local path check for S3/remote repositories)
if [[ ! "$REPO_PATH" =~ ^(s3:|sftp:|rest:|rclone:) && ! -d "$REPO_PATH" ]]; then
log_warn "Local Restic repository does not exist: $REPO_PATH"
log_warn "This might be the first startup, repository not yet initialized"
log_warn "Please check RESTIC_REPOSITORY_PATH environment variable or use -r option"
return 1
fi
# Set password
if [[ -n "$RESTIC_PASSWORD" ]]; then
export RESTIC_PASSWORD
elif [[ -f "$PASSWORD_FILE" ]]; then
export RESTIC_PASSWORD="$(cat "$PASSWORD_FILE")"
log_info "Reading password from file: $PASSWORD_FILE"
else
log_error "Password not found, please set RESTIC_PASSWORD environment variable or password file: $PASSWORD_FILE"
return 1
fi
# Set repository path
export RESTIC_REPOSITORY="$REPO_PATH"
log_info "Environment configuration:"
log_info " Repository path: $REPO_PATH"
log_info " Target path: $TARGET_PATH"
log_info " Restore mode: $RESTORE_MODE"
log_info " Snapshot: latest (fixed to use latest snapshot)"
return 0
}
# Check repository status
check_repository() {
log_info "Checking repository status..."
if ! restic snapshots > /dev/null 2>&1; then
log_warn "Cannot access Restic repository, please check repository path and password"
log_warn "This might be the first startup or repository not yet initialized"
return 1
fi
log_success "Repository status normal"
return 0
}
# List snapshots
list_snapshots() {
log_info "Available snapshots list:"
restic snapshots --compact
}
# Perform restore - directly use latest snapshot
perform_restore() {
local target_path="$1"
log_info "Restoring latest snapshot to: $target_path"
# Simplified content restore logic
log_info "Analyzing latest snapshot backup paths, preparing for content restore..."
# Get original backup paths from latest snapshot metadata
local backup_paths=()
local snapshot_info
if snapshot_info=$(restic snapshots --json --latest 1 2>/dev/null); then
local paths_json
paths_json=$(echo "$snapshot_info" | jq -r '.[0].paths[]?' 2>/dev/null)
if [[ -n "$paths_json" ]]; then
while IFS= read -r path; do
[[ -n "$path" ]] && backup_paths+=("$path")
done <<< "$paths_json"
log_info "Detected backup paths: ${backup_paths[*]}"
fi
fi
# Build restore parameters
local snapshot_target="latest"
if [[ ${#backup_paths[@]} -gt 0 ]]; then
# Use first backup path as content root directory
local content_root="${backup_paths[0]}"
log_info "Using content restore mode, only restore $content_root contents to $target_path"
# Modify snapshot to latest:subfolder format
snapshot_target="latest:$content_root"
log_info "Converting to content restore: $snapshot_target"
else
log_warn "Cannot determine backup paths, using traditional full path restore"
fi
# Replace mode: clear target directory
if [[ "$RESTORE_MODE" == "replace" ]]; then
log_info "Replace mode: preparing to clear target directory..."
if [[ -d "$target_path" && -n "$(ls -A "$target_path" 2>/dev/null)" ]]; then
log_warn "Replace mode will clear target directory: $target_path"
if [[ "$FORCE" != "true" ]]; then
read -p "Confirm clear target directory and restore? (y/N): " -n 1 -r
echo
[[ ! $REPLY =~ ^[Yy]$ ]] && { log_info "User cancelled operation"; return 1; }
fi
if [[ "$DRY_RUN" == "true" ]]; then
log_info "Dry run mode: will clear $target_path"
else
log_info "Clearing target directory: $target_path"
# Safety check: prevent deletion of important system directories
if [[ "$target_path" =~ ^/(bin|sbin|usr|lib|lib64|etc|proc|sys|dev|boot)$ ]]; then
log_error "Refusing to clear system directory: $target_path"
return 1
fi
# Clear directory contents but keep directory itself
find "$target_path" -mindepth 1 -delete 2>/dev/null || {
# If find fails, try deleting one by one
for item in "$target_path"/{*,.[^.]*}; do
[[ -e "$item" ]] && rm -rf "$item"
done
}
log_success "Target directory cleared: $target_path"
fi
else
log_info "Target directory is empty or does not exist, skipping clear: $target_path"
fi
fi
# If direct mode and target path has content, give warning
if [[ "$RESTORE_MODE" == "direct" && -d "$target_path" && -n "$(ls -A "$target_path" 2>/dev/null)" ]]; then
log_warn "Target path is not empty, may overwrite existing files: $target_path"
if [[ "$FORCE" != "true" ]]; then
read -p "Continue? (y/N): " -n 1 -r
echo
[[ ! $REPLY =~ ^[Yy]$ ]] && { log_info "User cancelled operation"; return 1; }
fi
fi
# Create target directory
mkdir -p "$target_path"
# Build restore command arguments
local args=("restore" "$snapshot_target" "--target" "$target_path")
# Add include/exclude options
for pattern in "${INCLUDE_PATTERNS[@]}"; do
args+=("--include" "$pattern")
done
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
args+=("--exclude" "$pattern")
done
[[ "$VERBOSE" == "true" ]] && args+=("--verbose")
log_info "Executing restore command: restic ${args[*]}"
if [[ "$DRY_RUN" == "true" ]]; then
log_info "Dry run mode, not performing actual restore"
return 0
fi
if restic "${args[@]}"; then
log_success "Restore completed: $target_path"
# Give specific next steps based on mode
if [[ "$RESTORE_MODE" == "safe" ]]; then
log_info "Safe mode: Data restored to safe directory, please check and manually copy to final location"
log_info "Example: rsync -av $target_path/ /desired/location/"
elif [[ "$RESTORE_MODE" == "direct" ]]; then
log_info "Direct mode: Data restored directly to specified directory"
elif [[ "$RESTORE_MODE" == "replace" ]]; then
log_info "Replace mode: Target directory intelligently cleared and data restored"
fi
return 0
else
log_error "Restore failed"
return 1
fi
}
# Main function
main() {
local action="restore"
# Initialize arrays
INCLUDE_PATTERNS=()
EXCLUDE_PATTERNS=()
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-l|--list)
action="list"
shift
;;
-c|--check)
action="check"
shift
;;
-r|--repo)
RESTIC_REPOSITORY_PATH="$2"
shift 2
;;
-p|--password-file)
PASSWORD_FILE="$2"
shift 2
;;
-t|--target)
TARGET_PATH="$2"
shift 2
;;
-m|--mode)
RESTORE_MODE="$2"
if [[ "$RESTORE_MODE" != "safe" && "$RESTORE_MODE" != "direct" && "$RESTORE_MODE" != "replace" ]]; then
log_error "Invalid restore mode: $RESTORE_MODE (only supports: safe, direct, replace)"
exit 1
fi
shift 2
;;
-i|--include)
INCLUDE_PATTERNS+=("$2")
shift 2
;;
-e|--exclude)
EXCLUDE_PATTERNS+=("$2")
shift 2
;;
-v|--verbose)
VERBOSE="true"
shift
;;
-f|--force)
FORCE="true"
shift
;;
--dry-run)
DRY_RUN="true"
shift
;;
-*)
log_error "Unknown option: $1"
show_usage
exit 1
;;
*)
log_warn "Ignoring argument: $1 (this version only supports latest snapshot)"
shift
;;
esac
done
# Check restic
if ! check_restic; then
exit 1
fi
# Setup environment
if ! setup_environment; then
exit 1
fi
# Check repository
if ! check_repository; then
exit 1
fi
# Execute corresponding operation
case $action in
list)
list_snapshots
;;
check)
log_info "Performing repository integrity check..."
if restic check; then
log_success "Repository integrity check passed"
else
log_error "Repository integrity check failed"
exit 1
fi
;;
restore)
# Execute restore directly, using latest snapshot
perform_restore "$TARGET_PATH"
;;
esac
}
# Script entry point
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi