feat: xavis ai_platform 기능 이전 및 ncue 환경 전환
xavis 소스·DB 스키마·활용사례/F-Scan/프롬프트 라이브러리 등 기능 반영. @xavis.co.kr → @ncue.net, 관리자 토큰 ncue-admin, 런타임 data/ Git 추적 제외. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
225
scripts/pg-restore.sh
Executable file
225
scripts/pg-restore.sh
Executable file
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env bash
|
||||
# PostgreSQL 논리 복원 (pg_restore). custom format(.dump) 전용.
|
||||
# 사용 예:
|
||||
# bash scripts/pg-restore.sh /home/xavis/workspace/backup/ai_platform/latest/ai_web_platform.dump
|
||||
# bash scripts/pg-restore.sh --all --globals --confirm /home/xavis/workspace/backup/ai_platform/latest/
|
||||
# bash scripts/pg-restore.sh --test --confirm /home/xavis/workspace/backup/ai_platform/latest/ai_web_platform.dump
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: bash scripts/pg-restore.sh [options] <path-to.dump | backup-dir>
|
||||
|
||||
Options:
|
||||
--all 백업 디렉터리 안의 *.dump 전체 복원 (00_globals.sql 있으면 --globals 권장)
|
||||
--test 복원 대상 DB를 {dbname}_restore_test 로 생성 (단일 dump 또는 --all)
|
||||
--clean pg_restore --clean --if-exists (기존 객체 삭제 후 복원, --test 미사용 시 위험)
|
||||
--confirm 확인 없이 실행 (cron·자동화용; --test 없이 --clean 시 필수)
|
||||
--globals 같은 디렉터리의 00_globals.sql 을 먼저 적용 (슈퍼유저 env 필요)
|
||||
-h, --help 도움말
|
||||
|
||||
환경 변수 (.env):
|
||||
DB_* 연결 정보
|
||||
PG_BACKUP_SUPERUSER --globals 시 사용 (기본 postgres)
|
||||
PG_BACKUP_SUPERUSER_PASSWORD
|
||||
EOF
|
||||
}
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
# shellcheck source=scripts/lib/load-env.sh
|
||||
source "$REPO_ROOT/scripts/lib/load-env.sh"
|
||||
|
||||
MODE="prod"
|
||||
CLEAN=0
|
||||
CONFIRM=0
|
||||
GLOBALS=0
|
||||
RESTORE_ALL=0
|
||||
TARGET_PATH=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--test) MODE="test"; shift ;;
|
||||
--clean) CLEAN=1; shift ;;
|
||||
--confirm) CONFIRM=1; shift ;;
|
||||
--globals) GLOBALS=1; shift ;;
|
||||
--all) RESTORE_ALL=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
-*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||
*)
|
||||
if [[ -n "$TARGET_PATH" ]]; then
|
||||
echo "Unexpected argument: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
TARGET_PATH="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$TARGET_PATH" ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
load_project_env "$REPO_ROOT/.env"
|
||||
|
||||
: "${DB_HOST:?DB_HOST is required in .env}"
|
||||
: "${DB_DATABASE:?DB_DATABASE is required in .env}"
|
||||
: "${DB_USERNAME:?DB_USERNAME is required in .env}"
|
||||
: "${DB_PASSWORD:?DB_PASSWORD is required in .env}"
|
||||
|
||||
DB_PORT="${DB_PORT:-5432}"
|
||||
|
||||
export PGHOST="$DB_HOST"
|
||||
export PGPORT="$DB_PORT"
|
||||
export PGUSER="$DB_USERNAME"
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
|
||||
if ! command -v pg_restore >/dev/null 2>&1; then
|
||||
echo "pg-restore: pg_restore not found. Install postgresql-client." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_ts() { date '+%Y-%m-%dT%H:%M:%S%z'; }
|
||||
|
||||
resolve_superuser_creds() {
|
||||
SUPERUSER="${PG_BACKUP_SUPERUSER:-postgres}"
|
||||
if [[ -n "${PG_BACKUP_SUPERUSER_PASSWORD:-}" ]]; then
|
||||
SU_USER="$SUPERUSER"
|
||||
SU_PASS="$PG_BACKUP_SUPERUSER_PASSWORD"
|
||||
else
|
||||
SU_USER="$DB_USERNAME"
|
||||
SU_PASS="$DB_PASSWORD"
|
||||
fi
|
||||
}
|
||||
|
||||
apply_globals() {
|
||||
local globals_file="$1"
|
||||
if [[ ! -f "$globals_file" ]]; then
|
||||
echo "pg-restore: globals file not found: $globals_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
: "${PG_BACKUP_SUPERUSER_PASSWORD:?PG_BACKUP_SUPERUSER_PASSWORD required for --globals}"
|
||||
resolve_superuser_creds
|
||||
export PGUSER="$SU_USER"
|
||||
export PGPASSWORD="$SU_PASS"
|
||||
echo "[$(log_ts)] applying globals: $globals_file"
|
||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -v ON_ERROR_STOP=1 -f "$globals_file"
|
||||
export PGUSER="$DB_USERNAME"
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
}
|
||||
|
||||
ensure_database() {
|
||||
local db_name="$1"
|
||||
resolve_superuser_creds
|
||||
export PGUSER="$SU_USER"
|
||||
export PGPASSWORD="$SU_PASS"
|
||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -v ON_ERROR_STOP=1 -tc \
|
||||
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$db_name' AND pid <> pg_backend_pid();" \
|
||||
>/dev/null 2>&1 || true
|
||||
if [[ "$MODE" == "test" ]]; then
|
||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -v ON_ERROR_STOP=1 -c "DROP DATABASE IF EXISTS \"$db_name\";"
|
||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -v ON_ERROR_STOP=1 -c \
|
||||
"CREATE DATABASE \"$db_name\" OWNER \"$DB_USERNAME\";"
|
||||
fi
|
||||
export PGUSER="$DB_USERNAME"
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
}
|
||||
|
||||
restore_one_dump() {
|
||||
local dump_path="$1"
|
||||
local source_db
|
||||
source_db="$(basename "$dump_path" .dump)"
|
||||
local target_db="$source_db"
|
||||
if [[ "$MODE" == "test" ]]; then
|
||||
target_db="${source_db}_restore_test"
|
||||
ensure_database "$target_db"
|
||||
fi
|
||||
|
||||
local restore_args=(
|
||||
-h "$PGHOST"
|
||||
-p "$PGPORT"
|
||||
-U "$PGUSER"
|
||||
-d "$target_db"
|
||||
--no-owner
|
||||
--role="$DB_USERNAME"
|
||||
-v
|
||||
)
|
||||
if [[ "$CLEAN" -eq 1 ]]; then
|
||||
restore_args+=(--clean --if-exists)
|
||||
fi
|
||||
|
||||
echo "[$(log_ts)] pg_restore → $target_db ($dump_path)"
|
||||
pg_restore "${restore_args[@]}" "$dump_path"
|
||||
echo "[$(log_ts)] pg-restore done: $target_db"
|
||||
}
|
||||
|
||||
confirm_restore() {
|
||||
local message="$1"
|
||||
if [[ "$MODE" == "prod" && "$CLEAN" -eq 1 && "$CONFIRM" -ne 1 ]]; then
|
||||
echo "pg-restore: --clean on production DB requires --confirm" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$CONFIRM" -eq 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
echo "$message"
|
||||
read -r -p "Continue? [y/N] " ans
|
||||
[[ "$ans" == "y" || "$ans" == "Y" ]] || exit 0
|
||||
}
|
||||
|
||||
if [[ -d "$TARGET_PATH" ]]; then
|
||||
BACKUP_DIR="${TARGET_PATH%/}"
|
||||
if [[ "$RESTORE_ALL" -ne 1 ]]; then
|
||||
echo "pg-restore: path is a directory. Use --all to restore every *.dump inside." >&2
|
||||
exit 1
|
||||
fi
|
||||
confirm_restore "About to restore ALL dumps in: $BACKUP_DIR on $DB_HOST:$PGPORT"
|
||||
if [[ "$GLOBALS" -eq 1 ]]; then
|
||||
apply_globals "$BACKUP_DIR/00_globals.sql"
|
||||
fi
|
||||
shopt -s nullglob
|
||||
dumps=( "$BACKUP_DIR"/*.dump )
|
||||
shopt -u nullglob
|
||||
if [[ ${#dumps[@]} -eq 0 ]]; then
|
||||
echo "pg-restore: no .dump files in $BACKUP_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
for dump in "${dumps[@]}"; do
|
||||
restore_one_dump "$dump"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$TARGET_PATH" ]]; then
|
||||
echo "pg-restore: path not found: $TARGET_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$RESTORE_ALL" -eq 1 ]]; then
|
||||
echo "pg-restore: --all requires a backup directory path." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET_DB="$DB_DATABASE"
|
||||
if [[ "$MODE" == "test" ]]; then
|
||||
TARGET_DB="${DB_DATABASE}_restore_test"
|
||||
fi
|
||||
|
||||
confirm_restore "About to restore into database: $TARGET_DB on $DB_HOST:$PGPORT
|
||||
Dump: $TARGET_PATH"
|
||||
|
||||
if [[ "$GLOBALS" -eq 1 ]]; then
|
||||
apply_globals "$(dirname "$TARGET_PATH")/00_globals.sql"
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "test" ]]; then
|
||||
ensure_database "$TARGET_DB"
|
||||
fi
|
||||
|
||||
restore_one_dump "$TARGET_PATH"
|
||||
|
||||
if [[ "$MODE" == "test" ]]; then
|
||||
echo "Verify with: psql -h $DB_HOST -U $DB_USERNAME -d $TARGET_DB -c '\\dt'"
|
||||
echo "Drop test DB: psql ... -c 'DROP DATABASE \"$TARGET_DB\";'"
|
||||
fi
|
||||
Reference in New Issue
Block a user