chore(scripts): add sync-prod deployment helper script
This commit is contained in:
parent
7f19b35b40
commit
8dc25740da
1 changed files with 506 additions and 0 deletions
506
scripts/sync-prod.sh
Executable file
506
scripts/sync-prod.sh
Executable file
|
|
@ -0,0 +1,506 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# sync-prod.sh — Sync production data to dev or staging.
|
||||
#
|
||||
# Dumps the production database (ptf_app_prd) and restores it into the target
|
||||
# environment's database. Optionally syncs media uploads via SSH. Backs up the
|
||||
# target database and media before overwriting (one backup retained).
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/sync-prod.sh [OPTIONS]
|
||||
#
|
||||
# Examples:
|
||||
# ./scripts/sync-prod.sh # sync DB + media → dev
|
||||
# ./scripts/sync-prod.sh --target stg # sync DB + media → staging
|
||||
# ./scripts/sync-prod.sh --dry-run # preview without writing
|
||||
# ./scripts/sync-prod.sh --skip-media # DB only, no media sync
|
||||
# ./scripts/sync-prod.sh --backup-only # backup current target state
|
||||
# ./scripts/sync-prod.sh --list-backups # show available backups
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ─── Configuration ────────────────────────────────────────────────────────────
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
BACKUP_DIR="${PROJECT_ROOT}/backups"
|
||||
|
||||
# Environment targets
|
||||
declare -A ENV_CONFIG=(
|
||||
[dev]=".env"
|
||||
[stg]=".env.stg"
|
||||
)
|
||||
|
||||
# Defaults
|
||||
TARGET="dev"
|
||||
DRY_RUN=false
|
||||
AUTO_YES=false
|
||||
SKIP_MEDIA=false
|
||||
BACKUP_ONLY=false
|
||||
LIST_BACKUPS=false
|
||||
NO_MIGRATE=false
|
||||
SOURCE_FILE="${PROJECT_ROOT}/.env.prod"
|
||||
|
||||
# Media sync defaults — override via env or CLI flags
|
||||
MEDIA_HOST="${MEDIA_HOST:-192.168.1.4}"
|
||||
MEDIA_USER="${MEDIA_USER:-root}"
|
||||
MEDIA_PATH="${MEDIA_PATH:-/app/media}"
|
||||
PROD_CONTAINER="${PROD_CONTAINER:-polaris-task-force-main-website}"
|
||||
|
||||
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
die() { err "$@"; exit 1; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
sync-prod.sh — Sync production data to dev or staging databases.
|
||||
|
||||
USAGE
|
||||
./scripts/sync-prod.sh [OPTIONS]
|
||||
|
||||
OPTIONS
|
||||
--target, -t DEV|STG Target environment (default: dev)
|
||||
--dry-run, -n Show what would happen without making changes
|
||||
--yes, -y Skip confirmation prompts
|
||||
--skip-media Sync database only, skip media uploads
|
||||
--no-migrate Skip running pending Payload migrations after restore
|
||||
--backup-only Backup target state without syncing
|
||||
--list-backups List available backups
|
||||
--source FILE Production env file (default: .env.prod)
|
||||
--media-host HOST Media server hostname (default: 192.168.1.4)
|
||||
--media-user USER SSH user for media server (default: root)
|
||||
--media-path PATH Remote media directory (default: /app/media)
|
||||
--prod-container NAME Production Docker container name
|
||||
--help, -h Show this help
|
||||
|
||||
ENVIRONMENT VARIABLES
|
||||
These can be set in .env.prod or exported in your shell:
|
||||
|
||||
PROD_DATABASE_URI Production PostgreSQL connection string (required)
|
||||
PROD_HOST Production server hostname (for SSH media sync)
|
||||
PROD_USER SSH user for production server
|
||||
MEDIA_HOST Media server hostname (default: 192.168.1.4)
|
||||
MEDIA_USER SSH user for media server (default: root)
|
||||
MEDIA_PATH Remote media path (default: /app/media)
|
||||
|
||||
SETUP
|
||||
1. Copy .env.prod.example to .env.prod and fill in production credentials.
|
||||
2. Ensure SSH key access to the production server (for media sync).
|
||||
3. Run: ./scripts/sync-prod.sh
|
||||
|
||||
WHAT IT DOES
|
||||
Database:
|
||||
- Dumps production DB (ptf_app_prd) via pg_dump
|
||||
- Backs up the target database before restore
|
||||
- Restores into the target environment's database
|
||||
- Runs pending Payload migrations (keeps schema in sync with dev code)
|
||||
- Resets all sequences to prevent ID collisions
|
||||
|
||||
Media (unless --skip-media):
|
||||
- Backs up current media directory
|
||||
- Streams /app/media from the production Docker container via SSH
|
||||
- Extracts to local media directory
|
||||
|
||||
Safety:
|
||||
- Confirmation prompt before any changes (skip with --yes)
|
||||
- Creates timestamped backups before overwriting
|
||||
- --dry-run shows what would happen without writing
|
||||
- Backups stored in backups/ (gitignored)
|
||||
|
||||
BACKUPS
|
||||
./scripts/sync-prod.sh --list-backups # show available backups
|
||||
backups/<timestamp>/ # DB dump + media archive
|
||||
backups/current-media-backup -> latest # symlink to most recent
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ─── Parse arguments ──────────────────────────────────────────────────────────
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--target|-t) TARGET="$2"; shift 2 ;;
|
||||
--dry-run|-n) DRY_RUN=true; shift ;;
|
||||
--yes|-y) AUTO_YES=true; shift ;;
|
||||
--skip-media) SKIP_MEDIA=true; shift ;;
|
||||
--no-migrate) NO_MIGRATE=true; shift ;;
|
||||
--backup-only) BACKUP_ONLY=true; shift ;;
|
||||
--list-backups) LIST_BACKUPS=true; shift ;;
|
||||
--source) SOURCE_FILE="$2"; shift 2 ;;
|
||||
--media-host) MEDIA_HOST="$2"; shift 2 ;;
|
||||
--media-user) MEDIA_USER="$2"; shift 2 ;;
|
||||
--media-path) MEDIA_PATH="$2"; shift 2 ;;
|
||||
--prod-container) PROD_CONTAINER="$2"; shift 2 ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*) die "Unknown option: $1 (use --help for usage)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ─── Load environment ─────────────────────────────────────────────────────────
|
||||
|
||||
load_env() {
|
||||
local src="$1"
|
||||
[[ -f "$src" ]] || die "Source file not found: $src
|
||||
|
||||
Create it from the template:
|
||||
cp .env.prod.example .env.prod
|
||||
# Edit .env.prod with your production credentials"
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$src"
|
||||
set +a
|
||||
}
|
||||
|
||||
# ─── Backup helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
TS=""
|
||||
BACKUP_PATH=""
|
||||
MEDIA_BACKUP_PATH=""
|
||||
|
||||
setup_backup_dirs() {
|
||||
BACKUP_PATH="${BACKUP_DIR}/${TS}"
|
||||
mkdir -p "${BACKUP_PATH}/db"
|
||||
mkdir -p "${BACKUP_PATH}/media"
|
||||
ok "Backup directory: ${BACKUP_PATH}"
|
||||
}
|
||||
|
||||
backup_target_db() {
|
||||
info "Backing up target database (${TARGET_DB_NAME})..."
|
||||
|
||||
if $DRY_RUN; then
|
||||
info "[DRY RUN] Would dump target database to ${BACKUP_PATH}/db/"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! PGPASSWORD="$TARGET_DB_PASS" pg_dump \
|
||||
-h "$TARGET_DB_HOST" \
|
||||
-p "$TARGET_DB_PORT" \
|
||||
-U "$TARGET_DB_USER" \
|
||||
-d "$TARGET_DB_NAME" \
|
||||
--no-owner --no-privileges --no-acl \
|
||||
-Fc \
|
||||
-f "${BACKUP_PATH}/db/${TARGET}.dump" 2>&1; then
|
||||
warn "Target database backup failed — continuing anyway (target may be empty)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
ok "Target database backed up → ${BACKUP_PATH}/db/${TARGET}.dump"
|
||||
}
|
||||
|
||||
backup_media() {
|
||||
info "Backing up current media..."
|
||||
|
||||
if $DRY_RUN; then
|
||||
info "[DRY RUN] Would backup media to ${BACKUP_PATH}/media/"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -d "$PROJECT_ROOT/media" ]]; then
|
||||
warn "No local media directory to backup"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! tar -czf "${BACKUP_PATH}/media/media.tar.gz" \
|
||||
-C "$PROJECT_ROOT" media 2>&1; then
|
||||
warn "Media backup failed — continuing anyway"
|
||||
return 0
|
||||
fi
|
||||
|
||||
MEDIA_BACKUP_PATH="${BACKUP_PATH}/media/media.tar.gz"
|
||||
|
||||
# Update the "current" symlink
|
||||
rm -f "${BACKUP_DIR}/current-media-backup"
|
||||
ln -s "$BACKUP_PATH" "${BACKUP_DIR}/current-media-backup"
|
||||
|
||||
ok "Media backed up → ${BACKUP_PATH}/media/media.tar.gz"
|
||||
}
|
||||
|
||||
# ─── Database sync ────────────────────────────────────────────────────────────
|
||||
|
||||
sync_database() {
|
||||
info "Syncing database: ${PROD_DB_NAME} → ${TARGET_DB_NAME}"
|
||||
info " Source: ${PROD_DB_HOST}:${PROD_DB_PORT}"
|
||||
info " Target: ${TARGET_DB_HOST}:${TARGET_DB_PORT}"
|
||||
|
||||
if $DRY_RUN; then
|
||||
info "[DRY RUN] Would dump production database and restore to target"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Dumping production database..."
|
||||
local dump_file="${BACKUP_PATH}/db/prod.dump"
|
||||
|
||||
if ! PGPASSWORD="$PROD_DB_PASS" pg_dump \
|
||||
-h "$PROD_DB_HOST" \
|
||||
-p "$PROD_DB_PORT" \
|
||||
-U "$PROD_DB_USER" \
|
||||
-d "$PROD_DB_NAME" \
|
||||
--no-owner --no-privileges --no-acl \
|
||||
-Fc \
|
||||
-f "$dump_file" 2>&1; then
|
||||
die "pg_dump failed. Check production database credentials and connectivity."
|
||||
fi
|
||||
|
||||
local dump_size
|
||||
dump_size=$(du -h "$dump_file" | cut -f1)
|
||||
ok "Production dump complete (${dump_size})"
|
||||
|
||||
info "Recreating target database..."
|
||||
if ! PGPASSWORD="$TARGET_DB_PASS" psql \
|
||||
-h "$TARGET_DB_HOST" \
|
||||
-p "$TARGET_DB_PORT" \
|
||||
-U "$TARGET_DB_USER" \
|
||||
-d postgres \
|
||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${TARGET_DB_NAME}' AND pid <> pg_backend_pid();" \
|
||||
-c "DROP DATABASE IF EXISTS \"${TARGET_DB_NAME}\" WITH (FORCE);" \
|
||||
-c "CREATE DATABASE \"${TARGET_DB_NAME}\" OWNER \"${TARGET_DB_USER}\";" 2>&1; then
|
||||
die "Failed to drop/recreate target database. Check target database permissions."
|
||||
fi
|
||||
|
||||
info "Restoring into target database..."
|
||||
local restore_exit=0
|
||||
PGPASSWORD="$TARGET_DB_PASS" pg_restore \
|
||||
-h "$TARGET_DB_HOST" \
|
||||
-p "$TARGET_DB_PORT" \
|
||||
-U "$TARGET_DB_USER" \
|
||||
-d "$TARGET_DB_NAME" \
|
||||
--no-owner --no-privileges --no-acl \
|
||||
--if-exists --clean \
|
||||
"$dump_file" 2>&1 || restore_exit=$?
|
||||
|
||||
if [[ $restore_exit -gt 1 ]]; then
|
||||
die "pg_restore failed (exit code ${restore_exit}). Check error output above."
|
||||
fi
|
||||
if [[ $restore_exit -eq 1 ]]; then
|
||||
warn "pg_restore completed with warnings (exit code 1) — this is usually fine."
|
||||
fi
|
||||
|
||||
if ! $NO_MIGRATE; then
|
||||
info "Running pending migrations..."
|
||||
if ! DATABASE_URI="$TARGET_DB_URI" bun run payload migrate 2>&1; then
|
||||
warn "Migration step failed — data is restored but schema may be behind dev"
|
||||
else
|
||||
ok "Migrations applied"
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Resetting sequences..."
|
||||
PGPASSWORD="$TARGET_DB_PASS" psql \
|
||||
-h "$TARGET_DB_HOST" \
|
||||
-p "$TARGET_DB_PORT" \
|
||||
-U "$TARGET_DB_USER" \
|
||||
-d "$TARGET_DB_NAME" \
|
||||
-c "SELECT setval(pg_get_serial_sequence(t.table_name, c.column_name), (SELECT COALESCE(MAX(c.column_name::text::bigint), 1) FROM information_schema.columns c JOIN information_schema.tables t ON c.table_name = t.table_name WHERE c.column_name = 'id')) FROM information_schema.columns c JOIN information_schema.tables t ON c.table_name = t.table_name WHERE c.column_name = 'id' AND c.column_default LIKE 'nextval%' AND t.table_schema = 'public';" 2>/dev/null || true
|
||||
|
||||
ok "Database sync complete: ${TARGET_DB_NAME}"
|
||||
}
|
||||
|
||||
# ─── Media sync ───────────────────────────────────────────────────────────────
|
||||
|
||||
sync_media() {
|
||||
if $SKIP_MEDIA; then
|
||||
info "Skipping media sync (--skip-media)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Determine SSH target
|
||||
local ssh_host="${MEDIA_HOST:-${PROD_HOST:-}}"
|
||||
local ssh_user="${MEDIA_USER:-${PROD_USER:-root}}"
|
||||
local container="$PROD_CONTAINER"
|
||||
|
||||
[[ -n "$ssh_host" ]] || die "No media server host. Set MEDIA_HOST or PROD_HOST."
|
||||
|
||||
info "Syncing media from ${ssh_user}@${ssh_host}:${MEDIA_PATH}"
|
||||
|
||||
if $DRY_RUN; then
|
||||
info "[DRY RUN] Would stream /app/media from ${container} via SSH"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Create local media directory if it doesn't exist
|
||||
mkdir -p "$PROJECT_ROOT/media"
|
||||
|
||||
# Stream media from production container via SSH
|
||||
# Using docker exec + tar to read from inside the container (works with
|
||||
# Docker volumes regardless of mount path on the host)
|
||||
info "Streaming media from production container..."
|
||||
if ! ssh -o ConnectTimeout=10 "${ssh_user}@${ssh_host}" \
|
||||
"docker exec ${container} tar -cf - -C /app media" \
|
||||
| tar -xf - -C "$PROJECT_ROOT"; then
|
||||
die "Media sync failed. Check SSH access to ${ssh_host} and container '${container}' exists."
|
||||
fi
|
||||
|
||||
ok "Media sync complete → ${PROJECT_ROOT}/media/"
|
||||
}
|
||||
|
||||
# ─── Summary ──────────────────────────────────────────────────────────────────
|
||||
|
||||
print_summary() {
|
||||
local elapsed=$1
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
if $DRY_RUN; then
|
||||
echo -e " ${YELLOW}DRY RUN COMPLETE${NC} — nothing was changed"
|
||||
else
|
||||
echo -e " ${GREEN}SYNC COMPLETE${NC}"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Target: ${TARGET} (${TARGET_DB_NAME})"
|
||||
echo " Source: production (${PROD_DB_NAME})"
|
||||
if [[ -n "$BACKUP_PATH" ]] && [[ -d "$BACKUP_PATH" ]]; then
|
||||
echo " Backup: ${BACKUP_PATH}"
|
||||
fi
|
||||
echo " Duration: ${elapsed}s"
|
||||
echo ""
|
||||
echo " To rollback:"
|
||||
echo " Database: pg_restore -h $TARGET_DB_HOST -p $TARGET_DB_PORT -U $TARGET_DB_USER -d $TARGET_DB_NAME --clean --if-exists ${BACKUP_PATH}/db/${TARGET}.dump"
|
||||
echo " Media: tar -xf ${BACKUP_PATH}/media/media.tar.gz -C ${PROJECT_ROOT}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
}
|
||||
|
||||
# ─── Backup listing ───────────────────────────────────────────────────────────
|
||||
|
||||
list_backups() {
|
||||
echo "Available backups:"
|
||||
echo ""
|
||||
|
||||
if [[ ! -d "$BACKUP_DIR" ]] || [[ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]]; then
|
||||
echo " No backups found."
|
||||
return 0
|
||||
fi
|
||||
|
||||
for dir in "${BACKUP_DIR}"/*/; do
|
||||
[[ -d "$dir" ]] || continue
|
||||
local name
|
||||
name=$(basename "$dir")
|
||||
local db_size="—"
|
||||
local media_size="—"
|
||||
|
||||
if [[ -f "${dir}db/${TARGET:-dev}.dump" ]]; then
|
||||
db_size=$(du -h "${dir}db/${TARGET:-dev}.dump" | cut -f1)
|
||||
fi
|
||||
if [[ -f "${dir}media/media.tar.gz" ]]; then
|
||||
media_size=$(du -h "${dir}media/media.tar.gz" | cut -f1)
|
||||
fi
|
||||
|
||||
printf " %-20s DB: %-8s Media: %s\n" "$name" "$db_size" "$media_size"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
if $LIST_BACKUPS; then
|
||||
list_backups
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Load environment (not needed for --list-backups or --help)
|
||||
load_env "$SOURCE_FILE"
|
||||
|
||||
# Parse production database URI
|
||||
PROD_DB_USER=""
|
||||
PROD_DB_PASS=""
|
||||
PROD_DB_HOST=""
|
||||
PROD_DB_PORT=""
|
||||
PROD_DB_NAME=""
|
||||
|
||||
if [[ -n "${PROD_DATABASE_URI:-}" ]]; then
|
||||
PROD_DB_USER=$(echo "$PROD_DATABASE_URI" | sed -n 's|.*://\([^:]*\):.*|\1|p')
|
||||
PROD_DB_PASS=$(echo "$PROD_DATABASE_URI" | sed -n 's|.*://[^:]*:\([^@]*\)@.*|\1|p')
|
||||
PROD_DB_HOST=$(echo "$PROD_DATABASE_URI" | sed -n 's|.*@\([^:]*\):.*|\1|p')
|
||||
PROD_DB_PORT=$(echo "$PROD_DATABASE_URI" | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
|
||||
PROD_DB_NAME=$(echo "$PROD_DATABASE_URI" | sed -n 's|.*/\([^?]*\).*|\1|p')
|
||||
fi
|
||||
|
||||
# Validate required production config
|
||||
[[ -n "$PROD_DB_HOST" ]] || die "PROD_DATABASE_URI not set or missing host in $SOURCE_FILE"
|
||||
[[ -n "$PROD_DB_PORT" ]] || die "PROD_DATABASE_URI not set or missing port in $SOURCE_FILE"
|
||||
[[ -n "$PROD_DB_NAME" ]] || die "PROD_DATABASE_URI not set or missing database name in $SOURCE_FILE"
|
||||
[[ -n "$PROD_DB_USER" ]] || die "PROD_DATABASE_URI not set or missing user in $SOURCE_FILE"
|
||||
[[ -n "$PROD_DB_PASS" ]] || die "PROD_DATABASE_URI not set or missing password in $SOURCE_FILE"
|
||||
|
||||
# Validate target
|
||||
[[ -n "${ENV_CONFIG[$TARGET]:-}" ]] || die "Invalid target: $TARGET (must be: dev, stg)"
|
||||
|
||||
# Check PostgreSQL tools
|
||||
for cmd in pg_dump pg_restore psql; do
|
||||
command -v "$cmd" >/dev/null 2>&1 || die "$cmd not found. Install postgresql-client."
|
||||
done
|
||||
|
||||
# Resolve target database URI
|
||||
TARGET_ENV_FILE="${PROJECT_ROOT}/${ENV_CONFIG[$TARGET]}"
|
||||
[[ -f "$TARGET_ENV_FILE" ]] || die "Target env file not found: $TARGET_ENV_FILE"
|
||||
|
||||
TARGET_DB_URI=$(grep -E '^DATABASE_URI=' "$TARGET_ENV_FILE" | head -1 | cut -d'=' -f2-)
|
||||
[[ -n "$TARGET_DB_URI" ]] || die "DATABASE_URI not found in $TARGET_ENV_FILE"
|
||||
|
||||
TARGET_DB_USER=$(echo "$TARGET_DB_URI" | sed -n 's|.*://\([^:]*\):.*|\1|p')
|
||||
TARGET_DB_PASS=$(echo "$TARGET_DB_URI" | sed -n 's|.*://[^:]*:\([^@]*\)@.*|\1|p')
|
||||
TARGET_DB_HOST=$(echo "$TARGET_DB_URI" | sed -n 's|.*@\([^:]*\):.*|\1|p')
|
||||
TARGET_DB_PORT=$(echo "$TARGET_DB_URI" | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
|
||||
TARGET_DB_NAME=$(echo "$TARGET_DB_URI" | sed -n 's|.*/\([^?]*\).*|\1|p')
|
||||
|
||||
TARGET_DB_HOST="${PROD_HOST:-$TARGET_DB_HOST}"
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Polaris Task Force — Production Data Sync"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo " Source: ${PROD_DB_NAME} @ ${PROD_DB_HOST}:${PROD_DB_PORT}"
|
||||
echo " Target: ${TARGET_DB_NAME} @ ${TARGET_DB_HOST}:${TARGET_DB_PORT}"
|
||||
if ! $SKIP_MEDIA; then
|
||||
echo " Media: ${MEDIA_HOST:-${PROD_HOST:-?}} → local media/"
|
||||
else
|
||||
echo " Media: skipped"
|
||||
fi
|
||||
if $DRY_RUN; then
|
||||
echo -e " Mode: ${YELLOW}DRY RUN${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Confirmation prompt
|
||||
if ! $AUTO_YES && ! $DRY_RUN; then
|
||||
echo -e " ${RED}WARNING: This will OVERWRITE the ${TARGET} database and media.${NC}"
|
||||
echo ""
|
||||
read -rp " Continue? [y/N] " confirm
|
||||
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
|
||||
echo ""
|
||||
fi
|
||||
|
||||
local start_time
|
||||
start_time=$(date +%s)
|
||||
|
||||
TS=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
if $BACKUP_ONLY; then
|
||||
setup_backup_dirs
|
||||
backup_target_db
|
||||
backup_media
|
||||
else
|
||||
setup_backup_dirs
|
||||
backup_target_db
|
||||
backup_media
|
||||
sync_database
|
||||
sync_media
|
||||
fi
|
||||
|
||||
local end_time elapsed
|
||||
end_time=$(date +%s)
|
||||
elapsed=$((end_time - start_time))
|
||||
|
||||
print_summary "$elapsed"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in a new issue