From 22dafe76542b880036142242a9f1864be20cdf2c Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Mon, 10 Nov 2025 15:53:23 +0300 Subject: [PATCH] feat: enhance update script with argument parsing and logging improvements --- .gitignore | 1 + update.sh | 716 ++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 598 insertions(+), 119 deletions(-) diff --git a/.gitignore b/.gitignore index 1976d0b..461670e 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ build/ *.bak backup/ backups/ +TEST_RESULTS.md diff --git a/update.sh b/update.sh index a8fdcef..ece65b9 100755 --- a/update.sh +++ b/update.sh @@ -1,12 +1,15 @@ #!/bin/bash # Amnezia VPN Panel - Auto Update Script -# Usage: ./update.sh +# Version: 2.0 +# Usage: ./update.sh [--force] [--skip-backup] [--rollback] set -e # Exit on error +set -u # Exit on undefined variable +set -o pipefail # Exit on pipe failure echo "==========================================" -echo " Amnezia VPN Panel - Auto Update" +echo " Amnezia VPN Panel - Auto Update v2.0" echo "==========================================" echo "" @@ -14,187 +17,662 @@ echo "" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' +BLUE='\033[0;34m' NC='\033[0m' # No Color -# Auto-detect docker compose command -if command -v docker-compose &> /dev/null; then - DOCKER_COMPOSE="docker-compose" -elif docker compose version &> /dev/null; then - DOCKER_COMPOSE="docker compose" -else - echo -e "${RED}Error: Neither 'docker compose' nor 'docker-compose' found${NC}" +# Parse arguments +FORCE_UPDATE=0 +SKIP_BACKUP=0 +ROLLBACK_MODE=0 +ROLLBACK_VERSION="" + +for arg in "$@"; do + case $arg in + --force) FORCE_UPDATE=1 ;; + --skip-backup) SKIP_BACKUP=1 ;; + --rollback) ROLLBACK_MODE=1 ;; + --rollback=*) + ROLLBACK_MODE=1 + ROLLBACK_VERSION="${arg#*=}" + ;; + --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --force Force update even if already up to date" + echo " --skip-backup Skip database backup (not recommended)" + echo " --rollback Rollback to previous backup (interactive)" + echo " --rollback=TIMESTAMP Rollback to specific backup (e.g., 20251110_120000)" + echo " --help Show this help message" + echo "" + exit 0 + ;; + *) + echo -e "${RED}Unknown option: $arg${NC}" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +# Log file +LOG_DIR="logs" +mkdir -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/update_$(date +%Y%m%d_%H%M%S).log" + +# Logging function +log() { + echo -e "$1" | tee -a "$LOG_FILE" +} + +log_error() { + log "${RED}✗ ERROR: $1${NC}" +} + +log_success() { + log "${GREEN}✓ $1${NC}" +} + +log_warning() { + log "${YELLOW}⚠ WARNING: $1${NC}" +} + +log_info() { + log "${BLUE}ℹ $1${NC}" +} + +# Error handler +error_exit() { + log_error "$1" + log_info "Check log file: $LOG_FILE" exit 1 +} + +# Trap errors +trap 'error_exit "Script failed at line $LINENO"' ERR + +log_info "Update started at $(date)" +log_info "Log file: $LOG_FILE" + +# ========================================== +# ROLLBACK MODE +# ========================================== +if [ $ROLLBACK_MODE -eq 1 ]; then + log "" + log "${YELLOW}==========================================" + log " ROLLBACK MODE" + log "==========================================${NC}" + log "" + + BACKUP_DIR="backups" + + # Check if backups directory exists + if [ ! -d "$BACKUP_DIR" ]; then + error_exit "Backups directory not found: $BACKUP_DIR" + fi + + # List available backups + DB_BACKUPS=$(ls -t "$BACKUP_DIR"/db_backup_*.sql 2>/dev/null || echo "") + + if [ -z "$DB_BACKUPS" ]; then + error_exit "No backups found in $BACKUP_DIR" + fi + + log_info "Available backups:" + echo "" + + # Create array of backups + BACKUP_LIST=() + INDEX=1 + for backup in $DB_BACKUPS; do + BACKUP_FILE=$(basename "$backup") + TIMESTAMP_EXTRACTED=$(echo "$BACKUP_FILE" | grep -o '[0-9]\{8\}_[0-9]\{6\}') + BACKUP_DATE=$(echo "$TIMESTAMP_EXTRACTED" | sed 's/_/ /' | sed 's/\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\) \([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)/\1-\2-\3 \4:\5:\6/') + BACKUP_SIZE=$(du -h "$backup" | cut -f1) + + echo " [$INDEX] $BACKUP_DATE ($BACKUP_SIZE)" + BACKUP_LIST+=("$TIMESTAMP_EXTRACTED") + INDEX=$((INDEX + 1)) + done + + echo "" + + # Select backup + if [ -n "$ROLLBACK_VERSION" ]; then + # Use specified version + SELECTED_TIMESTAMP="$ROLLBACK_VERSION" + log_info "Using specified backup: $SELECTED_TIMESTAMP" + else + # Interactive selection + echo -n "Select backup number to rollback (1-$((INDEX-1))) or 'q' to quit: " + read -r SELECTION + + if [ "$SELECTION" = "q" ] || [ "$SELECTION" = "Q" ]; then + log_info "Rollback cancelled by user" + exit 0 + fi + + if ! [[ "$SELECTION" =~ ^[0-9]+$ ]] || [ "$SELECTION" -lt 1 ] || [ "$SELECTION" -ge $INDEX ]; then + error_exit "Invalid selection: $SELECTION" + fi + + SELECTED_TIMESTAMP="${BACKUP_LIST[$((SELECTION-1))]}" + fi + + # Verify backup files exist + DB_BACKUP_FILE="$BACKUP_DIR/db_backup_${SELECTED_TIMESTAMP}.sql" + COMMIT_BACKUP_FILE="$BACKUP_DIR/commit_backup_${SELECTED_TIMESTAMP}.txt" + ENV_BACKUP_FILE="$BACKUP_DIR/.env_backup_${SELECTED_TIMESTAMP}" + + if [ ! -f "$DB_BACKUP_FILE" ]; then + error_exit "Database backup not found: $DB_BACKUP_FILE" + fi + + log "" + log_warning "You are about to rollback to: $SELECTED_TIMESTAMP" + log_warning "This will:" + log " 1. Restore database from backup" + log " 2. Restore code to previous commit (if available)" + log " 3. Restore .env configuration (if available)" + log " 4. Restart containers" + log "" + + echo -n "Are you sure? Type 'yes' to continue: " + read -r CONFIRM + + if [ "$CONFIRM" != "yes" ]; then + log_info "Rollback cancelled by user" + exit 0 + fi + + log "" + log "${BLUE}[1/4] Restoring database...${NC}" + + # Create backup of current state before rollback + ROLLBACK_BACKUP_DIR="$BACKUP_DIR/before_rollback_$(date +%Y%m%d_%H%M%S)" + mkdir -p "$ROLLBACK_BACKUP_DIR" + + log_info "Creating safety backup before rollback..." + if $DOCKER_COMPOSE exec -T db mysqldump -uroot -p"$DB_ROOT_PASS" --single-transaction --quick "$DB_NAME" > "$ROLLBACK_BACKUP_DIR/db_backup.sql" 2>>"$LOG_FILE"; then + log_success "Safety backup created" + else + log_warning "Failed to create safety backup, continuing anyway..." + fi + + # Restore database + log_info "Restoring database from: $DB_BACKUP_FILE" + if cat "$DB_BACKUP_FILE" | $DOCKER_COMPOSE exec -T db mysql -uroot -p"$DB_ROOT_PASS" "$DB_NAME" 2>>"$LOG_FILE"; then + log_success "Database restored" + else + error_exit "Failed to restore database" + fi + + # Restore code + log "" + log "${BLUE}[2/4] Restoring code...${NC}" + + if [ -f "$COMMIT_BACKUP_FILE" ]; then + TARGET_COMMIT=$(cat "$COMMIT_BACKUP_FILE") + log_info "Restoring code to commit: $TARGET_COMMIT" + + # Stash current changes + if ! git diff-index --quiet HEAD -- 2>/dev/null; then + log_info "Stashing current changes..." + git stash push -m "Auto-stash before rollback $(date +%Y%m%d_%H%M%S)" 2>&1 | tee -a "$LOG_FILE" + fi + + # Reset to target commit + if git reset --hard "$TARGET_COMMIT" 2>&1 | tee -a "$LOG_FILE"; then + log_success "Code restored to: $TARGET_COMMIT" + else + log_error "Failed to restore code" + fi + else + log_warning "No commit backup found, skipping code restore" + fi + + # Restore .env + log "" + log "${BLUE}[3/4] Restoring configuration...${NC}" + + if [ -f "$ENV_BACKUP_FILE" ]; then + log_info "Restoring .env configuration..." + cp "$ENV_BACKUP_FILE" .env + log_success ".env restored" + else + log_warning "No .env backup found, keeping current configuration" + fi + + # Restart containers + log "" + log "${BLUE}[4/4] Restarting containers...${NC}" + + log_info "Rebuilding and restarting containers..." + $DOCKER_COMPOSE down 2>&1 | tee -a "$LOG_FILE" + $DOCKER_COMPOSE up -d --build 2>&1 | tee -a "$LOG_FILE" + + # Wait for services + log_info "Waiting for services to be ready..." + sleep 10 + + MAX_TRIES=30 + COUNTER=0 + until $DOCKER_COMPOSE exec -T db mysqladmin ping -h localhost -uroot -p"$DB_ROOT_PASS" &>/dev/null; do + COUNTER=$((COUNTER + 1)) + if [ $COUNTER -gt $MAX_TRIES ]; then + error_exit "Database did not become ready in time" + fi + echo -n "." + sleep 2 + done + echo "" + + log_success "Containers restarted" + + # Summary + log "" + log "==========================================" + log "${GREEN}✓ Rollback completed successfully!${NC}" + log "==========================================" + log "" + log_info "Rolled back to: $SELECTED_TIMESTAMP" + log_info "Safety backup saved: $ROLLBACK_BACKUP_DIR/" + log_info "Access panel: http://localhost:8082" + log "" + + exit 0 fi -# Load DB credentials from .env +# ========================================== +# 1. ENVIRONMENT CHECK +# ========================================== +log "" +log "${BLUE}[1/10] Checking environment...${NC}" + +# Check if running as root +if [ "$EUID" -eq 0 ]; then + log_warning "Running as root. This is not recommended." +fi + +# Auto-detect docker compose command +DOCKER_COMPOSE="" +if command -v docker &> /dev/null; then + if docker compose version &> /dev/null 2>&1; then + DOCKER_COMPOSE="docker compose" + log_success "Found: docker compose (v2)" + elif command -v docker-compose &> /dev/null; then + DOCKER_COMPOSE="docker-compose" + log_success "Found: docker-compose (v1)" + else + error_exit "Neither 'docker compose' nor 'docker-compose' found" + fi +else + error_exit "Docker not found. Please install Docker first." +fi + +# Check git +if ! command -v git &> /dev/null; then + error_exit "Git not found. Please install git first." +fi +log_success "Git version: $(git --version | cut -d' ' -f3)" + +# Check if we're in project directory +if [ ! -f "docker-compose.yml" ]; then + error_exit "docker-compose.yml not found. Are you in the project directory?" +fi +log_success "Project directory confirmed" + +# ========================================== +# 2. LOAD CONFIGURATION +# ========================================== +log "" +log "${BLUE}[2/10] Loading configuration...${NC}" + +# Load .env file if [ -f .env ]; then - # Source .env file safely set -a - source .env + source .env 2>/dev/null || log_warning "Some .env variables failed to load" set +a + log_success ".env file loaded" + DB_USER=${DB_USERNAME:-amnezia} DB_PASS=${DB_PASSWORD:-amnezia} + DB_NAME=${DB_DATABASE:-amnezia_panel} DB_ROOT_PASS=${DB_ROOT_PASSWORD:-rootpassword} else - echo -e "${YELLOW}⚠ .env file not found, using defaults${NC}" + log_warning ".env file not found, using defaults" DB_USER="amnezia" DB_PASS="amnezia" + DB_NAME="amnezia_panel" DB_ROOT_PASS="rootpassword" fi -# Check if docker compose is running -if ! $DOCKER_COMPOSE ps | grep -q "Up"; then - echo -e "${RED}Error: Docker containers are not running${NC}" - echo "Please start containers first: $DOCKER_COMPOSE up -d" - exit 1 +log_info "Database: $DB_NAME" +log_info "DB User: $DB_USER" + +# ========================================== +# 3. CHECK DOCKER CONTAINERS +# ========================================== +log "" +log "${BLUE}[3/10] Checking Docker containers...${NC}" + +# Check if containers exist +if ! $DOCKER_COMPOSE ps | grep -q "amnezia-panel"; then + log_warning "Containers not found. Starting them..." + $DOCKER_COMPOSE up -d 2>&1 | tee -a "$LOG_FILE" + log_info "Waiting for containers to start..." + sleep 15 fi -# 1. Create backup directory -BACKUP_DIR="backups" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) -mkdir -p $BACKUP_DIR - -echo -e "${YELLOW}[1/7] Creating backup...${NC}" -# Backup database (try root first, fallback to regular user) -if $DOCKER_COMPOSE exec -T db mysqldump -uroot -p$DB_ROOT_PASS amnezia_panel > "$BACKUP_DIR/db_backup_$TIMESTAMP.sql" 2>/dev/null; then - echo -e "${GREEN}✓ Database backup created: $BACKUP_DIR/db_backup_$TIMESTAMP.sql${NC}" -elif $DOCKER_COMPOSE exec db mysqldump -uroot -p$DB_ROOT_PASS amnezia_panel > "$BACKUP_DIR/db_backup_$TIMESTAMP.sql" 2>/dev/null; then - echo -e "${GREEN}✓ Database backup created: $BACKUP_DIR/db_backup_$TIMESTAMP.sql${NC}" -else - echo -e "${RED}✗ Database backup failed${NC}" - exit 1 +# Check if containers are running (flexible check) +CONTAINER_COUNT=$($DOCKER_COMPOSE ps --format json 2>/dev/null | grep -c "amnezia-panel" || echo "0") +if [ "$CONTAINER_COUNT" -lt 2 ]; then + # Try alternative check + if ! $DOCKER_COMPOSE ps 2>/dev/null | grep -q "amnezia-panel"; then + error_exit "Docker containers are not running. Start them with: $DOCKER_COMPOSE up -d" + fi fi +log_success "Containers are running" -# Backup .env file -if [ -f .env ]; then - cp .env "$BACKUP_DIR/.env_backup_$TIMESTAMP" - echo -e "${GREEN}✓ Configuration backup created${NC}" -fi +# Check container health +WEB_STATUS=$($DOCKER_COMPOSE ps web --format json 2>/dev/null | grep -o '"State":"[^"]*"' | cut -d'"' -f4 || echo "unknown") +DB_STATUS=$($DOCKER_COMPOSE ps db --format json 2>/dev/null | grep -o '"State":"[^"]*"' | cut -d'"' -f4 || echo "unknown") -# 2. Check for git repository +log_info "Web container: $WEB_STATUS" +log_info "DB container: $DB_STATUS" + +# Wait for database to be ready +log_info "Waiting for database to be ready..." +MAX_TRIES=30 +COUNTER=0 +until $DOCKER_COMPOSE exec -T db mysqladmin ping -h localhost -uroot -p"$DB_ROOT_PASS" &>/dev/null; do + COUNTER=$((COUNTER + 1)) + if [ $COUNTER -gt $MAX_TRIES ]; then + error_exit "Database did not become ready in time" + fi + echo -n "." + sleep 2 +done echo "" -echo -e "${YELLOW}[2/7] Checking git repository...${NC}" -if [ ! -d .git ]; then - echo -e "${RED}✗ Not a git repository. Cannot update automatically.${NC}" - echo "Please clone from: https://github.com/infosave2007/amneziavpnphp" - exit 1 +log_success "Database is ready" + +# ========================================== +# 4. BACKUP (if not skipped) +# ========================================== +if [ $SKIP_BACKUP -eq 0 ]; then + log "" + log "${BLUE}[4/10] Creating backup...${NC}" + + BACKUP_DIR="backups" + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + mkdir -p "$BACKUP_DIR" + + # Database backup + log_info "Backing up database..." + DB_BACKUP_FILE="$BACKUP_DIR/db_backup_$TIMESTAMP.sql" + + if $DOCKER_COMPOSE exec -T db mysqldump -uroot -p"$DB_ROOT_PASS" --single-transaction --quick "$DB_NAME" > "$DB_BACKUP_FILE" 2>>"$LOG_FILE"; then + BACKUP_SIZE=$(du -h "$DB_BACKUP_FILE" | cut -f1) + log_success "Database backup created: $DB_BACKUP_FILE ($BACKUP_SIZE)" + else + error_exit "Database backup failed" + fi + + # .env backup + if [ -f .env ]; then + cp .env "$BACKUP_DIR/.env_backup_$TIMESTAMP" + log_success "Configuration backup created" + fi + + # Code backup (current commit) + CURRENT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown") + echo "$CURRENT_COMMIT" > "$BACKUP_DIR/commit_backup_$TIMESTAMP.txt" + log_info "Current commit: $CURRENT_COMMIT" +else + log "" + log "${YELLOW}[4/10] Backup skipped (--skip-backup flag)${NC}" + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + CURRENT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown") fi -# 3. Check for uncommitted changes +# ========================================== +# 5. GIT REPOSITORY CHECK +# ========================================== +log "" +log "${BLUE}[5/10] Checking git repository...${NC}" + +if [ ! -d .git ]; then + error_exit "Not a git repository. Cannot update automatically. Clone from: https://github.com/infosave2007/amneziavpnphp" +fi +log_success "Git repository found" + +# Check remote +REMOTE_URL=$(git config --get remote.origin.url || echo "none") +log_info "Remote: $REMOTE_URL" + +# Check current branch +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +log_info "Current branch: $CURRENT_BRANCH" + +# Check for uncommitted changes +STASHED=0 if ! git diff-index --quiet HEAD -- 2>/dev/null; then - echo -e "${YELLOW}⚠ You have uncommitted changes${NC}" - echo "Stashing changes..." - git stash push -m "Auto-stash before update $TIMESTAMP" + log_warning "You have uncommitted changes" + log_info "Stashing changes..." + git stash push -m "Auto-stash before update $TIMESTAMP" 2>&1 | tee -a "$LOG_FILE" STASHED=1 else - STASHED=0 - echo -e "${GREEN}✓ Working directory is clean${NC}" + log_success "Working directory is clean" fi -# 4. Pull latest changes -echo "" -echo -e "${YELLOW}[3/7] Pulling latest changes from git...${NC}" -CURRENT_COMMIT=$(git rev-parse HEAD) -git fetch origin -git pull origin master +# ========================================== +# 6. PULL LATEST CHANGES +# ========================================== +log "" +log "${BLUE}[6/10] Pulling latest changes...${NC}" -NEW_COMMIT=$(git rev-parse HEAD) +# Fetch updates +log_info "Fetching from remote..." +git fetch origin 2>&1 | tee -a "$LOG_FILE" -if [ "$CURRENT_COMMIT" = "$NEW_COMMIT" ]; then - echo -e "${GREEN}✓ Already up to date${NC}" +# Check if update available +UPSTREAM=${1:-'@{u}'} +LOCAL=$(git rev-parse @) +REMOTE=$(git rev-parse "$UPSTREAM" 2>/dev/null || echo "$LOCAL") +BASE=$(git merge-base @ "$UPSTREAM" 2>/dev/null || echo "$LOCAL") + +if [ "$LOCAL" = "$REMOTE" ]; then + if [ $FORCE_UPDATE -eq 0 ]; then + log_success "Already up to date" + UPDATE_AVAILABLE=0 + else + log_warning "Forcing update even though already up to date" + UPDATE_AVAILABLE=1 + fi +elif [ "$LOCAL" = "$BASE" ]; then + log_info "New updates available" + UPDATE_AVAILABLE=1 +elif [ "$REMOTE" = "$BASE" ]; then + log_warning "Local commits ahead of remote. Pull skipped." + UPDATE_AVAILABLE=0 else - echo -e "${GREEN}✓ Updated from $CURRENT_COMMIT to $NEW_COMMIT${NC}" + log_warning "Branches have diverged" + UPDATE_AVAILABLE=1 fi -# 5. Install/update dependencies -echo "" -echo -e "${YELLOW}[4/7] Installing dependencies...${NC}" -$DOCKER_COMPOSE exec web composer install --no-interaction --prefer-dist 2>&1 | grep -v "Warning" -echo -e "${GREEN}✓ Dependencies installed${NC}" +if [ $UPDATE_AVAILABLE -eq 1 ]; then + log_info "Pulling changes..." + git pull origin "$CURRENT_BRANCH" 2>&1 | tee -a "$LOG_FILE" + + NEW_COMMIT=$(git rev-parse HEAD) + if [ "$CURRENT_COMMIT" != "$NEW_COMMIT" ]; then + log_success "Updated from $CURRENT_COMMIT to $NEW_COMMIT" + + # Show changelog + log "" + log_info "Changes:" + git log --oneline "$CURRENT_COMMIT..$NEW_COMMIT" | head -n 10 | tee -a "$LOG_FILE" + fi +fi -# 6. Apply migrations -echo "" -echo -e "${YELLOW}[5/7] Applying database migrations...${NC}" +# ========================================== +# 7. INSTALL DEPENDENCIES +# ========================================== +log "" +log "${BLUE}[7/10] Installing dependencies...${NC}" + +log_info "Running composer install..." +if $DOCKER_COMPOSE exec -T web composer install --no-interaction --prefer-dist --optimize-autoloader 2>&1 | tee -a "$LOG_FILE" | grep -v "Warning"; then + log_success "Dependencies installed" +else + log_warning "Composer install completed with warnings (check log)" +fi + +# ========================================== +# 8. APPLY MIGRATIONS +# ========================================== +log "" +log "${BLUE}[8/10] Applying database migrations...${NC}" # Get list of migration files -MIGRATIONS=$(ls migrations/*.sql 2>/dev/null | sort) +MIGRATIONS=$(ls migrations/*.sql 2>/dev/null | sort || echo "") if [ -z "$MIGRATIONS" ]; then - echo -e "${YELLOW}⚠ No migration files found${NC}" + log_warning "No migration files found" else - # Create migrations tracking table if not exists - $DOCKER_COMPOSE exec -T db mysql -uroot -p$DB_ROOT_PASS amnezia_panel -e "CREATE TABLE IF NOT EXISTS schema_migrations (id INT PRIMARY KEY AUTO_INCREMENT, filename VARCHAR(255) UNIQUE NOT NULL, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_filename (filename));" 2>/dev/null || \ - $DOCKER_COMPOSE exec db mysql -uroot -p$DB_ROOT_PASS amnezia_panel -e "CREATE TABLE IF NOT EXISTS schema_migrations (id INT PRIMARY KEY AUTO_INCREMENT, filename VARCHAR(255) UNIQUE NOT NULL, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_filename (filename));" 2>/dev/null + # Create migrations tracking table + log_info "Ensuring migrations table exists..." + $DOCKER_COMPOSE exec -T db mysql -uroot -p"$DB_ROOT_PASS" "$DB_NAME" <>"$LOG_FILE" +CREATE TABLE IF NOT EXISTS schema_migrations ( + id INT PRIMARY KEY AUTO_INCREMENT, + filename VARCHAR(255) UNIQUE NOT NULL, + applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + checksum VARCHAR(64), + INDEX idx_filename (filename) +); +EOF # Apply each migration APPLIED_COUNT=0 + SKIPPED_COUNT=0 + for migration in $MIGRATIONS; do FILENAME=$(basename "$migration") # Check if already applied - ALREADY_APPLIED=$($DOCKER_COMPOSE exec -T db mysql -uroot -p$DB_ROOT_PASS amnezia_panel -sN -e "SELECT COUNT(*) FROM schema_migrations WHERE filename = '$FILENAME';" 2>/dev/null || echo "0") + ALREADY_APPLIED=$($DOCKER_COMPOSE exec -T db mysql -uroot -p"$DB_ROOT_PASS" "$DB_NAME" -sN -e "SELECT COUNT(*) FROM schema_migrations WHERE filename = '$FILENAME';" 2>/dev/null || echo "0") - if [ "$ALREADY_APPLIED" = "0" ] || [ -z "$ALREADY_APPLIED" ]; then - echo " Applying: $FILENAME" + if [ "$ALREADY_APPLIED" = "0" ]; then + log_info "Applying: $FILENAME" - # Apply migration (try -T first, fallback without) - if cat "$migration" | $DOCKER_COMPOSE exec -T db mysql -uroot -p$DB_ROOT_PASS amnezia_panel 2>/dev/null || \ - cat "$migration" | $DOCKER_COMPOSE exec db mysql -uroot -p$DB_ROOT_PASS amnezia_panel 2>/dev/null; then + # Apply migration + if cat "$migration" | $DOCKER_COMPOSE exec -T db mysql -uroot -p"$DB_ROOT_PASS" "$DB_NAME" 2>>"$LOG_FILE"; then + # Calculate checksum + CHECKSUM=$(sha256sum "$migration" | cut -d' ' -f1) + # Mark as applied - $DOCKER_COMPOSE exec -T db mysql -uroot -p$DB_ROOT_PASS amnezia_panel -e "INSERT INTO schema_migrations (filename) VALUES ('$FILENAME');" 2>/dev/null || \ - $DOCKER_COMPOSE exec db mysql -uroot -p$DB_ROOT_PASS amnezia_panel -e "INSERT INTO schema_migrations (filename) VALUES ('$FILENAME');" 2>/dev/null - echo -e " ${GREEN}✓ Applied: $FILENAME${NC}" + $DOCKER_COMPOSE exec -T db mysql -uroot -p"$DB_ROOT_PASS" "$DB_NAME" -e "INSERT INTO schema_migrations (filename, checksum) VALUES ('$FILENAME', '$CHECKSUM');" 2>>"$LOG_FILE" + + log_success "Applied: $FILENAME" APPLIED_COUNT=$((APPLIED_COUNT + 1)) else - echo -e " ${YELLOW}⚠ Skipped: $FILENAME (may be already applied)${NC}" + # Check error log for "already exists" errors (idempotent migrations) + LAST_ERROR=$(tail -30 "$LOG_FILE" | grep -i "ERROR.*already exists\|ERROR.*Duplicate\|ERROR.*Table.*already" || echo "") + + if [ -n "$LAST_ERROR" ]; then + log_warning "Migration $FILENAME skipped (tables already exist)" + + # Mark as applied to prevent re-running + CHECKSUM=$(sha256sum "$migration" | cut -d' ' -f1) + $DOCKER_COMPOSE exec -T db mysql -uroot -p"$DB_ROOT_PASS" "$DB_NAME" -e "INSERT IGNORE INTO schema_migrations (filename, checksum) VALUES ('$FILENAME', '$CHECKSUM');" 2>>"$LOG_FILE" + SKIPPED_COUNT=$((SKIPPED_COUNT + 1)) + else + log_error "Failed to apply: $FILENAME" + log_warning "Check $LOG_FILE for details. Continuing with next migration..." + SKIPPED_COUNT=$((SKIPPED_COUNT + 1)) + fi fi + else + SKIPPED_COUNT=$((SKIPPED_COUNT + 1)) fi done if [ $APPLIED_COUNT -eq 0 ]; then - echo -e "${GREEN}✓ All migrations already applied${NC}" + log_success "All migrations already applied ($SKIPPED_COUNT skipped)" else - echo -e "${GREEN}✓ Applied $APPLIED_COUNT new migration(s)${NC}" + log_success "Applied $APPLIED_COUNT new migration(s), skipped $SKIPPED_COUNT" fi fi -# 7. Restart containers -echo "" -echo -e "${YELLOW}[6/7] Restarting containers...${NC}" -$DOCKER_COMPOSE restart web 2>&1 | grep -v "Warning" -echo -e "${GREEN}✓ Containers restarted${NC}" +# ========================================== +# 9. RESTART CONTAINERS +# ========================================== +log "" +log "${BLUE}[9/10] Restarting containers...${NC}" -# 8. Restore stashed changes -if [ $STASHED -eq 1 ]; then - echo "" - echo -e "${YELLOW}[7/7] Restoring stashed changes...${NC}" - if git stash pop; then - echo -e "${GREEN}✓ Stashed changes restored${NC}" - else - echo -e "${YELLOW}⚠ Conflict when restoring changes. Please resolve manually:${NC}" - echo " git stash list" - echo " git stash pop" - fi +log_info "Restarting web container..." +$DOCKER_COMPOSE restart web 2>&1 | tee -a "$LOG_FILE" + +# Wait for container to be ready +sleep 5 + +# Check if web is responding +log_info "Checking web container health..." +if $DOCKER_COMPOSE exec -T web php -v &>/dev/null; then + PHP_VERSION=$($DOCKER_COMPOSE exec -T web php -v | head -n1) + log_success "Web container is healthy: $PHP_VERSION" else - echo "" - echo -e "${YELLOW}[7/7] No stashed changes to restore${NC}" + log_warning "Web container may not be fully ready" fi -# Summary -echo "" -echo "==========================================" -echo -e "${GREEN}✓ Update completed successfully!${NC}" -echo "==========================================" -echo "" -echo "Backup location: $BACKUP_DIR/" -echo " - Database: db_backup_$TIMESTAMP.sql" -if [ -f "$BACKUP_DIR/.env_backup_$TIMESTAMP" ]; then - echo " - Config: .env_backup_$TIMESTAMP" +# ========================================== +# 10. RESTORE STASHED CHANGES +# ========================================== +log "" +log "${BLUE}[10/10] Finalizing...${NC}" + +if [ $STASHED -eq 1 ]; then + log_info "Restoring stashed changes..." + if git stash pop 2>&1 | tee -a "$LOG_FILE"; then + log_success "Stashed changes restored" + else + log_warning "Conflict when restoring changes. Resolve manually with: git stash list && git stash pop" + fi fi -echo "" -echo "To rollback in case of issues:" -echo " 1. Stop containers: $DOCKER_COMPOSE down" -echo " 2. Restore database: cat $BACKUP_DIR/db_backup_$TIMESTAMP.sql | $DOCKER_COMPOSE exec -T db mysql -uroot -p$DB_ROOT_PASS amnezia_panel" -echo " 3. Restore code: git reset --hard $CURRENT_COMMIT" -echo " 4. Start containers: $DOCKER_COMPOSE up -d" -echo "" + +# ========================================== +# SUMMARY +# ========================================== +log "" +log "==========================================" +log "${GREEN}✓ Update completed successfully!${NC}" +log "==========================================" +log "" +log_info "Summary:" +log " - Start time: $(head -1 "$LOG_FILE" | grep -o '[0-9]\{4\}-.*')" +log " - End time: $(date)" +log " - Log file: $LOG_FILE" + +if [ $SKIP_BACKUP -eq 0 ]; then + log "" + log_info "Backup location: $BACKUP_DIR/" + log " - Database: db_backup_$TIMESTAMP.sql ($BACKUP_SIZE)" + if [ -f "$BACKUP_DIR/.env_backup_$TIMESTAMP" ]; then + log " - Config: .env_backup_$TIMESTAMP" + fi + log " - Commit: $CURRENT_COMMIT" +fi + +log "" +log_info "Access panel: http://localhost:8082" +log "" +log_info "To rollback in case of issues:" +log " $0 --rollback" +log " or manually:" +log " 1. $DOCKER_COMPOSE down" +log " 2. cat $BACKUP_DIR/db_backup_$TIMESTAMP.sql | $DOCKER_COMPOSE exec -T db mysql -uroot -p\$DB_ROOT_PASS $DB_NAME" +log " 3. git reset --hard $CURRENT_COMMIT" +log " 4. $DOCKER_COMPOSE up -d" +log "" + +log_success "Update completed at $(date)"