feat: enhance update script with argument parsing and logging improvements

This commit is contained in:
infosave2007
2025-11-10 15:53:23 +03:00
parent 2de1b58fa4
commit 523a1a1836
2 changed files with 598 additions and 119 deletions
+597 -119
View File
@@ -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" <<EOF 2>>"$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)"