2026-01-31 08:33:19 +09:00
|
|
|
#!/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"
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 10:32:56 +09:00
|
|
|
create_mycnf() {
|
|
|
|
|
local tmp_cnf
|
|
|
|
|
tmp_cnf=$(mktemp /tmp/.my.cnf.XXXXXX)
|
|
|
|
|
chmod 600 "$tmp_cnf"
|
|
|
|
|
cat > "$tmp_cnf" <<MYCNF
|
|
|
|
|
[mysqldump]
|
|
|
|
|
user=${DB_USER}
|
|
|
|
|
password=${DB_PASS}
|
|
|
|
|
host=${DB_HOST}
|
|
|
|
|
port=${DB_PORT}
|
|
|
|
|
MYCNF
|
|
|
|
|
echo "$tmp_cnf"
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 08:33:19 +09:00
|
|
|
backup_database() {
|
|
|
|
|
local db_name="$1"
|
|
|
|
|
local output_file="${DAILY_DIR}/${db_name}_${TIMESTAMP}.sql.gz"
|
2026-02-03 10:32:56 +09:00
|
|
|
local mycnf
|
|
|
|
|
mycnf=$(create_mycnf)
|
2026-01-31 08:33:19 +09:00
|
|
|
|
|
|
|
|
log "INFO" "백업 시작: ${db_name}"
|
|
|
|
|
|
|
|
|
|
if mysqldump \
|
2026-02-03 10:32:56 +09:00
|
|
|
--defaults-extra-file="$mycnf" \
|
2026-01-31 08:33:19 +09:00
|
|
|
--single-transaction \
|
|
|
|
|
--routines \
|
|
|
|
|
--triggers \
|
|
|
|
|
--quick \
|
|
|
|
|
--lock-tables=false \
|
|
|
|
|
"$db_name" 2>>"$LOG_FILE" | gzip > "$output_file"; then
|
2026-02-03 10:32:56 +09:00
|
|
|
rm -f "$mycnf"
|
2026-01-31 08:33:19 +09:00
|
|
|
|
|
|
|
|
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
|
2026-02-03 10:32:56 +09:00
|
|
|
rm -f "$mycnf"
|
2026-01-31 08:33:19 +09:00
|
|
|
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" <<EOF
|
|
|
|
|
{
|
|
|
|
|
"last_run": "$(date '+%Y-%m-%dT%H:%M:%S%z' | sed 's/\(..\)$/:\1/')",
|
|
|
|
|
"status": "${status}",
|
|
|
|
|
"databases": ${databases_json},
|
|
|
|
|
"errors": ${errors_json}
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
log "INFO" "상태 파일 기록: ${status}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# 메인 실행
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
main() {
|
|
|
|
|
log "INFO" "========== SAM DB 백업 시작 =========="
|
|
|
|
|
|
|
|
|
|
ensure_dirs
|
|
|
|
|
|
|
|
|
|
local has_error=0
|
|
|
|
|
|
|
|
|
|
for db in $DATABASES; do
|
|
|
|
|
if ! backup_database "$db"; then
|
|
|
|
|
has_error=1
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
cleanup_old_backups
|
|
|
|
|
write_status_file
|
|
|
|
|
|
|
|
|
|
if [[ $has_error -eq 1 ]]; then
|
|
|
|
|
log "ERROR" "========== SAM DB 백업 완료 (일부 실패) =========="
|
|
|
|
|
exit 1
|
|
|
|
|
else
|
|
|
|
|
log "INFO" "========== SAM DB 백업 완료 (성공) =========="
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main "$@"
|