| | #!/bin/bash |
| |
|
| | |
| | |
| |
|
| | set -e |
| |
|
| | |
| | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| | source "$SCRIPT_DIR/deployment-utils.sh" |
| |
|
| | |
| | ALEMBIC_DIR="alembic" |
| | BACKUP_DIR="backups/migrations" |
| |
|
| | |
| | mkdir -p "$BACKUP_DIR" |
| |
|
| | |
| | backup_database() { |
| | local database_url=$1 |
| | local backup_name=${2:-"pre_migration_$(date +%Y%m%d_%H%M%S)"} |
| | |
| | log "Creating database backup before migration..." |
| | |
| | if [[ "$database_url" == sqlite* ]]; then |
| | |
| | local db_file=$(echo "$database_url" | sed 's/sqlite:\/\/\///') |
| | if [ -f "$db_file" ]; then |
| | cp "$db_file" "$BACKUP_DIR/${backup_name}.db" |
| | gzip "$BACKUP_DIR/${backup_name}.db" |
| | success "SQLite database backed up to $BACKUP_DIR/${backup_name}.db.gz" |
| | else |
| | warning "SQLite database file not found: $db_file" |
| | fi |
| | elif [[ "$database_url" == postgresql* ]]; then |
| | |
| | if command -v pg_dump &> /dev/null; then |
| | pg_dump "$database_url" | gzip > "$BACKUP_DIR/${backup_name}.sql.gz" |
| | success "PostgreSQL database backed up to $BACKUP_DIR/${backup_name}.sql.gz" |
| | else |
| | warning "pg_dump not available, skipping PostgreSQL backup" |
| | fi |
| | else |
| | warning "Unknown database type, skipping backup" |
| | fi |
| | } |
| |
|
| | |
| | check_migration_status() { |
| | local database_url=$1 |
| | |
| | log "Checking current migration status..." |
| | |
| | export DATABASE_URL="$database_url" |
| | |
| | if [ -d "$ALEMBIC_DIR" ] && command -v alembic &> /dev/null; then |
| | local current_revision |
| | current_revision=$(alembic current 2>/dev/null | grep -o '[a-f0-9]\{12\}' | head -1 || echo "none") |
| | |
| | local head_revision |
| | head_revision=$(alembic heads 2>/dev/null | grep -o '[a-f0-9]\{12\}' | head -1 || echo "unknown") |
| | |
| | info "Current revision: $current_revision" |
| | info "Head revision: $head_revision" |
| | |
| | if [ "$current_revision" = "$head_revision" ]; then |
| | success "Database is up to date" |
| | return 0 |
| | elif [ "$current_revision" = "none" ]; then |
| | warning "Database has no migration history" |
| | return 1 |
| | else |
| | warning "Database needs migration" |
| | return 1 |
| | fi |
| | else |
| | warning "Alembic not available or not configured" |
| | return 1 |
| | fi |
| | } |
| |
|
| | |
| | run_migrations() { |
| | local database_url=$1 |
| | local target_revision=${2:-"head"} |
| | |
| | log "Running database migrations to $target_revision..." |
| | |
| | export DATABASE_URL="$database_url" |
| | |
| | if [ ! -d "$ALEMBIC_DIR" ]; then |
| | error "Alembic directory not found: $ALEMBIC_DIR" |
| | return 1 |
| | fi |
| | |
| | if ! command -v alembic &> /dev/null; then |
| | error "Alembic not installed. Install with: pip install alembic" |
| | return 1 |
| | fi |
| | |
| | |
| | if ! check_database_connection "$database_url"; then |
| | error "Cannot connect to database" |
| | return 1 |
| | fi |
| | |
| | |
| | alembic upgrade "$target_revision" |
| | success "Database migrations completed" |
| | } |
| |
|
| | |
| | check_database_connection() { |
| | local database_url=$1 |
| | |
| | if [[ "$database_url" == sqlite* ]]; then |
| | |
| | local db_file=$(echo "$database_url" | sed 's/sqlite:\/\/\///') |
| | local db_dir=$(dirname "$db_file") |
| | |
| | |
| | mkdir -p "$db_dir" |
| | |
| | |
| | if sqlite3 "$db_file" "SELECT 1;" &> /dev/null; then |
| | return 0 |
| | else |
| | return 1 |
| | fi |
| | elif [[ "$database_url" == postgresql* ]]; then |
| | |
| | if command -v psql &> /dev/null; then |
| | if psql "$database_url" -c "SELECT 1;" &> /dev/null; then |
| | return 0 |
| | else |
| | return 1 |
| | fi |
| | else |
| | warning "psql not available, cannot verify PostgreSQL connection" |
| | return 1 |
| | fi |
| | else |
| | warning "Unknown database type, cannot verify connection" |
| | return 1 |
| | fi |
| | } |
| |
|
| | |
| | initialize_database() { |
| | local database_url=$1 |
| | |
| | log "Initializing database..." |
| | |
| | export DATABASE_URL="$database_url" |
| | |
| | |
| | if check_migration_status "$database_url"; then |
| | info "Database is already initialized and up to date" |
| | return 0 |
| | fi |
| | |
| | |
| | local has_tables=false |
| | if [[ "$database_url" == sqlite* ]]; then |
| | local db_file=$(echo "$database_url" | sed 's/sqlite:\/\/\///') |
| | if [ -f "$db_file" ]; then |
| | local table_count |
| | table_count=$(sqlite3 "$db_file" "SELECT COUNT(*) FROM sqlite_master WHERE type='table';" 2>/dev/null || echo "0") |
| | if [ "$table_count" -gt 0 ]; then |
| | has_tables=true |
| | fi |
| | fi |
| | elif [[ "$database_url" == postgresql* ]]; then |
| | if command -v psql &> /dev/null; then |
| | local table_count |
| | table_count=$(psql "$database_url" -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public';" 2>/dev/null | tr -d ' ' || echo "0") |
| | if [ "$table_count" -gt 0 ]; then |
| | has_tables=true |
| | fi |
| | fi |
| | fi |
| | |
| | if [ "$has_tables" = true ]; then |
| | warning "Database has existing tables. This might be a partially initialized database." |
| | echo "Options:" |
| | echo "1) Run migrations anyway (recommended)" |
| | echo "2) Skip initialization" |
| | echo "3) Reset database (DANGEROUS - will delete all data)" |
| | read -p "Choose option (1-3): " choice |
| | |
| | case $choice in |
| | 1) |
| | run_migrations "$database_url" |
| | ;; |
| | 2) |
| | info "Skipping database initialization" |
| | return 0 |
| | ;; |
| | 3) |
| | warning "This will delete all data in the database!" |
| | read -p "Are you sure? Type 'yes' to confirm: " confirm |
| | if [ "$confirm" = "yes" ]; then |
| | reset_database "$database_url" |
| | run_migrations "$database_url" |
| | else |
| | info "Database reset cancelled" |
| | return 1 |
| | fi |
| | ;; |
| | *) |
| | error "Invalid choice" |
| | return 1 |
| | ;; |
| | esac |
| | else |
| | |
| | run_migrations "$database_url" |
| | fi |
| | } |
| |
|
| | |
| | reset_database() { |
| | local database_url=$1 |
| | |
| | warning "Resetting database - this will delete all data!" |
| | |
| | if [[ "$database_url" == sqlite* ]]; then |
| | local db_file=$(echo "$database_url" | sed 's/sqlite:\/\/\///') |
| | if [ -f "$db_file" ]; then |
| | rm "$db_file" |
| | success "SQLite database file deleted" |
| | fi |
| | elif [[ "$database_url" == postgresql* ]]; then |
| | if command -v psql &> /dev/null; then |
| | |
| | psql "$database_url" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" |
| | success "PostgreSQL database reset" |
| | else |
| | error "psql not available, cannot reset PostgreSQL database" |
| | return 1 |
| | fi |
| | else |
| | error "Cannot reset unknown database type" |
| | return 1 |
| | fi |
| | } |
| |
|
| | |
| | create_migration() { |
| | local message=$1 |
| | |
| | if [ -z "$message" ]; then |
| | error "Migration message is required" |
| | return 1 |
| | fi |
| | |
| | log "Creating new migration: $message" |
| | |
| | if [ ! -d "$ALEMBIC_DIR" ]; then |
| | error "Alembic directory not found: $ALEMBIC_DIR" |
| | return 1 |
| | fi |
| | |
| | if ! command -v alembic &> /dev/null; then |
| | error "Alembic not installed" |
| | return 1 |
| | fi |
| | |
| | alembic revision --autogenerate -m "$message" |
| | success "Migration created successfully" |
| | } |
| |
|
| | |
| | rollback_migration() { |
| | local database_url=$1 |
| | local target_revision=${2:-"-1"} |
| | |
| | log "Rolling back migration to $target_revision..." |
| | |
| | export DATABASE_URL="$database_url" |
| | |
| | |
| | backup_database "$database_url" "pre_rollback_$(date +%Y%m%d_%H%M%S)" |
| | |
| | |
| | alembic downgrade "$target_revision" |
| | success "Migration rollback completed" |
| | } |
| |
|
| | |
| | show_migration_history() { |
| | local database_url=$1 |
| | |
| | export DATABASE_URL="$database_url" |
| | |
| | log "Migration history:" |
| | |
| | if [ -d "$ALEMBIC_DIR" ] && command -v alembic &> /dev/null; then |
| | alembic history --verbose |
| | else |
| | warning "Alembic not available" |
| | fi |
| | } |
| |
|
| | |
| | validate_migrations() { |
| | log "Validating migration files..." |
| | |
| | if [ ! -d "$ALEMBIC_DIR/versions" ]; then |
| | warning "No migration files found" |
| | return 0 |
| | fi |
| | |
| | local migration_count |
| | migration_count=$(find "$ALEMBIC_DIR/versions" -name "*.py" -not -name "__*" | wc -l) |
| | |
| | info "Found $migration_count migration files" |
| | |
| | |
| | local errors=0 |
| | for migration_file in "$ALEMBIC_DIR/versions"/*.py; do |
| | if [ -f "$migration_file" ]; then |
| | if ! python -m py_compile "$migration_file" 2>/dev/null; then |
| | error "Syntax error in migration file: $migration_file" |
| | errors=$((errors + 1)) |
| | fi |
| | fi |
| | done |
| | |
| | if [ $errors -eq 0 ]; then |
| | success "All migration files are valid" |
| | return 0 |
| | else |
| | error "$errors migration files have syntax errors" |
| | return 1 |
| | fi |
| | } |
| |
|
| | |
| | main() { |
| | local action="" |
| | local database_url="" |
| | local message="" |
| | local target="" |
| | local env_file="" |
| | |
| | |
| | while [[ $# -gt 0 ]]; do |
| | case $1 in |
| | init|initialize) |
| | action="init" |
| | shift |
| | ;; |
| | migrate|upgrade) |
| | action="migrate" |
| | shift |
| | ;; |
| | rollback|downgrade) |
| | action="rollback" |
| | shift |
| | ;; |
| | status) |
| | action="status" |
| | shift |
| | ;; |
| | history) |
| | action="history" |
| | shift |
| | ;; |
| | create) |
| | action="create" |
| | shift |
| | ;; |
| | reset) |
| | action="reset" |
| | shift |
| | ;; |
| | validate) |
| | action="validate" |
| | shift |
| | ;; |
| | backup) |
| | action="backup" |
| | shift |
| | ;; |
| | --database-url) |
| | database_url="$2" |
| | shift 2 |
| | ;; |
| | --env-file) |
| | env_file="$2" |
| | shift 2 |
| | ;; |
| | --message) |
| | message="$2" |
| | shift 2 |
| | ;; |
| | --target) |
| | target="$2" |
| | shift 2 |
| | ;; |
| | --help) |
| | echo "Database Migration Utility for Knowledge Assistant RAG" |
| | echo "" |
| | echo "Usage: $0 ACTION [OPTIONS]" |
| | echo "" |
| | echo "Actions:" |
| | echo " init Initialize database with migrations" |
| | echo " migrate Run pending migrations" |
| | echo " rollback Rollback last migration" |
| | echo " status Show current migration status" |
| | echo " history Show migration history" |
| | echo " create Create new migration" |
| | echo " reset Reset database (DANGEROUS)" |
| | echo " validate Validate migration files" |
| | echo " backup Create database backup" |
| | echo "" |
| | echo "Options:" |
| | echo " --database-url URL Database connection URL" |
| | echo " --env-file FILE Environment file to load" |
| | echo " --message MSG Migration message (for create)" |
| | echo " --target REV Target revision (for migrate/rollback)" |
| | echo " --help Show this help" |
| | echo "" |
| | exit 0 |
| | ;; |
| | *) |
| | error "Unknown option: $1" |
| | exit 1 |
| | ;; |
| | esac |
| | done |
| | |
| | if [ -z "$action" ]; then |
| | error "Action is required. Use --help for usage information." |
| | exit 1 |
| | fi |
| | |
| | |
| | if [ -n "$env_file" ] && [ -f "$env_file" ]; then |
| | source "$env_file" |
| | elif [ -f ".env" ]; then |
| | source ".env" |
| | fi |
| | |
| | |
| | database_url=${database_url:-$DATABASE_URL} |
| | |
| | if [ -z "$database_url" ]; then |
| | error "Database URL is required. Set DATABASE_URL environment variable or use --database-url" |
| | exit 1 |
| | fi |
| | |
| | log "Using database: $database_url" |
| | |
| | |
| | case $action in |
| | init) |
| | initialize_database "$database_url" |
| | ;; |
| | migrate) |
| | run_migrations "$database_url" "${target:-head}" |
| | ;; |
| | rollback) |
| | rollback_migration "$database_url" "${target:--1}" |
| | ;; |
| | status) |
| | check_migration_status "$database_url" |
| | ;; |
| | history) |
| | show_migration_history "$database_url" |
| | ;; |
| | create) |
| | if [ -z "$message" ]; then |
| | error "Migration message is required for create action. Use --message" |
| | exit 1 |
| | fi |
| | create_migration "$message" |
| | ;; |
| | reset) |
| | reset_database "$database_url" |
| | ;; |
| | validate) |
| | validate_migrations |
| | ;; |
| | backup) |
| | backup_database "$database_url" |
| | ;; |
| | *) |
| | error "Unknown action: $action" |
| | exit 1 |
| | ;; |
| | esac |
| | } |
| |
|
| | |
| | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then |
| | main "$@" |
| | fi |