#!/usr/bin/env bash set -euo pipefail # Proxmox VE - PBS 백업 복구 스크립트 # PBS에서 VM/Container 백업을 쉽게 복구 # 색상 정의 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' NC='\033[0m' # No Color # 로그 함수 log_info() { echo -e "${BLUE}ℹ${NC} $1"; } log_success() { echo -e "${GREEN}✓${NC} $1"; } log_warning() { echo -e "${YELLOW}⚠${NC} $1"; } log_error() { echo -e "${RED}✗${NC} $1"; } log_step() { echo -e "${CYAN}➜${NC} $1"; } # 배너 출력 print_banner() { echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " PBS 백업 복구 스크립트" echo " Proxmox Backup Server Restore Tool" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" } # Root 권한 확인 check_root() { if [[ $EUID -ne 0 ]]; then log_error "이 스크립트는 root 권한이 필요합니다." exit 1 fi } # Proxmox VE 환경 확인 check_proxmox() { if ! command -v qmrestore &> /dev/null && ! command -v pct &> /dev/null; then log_error "Proxmox VE가 설치되어 있지 않습니다." exit 1 fi log_success "Proxmox VE 환경 확인 완료" } # PBS 스토리지 목록 가져오기 list_pbs_storages() { log_step "사용 가능한 PBS 스토리지 목록:" echo "" pvesm status --type pbs 2>/dev/null | tail -n +2 | awk '{print " • " $1}' echo "" } # PBS 스토리지에서 백업 목록 가져오기 list_backups() { local storage="$1" log_step "PBS 스토리지 '${storage}'의 백업 목록을 가져오는 중..." echo "" # 백업 목록 가져오기 local backups=$(pvesm list "$storage" 2>/dev/null | grep -E '\.vma|\.tar' || true) if [[ -z "$backups" ]]; then log_warning "백업을 찾을 수 없습니다." return 1 fi # 백업 목록 파싱 및 출력 echo "$backups" | while IFS= read -r line; do local volid=$(echo "$line" | awk '{print $1}') local type=$(echo "$line" | awk '{print $2}') local size=$(echo "$line" | awk '{print $3}') # VMID 추출 local vmid=$(echo "$volid" | grep -oP 'vm-\K[0-9]+' || echo "N/A") if [[ "$vmid" == "N/A" ]]; then vmid=$(echo "$volid" | grep -oP 'ct-\K[0-9]+' || echo "N/A") fi # 날짜 추출 시도 local date=$(echo "$volid" | grep -oP '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z' || echo "") echo -e "${CYAN}VMID:${NC} $vmid ${MAGENTA}Type:${NC} $type ${YELLOW}Size:${NC} $size" echo " Volume ID: $volid" if [[ -n "$date" ]]; then echo " Date: $date" fi echo "" done } # 특정 VMID의 백업 목록 가져오기 list_backups_by_vmid() { local storage="$1" local vmid="$2" log_step "VMID ${vmid}의 백업 목록을 검색 중..." echo "" local backups=$(pvesm list "$storage" 2>/dev/null | grep -E "vm-${vmid}-|ct-${vmid}-" || true) if [[ -z "$backups" ]]; then log_warning "VMID ${vmid}의 백업을 찾을 수 없습니다." return 1 fi local count=0 declare -g -A BACKUP_LIST echo "$backups" | while IFS= read -r line; do ((count++)) local volid=$(echo "$line" | awk '{print $1}') local type=$(echo "$line" | awk '{print $2}') local size=$(echo "$line" | awk '{print $3}') local date=$(echo "$volid" | grep -oP '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z' || echo "Unknown") BACKUP_LIST[$count]="$volid" echo -e "${GREEN}[$count]${NC} ${CYAN}Date:${NC} $date ${MAGENTA}Type:${NC} $type ${YELLOW}Size:${NC} $size" echo " Volume ID: $volid" echo "" done return 0 } # 백업 검색 (대화형) search_backups_interactive() { list_pbs_storages read -p "PBS 스토리지 이름을 입력하세요: " PBS_STORAGE if ! pvesm status --storage "$PBS_STORAGE" &> /dev/null; then log_error "스토리지 '${PBS_STORAGE}'를 찾을 수 없습니다." exit 1 fi echo "" read -p "복구할 VMID를 입력하세요 (모두 보려면 Enter): " SEARCH_VMID echo "" if [[ -n "$SEARCH_VMID" ]]; then list_backups_by_vmid "$PBS_STORAGE" "$SEARCH_VMID" else list_backups "$PBS_STORAGE" fi } # 백업 복구 (VM) restore_vm() { local volid="$1" local target_vmid="$2" local storage="$3" local options="$4" log_step "VM 백업 복구 시작..." log_info "Volume ID: $volid" log_info "Target VMID: $target_vmid" log_info "Storage: $storage" # 기본 복구 명령어 local cmd="qmrestore '$volid' $target_vmid" # 스토리지 지정 (선택사항) if [[ -n "$storage" ]]; then cmd+=" --storage $storage" fi # 추가 옵션 if [[ -n "$options" ]]; then cmd+=" $options" fi log_info "실행 명령어: $cmd" echo "" # 확인 read -p "복구를 진행하시겠습니까? (y/N): " confirm if [[ ! "$confirm" =~ ^[Yy]$ ]]; then log_warning "복구가 취소되었습니다." exit 0 fi # 복구 실행 echo "" log_step "복구 진행 중..." if eval "$cmd"; then log_success "VM 복구가 완료되었습니다!" log_info "VMID: $target_vmid" return 0 else log_error "VM 복구에 실패했습니다." return 1 fi } # 백업 복구 (Container) restore_container() { local volid="$1" local target_vmid="$2" local storage="$3" local options="$4" log_step "Container 백업 복구 시작..." log_info "Volume ID: $volid" log_info "Target VMID: $target_vmid" log_info "Storage: $storage" # 기본 복구 명령어 local cmd="pct restore $target_vmid '$volid'" # 스토리지 지정 (선택사항) if [[ -n "$storage" ]]; then cmd+=" --storage $storage" fi # 추가 옵션 if [[ -n "$options" ]]; then cmd+=" $options" fi log_info "실행 명령어: $cmd" echo "" # 확인 read -p "복구를 진행하시겠습니까? (y/N): " confirm if [[ ! "$confirm" =~ ^[Yy]$ ]]; then log_warning "복구가 취소되었습니다." exit 0 fi # 복구 실행 echo "" log_step "복구 진행 중..." if eval "$cmd"; then log_success "Container 복구가 완료되었습니다!" log_info "VMID: $target_vmid" return 0 else log_error "Container 복구에 실패했습니다." return 1 fi } # VMID 사용 가능 여부 확인 check_vmid_available() { local vmid="$1" if qm status "$vmid" &> /dev/null || pct status "$vmid" &> /dev/null; then log_error "VMID ${vmid}는 이미 사용 중입니다." log_warning "다른 VMID를 사용하거나 기존 VM/CT를 삭제하세요." return 1 fi return 0 } # 스토리지 목록 출력 list_storages() { log_step "사용 가능한 스토리지 목록:" echo "" pvesm status | tail -n +2 | awk '{print " • " $1 " (" $2 ")"}' echo "" } # 대화형 모드 interactive_mode() { echo "" log_info "PBS 백업 복구 - 대화형 모드" echo "" # PBS 스토리지 선택 list_pbs_storages read -p "PBS 스토리지 이름: " PBS_STORAGE if ! pvesm status --storage "$PBS_STORAGE" &> /dev/null; then log_error "스토리지 '${PBS_STORAGE}'를 찾을 수 없습니다." exit 1 fi # VMID 입력 echo "" read -p "복구할 원본 VMID: " SOURCE_VMID # 백업 목록 표시 echo "" if ! list_backups_by_vmid "$PBS_STORAGE" "$SOURCE_VMID"; then exit 1 fi # 백업 선택 read -p "복구할 백업 번호를 선택하세요: " backup_num # 백업 목록 다시 생성 (서브셸 문제 해결) local count=0 declare -A BACKUP_LIST_LOCAL while IFS= read -r line; do ((count++)) local volid=$(echo "$line" | awk '{print $1}') BACKUP_LIST_LOCAL[$count]="$volid" done < <(pvesm list "$PBS_STORAGE" 2>/dev/null | grep -E "vm-${SOURCE_VMID}-|ct-${SOURCE_VMID}-") SELECTED_VOLID="${BACKUP_LIST_LOCAL[$backup_num]}" if [[ -z "$SELECTED_VOLID" ]]; then log_error "잘못된 선택입니다." exit 1 fi # 타겟 VMID 입력 echo "" read -p "복구할 타겟 VMID (기본값: ${SOURCE_VMID}): " TARGET_VMID TARGET_VMID=${TARGET_VMID:-$SOURCE_VMID} # VMID 사용 가능 여부 확인 if ! check_vmid_available "$TARGET_VMID"; then read -p "새로운 VMID를 입력하세요: " TARGET_VMID if ! check_vmid_available "$TARGET_VMID"; then exit 1 fi fi # 스토리지 선택 echo "" list_storages read -p "복구할 스토리지 (Enter로 원본 스토리지 사용): " TARGET_STORAGE # VM 타입 판단 echo "" if [[ "$SELECTED_VOLID" =~ "vm-" ]]; then log_info "VM 백업으로 감지되었습니다." BACKUP_TYPE="vm" elif [[ "$SELECTED_VOLID" =~ "ct-" ]]; then log_info "Container 백업으로 감지되었습니다." BACKUP_TYPE="ct" else read -p "백업 타입 (vm/ct): " BACKUP_TYPE fi # 추가 옵션 echo "" log_info "추가 옵션 (선택사항)" read -p "추가 옵션 (예: --force): " EXTRA_OPTIONS # 복구 실행 echo "" if [[ "$BACKUP_TYPE" == "vm" ]]; then restore_vm "$SELECTED_VOLID" "$TARGET_VMID" "$TARGET_STORAGE" "$EXTRA_OPTIONS" else restore_container "$SELECTED_VOLID" "$TARGET_VMID" "$TARGET_STORAGE" "$EXTRA_OPTIONS" fi } # 사용법 usage() { cat << EOF 사용법: $0 [옵션] 대화형 모드 (권장): $0 리스트 모드: $0 -l -s PBS_STORAGE [-v VMID] 복구 모드: $0 -r -V VOLID -t TARGET_VMID [-S STORAGE] [-T TYPE] 옵션: -l 백업 목록 보기 -r 백업 복구 -s STORAGE PBS 스토리지 이름 -v VMID 검색할 VMID -V VOLID 복구할 Volume ID -t VMID 타겟 VMID -S STORAGE 복구할 스토리지 -T TYPE 백업 타입 (vm 또는 ct) -o OPTIONS 추가 옵션 -h 도움말 표시 예시: # 대화형 모드 $0 # PBS 스토리지의 모든 백업 보기 $0 -l -s pbs-backup # 특정 VMID의 백업 보기 $0 -l -s pbs-backup -v 100 # VM 백업 복구 $0 -r -V "pbs-backup:backup/vm-100-2025_01_28-10_00_00.vma.zst" \\ -t 100 -T vm # Container 복구 (다른 VMID로) $0 -r -V "pbs-backup:backup/ct-200-2025_01_28-10_00_00.tar.zst" \\ -t 201 -S local-lvm -T ct EOF exit 0 } # 메인 함수 main() { print_banner check_root check_proxmox # 옵션 없으면 대화형 모드 if [[ $# -eq 0 ]]; then interactive_mode exit 0 fi local MODE="" local PBS_STORAGE="" local VMID="" local VOLID="" local TARGET_VMID="" local TARGET_STORAGE="" local BACKUP_TYPE="" local EXTRA_OPTIONS="" # 옵션 파싱 while getopts "lrs:v:V:t:S:T:o:h" opt; do case $opt in l) MODE="list" ;; r) MODE="restore" ;; s) PBS_STORAGE="$OPTARG" ;; v) VMID="$OPTARG" ;; V) VOLID="$OPTARG" ;; t) TARGET_VMID="$OPTARG" ;; S) TARGET_STORAGE="$OPTARG" ;; T) BACKUP_TYPE="$OPTARG" ;; o) EXTRA_OPTIONS="$OPTARG" ;; h) usage ;; *) usage ;; esac done # 모드별 실행 case "$MODE" in list) if [[ -z "$PBS_STORAGE" ]]; then log_error "PBS 스토리지를 지정하세요 (-s 옵션)" exit 1 fi if [[ -n "$VMID" ]]; then list_backups_by_vmid "$PBS_STORAGE" "$VMID" else list_backups "$PBS_STORAGE" fi ;; restore) if [[ -z "$VOLID" ]] || [[ -z "$TARGET_VMID" ]]; then log_error "Volume ID(-V)와 타겟 VMID(-t)를 지정하세요" exit 1 fi if ! check_vmid_available "$TARGET_VMID"; then exit 1 fi # 타입 자동 감지 if [[ -z "$BACKUP_TYPE" ]]; then if [[ "$VOLID" =~ "vm-" ]]; then BACKUP_TYPE="vm" elif [[ "$VOLID" =~ "ct-" ]]; then BACKUP_TYPE="ct" else log_error "백업 타입을 지정하세요 (-T vm 또는 -T ct)" exit 1 fi fi if [[ "$BACKUP_TYPE" == "vm" ]]; then restore_vm "$VOLID" "$TARGET_VMID" "$TARGET_STORAGE" "$EXTRA_OPTIONS" else restore_container "$VOLID" "$TARGET_VMID" "$TARGET_STORAGE" "$EXTRA_OPTIONS" fi ;; *) usage ;; esac } # 스크립트 실행 main "$@"