- Add bash scripts for automated database backup and restore - Support both full and data-only backups - Add npm scripts for easy database management - Add backups/ directory to .gitignore - Include documentation for backup procedures 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
256 lines
No EOL
7.9 KiB
Bash
Executable file
256 lines
No EOL
7.9 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# Database Backup Script
|
|
# Usage: ./scripts/backup-db.sh [local|remote|sync]
|
|
# local - Backup local database
|
|
# remote - Backup remote database
|
|
# sync - Copy remote database to local
|
|
|
|
set -e
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Load environment variables
|
|
if [ -f ".env" ]; then
|
|
set -a
|
|
source .env
|
|
set +a
|
|
fi
|
|
|
|
if [ -f ".env.local" ]; then
|
|
set -a
|
|
source .env.local
|
|
set +a
|
|
fi
|
|
|
|
# Check if required environment variables are set
|
|
if [ -z "$DATABASE_URL" ]; then
|
|
echo -e "${RED}Error: DATABASE_URL is not set${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Parse DATABASE_URL for local database
|
|
# Format: postgresql://user:password@host:port/database
|
|
LOCAL_DB_URL=$DATABASE_URL
|
|
LOCAL_DB_NAME=$(echo $LOCAL_DB_URL | sed -E 's/.*\/([^?]+).*/\1/')
|
|
LOCAL_DB_USER=$(echo $LOCAL_DB_URL | sed -E 's/postgresql:\/\/([^:]+):.*/\1/')
|
|
LOCAL_DB_HOST=$(echo $LOCAL_DB_URL | sed -E 's/.*@([^:]+):.*/\1/')
|
|
LOCAL_DB_PORT=$(echo $LOCAL_DB_URL | sed -E 's/.*:([0-9]+)\/.*/\1/')
|
|
|
|
# Remote database URL (can be set as REMOTE_DATABASE_URL or passed as env var)
|
|
REMOTE_DB_URL=${REMOTE_DATABASE_URL:-$DATABASE_URL_PRODUCTION}
|
|
|
|
if [ -z "$REMOTE_DB_URL" ] && [ "$1" != "local" ]; then
|
|
echo -e "${YELLOW}Warning: REMOTE_DATABASE_URL or DATABASE_URL_PRODUCTION not set${NC}"
|
|
echo "For remote operations, set one of these environment variables or pass it:"
|
|
echo "REMOTE_DATABASE_URL='postgresql://...' ./scripts/backup-db.sh remote"
|
|
fi
|
|
|
|
# Create backups directory if it doesn't exist
|
|
BACKUP_DIR="./backups"
|
|
mkdir -p $BACKUP_DIR
|
|
|
|
# Generate timestamp for backup files
|
|
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
|
|
|
# Function to parse database URL
|
|
parse_db_url() {
|
|
local url=$1
|
|
# Debug: Show input URL
|
|
>&2 echo "Debug - Input URL: $url"
|
|
|
|
# postgresql://user:password@host:port/database
|
|
# Remove the postgresql:// prefix
|
|
local stripped=$(echo $url | sed 's|postgresql://||')
|
|
>&2 echo "Debug - Stripped: $stripped"
|
|
|
|
# Extract user:password@host:port/database
|
|
local user_pass=$(echo $stripped | cut -d@ -f1)
|
|
local host_port_db=$(echo $stripped | cut -d@ -f2)
|
|
>&2 echo "Debug - User/Pass: $user_pass"
|
|
>&2 echo "Debug - Host/Port/DB: $host_port_db"
|
|
|
|
# Extract user and password
|
|
local db_user=$(echo $user_pass | cut -d: -f1)
|
|
local db_password=$(echo $user_pass | cut -d: -f2)
|
|
|
|
# Extract host, port, and database
|
|
local host_port=$(echo $host_port_db | cut -d/ -f1)
|
|
local db_name=$(echo $host_port_db | cut -d/ -f2 | cut -d? -f1)
|
|
|
|
# Extract host and port
|
|
local db_host=$(echo $host_port | cut -d: -f1)
|
|
local db_port=$(echo $host_port | cut -d: -f2)
|
|
|
|
>&2 echo "Debug - Final parsed: host=$db_host, port=$db_port, db=$db_name, user=$db_user"
|
|
|
|
echo "$db_host|$db_port|$db_name|$db_user|$db_password"
|
|
}
|
|
|
|
# Function to backup database
|
|
backup_database() {
|
|
local db_url=$1
|
|
local backup_name=$2
|
|
local description=$3
|
|
|
|
echo -e "${GREEN}Starting backup: $description${NC}"
|
|
|
|
# Parse database URL
|
|
local parsed_url=$(parse_db_url "$db_url")
|
|
IFS='|' read -r db_host db_port db_name db_user db_password <<< "$parsed_url"
|
|
|
|
# Create backup filename
|
|
local backup_file="${BACKUP_DIR}/${backup_name}_${TIMESTAMP}.sql"
|
|
|
|
# Set PGPASSWORD to avoid password prompt
|
|
export PGPASSWORD=$db_password
|
|
|
|
# Debug: Show parsed values
|
|
echo "Debug - Parsed values:"
|
|
echo " Host: '$db_host'"
|
|
echo " Port: '$db_port'"
|
|
echo " Database: '$db_name'"
|
|
echo " User: '$db_user'"
|
|
|
|
# Run pg_dump
|
|
echo "Backing up database: $db_name from $db_host:$db_port"
|
|
pg_dump -h "$db_host" -p "$db_port" -U "$db_user" -d "$db_name" -f "$backup_file" --verbose --no-owner --no-acl
|
|
|
|
# Compress the backup
|
|
echo "Compressing backup..."
|
|
gzip $backup_file
|
|
|
|
unset PGPASSWORD
|
|
|
|
echo -e "${GREEN}Backup completed: ${backup_file}.gz${NC}"
|
|
echo "Size: $(ls -lh ${backup_file}.gz | awk '{print $5}')"
|
|
}
|
|
|
|
# Function to restore database
|
|
restore_database() {
|
|
local backup_file=$1
|
|
local target_db_url=$2
|
|
local description=$3
|
|
|
|
echo -e "${GREEN}Starting restore: $description${NC}"
|
|
|
|
# Parse database URL
|
|
local parsed_url=$(parse_db_url "$target_db_url")
|
|
IFS='|' read -r db_host db_port db_name db_user db_password <<< "$parsed_url"
|
|
|
|
# Set PGPASSWORD to avoid password prompt
|
|
export PGPASSWORD=$db_password
|
|
|
|
# Drop and recreate database
|
|
echo -e "${YELLOW}Warning: This will drop and recreate the database: $db_name${NC}"
|
|
echo -n "Are you sure you want to continue? (y/N): "
|
|
read confirm
|
|
|
|
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
|
echo "Restore cancelled"
|
|
return
|
|
fi
|
|
|
|
# Drop existing connections
|
|
echo "Dropping existing connections..."
|
|
psql -h $db_host -p $db_port -U $db_user -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$db_name' AND pid <> pg_backend_pid();" 2>/dev/null || true
|
|
|
|
# Drop and recreate database
|
|
echo "Dropping database..."
|
|
psql -h $db_host -p $db_port -U $db_user -d postgres -c "DROP DATABASE IF EXISTS $db_name;"
|
|
|
|
echo "Creating database..."
|
|
psql -h $db_host -p $db_port -U $db_user -d postgres -c "CREATE DATABASE $db_name;"
|
|
|
|
# Decompress if needed
|
|
if [[ $backup_file == *.gz ]]; then
|
|
echo "Decompressing backup..."
|
|
gunzip -c $backup_file > ${backup_file%.gz}
|
|
backup_file=${backup_file%.gz}
|
|
temp_file=true
|
|
fi
|
|
|
|
# Restore database
|
|
echo "Restoring database..."
|
|
psql -h $db_host -p $db_port -U $db_user -d $db_name -f $backup_file
|
|
|
|
# Clean up temp file
|
|
if [ "$temp_file" = true ]; then
|
|
rm $backup_file
|
|
fi
|
|
|
|
unset PGPASSWORD
|
|
|
|
# Run Prisma migrations to ensure schema is up to date
|
|
echo "Running Prisma migrations..."
|
|
npm run db:deploy
|
|
|
|
echo -e "${GREEN}Restore completed${NC}"
|
|
}
|
|
|
|
# Function to sync remote to local
|
|
sync_remote_to_local() {
|
|
if [ -z "$REMOTE_DB_URL" ]; then
|
|
echo -e "${RED}Error: REMOTE_DATABASE_URL is not set${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}Syncing remote database to local${NC}"
|
|
|
|
# First, backup the local database
|
|
echo "Creating backup of local database first..."
|
|
backup_database "$LOCAL_DB_URL" "local_before_sync" "Local database (before sync)"
|
|
|
|
# Backup remote database
|
|
backup_database "$REMOTE_DB_URL" "remote_for_sync" "Remote database"
|
|
|
|
# Find the latest remote backup
|
|
latest_remote_backup=$(ls -t ${BACKUP_DIR}/remote_for_sync_*.sql.gz | head -1)
|
|
|
|
# Restore remote backup to local
|
|
restore_database "$latest_remote_backup" "$LOCAL_DB_URL" "Remote database to local"
|
|
}
|
|
|
|
# Main script logic
|
|
case "$1" in
|
|
"local")
|
|
backup_database "$LOCAL_DB_URL" "local" "Local database"
|
|
;;
|
|
"remote")
|
|
if [ -z "$REMOTE_DB_URL" ]; then
|
|
echo -e "${RED}Error: REMOTE_DATABASE_URL is not set${NC}"
|
|
exit 1
|
|
fi
|
|
backup_database "$REMOTE_DB_URL" "remote" "Remote database"
|
|
;;
|
|
"sync")
|
|
sync_remote_to_local
|
|
;;
|
|
*)
|
|
echo "Database Backup Utility"
|
|
echo ""
|
|
echo "Usage: $0 [local|remote|sync]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " local - Backup local database"
|
|
echo " remote - Backup remote database"
|
|
echo " sync - Copy remote database to local (backs up both first)"
|
|
echo ""
|
|
echo "Environment variables:"
|
|
echo " DATABASE_URL - Local database connection URL (required)"
|
|
echo " REMOTE_DATABASE_URL - Remote database connection URL"
|
|
echo " DATABASE_URL_PRODUCTION - Alternative remote database URL"
|
|
echo ""
|
|
echo "Backups are stored in: ./backups/"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# List recent backups
|
|
echo ""
|
|
echo "Recent backups:"
|
|
ls -lht $BACKUP_DIR/*.sql.gz 2>/dev/null | head -5 || echo "No backups found" |