마에스트로 DB 데이터 백업+삭제, 복원 스크립트
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# backup_maestro.sh
|
||||
# ------------------------------------------------------------
|
||||
# 지정한 MaestroID 와 관련된 12개 테이블의 데이터를
|
||||
# 1) mariadb-dump(mysqldump) 로 백업하고
|
||||
# 2) (재확인 후) 운영 DB 에서 실제 삭제까지 수행하는 스크립트.
|
||||
#
|
||||
# 실행 위치: Synology NAS (운영 서버가 아님)
|
||||
# - NAS에 설치된 MariaDB client 로 AWS 운영 DB 서버에
|
||||
# 원격 접속해서 덤프/삭제를 수행합니다.
|
||||
# - 운영 DB 서버의 보안 그룹/방화벽에서 NAS 의 공인 IP
|
||||
# (또는 VPN 대역)에 대해 3306 포트가 열려 있어야 합니다.
|
||||
#
|
||||
# 결과물은 "복원 가능한 INSERT 문(.sql)" 한 개 파일로 생성됩니다.
|
||||
# (테이블 구조는 포함하지 않음 — 복원 대상 DB에 스키마가 이미
|
||||
# 존재한다고 가정. 스키마까지 필요하면 아래 USE_NO_CREATE_INFO
|
||||
# 를 false 로 바꾸세요.)
|
||||
#
|
||||
# 삭제 단계는:
|
||||
# - 백업이 모든 테이블에서 성공했을 때만 진행합니다.
|
||||
# - MaestroID 를 다시 한번 입력해야만 실행됩니다.
|
||||
# - 자식 테이블 → 부모(maestro) 순으로 하나의 트랜잭션에서 삭제합니다.
|
||||
# - 문제 발생 시 방금 만든 백업 파일로 restore_maestro.sh 로 복원 가능.
|
||||
#
|
||||
# 사용법:
|
||||
# chmod +x backup_maestro.sh
|
||||
# ./backup_maestro.sh <MaestroID> # 운영 DB (기본)
|
||||
# ./backup_maestro.sh --test <MaestroID> # 테스트 DB
|
||||
# 예) ./backup_maestro.sh 317
|
||||
# ./backup_maestro.sh --test 317
|
||||
# ------------------------------------------------------------
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Synology NAS 의 MariaDB10 패키지 바이너리 경로.
|
||||
# 기본 PATH 에 포함되지 않는 경우가 많아 명시적으로 추가합니다.
|
||||
export PATH="$PATH:/var/packages/MariaDB10/target/usr/local/mariadb10.11/bin"
|
||||
|
||||
# ===================== DB 접속 정보 =====================
|
||||
# 운영 DB (기본)
|
||||
PROD_DB_HOST="chocomae.jinaju.com"
|
||||
PROD_DB_PORT="3306"
|
||||
PROD_DB_USER="chocomae"
|
||||
PROD_DB_NAME="chocomae"
|
||||
|
||||
# 테스트 DB (--test 옵션으로 활성화)
|
||||
TEST_DB_HOST="mariadb.jisangs.com"
|
||||
TEST_DB_PORT="30001"
|
||||
TEST_DB_USER="chocomae"
|
||||
TEST_DB_NAME="chocomae"
|
||||
|
||||
# true : 데이터(INSERT)만 백업 (기본, 스키마는 이미 있다고 가정)
|
||||
# false : CREATE TABLE 구조까지 포함해서 백업
|
||||
USE_NO_CREATE_INFO=true
|
||||
|
||||
OUTPUT_DIR="./backup"
|
||||
# =========================================================
|
||||
|
||||
# ---- 인자 파싱 (순서 자유: --test 는 앞/뒤 어디에 와도 됨) ----
|
||||
USE_TEST=false
|
||||
MAESTRO_ID=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-t|--test)
|
||||
USE_TEST=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "사용법: $0 [--test] <MaestroID>"
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
echo "알 수 없는 옵션: $1"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [ -z "$MAESTRO_ID" ]; then
|
||||
MAESTRO_ID="$1"
|
||||
else
|
||||
echo "인자가 너무 많습니다: $1"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$MAESTRO_ID" ]; then
|
||||
echo "사용법: $0 [--test] <MaestroID>"
|
||||
echo "예) $0 317"
|
||||
echo " $0 --test 317"
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$MAESTRO_ID" =~ ^[0-9]+$ ]]; then
|
||||
echo "MaestroID 는 정수여야 합니다: '${MAESTRO_ID}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- 대상 DB 확정 ----
|
||||
if [ "$USE_TEST" = true ]; then
|
||||
DB_HOST="$TEST_DB_HOST"
|
||||
DB_PORT="$TEST_DB_PORT"
|
||||
DB_USER="$TEST_DB_USER"
|
||||
DB_NAME="$TEST_DB_NAME"
|
||||
TARGET_LABEL="TEST"
|
||||
else
|
||||
DB_HOST="$PROD_DB_HOST"
|
||||
DB_PORT="$PROD_DB_PORT"
|
||||
DB_USER="$PROD_DB_USER"
|
||||
DB_NAME="$PROD_DB_NAME"
|
||||
TARGET_LABEL="PROD"
|
||||
fi
|
||||
|
||||
echo "접속 대상: [${TARGET_LABEL}] ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
|
||||
|
||||
# 운영 서버 대상일 경우 실수 방지용 재확인.
|
||||
if [ "$TARGET_LABEL" = "PROD" ]; then
|
||||
echo
|
||||
echo "!! 대상은 운영(PROD) DB 입니다. 백업 후 삭제까지 실제로 수행됩니다."
|
||||
read -r -p "계속하려면 'PROD' 를 입력하세요: " PROD_CONFIRM
|
||||
if [ "$PROD_CONFIRM" != "PROD" ]; then
|
||||
echo "취소되었습니다."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
OUTPUT_FILE="${OUTPUT_DIR}/maestro_${MAESTRO_ID}_backup_${TIMESTAMP}.sql"
|
||||
|
||||
# 복원 편의를 위해 부모 테이블(maestro)을 가장 먼저 나열
|
||||
TABLES=(
|
||||
maestro
|
||||
app_highest_record
|
||||
best_record
|
||||
license_maestro_password
|
||||
license_score
|
||||
license_time
|
||||
maestro_extension
|
||||
maestro_log
|
||||
maestro_upgrade
|
||||
player
|
||||
typing_exam_highest_record
|
||||
typing_exam_record
|
||||
)
|
||||
|
||||
# Synology NAS 에는 mariadb-dump / mariadb 로 설치되는 경우가 많고,
|
||||
# 배포판에 따라 mysqldump / mysql 심볼릭 링크만 있는 경우도 있어 둘 다 지원합니다.
|
||||
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
|
||||
echo "mariadb-dump / mysqldump 명령을 찾을 수 없습니다. NAS 에 MariaDB client 를 먼저 설치하세요."
|
||||
exit 1
|
||||
fi
|
||||
if command -v mariadb >/dev/null 2>&1; then
|
||||
MYSQL_CMD="mariadb"
|
||||
elif command -v mysql >/dev/null 2>&1; then
|
||||
MYSQL_CMD="mysql"
|
||||
else
|
||||
echo "mariadb / mysql 명령을 찾을 수 없습니다. NAS 에 MariaDB client 를 먼저 설치하세요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# 비밀번호는 명령행 인자로 넘기지 않고(ps 로 노출되는 것을 방지)
|
||||
# 프롬프트로 입력받아 MYSQL_PWD 환경변수로 전달합니다.
|
||||
read -r -s -p "DB(${DB_USER}@${DB_HOST}) 비밀번호: " DB_PASS
|
||||
echo
|
||||
export MYSQL_PWD="$DB_PASS"
|
||||
|
||||
# ---- MaestroID → Name 조회로 대상 계정 확인 ----
|
||||
if ! MAESTRO_NAME=$("$MYSQL_CMD" --protocol=TCP --default-character-set=utf8mb4 \
|
||||
-h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" "$DB_NAME" \
|
||||
-sN -e "SELECT \`Name\` FROM \`maestro\` WHERE MaestroID=${MAESTRO_ID};" \
|
||||
2>/tmp/mysql_err.$$); then
|
||||
echo "DB 조회에 실패했습니다: $(cat /tmp/mysql_err.$$)"
|
||||
rm -f /tmp/mysql_err.$$
|
||||
unset MYSQL_PWD
|
||||
exit 1
|
||||
fi
|
||||
rm -f /tmp/mysql_err.$$
|
||||
|
||||
if [ -z "$MAESTRO_NAME" ]; then
|
||||
echo "MaestroID=${MAESTRO_ID} 에 해당하는 계정이 [${TARGET_LABEL}] DB 에 없습니다."
|
||||
unset MYSQL_PWD
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "대상 계정: MaestroID=${MAESTRO_ID} / Name='${MAESTRO_NAME}'"
|
||||
read -r -p "이 계정이 맞으면 진행합니다. 계속하시겠습니까? (y/N) " NAME_CONFIRM
|
||||
if [[ "$NAME_CONFIRM" != "y" && "$NAME_CONFIRM" != "Y" ]]; then
|
||||
echo "취소되었습니다."
|
||||
unset MYSQL_PWD
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 원격(AWS) DB 로 접속하므로 네트워크 관련 옵션을 추가합니다.
|
||||
# --protocol=TCP : 로컬 소켓이 아닌 TCP 로 강제 접속
|
||||
# --default-character-set : 한글 데이터 안전하게 덤프
|
||||
# --set-gtid-purged=OFF : (MySQL 계열에서만 유효, MariaDB 는 무시)
|
||||
DUMP_OPTS=(
|
||||
--protocol=TCP
|
||||
--default-character-set=utf8mb4
|
||||
--no-create-info
|
||||
--compact
|
||||
--complete-insert
|
||||
--skip-lock-tables
|
||||
)
|
||||
if [ "$USE_NO_CREATE_INFO" = false ]; then
|
||||
DUMP_OPTS=(
|
||||
--protocol=TCP
|
||||
--default-character-set=utf8mb4
|
||||
--compact
|
||||
--complete-insert
|
||||
--skip-lock-tables
|
||||
)
|
||||
fi
|
||||
|
||||
{
|
||||
echo "-- MaestroID=${MAESTRO_ID} 데이터 백업"
|
||||
echo "-- 생성 시각: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "SET FOREIGN_KEY_CHECKS=0;"
|
||||
echo "SET AUTOCOMMIT=0;"
|
||||
echo "START TRANSACTION;"
|
||||
echo
|
||||
} > "$OUTPUT_FILE"
|
||||
|
||||
FAILED_TABLES=()
|
||||
|
||||
for TABLE in "${TABLES[@]}"; do
|
||||
echo ">> ${TABLE} 백업 중..."
|
||||
{
|
||||
echo "-- ---------------------------------------------"
|
||||
echo "-- ${TABLE}"
|
||||
echo "-- ---------------------------------------------"
|
||||
} >> "$OUTPUT_FILE"
|
||||
if ! "$DUMP_CMD" -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" \
|
||||
"${DUMP_OPTS[@]}" \
|
||||
--where="MaestroID=${MAESTRO_ID}" \
|
||||
"$DB_NAME" "$TABLE" >> "$OUTPUT_FILE" 2>/tmp/mysqldump_err.$$; then
|
||||
echo " 경고: ${TABLE} 덤프 실패 - $(cat /tmp/mysqldump_err.$$)"
|
||||
FAILED_TABLES+=("$TABLE")
|
||||
fi
|
||||
rm -f /tmp/mysqldump_err.$$
|
||||
echo >> "$OUTPUT_FILE"
|
||||
done
|
||||
|
||||
{
|
||||
echo "COMMIT;"
|
||||
echo "SET FOREIGN_KEY_CHECKS=1;"
|
||||
} >> "$OUTPUT_FILE"
|
||||
|
||||
echo
|
||||
echo "백업 완료: ${OUTPUT_FILE}"
|
||||
echo "총 라인 수: $(wc -l < "$OUTPUT_FILE")"
|
||||
|
||||
# ---------------- 원본 데이터 삭제 ----------------
|
||||
# 백업 도중 실패가 있었다면 삭제를 절대 진행하지 않습니다.
|
||||
if [ ${#FAILED_TABLES[@]} -gt 0 ]; then
|
||||
echo "다음 테이블에서 문제가 발생했습니다 (확인 필요): ${FAILED_TABLES[*]}"
|
||||
echo "백업이 불완전하므로 원본 데이터 삭제를 건너뜁니다."
|
||||
unset MYSQL_PWD
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "===================================================="
|
||||
echo " WARNING: 이제 [${TARGET_LABEL}] DB(${DB_HOST}) 에서"
|
||||
echo " MaestroID=${MAESTRO_ID} 관련 모든 데이터를 삭제합니다."
|
||||
echo " 되돌리려면 방금 만든 백업 파일로 restore 해야 합니다."
|
||||
echo "===================================================="
|
||||
read -r -p "정말 삭제하려면 MaestroID 값(${MAESTRO_ID}) 숫자를 다시 입력하세요: " CONFIRM_ID
|
||||
if [ "$CONFIRM_ID" != "$MAESTRO_ID" ]; then
|
||||
echo "취소되었습니다. (원본 데이터는 그대로 유지됨)"
|
||||
unset MYSQL_PWD
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 자식 테이블부터 삭제 → 마지막에 maestro(부모).
|
||||
# 단일 트랜잭션으로 원자성 보장, FK 는 잠시 꺼두어 순서 의존성 회피.
|
||||
DELETE_SQL="SET FOREIGN_KEY_CHECKS=0;
|
||||
START TRANSACTION;
|
||||
"
|
||||
for (( i=${#TABLES[@]}-1; i>=0; i-- )); do
|
||||
DELETE_SQL+="DELETE FROM \`${TABLES[$i]}\` WHERE MaestroID=${MAESTRO_ID};
|
||||
SELECT '${TABLES[$i]}' AS tbl, ROW_COUNT() AS deleted;
|
||||
"
|
||||
done
|
||||
DELETE_SQL+="COMMIT;
|
||||
SET FOREIGN_KEY_CHECKS=1;"
|
||||
|
||||
echo
|
||||
echo ">> 삭제 실행 중..."
|
||||
if printf '%s\n' "$DELETE_SQL" | "$MYSQL_CMD" \
|
||||
--protocol=TCP --default-character-set=utf8mb4 \
|
||||
-h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" "$DB_NAME"; then
|
||||
echo "삭제 완료."
|
||||
else
|
||||
echo "삭제 중 오류가 발생했습니다. 트랜잭션이 롤백되었을 수 있으니 DB 상태를 반드시 확인하세요."
|
||||
unset MYSQL_PWD
|
||||
exit 1
|
||||
fi
|
||||
|
||||
unset MYSQL_PWD
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# restore_maestro.sh
|
||||
# ------------------------------------------------------------
|
||||
# backup_maestro.sh 로 만든 백업(.sql) 파일을 지정한 DB에
|
||||
# 복원하는 스크립트.
|
||||
#
|
||||
# 사용법:
|
||||
# chmod +x restore_maestro.sh
|
||||
# ./restore_maestro.sh <backup_file.sql> # 운영 DB (기본)
|
||||
# ./restore_maestro.sh --test <backup_file.sql> # 테스트 DB
|
||||
# ------------------------------------------------------------
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Synology NAS 의 MariaDB10 패키지 바이너리 경로.
|
||||
# 기본 PATH 에 포함되지 않는 경우가 많아 명시적으로 추가합니다.
|
||||
export PATH="$PATH:/var/packages/MariaDB10/target/usr/local/mariadb10.11/bin"
|
||||
|
||||
# ===================== DB 접속 정보 =====================
|
||||
# 운영 DB (기본)
|
||||
PROD_DB_HOST="chocomae.jinaju.com"
|
||||
PROD_DB_PORT="3306"
|
||||
PROD_DB_USER="chocomae"
|
||||
PROD_DB_NAME="chocomae"
|
||||
|
||||
# 테스트 DB (--test 옵션으로 활성화)
|
||||
TEST_DB_HOST="mariadb.jisangs.com"
|
||||
TEST_DB_PORT="30001"
|
||||
TEST_DB_USER="chocomae"
|
||||
TEST_DB_NAME="chocomae"
|
||||
# =========================================================
|
||||
|
||||
# ---- 인자 파싱 (순서 자유: --test 는 앞/뒤 어디에 와도 됨) ----
|
||||
USE_TEST=false
|
||||
BACKUP_FILE=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-t|--test)
|
||||
USE_TEST=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "사용법: $0 [--test] <backup_file.sql>"
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
echo "알 수 없는 옵션: $1"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
BACKUP_FILE="$1"
|
||||
else
|
||||
echo "인자가 너무 많습니다: $1"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
echo "사용법: $0 [--test] <backup_file.sql>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
echo "파일을 찾을 수 없습니다: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- 대상 DB 확정 ----
|
||||
if [ "$USE_TEST" = true ]; then
|
||||
DB_HOST="$TEST_DB_HOST"
|
||||
DB_PORT="$TEST_DB_PORT"
|
||||
DB_USER="$TEST_DB_USER"
|
||||
DB_NAME="$TEST_DB_NAME"
|
||||
TARGET_LABEL="TEST"
|
||||
else
|
||||
DB_HOST="$PROD_DB_HOST"
|
||||
DB_PORT="$PROD_DB_PORT"
|
||||
DB_USER="$PROD_DB_USER"
|
||||
DB_NAME="$PROD_DB_NAME"
|
||||
TARGET_LABEL="PROD"
|
||||
fi
|
||||
|
||||
# Synology NAS 에서는 mariadb CLI 로 설치되는 경우가 많습니다.
|
||||
if command -v mariadb >/dev/null 2>&1; then
|
||||
MYSQL_CMD="mariadb"
|
||||
elif command -v mysql >/dev/null 2>&1; then
|
||||
MYSQL_CMD="mysql"
|
||||
else
|
||||
echo "mariadb / mysql 명령을 찾을 수 없습니다. NAS 에 MariaDB client 를 먼저 설치하세요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 파일명 규칙: maestro_<MaestroID>_backup_<타임스탬프>.sql
|
||||
# 규칙에 맞으면 MaestroID 를 뽑아 안내에 노출합니다. (없어도 복원은 정상 동작)
|
||||
BACKUP_BASENAME=$(basename "$BACKUP_FILE")
|
||||
DETECTED_MAESTRO_ID=""
|
||||
if [[ "$BACKUP_BASENAME" =~ ^maestro_([0-9]+)_backup_ ]]; then
|
||||
DETECTED_MAESTRO_ID="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
echo "복원 대상 : [${TARGET_LABEL}] ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
|
||||
echo "백업 파일 : ${BACKUP_FILE}"
|
||||
if [ -n "$DETECTED_MAESTRO_ID" ]; then
|
||||
echo "MaestroID : ${DETECTED_MAESTRO_ID} (파일명에서 추출)"
|
||||
else
|
||||
echo "MaestroID : (파일명 규칙과 달라 확인 불가)"
|
||||
fi
|
||||
echo
|
||||
echo "주의: 이미 동일한 MaestroID 데이터가 대상 DB에 존재하면"
|
||||
echo " Primary/Unique Key 충돌로 실패할 수 있습니다."
|
||||
echo " (신규 서버 또는 데이터가 없는 상태에서 복원을 권장)"
|
||||
echo
|
||||
read -r -p "계속 진행하시겠습니까? (y/N) " CONFIRM
|
||||
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
|
||||
echo "취소되었습니다."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 운영 서버 대상일 경우 실수 방지용 재확인.
|
||||
if [ "$TARGET_LABEL" = "PROD" ]; then
|
||||
echo
|
||||
echo "!! 대상은 운영(PROD) DB 입니다. 실제 데이터가 변경됩니다."
|
||||
read -r -p "계속하려면 'PROD' 를 입력하세요: " PROD_CONFIRM
|
||||
if [ "$PROD_CONFIRM" != "PROD" ]; then
|
||||
echo "취소되었습니다."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
read -r -s -p "DB(${DB_USER}@${DB_HOST}) 비밀번호: " DB_PASS
|
||||
echo
|
||||
export MYSQL_PWD="$DB_PASS"
|
||||
|
||||
echo ">> 복원을 시작합니다..."
|
||||
if "$MYSQL_CMD" --protocol=TCP --default-character-set=utf8mb4 \
|
||||
-h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" "$DB_NAME" < "$BACKUP_FILE"; then
|
||||
echo "복원이 완료되었습니다."
|
||||
else
|
||||
echo "복원 중 오류가 발생했습니다. 백업 파일 내용과 대상 DB 스키마를 확인하세요."
|
||||
unset MYSQL_PWD
|
||||
exit 1
|
||||
fi
|
||||
|
||||
unset MYSQL_PWD
|
||||
Reference in New Issue
Block a user