운영 DB 백업 스크립트
- 백업용 DB 계정 생성: backup
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# backup_db.sh
|
||||
# ------------------------------------------------------------
|
||||
# chocomae 운영 DB(database 전체) 를 gzip 압축 덤프로 저장하고,
|
||||
# 4주(28일) 초과 백업 파일은 자동 정리한다.
|
||||
#
|
||||
# 실행 위치 : Synology NAS (DSM Task Scheduler)
|
||||
# 실행 주기 : 매일 1회 (트래픽 낮은 새벽 권장)
|
||||
#
|
||||
# 자격증명은 --defaults-extra-file 로 참조하는 옵션 파일에서 읽어옵니다.
|
||||
# (아래 "1회 초기 설정" 참고)
|
||||
#
|
||||
# 1회 초기 설정:
|
||||
# 1) chmod +x backup_db.sh
|
||||
# 2) 자격증명 파일 생성 (CREDENTIALS_FILE 경로에):
|
||||
# [client]
|
||||
# user=chocomae_backup
|
||||
# password=여기에_비밀번호
|
||||
# host=chocomae.jinaju.com
|
||||
# port=3306
|
||||
# * 백업 전용 DB 계정을 만들어 그 자격증명만 저장할 것을 권장.
|
||||
# (필요 권한 예: SELECT, LOCK TABLES, EVENT, TRIGGER, PROCESS, SHOW VIEW)
|
||||
# 3) chmod 600 <CREDENTIALS_FILE> # 매우 중요 (권한 검사에 걸림)
|
||||
# 4) DSM > 제어판 > 작업 스케줄러 > 사용자 정의 스크립트로 등록,
|
||||
# "매일 <원하는 시각>" 스케줄 설정.
|
||||
#
|
||||
# 로그: OUTPUT_DIR/backup.log 에 append 됩니다.
|
||||
# ------------------------------------------------------------
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 배포된 스크립트가 최신인지 로그로 확인할 수 있게 버전 문자열을 남김.
|
||||
# 코드 변경 시 이 값을 손으로 올린 뒤 배포.
|
||||
SCRIPT_VERSION="2026-09-07.1"
|
||||
|
||||
# 새로 만드는 파일이 세계-읽기가 되지 않도록 umask 를 잠금.
|
||||
# (Synology 공유 폴더는 상위 디렉터리 ACL 로 777 이 상속되는 경우가 있음)
|
||||
umask 077
|
||||
|
||||
# Synology NAS 의 MariaDB10 패키지 바이너리 경로.
|
||||
# 배치는 로그인 셸이 아니라 PATH 가 비어 있으므로 명시적으로 추가.
|
||||
export PATH="$PATH:/var/packages/MariaDB10/target/usr/local/mariadb10.11/bin"
|
||||
|
||||
# ===================== 설정 =====================
|
||||
DB_HOST="chocomae.jinaju.com"
|
||||
DB_PORT="3306"
|
||||
DB_USER="backup"
|
||||
DB_NAME="chocomae"
|
||||
|
||||
# MySQL 옵션 파일. [client] 섹션에 user/password/host/port 저장.
|
||||
# 배치가 실행되는 사용자(보통 root) 홈 아래에 두는 것이 자연스럽다.
|
||||
CREDENTIALS_FILE="/root/.chocomae_db.cnf"
|
||||
|
||||
# 백업 파일 저장 위치: 이 스크립트 파일이 있는 디렉터리.
|
||||
# (호출 위치와 무관하게 항상 스크립트 옆에 저장되도록 절대 경로로 확정)
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
OUTPUT_DIR="$SCRIPT_DIR"
|
||||
|
||||
# 보관 기간(일). 이 값을 초과한 파일은 스크립트가 실행될 때 삭제.
|
||||
RETENTION_DAYS=28
|
||||
|
||||
# 로그 파일 크기 제한. 초과 시 마지막 LOG_KEEP_LINES 줄만 남기고 잘라냄.
|
||||
LOG_MAX_BYTES=$((1 * 1024 * 1024)) # 1 MB
|
||||
LOG_KEEP_LINES=500
|
||||
# =================================================
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
OUTPUT_FILE="${OUTPUT_DIR}/chocomae_${TIMESTAMP}.sql.gz"
|
||||
LOG_FILE="${OUTPUT_DIR}/backup.log"
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# ---- 사전 점검 ----
|
||||
if [ ! -f "$CREDENTIALS_FILE" ]; then
|
||||
log "ERROR: 자격증명 파일이 없습니다: ${CREDENTIALS_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 권한이 600 또는 400 이 아니면 실행 중단 (평문 비밀번호 파일 보호)
|
||||
PERM=$(stat -c '%a' "$CREDENTIALS_FILE" 2>/dev/null || echo "unknown")
|
||||
if [ "$PERM" != "600" ] && [ "$PERM" != "400" ]; then
|
||||
log "ERROR: 자격증명 파일 권한이 안전하지 않습니다 (현재: ${PERM}). 'chmod 600 ${CREDENTIALS_FILE}' 실행 필요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- 덤프 명령 탐지 ----
|
||||
if command -v mariadb-dump >/dev/null 2>&1; then
|
||||
DUMP_CMD="mariadb-dump"
|
||||
elif command -v mysqldump >/dev/null 2>&1; then
|
||||
DUMP_CMD="mysqldump"
|
||||
else
|
||||
log "ERROR: mariadb-dump / mysqldump 명령을 찾을 수 없습니다."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- 백업 실행 ----
|
||||
log "backup-db.sh 시작 (v${SCRIPT_VERSION})"
|
||||
log "백업 시작 : ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
|
||||
log "출력 파일 : ${OUTPUT_FILE}"
|
||||
|
||||
# 옵션 설명:
|
||||
# --defaults-extra-file : 자격증명 파일 (반드시 다른 옵션보다 먼저 위치)
|
||||
# --single-transaction : InnoDB 온라인 백업. 락 없이 일관된 스냅샷.
|
||||
# --routines/--triggers/--events : 스토어드 프로시저/트리거/이벤트 포함.
|
||||
# --hex-blob : 바이너리 데이터 안전 직렬화.
|
||||
# --protocol=TCP : NAS→AWS 원격 접속이므로 TCP 강제.
|
||||
# --default-character-set=utf8mb4 : 한글 안전.
|
||||
DUMP_ERR=$(mktemp)
|
||||
if "$DUMP_CMD" \
|
||||
"--defaults-extra-file=${CREDENTIALS_FILE}" \
|
||||
--protocol=TCP \
|
||||
--default-character-set=utf8mb4 \
|
||||
--single-transaction \
|
||||
--routines --triggers --events \
|
||||
--hex-blob \
|
||||
-h"$DB_HOST" -P"$DB_PORT" \
|
||||
"$DB_NAME" 2>"$DUMP_ERR" | gzip -c > "$OUTPUT_FILE"; then
|
||||
# 확장 ACL 이 상속되어 붙어 있을 수 있으므로 명시적으로 제거하고 600 으로 잠금.
|
||||
if command -v setfacl >/dev/null 2>&1; then
|
||||
setfacl -b "$OUTPUT_FILE" 2>/dev/null || true
|
||||
fi
|
||||
chmod 600 "$OUTPUT_FILE"
|
||||
SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
|
||||
log "백업 성공 (크기: ${SIZE})"
|
||||
else
|
||||
log "ERROR: 백업 실패 - $(cat "$DUMP_ERR")"
|
||||
rm -f "$OUTPUT_FILE" "$DUMP_ERR"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$DUMP_ERR"
|
||||
|
||||
# ---- 오래된 백업 정리 ----
|
||||
log "${RETENTION_DAYS}일 초과 백업 파일 정리 중..."
|
||||
DELETED_COUNT=0
|
||||
while IFS= read -r -d '' OLD_FILE; do
|
||||
log " 삭제: ${OLD_FILE}"
|
||||
rm -f "$OLD_FILE"
|
||||
DELETED_COUNT=$((DELETED_COUNT + 1))
|
||||
done < <(find "$OUTPUT_DIR" -maxdepth 1 -type f \
|
||||
-name 'chocomae_*.sql.gz' \
|
||||
-mtime +"${RETENTION_DAYS}" -print0)
|
||||
log "정리 완료: ${DELETED_COUNT}개 파일 삭제"
|
||||
|
||||
# ---- 로그 크기 제한 (rotate) ----
|
||||
# LOG_MAX_BYTES 초과 시 마지막 LOG_KEEP_LINES 줄만 남기고 축소.
|
||||
# 이 스크립트만 로그를 쓰는 구조라 tail | mv 방식으로도 레이스가 없음.
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
LOG_SIZE=$(wc -c < "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
if [ "$LOG_SIZE" -gt "$LOG_MAX_BYTES" ]; then
|
||||
tail -n "$LOG_KEEP_LINES" "$LOG_FILE" > "${LOG_FILE}.tmp"
|
||||
mv "${LOG_FILE}.tmp" "$LOG_FILE"
|
||||
log "로그 로테이션: ${LOG_SIZE} 바이트 → 최근 ${LOG_KEEP_LINES}줄로 축소"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "완료"
|
||||
|
||||
# 로그 파일 권한 잠금: 상위 디렉터리 ACL 상속으로 777 이 되는 것을 방지.
|
||||
# 매 실행 끝에 한 번 정규화하므로 첫 실행/로그 로테이션 모든 경로를 커버함.
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
if command -v setfacl >/dev/null 2>&1; then
|
||||
setfacl -b "$LOG_FILE" 2>/dev/null || true
|
||||
fi
|
||||
chmod 640 "$LOG_FILE"
|
||||
fi
|
||||
Reference in New Issue
Block a user