#!/usr/bin/env bash # ============================================================================= # SAM DB Backup Script # ============================================================================= # 용도: MySQL 데이터베이스 백업 (mysqldump + gzip) # 실행: crontab에서 매일 04:30 실행 # 30 4 * * * /home/webservice/api/scripts/backup/sam-db-backup.sh # ============================================================================= set -euo pipefail # 스크립트 경로 기준으로 설정 파일 로드 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONF_FILE="${SCRIPT_DIR}/backup.conf" if [[ ! -f "$CONF_FILE" ]]; then echo "[FATAL] 설정 파일 없음: $CONF_FILE" >&2 exit 1 fi # shellcheck source=backup.conf.example source "$CONF_FILE" # ============================================================================= # 변수 초기화 # ============================================================================= TODAY=$(date +%Y-%m-%d) TIMESTAMP=$(date +%Y%m%d_%H%M) DAY_OF_WEEK=$(date +%u) # 1=월 ~ 7=일 DAILY_DIR="${BACKUP_BASE_DIR}/daily/${TODAY}" WEEKLY_DIR="${BACKUP_BASE_DIR}/weekly" LOG_DIR=$(dirname "$LOG_FILE") ERRORS=() DB_RESULTS=() # ============================================================================= # 함수 정의 # ============================================================================= log() { local level="$1" shift echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE" } ensure_dirs() { mkdir -p "$DAILY_DIR" "$WEEKLY_DIR" "$LOG_DIR" } create_mycnf() { local tmp_cnf tmp_cnf=$(mktemp /tmp/.my.cnf.XXXXXX) chmod 600 "$tmp_cnf" cat > "$tmp_cnf" <>"$LOG_FILE" | gzip > "$output_file"; then rm -f "$mycnf" local file_size file_size=$(stat -f%z "$output_file" 2>/dev/null || stat -c%s "$output_file" 2>/dev/null || echo 0) # 최소 크기 검증 local min_size_var="MIN_SIZE_$(echo "$db_name" | tr '[:lower:]' '[:upper:]')" local min_size="${!min_size_var:-0}" if [[ "$file_size" -lt "$min_size" ]]; then log "ERROR" "백업 파일 크기 부족: ${db_name} (${file_size} bytes < ${min_size} bytes)" ERRORS+=("${db_name}: 파일 크기 부족 (${file_size} < ${min_size})") DB_RESULTS+=("{\"db\":\"${db_name}\",\"file\":\"$(basename "$output_file")\",\"size_bytes\":${file_size},\"status\":\"size_error\"}") return 1 fi log "INFO" "백업 완료: ${db_name} (${file_size} bytes)" DB_RESULTS+=("{\"db\":\"${db_name}\",\"file\":\"$(basename "$output_file")\",\"size_bytes\":${file_size},\"status\":\"success\"}") # 일요일이면 weekly 복사 if [[ "$DAY_OF_WEEK" -eq 7 ]]; then local weekly_file="${WEEKLY_DIR}/${db_name}_${TIMESTAMP}_week.sql.gz" cp "$output_file" "$weekly_file" log "INFO" "주간 백업 복사: ${db_name}" fi return 0 else rm -f "$mycnf" log "ERROR" "mysqldump 실패: ${db_name}" ERRORS+=("${db_name}: mysqldump 실패") DB_RESULTS+=("{\"db\":\"${db_name}\",\"file\":\"\",\"size_bytes\":0,\"status\":\"dump_error\"}") rm -f "$output_file" return 1 fi } cleanup_old_backups() { log "INFO" "오래된 백업 정리 시작" # daily: DAILY_RETENTION_DAYS일 초과 디렉토리 삭제 if [[ -d "${BACKUP_BASE_DIR}/daily" ]]; then find "${BACKUP_BASE_DIR}/daily" -mindepth 1 -maxdepth 1 -type d -mtime +"$DAILY_RETENTION_DAYS" -exec rm -rf {} \; 2>>"$LOG_FILE" local daily_deleted=$? log "INFO" "일간 백업 정리 완료 (${DAILY_RETENTION_DAYS}일 초과 삭제)" fi # weekly: WEEKLY_RETENTION_DAYS일 초과 파일 삭제 if [[ -d "$WEEKLY_DIR" ]]; then find "$WEEKLY_DIR" -type f -name "*.sql.gz" -mtime +"$WEEKLY_RETENTION_DAYS" -delete 2>>"$LOG_FILE" log "INFO" "주간 백업 정리 완료 (${WEEKLY_RETENTION_DAYS}일 초과 삭제)" fi } write_status_file() { local status="success" local errors_json="[]" if [[ ${#ERRORS[@]} -gt 0 ]]; then status="failure" # 에러 배열을 JSON 배열로 변환 errors_json="[" for i in "${!ERRORS[@]}"; do [[ $i -gt 0 ]] && errors_json+="," errors_json+="\"${ERRORS[$i]}\"" done errors_json+="]" fi # databases 객체 구성 local databases_json="{" for i in "${!DB_RESULTS[@]}"; do [[ $i -gt 0 ]] && databases_json+="," local result="${DB_RESULTS[$i]}" local db_name db_name=$(echo "$result" | sed 's/.*"db":"\([^"]*\)".*/\1/') local file_name file_name=$(echo "$result" | sed 's/.*"file":"\([^"]*\)".*/\1/') local size size=$(echo "$result" | sed 's/.*"size_bytes":\([0-9]*\).*/\1/') databases_json+="\"${db_name}\":{\"file\":\"${file_name}\",\"size_bytes\":${size}}" done databases_json+="}" cat > "$STATUS_FILE" <