#!/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 # 운영 DB (기본) # ./backup_maestro.sh --test # 테스트 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] " 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] " 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