pve9-repo-fix/pbs_auto_restore_103_to_203.sh
Claude 59a10f48e5 Fix bash syntax error in pipe execution
read 명령의 파이프 호환성 문제 수정:
- `< /dev/tty` 제거 (파이프 환경에서 오류 발생)
- `-p` 옵션을 별도 echo로 분리
- 에러 출력 억제로 안정성 향상

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 14:20:02 +00:00

472 lines
14 KiB
Bash
Executable File

#!/bin/bash
#
# PBS 자동 복구 스크립트 - VM 103 → 203
# 작성일: 2025-11-21
# 용도: PBS 등록 → VM 103 백업 자동 복구 → VM 203으로 설치
#
set -e
# 색상
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'
# PBS 서버 설정 (자동 입력)
PBS_SERVER="100.64.0.10"
PBS_PORT="8007"
PBS_USERNAME="0bin@pbs"
PBS_PASSWORD="@Trajet6640"
PBS_DATASTORE="PBS-DVA"
PBS_FINGERPRINT="24:42:c6:0f:a8:1b:93:32:32:44:84:be:6a:c5:71:97:e4:4d:61:fc:a4:48:12:0c:97:3b:9f:1f:cc:b2:54:e8"
PBS_STORAGE_NAME="PBS-Auto"
# 고정 복구 설정
SOURCE_VMID="103" # 백업 소스 VM ID
TARGET_VMID="203" # 복구할 VM ID
TARGET_STORAGE="local-lvm" # 저장 스토리지
BACKUP_TYPE="vm" # 백업 타입 (vm 고정)
LATEST_SNAPSHOT="" # 최신 스냅샷 (자동 조회)
# 로그 함수
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${CYAN}╰─► ${NC}$1"; }
# 배너
print_banner() {
clear
echo -e "${MAGENTA}"
cat << "EOF"
╔═══════════════════════════════════════════════════════════════╗
║ ║
║ PBS 자동 복구 스크립트 ║
║ VM 103 → VM 203 자동 복구 ║
║ ║
╚═══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
echo ""
}
# Proxmox 환경 확인
check_proxmox() {
if [ ! -f /etc/pve/storage.cfg ]; then
log_error "Proxmox VE가 설치되어 있지 않습니다."
exit 1
fi
log_success "Proxmox VE 환경 확인 완료"
}
# PBS 스토리지 등록
register_pbs() {
log_step "PBS 스토리지 등록 중..."
# 이미 등록되어 있는지 확인
if pvesm status | grep -q "^${PBS_STORAGE_NAME} "; then
log_warning "PBS 스토리지가 이미 등록되어 있습니다."
log_info "기존 PBS 스토리지 제거 중..."
pvesm remove "${PBS_STORAGE_NAME}" 2>/dev/null || true
sleep 1
fi
# PBS 등록
log_info "PBS 서버 등록 중..."
pvesm add pbs "${PBS_STORAGE_NAME}" \
--server "${PBS_SERVER}" \
--port "${PBS_PORT}" \
--datastore "${PBS_DATASTORE}" \
--username "${PBS_USERNAME}" \
--password "${PBS_PASSWORD}" \
--fingerprint "${PBS_FINGERPRINT}" \
--namespace "PQ" 2>&1
if [ $? -eq 0 ]; then
log_success "PBS 스토리지 등록 완료!"
else
log_error "PBS 스토리지 등록 실패"
return 1
fi
# 연결 테스트
log_info "PBS 연결 테스트 중..."
if pvesm status -storage "${PBS_STORAGE_NAME}" &>/dev/null; then
log_success "PBS 연결 성공!"
echo ""
pvesm status -storage "${PBS_STORAGE_NAME}"
echo ""
else
log_error "PBS 연결 테스트 실패"
return 1
fi
}
# PBS API 인증
pbs_login() {
log_info "PBS API 인증 중..."
curl -k -s -X POST "https://${PBS_SERVER}:${PBS_PORT}/api2/json/access/ticket" \
-d "username=${PBS_USERNAME}" \
-d "password=${PBS_PASSWORD}" > /tmp/pbs_auth.json
if [ ! -s /tmp/pbs_auth.json ]; then
log_error "PBS API 응답 없음. 서버 연결을 확인하세요."
return 1
fi
PBS_TICKET=$(python3 -c 'import json; data=json.load(open("/tmp/pbs_auth.json")); print(data["data"]["ticket"])' 2>/dev/null)
PBS_CSRF=$(python3 -c 'import json; data=json.load(open("/tmp/pbs_auth.json")); print(data["data"]["CSRFPreventionToken"])' 2>/dev/null)
if [ -z "$PBS_TICKET" ]; then
log_error "PBS API 인증 실패. 응답:"
cat /tmp/pbs_auth.json | head -5
return 1
fi
log_success "PBS API 인증 완료"
}
# VM 103 백업 확인
check_backup_exists() {
log_step "VM ${SOURCE_VMID} 백업 확인 중..."
curl -k -s -X GET "https://${PBS_SERVER}:${PBS_PORT}/api2/json/admin/datastore/${PBS_DATASTORE}/groups?ns=PQ" \
-H "Cookie: PBSAuthCookie=${PBS_TICKET}" \
-H "CSRFPreventionToken: ${PBS_CSRF}" > /tmp/pbs_groups.json
# VM 103 백업이 있는지 확인
local has_backup=$(python3 << PYEOF
import sys, json
try:
with open('/tmp/pbs_groups.json', 'r') as f:
data = json.load(f)
groups = data.get("data", [])
for group in groups:
if group.get("backup-type") == "vm" and group.get("backup-id") == "${SOURCE_VMID}":
backup_count = group.get("backup-count", 0)
last_backup = group.get("last-backup", 0)
from datetime import datetime
if last_backup > 0:
last_str = datetime.fromtimestamp(last_backup).strftime("%Y-%m-%d %H:%M")
else:
last_str = "N/A"
print(f"VM {group.get('backup-id')} - {backup_count}개 백업 (최근: {last_str})")
sys.exit(0)
print("")
sys.exit(1)
except Exception as e:
print("")
sys.exit(1)
PYEOF
)
if [ $? -eq 0 ]; then
log_success "백업 발견: ${has_backup}"
return 0
else
log_error "VM ${SOURCE_VMID} 백업을 찾을 수 없습니다"
return 1
fi
}
# 최신 스냅샷 조회
get_latest_snapshot() {
log_info "최신 백업 조회 중..."
curl -k -s -X GET "https://${PBS_SERVER}:${PBS_PORT}/api2/json/admin/datastore/${PBS_DATASTORE}/snapshots?backup-type=${BACKUP_TYPE}&backup-id=${SOURCE_VMID}&ns=PQ" \
-H "Cookie: PBSAuthCookie=${PBS_TICKET}" \
-H "CSRFPreventionToken: ${PBS_CSRF}" > /tmp/pbs_snapshots.json
LATEST_SNAPSHOT=$(python3 << 'PYEOF'
import sys, json
try:
with open('/tmp/pbs_snapshots.json', 'r') as f:
data = json.load(f)
snapshots = data.get("data", [])
if not snapshots:
print("")
sys.exit(1)
# 가장 최근 백업 선택
latest = max(snapshots, key=lambda x: x.get("backup-time", 0))
backup_time = latest.get("backup-time", 0)
from datetime import datetime
backup_time_str = datetime.utcfromtimestamp(backup_time).strftime("%Y-%m-%dT%H:%M:%SZ")
print(backup_time_str)
except Exception as e:
print("", file=sys.stderr)
sys.exit(1)
PYEOF
)
if [ -z "$LATEST_SNAPSHOT" ]; then
log_error "최신 백업을 찾을 수 없습니다"
return 1
fi
log_success "최신 백업: ${LATEST_SNAPSHOT}"
return 0
}
# 스토리지 확인
check_storage() {
log_step "저장 스토리지 확인 중..."
if pvesm status -storage "${TARGET_STORAGE}" &>/dev/null; then
# VM 이미지를 저장할 수 있는지 확인
if pvesm status -storage "${TARGET_STORAGE}" -content images &>/dev/null; then
log_success "스토리지 '${TARGET_STORAGE}' 확인 완료"
# 스토리지 정보 표시
local storage_info=$(pvesm status -storage "${TARGET_STORAGE}" 2>/dev/null | tail -n +2)
local total_mb=$(echo "$storage_info" | awk '{print $4}')
local avail_mb=$(echo "$storage_info" | awk '{print $6}')
local usage_pct=$(echo "$storage_info" | awk '{print $7}')
if [ -n "$total_mb" ]; then
local total_gb=$(echo "scale=2; $total_mb / 1024 / 1024" | bc 2>/dev/null || echo "N/A")
local avail_gb=$(echo "scale=2; $avail_mb / 1024 / 1024" | bc 2>/dev/null || echo "N/A")
log_info "스토리지 정보: ${total_gb}GB 총용량 | ${avail_gb}GB 여유 | ${usage_pct} 사용률"
fi
return 0
else
log_error "스토리지 '${TARGET_STORAGE}'는 VM 이미지를 저장할 수 없습니다"
return 1
fi
else
log_error "스토리지 '${TARGET_STORAGE}'를 찾을 수 없습니다"
return 1
fi
}
# 복구 설정 확인
show_restore_config() {
echo ""
log_info "복구 설정:"
echo " 백업 소스: VM ${SOURCE_VMID}"
echo " 백업 시점: ${LATEST_SNAPSHOT}"
echo " 복구 ID: VM ${TARGET_VMID}"
echo " 저장 위치: ${TARGET_STORAGE}"
echo ""
}
# VM 복구
restore_backup() {
log_step "백업 복구 중... (시간이 걸릴 수 있습니다)"
echo ""
# 백업 경로 구성
BACKUP_PATH="${PBS_STORAGE_NAME}:backup/${BACKUP_TYPE}/${SOURCE_VMID}/${LATEST_SNAPSHOT}"
log_info "백업 경로: ${BACKUP_PATH}"
# 기존 VM 확인
if qm status ${TARGET_VMID} 2>/dev/null; then
log_warning "VM ${TARGET_VMID}가 이미 존재합니다"
log_info "기존 VM 삭제 중..."
qm stop ${TARGET_VMID} --skiplock 2>/dev/null || true
sleep 2
qm destroy ${TARGET_VMID} --purge --skiplock 2>/dev/null || true
sleep 2
log_success "기존 VM 삭제 완료"
fi
# 복구 실행
log_info "VM 복구 시작..."
echo ""
qmrestore "${BACKUP_PATH}" ${TARGET_VMID} \
--storage ${TARGET_STORAGE} \
--unique 1 2>&1 | tee /tmp/pbs-restore.log
if [ ${PIPESTATUS[0]} -eq 0 ]; then
echo ""
log_success "VM 복구 완료!"
return 0
else
echo ""
log_error "VM 복구 실패. 로그: /tmp/pbs-restore.log"
return 1
fi
}
# VM 자동 시작 (선택)
start_vm() {
echo ""
log_info "VM ${TARGET_VMID}를 바로 시작하시겠습니까?"
echo -e "${YELLOW}10초 내 입력하세요 (y/N):${NC} "
if read -t 10 start_confirm 2>/dev/null; then
:
else
start_confirm="N"
fi
echo ""
if [[ "$start_confirm" =~ ^[Yy]$ ]]; then
log_info "VM 시작 중..."
qm start ${TARGET_VMID}
if [ $? -eq 0 ]; then
log_success "VM 시작 완료!"
sleep 3
log_info "상태 확인 중..."
qm status ${TARGET_VMID}
else
log_error "시작 실패"
return 1
fi
else
log_info "VM을 수동으로 시작하세요: qm start ${TARGET_VMID}"
fi
}
# 완료 메시지
print_summary() {
echo ""
echo -e "${GREEN}"
cat << "EOF"
╔═══════════════════════════════════════════════════════════════╗
║ ║
║ ✅ 복구 완료! ║
║ ║
╚═══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
echo ""
log_info "복구 정보:"
echo " 백업 소스: VM ${SOURCE_VMID}"
echo " 백업 시점: ${LATEST_SNAPSHOT}"
echo " 복구 ID: VM ${TARGET_VMID}"
echo " 스토리지: ${TARGET_STORAGE}"
echo ""
log_info "Proxmox Web UI:"
local pve_ip=$(hostname -I | awk '{print $1}')
echo " https://${pve_ip}:8006"
echo ""
log_info "유용한 명령어:"
echo " VM 상태: qm status ${TARGET_VMID}"
echo " VM 시작: qm start ${TARGET_VMID}"
echo " VM 중지: qm stop ${TARGET_VMID}"
echo " VM 정보: qm config ${TARGET_VMID}"
echo ""
log_info "PBS 스토리지:"
echo " 이름: ${PBS_STORAGE_NAME}"
echo " 제거: pvesm remove ${PBS_STORAGE_NAME}"
echo ""
}
# 메인 실행
main() {
print_banner
# Proxmox 환경 확인
check_proxmox
echo ""
# PBS 스토리지 등록
register_pbs || exit 1
echo ""
# PBS API 인증
pbs_login || exit 1
echo ""
# VM 103 백업 확인
check_backup_exists || exit 1
echo ""
# 최신 스냅샷 조회
get_latest_snapshot || exit 1
echo ""
# 스토리지 확인
check_storage || exit 1
echo ""
# 복구 설정 표시
show_restore_config
# 자동 복구 시작 (3초 대기)
log_warning "3초 후 자동으로 복구를 시작합니다..."
sleep 3
echo ""
# 백업 복구
restore_backup || exit 1
echo ""
# VM 시작 (선택)
start_vm
echo ""
# 완료 메시지
print_summary
}
# 도움말
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "PBS 자동 복구 스크립트 - VM 103 → VM 203"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "사용법: $0"
echo ""
echo "이 스크립트는:"
echo " 1. PBS 스토리지를 자동 등록"
echo " 2. VM 103 백업 자동 조회"
echo " 3. VM 203으로 자동 복구"
echo ""
echo "고정 설정:"
echo " 소스 VM ID: 103"
echo " 복구 VM ID: 203"
echo " 스토리지: local-lvm"
echo " PBS 서버: 100.64.0.10"
echo ""
echo "실행 예시:"
echo " bash $0"
echo ""
echo "사전 준비:"
echo " - Proxmox VE 설치 완료"
echo " - PBS 서버 (100.64.0.10) 접근 가능"
echo " - python3 설치 (보통 기본 설치됨)"
echo ""
echo "모든 설정이 자동화되어 있어 사용자 입력이 최소화됩니다!"
echo ""
exit 0
fi
# Root 권한 확인
if [ "$EUID" -ne 0 ]; then
log_error "이 스크립트는 root 권한이 필요합니다."
log_info "다음 명령으로 실행하세요: sudo $0"
exit 1
fi
# 스크립트 실행
main