File size: 12,716 Bytes
cf5db51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
#!/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