#!/bin/bash # ============================================================ # Server Deploy Script # ============================================================ # This script is the SINGLE ENTRY POINT for deployment. # GitHub Actions should call this script, NOT directly run commands. # # Path configuration is defined ON THE SERVER in this file. # GitHub Actions does NOT need to know the server path. # ============================================================ set -e # ============================================================ # CONFIGURATION - Only place where server paths are defined # ============================================================ PROJECT_DIR="/root/fetch-china" # <-- THIS IS THE ONLY PLACE TO CHANGE SERVER PATH # ============================================================ echo "[DEPLOY] Starting deployment at $(date)" echo "[DEPLOY] Project directory: $PROJECT_DIR" # Verify project directory exists if [ ! -d "$PROJECT_DIR" ]; then echo "[ERROR] Project directory $PROJECT_DIR does not exist!" exit 1 fi cd "$PROJECT_DIR" # Pull latest code (force reset to avoid local changes conflict) echo "[DEPLOY] Pulling latest code from GitHub..." git fetch origin git reset --hard origin/main # Rebuild frontend dist (because frontend/dist is mounted as volume) echo "[DEPLOY] Building frontend..." cd frontend if [ -f "package.json" ]; then # Install including dev dependencies for build npm install --include=dev 2>/dev/null || npm install npm run build fi cd .. # Determine docker compose command (v2 or v1) if docker compose version >/dev/null 2>&1; then DOCKER_COMPOSE="docker compose" echo "[DEPLOY] Using Docker Compose v2" else DOCKER_COMPOSE="docker-compose" echo "[DEPLOY] Using Docker Compose v1" fi # Clean up stale containers (prevents KeyError: 'ContainerConfig') echo "[DEPLOY] Cleaning up stale containers..." # Step 0: Check if old containers exist and remove them FIRST echo "[DEPLOY] Checking for existing containers..." EXISTING=$(docker ps -a --format '{{.Names}}' | grep -E '^fetch-china-(backend|frontend)$' || true) if [ -n "$EXISTING" ]; then echo "[DEPLOY] Found existing containers, removing: $EXISTING" docker rm -f $EXISTING 2>/dev/null || true fi # Step 1: Run docker-compose down first to stop services gracefully echo "[DEPLOY] Running docker-compose down..." $DOCKER_COMPOSE down -v --remove-orphans 2>/dev/null || true # Step 2: Force remove containers by name (handles stuck containers) echo "[DEPLOY] Force removing containers by name..." docker rm -f fetch-china-backend fetch-china-frontend 2>/dev/null || true # Step 3: Remove any containers using the same image echo "[DEPLOY] Removing containers by image..." docker ps -a --filter "ancestor=fetch-china_backend" -q | xargs -r docker rm -f 2>/dev/null || true docker ps -a --filter "ancestor=fetch-china_frontend" -q | xargs -r docker rm -f 2>/dev/null || true # Step 4: Clean up networks (more aggressive) echo "[DEPLOY] Cleaning up networks..." # First try to remove the specific network docker network rm fetch-china_fetch-china-network 2>/dev/null || true # Also try with underscore variations docker network rm fetch-china-network 2>/dev/null || true docker network rm fetchchina_fetch-china-network 2>/dev/null || true # Disconnect any containers still attached to the network NETWORK_ID=$(docker network ls --filter name=fetch-china --format "{{.ID}}" | head -1) if [ -n "$NETWORK_ID" ]; then echo "[DEPLOY] Found network $NETWORK_ID, disconnecting containers..." docker network inspect "$NETWORK_ID" --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | xargs -n1 -r docker network disconnect -f "$NETWORK_ID" 2>/dev/null || true docker network rm "$NETWORK_ID" 2>/dev/null || true fi # Prune unused networks docker network prune -f 2>/dev/null || true # Step 5: Stop AND REMOVE ANY container (running OR stopped) publishing ports 80/8000 # A stopped container can still hold the iptables/ip binding for a published port, # so `docker ps` (running only) is NOT enough - we must scan `docker ps -a`. echo "[DEPLOY] Removing any container publishing ports 80/8000 (running or stopped)..." for PORT in 80 8000; do # Stop running containers first (graceful) docker ps --filter "publish=${PORT}" -q 2>/dev/null | xargs -r docker stop 2>/dev/null || true # Force-remove ALL containers (including stopped) - they still hold port bindings docker ps -a --filter "publish=${PORT}" -q 2>/dev/null | xargs -r docker rm -f 2>/dev/null || true done # Step 5b: Catch non-docker processes squatting on port 80/8000 # (system nginx/apache, stray python -m http.server, etc.). These block docker # from binding the host port even when no docker container is on it. if command -v ss >/dev/null 2>&1; then for PORT in 80 8000; do HOLDER_PIDS=$(ss -ltnp "sport = :${PORT}" 2>/dev/null \ | grep -oE 'pid=[0-9]+' | cut -d= -f2 | sort -u || true) if [ -n "$HOLDER_PIDS" ]; then echo "[DEPLOY] Non-docker process(es) holding port ${PORT}: ${HOLDER_PIDS} - killing" for PID in $HOLDER_PIDS; do kill -9 "$PID" 2>/dev/null || true done fi done elif command -v fuser >/dev/null 2>&1; then fuser -k 80/tcp 2>/dev/null || true fuser -k 8000/tcp 2>/dev/null || true fi sleep 2 # Step 6: Verify cleanup echo "[DEPLOY] Verifying cleanup..." if docker ps -a | grep -E "fetch-china-(backend|frontend)"; then echo "[ERROR] Failed to remove containers, forcing cleanup..." docker ps -a | grep -E "fetch-china-(backend|frontend)" | awk '{print $1}' | xargs -r docker rm -f fi echo "[DEPLOY] Container cleanup completed" # Step 7: Final safety check - remove any containers with the exact names we need echo "[DEPLOY] Final safety check - removing containers by exact name..." docker rm -f fetch-china-backend 2>/dev/null || true docker rm -f fetch-china-frontend 2>/dev/null || true # Step 8: Ultra-aggressive cleanup - find and remove ALL containers using our images or names echo "[DEPLOY] Ultra-aggressive cleanup - removing all related containers..." docker ps -a -q --filter "ancestor=fetch-china_backend" | xargs -r docker rm -f 2>/dev/null || true docker ps -a -q --filter "ancestor=fetch-china_frontend" | xargs -r docker rm -f 2>/dev/null || true docker ps -a -q --filter "name=fetch-china" | xargs -r docker rm -f 2>/dev/null || true # Step 9: List any remaining containers for debugging echo "[DEPLOY] Current containers:" docker ps -a --format "table {{.Names}}\t{{.Status}}" | grep -E "fetch-china|NAMES" || echo "No fetch-china containers found"