#!/bin/bash # AutonetSellCar Main Deployment Script # Usage: ./deploy.sh {promote|rollback|rollback-to |status|cleanup} set -e STAGING_DIR="/opt/autonet/staging" PROD_DIR="/opt/autonet/production" RELEASES_DIR="/opt/autonet/releases" LOG_FILE="/opt/autonet/logs/deploy.log" TIMESTAMP=$(date +%Y%m%d_%H%M%S) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } mkdir -p /opt/autonet/logs health_check() { local port=$1 local name=$2 for i in {1..5}; do if curl -sf "http://localhost:$port" > /dev/null 2>&1; then return 0 fi log "$name not ready yet, retry $i/5..." sleep 5 done return 1 } case "$1" in promote) log "==========================================" log "Promoting Staging -> Production" log "==========================================" # 1. Backup current production if [ -d "$PROD_DIR/frontend" ]; then log "[1/6] Backing up current production to releases/$TIMESTAMP..." mkdir -p "$RELEASES_DIR/$TIMESTAMP" rsync -a --exclude='*.db' --exclude='uploads/' "$PROD_DIR/" "$RELEASES_DIR/$TIMESTAMP/" echo "$TIMESTAMP" > "$PROD_DIR/.current_release" echo "$TIMESTAMP" > "$PROD_DIR/.previous_release" else log "[1/6] No existing production to backup (first deployment)" fi # 2. Stop production containers log "[2/6] Stopping production containers..." cd "$PROD_DIR" 2>/dev/null && docker compose -f docker-compose.production.yml down 2>/dev/null || true # 3. Copy staging to production (exclude DB and uploads) log "[3/6] Copying staging code to production..." mkdir -p "$PROD_DIR/frontend" "$PROD_DIR/backend" "$PROD_DIR/agent" rsync -av --delete --exclude='*.db' --exclude='uploads/' --exclude='node_modules/' --exclude='__pycache__/' \ "$STAGING_DIR/frontend/" "$PROD_DIR/frontend/" rsync -av --delete --exclude='*.db' --exclude='uploads/' --exclude='__pycache__/' \ "$STAGING_DIR/backend/" "$PROD_DIR/backend/" rsync -av --delete --exclude='__pycache__/' \ "$STAGING_DIR/agent/" "$PROD_DIR/agent/" 2>/dev/null || true # Copy docker-compose file cp "$STAGING_DIR/docker-compose.production.yml" "$PROD_DIR/" # 4. Build production containers log "[4/6] Building production containers..." cd "$PROD_DIR" docker compose -f docker-compose.production.yml build # 5. Start production containers log "[5/6] Starting production containers..." docker compose -f docker-compose.production.yml up -d # 6. Health check log "[6/6] Running health checks..." sleep 15 BACKEND_OK=false FRONTEND_OK=false if health_check 8000 "Backend"; then BACKEND_OK=true log "Backend: OK" fi if health_check 3000 "Frontend"; then FRONTEND_OK=true log "Frontend: OK" fi if $BACKEND_OK && $FRONTEND_OK; then log "==========================================" log "Production Deployment SUCCESSFUL!" log "Release: $TIMESTAMP" log "Frontend: http://192.168.0.202:3000" log "Backend: http://192.168.0.202:8000/docs" log "==========================================" else log "==========================================" log "Health check FAILED! Auto-rolling back..." log "==========================================" $0 rollback exit 1 fi ;; rollback) log "==========================================" log "Rolling Back Production" log "==========================================" if [ -f "$PROD_DIR/.previous_release" ]; then PREV_RELEASE=$(cat "$PROD_DIR/.previous_release") elif [ -f "$PROD_DIR/.current_release" ]; then PREV_RELEASE=$(cat "$PROD_DIR/.current_release") else log "ERROR: No previous release found!" exit 1 fi if [ ! -d "$RELEASES_DIR/$PREV_RELEASE" ]; then log "ERROR: Release $PREV_RELEASE not found!" log "Available releases:" ls -1 "$RELEASES_DIR" 2>/dev/null || echo "None" exit 1 fi log "Rolling back to release: $PREV_RELEASE" # Stop production cd "$PROD_DIR" 2>/dev/null && docker compose -f docker-compose.production.yml down 2>/dev/null || true # Restore from release rsync -av --delete --exclude='*.db' --exclude='uploads/' \ "$RELEASES_DIR/$PREV_RELEASE/frontend/" "$PROD_DIR/frontend/" rsync -av --delete --exclude='*.db' --exclude='uploads/' \ "$RELEASES_DIR/$PREV_RELEASE/backend/" "$PROD_DIR/backend/" rsync -av --delete \ "$RELEASES_DIR/$PREV_RELEASE/agent/" "$PROD_DIR/agent/" 2>/dev/null || true # Copy docker-compose cp "$RELEASES_DIR/$PREV_RELEASE/docker-compose.production.yml" "$PROD_DIR/" 2>/dev/null || true # Rebuild and start cd "$PROD_DIR" docker compose -f docker-compose.production.yml build docker compose -f docker-compose.production.yml up -d log "==========================================" log "Rollback to $PREV_RELEASE COMPLETE!" log "==========================================" ;; rollback-to) if [ -z "$2" ]; then log "Usage: deploy.sh rollback-to " log "Available releases:" ls -1 "$RELEASES_DIR" 2>/dev/null || echo "None" exit 1 fi TARGET_RELEASE="$2" if [ ! -d "$RELEASES_DIR/$TARGET_RELEASE" ]; then log "ERROR: Release $TARGET_RELEASE not found!" log "Available releases:" ls -1 "$RELEASES_DIR" exit 1 fi log "==========================================" log "Rolling Back to Specific Release: $TARGET_RELEASE" log "==========================================" # Backup current before rollback if [ -d "$PROD_DIR/frontend" ]; then mkdir -p "$RELEASES_DIR/$TIMESTAMP" rsync -a --exclude='*.db' --exclude='uploads/' "$PROD_DIR/" "$RELEASES_DIR/$TIMESTAMP/" fi # Stop production cd "$PROD_DIR" 2>/dev/null && docker compose -f docker-compose.production.yml down 2>/dev/null || true # Restore from target release rsync -av --delete --exclude='*.db' --exclude='uploads/' \ "$RELEASES_DIR/$TARGET_RELEASE/frontend/" "$PROD_DIR/frontend/" rsync -av --delete --exclude='*.db' --exclude='uploads/' \ "$RELEASES_DIR/$TARGET_RELEASE/backend/" "$PROD_DIR/backend/" rsync -av --delete \ "$RELEASES_DIR/$TARGET_RELEASE/agent/" "$PROD_DIR/agent/" 2>/dev/null || true cp "$RELEASES_DIR/$TARGET_RELEASE/docker-compose.production.yml" "$PROD_DIR/" 2>/dev/null || true echo "$TARGET_RELEASE" > "$PROD_DIR/.current_release" # Rebuild and start cd "$PROD_DIR" docker compose -f docker-compose.production.yml build docker compose -f docker-compose.production.yml up -d log "==========================================" log "Rollback to $TARGET_RELEASE COMPLETE!" log "==========================================" ;; status) echo "" echo "=== Staging Containers ===" docker compose -f "$STAGING_DIR/docker-compose.staging.yml" ps 2>/dev/null || echo "Not running" echo "" echo "=== Production Containers ===" docker compose -f "$PROD_DIR/docker-compose.production.yml" ps 2>/dev/null || echo "Not running" echo "" echo "=== Current Release ===" if [ -f "$PROD_DIR/.current_release" ]; then cat "$PROD_DIR/.current_release" else echo "None" fi echo "" echo "=== Available Releases ===" ls -1 "$RELEASES_DIR" 2>/dev/null | tail -10 || echo "None" echo "" ;; cleanup) log "Cleaning up old releases (keeping last 10)..." cd "$RELEASES_DIR" 2>/dev/null || exit 0 ls -1 | head -n -10 | xargs -r rm -rf log "Cleanup complete!" ;; logs-staging) docker compose -f "$STAGING_DIR/docker-compose.staging.yml" logs -f --tail=100 ;; logs-prod) docker compose -f "$PROD_DIR/docker-compose.production.yml" logs -f --tail=100 ;; *) echo "AutonetSellCar Deployment Script" echo "" echo "Usage: ./deploy.sh " echo "" echo "Commands:" echo " promote Promote staging to production" echo " rollback Rollback to previous release" echo " rollback-to Rollback to specific release" echo " status Show container and release status" echo " cleanup Remove old releases (keep last 10)" echo " logs-staging Show staging logs" echo " logs-prod Show production logs" echo "" ;; esac