309 lines
9.7 KiB
Bash
309 lines
9.7 KiB
Bash
#!/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
|