docs: update SSH setup instructions in refresh-staging-db.sh
Add detailed step-by-step instructions for setting up SSH key-based authentication from staging to production, including proper directory and file permissions (0700 for .ssh, 0600 for authorized_keys). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
355
scripts/refresh-staging-db.sh
Executable file
355
scripts/refresh-staging-db.sh
Executable file
@@ -0,0 +1,355 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Staging Database Refresh Script for MotoVaultPro
|
||||
# Copies production database to staging (non-interactive)
|
||||
# Usage: ./scripts/refresh-staging-db.sh [options]
|
||||
#
|
||||
# Prerequisites:
|
||||
# SSH key-based access from staging act_runner to production act_runner.
|
||||
#
|
||||
# On STAGING (as act_runner):
|
||||
# ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
|
||||
# cat ~/.ssh/id_ed25519.pub # Copy this output
|
||||
#
|
||||
# On PRODUCTION (as root or sudo):
|
||||
# sudo -u act_runner mkdir -p ~/.ssh
|
||||
# sudo chmod 700 /home/act_runner/.ssh
|
||||
# sudo -u act_runner touch ~/.ssh/authorized_keys
|
||||
# sudo chmod 600 /home/act_runner/.ssh/authorized_keys
|
||||
# echo "PASTE_PUBLIC_KEY_HERE" | sudo tee -a /home/act_runner/.ssh/authorized_keys
|
||||
#
|
||||
# Verify from STAGING:
|
||||
# ssh act_runner@172.30.1.36 echo "SSH OK"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# Server configuration
|
||||
PRODUCTION_HOST="172.30.1.36"
|
||||
PRODUCTION_USER="act_runner"
|
||||
PRODUCTION_CONTAINER="mvp-postgres"
|
||||
|
||||
STAGING_CONTAINER="mvp-postgres-staging"
|
||||
STAGING_BACKEND_CONTAINER="mvp-backend-staging"
|
||||
|
||||
# Database configuration
|
||||
DATABASE_NAME="motovaultpro"
|
||||
DATABASE_USER="postgres"
|
||||
|
||||
# Paths
|
||||
BACKUP_DIR="/tmp/mvp-db-refresh"
|
||||
STAGING_BACKUP="${BACKUP_DIR}/staging_backup_${TIMESTAMP}.sql.gz"
|
||||
PRODUCTION_DUMP="${BACKUP_DIR}/production_dump_${TIMESTAMP}.sql"
|
||||
|
||||
# Options
|
||||
DRY_RUN=false
|
||||
SKIP_BACKUP=false
|
||||
KEEP_DUMP=false
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
print_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
print_dry() {
|
||||
echo -e "${YELLOW}[DRY-RUN]${NC} Would: $1"
|
||||
}
|
||||
|
||||
# Function to show usage
|
||||
show_usage() {
|
||||
cat << EOF
|
||||
Staging Database Refresh Script for MotoVaultPro
|
||||
|
||||
Copies the production database to staging. This script must be run on the
|
||||
staging server and requires SSH access to the production server.
|
||||
|
||||
Usage: $0 [options]
|
||||
|
||||
Options:
|
||||
-h, --help Show this help message
|
||||
--dry-run Show what would happen without making changes
|
||||
--skip-backup Skip staging backup (faster, less safe)
|
||||
--keep-dump Keep the production dump file after import
|
||||
|
||||
Prerequisites:
|
||||
SSH key-based access from staging to production.
|
||||
|
||||
# On STAGING (as act_runner):
|
||||
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
|
||||
cat ~/.ssh/id_ed25519.pub # Copy this output
|
||||
|
||||
# On PRODUCTION (as root or sudo):
|
||||
sudo -u act_runner mkdir -p ~/.ssh
|
||||
sudo chmod 700 /home/act_runner/.ssh
|
||||
sudo -u act_runner touch ~/.ssh/authorized_keys
|
||||
sudo chmod 600 /home/act_runner/.ssh/authorized_keys
|
||||
echo "PASTE_KEY" | sudo tee -a /home/act_runner/.ssh/authorized_keys
|
||||
|
||||
# Verify from STAGING:
|
||||
ssh ${PRODUCTION_USER}@${PRODUCTION_HOST} echo "SSH OK"
|
||||
|
||||
Examples:
|
||||
# Preview what would happen
|
||||
$0 --dry-run
|
||||
|
||||
# Full refresh (recommended)
|
||||
$0
|
||||
|
||||
# Quick refresh without staging backup
|
||||
$0 --skip-backup
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_usage
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--skip-backup)
|
||||
SKIP_BACKUP=true
|
||||
shift
|
||||
;;
|
||||
--keep-dump)
|
||||
KEEP_DUMP=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
show_usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
if [ "$KEEP_DUMP" = false ] && [ -f "$PRODUCTION_DUMP" ]; then
|
||||
rm -f "$PRODUCTION_DUMP"
|
||||
fi
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
print_error "Script failed with exit code $exit_code"
|
||||
if [ -f "$STAGING_BACKUP" ]; then
|
||||
print_warn "Staging backup available at: $STAGING_BACKUP"
|
||||
print_warn "To restore: gunzip -c $STAGING_BACKUP | docker exec -i $STAGING_CONTAINER psql -U $DATABASE_USER -d $DATABASE_NAME"
|
||||
fi
|
||||
fi
|
||||
exit $exit_code
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Header
|
||||
echo ""
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE} MotoVaultPro Staging Database Refresh${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_warn "DRY RUN MODE - No changes will be made"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Step 1: Validate prerequisites
|
||||
print_step "1/8 Validating prerequisites..."
|
||||
|
||||
# Check SSH to production
|
||||
print_info "Testing SSH connection to production..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "ssh ${PRODUCTION_USER}@${PRODUCTION_HOST} echo 'OK'"
|
||||
else
|
||||
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "${PRODUCTION_USER}@${PRODUCTION_HOST}" "echo 'OK'" > /dev/null 2>&1; then
|
||||
print_error "Cannot SSH to production server (${PRODUCTION_USER}@${PRODUCTION_HOST})"
|
||||
print_error "Ensure SSH key is set up. See script header for full instructions."
|
||||
print_error "Quick check: Does ~/.ssh/id_ed25519 exist? Is your public key in production's authorized_keys?"
|
||||
exit 1
|
||||
fi
|
||||
print_info "SSH connection OK"
|
||||
fi
|
||||
|
||||
# Check production container
|
||||
print_info "Checking production PostgreSQL container..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "ssh ${PRODUCTION_USER}@${PRODUCTION_HOST} docker ps --filter name=${PRODUCTION_CONTAINER}"
|
||||
else
|
||||
if ! ssh "${PRODUCTION_USER}@${PRODUCTION_HOST}" "docker ps --format '{{.Names}}' | grep -q '^${PRODUCTION_CONTAINER}$'"; then
|
||||
print_error "Production PostgreSQL container '${PRODUCTION_CONTAINER}' is not running"
|
||||
exit 1
|
||||
fi
|
||||
print_info "Production container OK"
|
||||
fi
|
||||
|
||||
# Check staging container
|
||||
print_info "Checking staging PostgreSQL container..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "docker ps --filter name=${STAGING_CONTAINER}"
|
||||
else
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${STAGING_CONTAINER}$"; then
|
||||
print_error "Staging PostgreSQL container '${STAGING_CONTAINER}' is not running"
|
||||
exit 1
|
||||
fi
|
||||
print_info "Staging container OK"
|
||||
fi
|
||||
|
||||
# Create backup directory
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
# Step 2: Backup staging database
|
||||
print_step "2/8 Backing up staging database..."
|
||||
if [ "$SKIP_BACKUP" = true ]; then
|
||||
print_warn "Skipping staging backup (--skip-backup)"
|
||||
elif [ "$DRY_RUN" = true ]; then
|
||||
print_dry "docker exec ${STAGING_CONTAINER} pg_dump -U ${DATABASE_USER} -d ${DATABASE_NAME} | gzip > ${STAGING_BACKUP}"
|
||||
else
|
||||
docker exec "$STAGING_CONTAINER" pg_dump -U "$DATABASE_USER" -d "$DATABASE_NAME" 2>/dev/null | gzip > "$STAGING_BACKUP"
|
||||
BACKUP_SIZE=$(du -h "$STAGING_BACKUP" | cut -f1)
|
||||
print_info "Staging backup created: $STAGING_BACKUP ($BACKUP_SIZE)"
|
||||
fi
|
||||
|
||||
# Step 3: Export production database via SSH
|
||||
print_step "3/8 Exporting production database..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "ssh ${PRODUCTION_USER}@${PRODUCTION_HOST} docker exec ${PRODUCTION_CONTAINER} pg_dump -U ${DATABASE_USER} -d ${DATABASE_NAME} > ${PRODUCTION_DUMP}"
|
||||
else
|
||||
print_info "Streaming production database (this may take a while)..."
|
||||
ssh "${PRODUCTION_USER}@${PRODUCTION_HOST}" "docker exec ${PRODUCTION_CONTAINER} pg_dump -U ${DATABASE_USER} -d ${DATABASE_NAME}" > "$PRODUCTION_DUMP"
|
||||
DUMP_SIZE=$(du -h "$PRODUCTION_DUMP" | cut -f1)
|
||||
print_info "Production dump received: $PRODUCTION_DUMP ($DUMP_SIZE)"
|
||||
fi
|
||||
|
||||
# Step 4: Stop staging backend
|
||||
print_step "4/8 Stopping staging backend..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "docker stop ${STAGING_BACKEND_CONTAINER}"
|
||||
else
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${STAGING_BACKEND_CONTAINER}$"; then
|
||||
docker stop "$STAGING_BACKEND_CONTAINER" > /dev/null
|
||||
print_info "Staging backend stopped"
|
||||
else
|
||||
print_warn "Staging backend not running, skipping stop"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 5: Drop and recreate staging database
|
||||
print_step "5/8 Recreating staging database..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "docker exec ${STAGING_CONTAINER} psql -U ${DATABASE_USER} -c 'DROP DATABASE IF EXISTS ${DATABASE_NAME}'"
|
||||
print_dry "docker exec ${STAGING_CONTAINER} psql -U ${DATABASE_USER} -c 'CREATE DATABASE ${DATABASE_NAME}'"
|
||||
else
|
||||
# Terminate existing connections
|
||||
docker exec "$STAGING_CONTAINER" psql -U "$DATABASE_USER" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${DATABASE_NAME}' AND pid <> pg_backend_pid();" > /dev/null 2>&1 || true
|
||||
|
||||
# Drop and create
|
||||
docker exec "$STAGING_CONTAINER" psql -U "$DATABASE_USER" -c "DROP DATABASE IF EXISTS ${DATABASE_NAME}" > /dev/null
|
||||
docker exec "$STAGING_CONTAINER" psql -U "$DATABASE_USER" -c "CREATE DATABASE ${DATABASE_NAME}" > /dev/null
|
||||
print_info "Staging database recreated"
|
||||
fi
|
||||
|
||||
# Step 6: Import production dump
|
||||
print_step "6/8 Importing production data..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "docker exec -i ${STAGING_CONTAINER} psql -U ${DATABASE_USER} -d ${DATABASE_NAME} < ${PRODUCTION_DUMP}"
|
||||
else
|
||||
print_info "Importing data (this may take a while)..."
|
||||
# Import with error suppression for non-critical issues (like role doesn't exist)
|
||||
docker exec -i "$STAGING_CONTAINER" psql -U "$DATABASE_USER" -d "$DATABASE_NAME" < "$PRODUCTION_DUMP" 2>&1 | grep -v "^ERROR: role" || true
|
||||
print_info "Import complete"
|
||||
fi
|
||||
|
||||
# Step 7: Restart staging backend
|
||||
print_step "7/8 Restarting staging backend..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "docker start ${STAGING_BACKEND_CONTAINER}"
|
||||
else
|
||||
docker start "$STAGING_BACKEND_CONTAINER" > /dev/null 2>&1 || true
|
||||
print_info "Staging backend started"
|
||||
|
||||
# Wait for backend to be healthy
|
||||
print_info "Waiting for backend to be ready..."
|
||||
for i in {1..30}; do
|
||||
if docker exec "$STAGING_BACKEND_CONTAINER" curl -sf http://localhost:3000/api/health > /dev/null 2>&1; then
|
||||
print_info "Backend is healthy"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 30 ]; then
|
||||
print_warn "Backend health check timed out (may still be starting)"
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
|
||||
# Step 8: Verify and cleanup
|
||||
print_step "8/8 Verifying refresh..."
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry "docker exec ${STAGING_CONTAINER} psql -U ${DATABASE_USER} -d ${DATABASE_NAME} -c 'SELECT COUNT(*) FROM information_schema.tables'"
|
||||
else
|
||||
# Get table count
|
||||
TABLE_COUNT=$(docker exec "$STAGING_CONTAINER" psql -U "$DATABASE_USER" -d "$DATABASE_NAME" -tAc "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public'")
|
||||
|
||||
# Get row counts for key tables
|
||||
print_info "Database statistics:"
|
||||
echo ""
|
||||
docker exec "$STAGING_CONTAINER" psql -U "$DATABASE_USER" -d "$DATABASE_NAME" -c "
|
||||
SELECT
|
||||
schemaname || '.' || relname AS table,
|
||||
n_live_tup AS rows
|
||||
FROM pg_stat_user_tables
|
||||
ORDER BY n_live_tup DESC
|
||||
LIMIT 10;
|
||||
" 2>/dev/null || true
|
||||
echo ""
|
||||
|
||||
# Cleanup dump file
|
||||
if [ "$KEEP_DUMP" = false ]; then
|
||||
rm -f "$PRODUCTION_DUMP"
|
||||
print_info "Production dump cleaned up"
|
||||
else
|
||||
print_info "Production dump kept at: $PRODUCTION_DUMP"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} Staging Database Refresh Complete!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_warn "This was a dry run. No changes were made."
|
||||
else
|
||||
print_info "Production data has been copied to staging"
|
||||
print_info "Tables: $TABLE_COUNT"
|
||||
if [ "$SKIP_BACKUP" = false ] && [ -f "$STAGING_BACKUP" ]; then
|
||||
print_info "Previous staging backup: $STAGING_BACKUP"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
Reference in New Issue
Block a user