#!/usr/bin/env bash # deploy.sh - quickstart with Docker MySQL + backend + frontend # Usage: ./deploy.sh set -euo pipefail # ---------- CONFIG ---------- ROOT_DIR="$(pwd)" BACKEND_DIR="backend" FRONTEND_DIR="frontend" BACKEND_LOG="backend.log" FRONTEND_LOG="frontend.log" VENV_DIR_ABS="$ROOT_DIR/$BACKEND_DIR/.venv" MYSQL_CONTAINER="sqlilab_mysql" MYSQL_PORT=3307 MYSQL_ROOT_PASSWORD="12345" MYSQL_DATABASE="sqlilab" PYTHON_BIN="python3" PIP_BIN="" DOCKER_CMD="" # ---------------------------- timestamp(){ date +"%Y-%m-%d %H:%M:%S"; } info(){ echo -e "$(timestamp) [INFO] $*"; } fail(){ echo -e "$(timestamp) [ERROR] $*" >&2; exit 1; } warn(){ echo -e "$(timestamp) [WARN] $*"; } run_quiet(){ "$@" >/dev/null 2>&1 || true; } # ---------- Docker helpers (unchanged) ---------- check_docker(){ if ! command -v docker >/dev/null 2>&1; then return 1 fi if docker info >/dev/null 2>&1; then DOCKER_CMD="docker" info "Docker CLI usable without sudo." return 0 fi if sudo docker info >/dev/null 2>&1; then DOCKER_CMD="sudo docker" info "Docker CLI usable with sudo." return 0 fi return 1 } start_docker_daemon(){ info "Attempting to start Docker daemon..." if command -v systemctl >/dev/null 2>&1; then run_quiet sudo systemctl daemon-reload run_quiet sudo systemctl enable docker || warn "systemctl enable returned non-zero" run_quiet sudo systemctl start docker || warn "systemctl start returned non-zero" for i in {1..30}; do if sudo docker info >/dev/null 2>&1; then DOCKER_CMD="sudo docker" info "Docker daemon ready (systemd)." return 0 fi sleep 1 done warn "Docker did not become ready via systemctl in 30s." else warn "systemctl not found; trying dockerd fallback." fi if command -v dockerd >/dev/null 2>&1; then if ! pgrep -x dockerd >/dev/null 2>&1; then sudo dockerd >> /var/log/docker-dockerd.log 2>&1 & disown || warn "Failed to start dockerd" info "Started dockerd in background (logs -> /var/log/docker-dockerd.log)" else warn "dockerd already running" fi for i in {1..30}; do if sudo docker info >/dev/null 2>&1; then DOCKER_CMD="sudo docker" info "Docker ready (dockerd fallback)." return 0 fi sleep 1 done fi return 1 } ensure_docker_ready(){ if check_docker; then return 0; fi warn "Docker CLI/daemon not usable; attempting to start daemon..." if start_docker_daemon && check_docker; then return 0; fi return 1 } # ---------- Python / Node / pip checks ---------- info "Checking python & pip..." if command -v python3 >/dev/null 2>&1; then PYTHON_BIN="python3" else fail "python3 not found. Install Python 3." fi if command -v pip3 >/dev/null 2>&1; then PIP_BIN="pip3" elif command -v pip >/dev/null 2>&1; then PIP_BIN="pip" else info "Installing python3-pip..." sudo apt update -y sudo apt install -y python3-pip PIP_BIN="pip3" fi # info "Checking Node.js & npm..." # if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then # info "Installing Node.js (LTS)..." # sudo apt update -y # sudo apt install -y ca-certificates curl gnupg # sudo mkdir -p /etc/apt/keyrings # curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg # NODE_MAJOR=20 # echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list # sudo apt update -y # sudo apt install -y nodejs # fi # ---------- helper to start background processes ---------- start_bg(){ local cmd="$1"; local logfile="$2"; local pidfile="$3" nohup bash -lc "$cmd" >>"$logfile" 2>&1 & local pid=$! echo "$pid" > "$pidfile" info "Started: '$cmd' (pid=$pid) -> $logfile" } # ---------- sanity checks ---------- [ -d "$BACKEND_DIR" ] || fail "Backend directory '$BACKEND_DIR' not found." # [ -d "$FRONTEND_DIR" ] || fail "Frontend directory '$FRONTEND_DIR' not found." [ -f "$BACKEND_DIR/init.sql" ] || fail "BACKEND_DIR/init.sql not found. Please create $BACKEND_DIR/init.sql" # ---------- Install Docker (if missing) ---------- info "Checking Docker installation..." DOCKER_WAS_INSTALLED=false if ! command -v docker >/dev/null 2>&1; then info "Docker not found. Installing Docker..." DOCKER_WAS_INSTALLED=true sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo usermod -aG docker "$USER" || warn "usermod returned non-zero" info "Docker installation completed." fi if ! ensure_docker_ready; then if [ "$DOCKER_WAS_INSTALLED" = true ]; then fail "Docker daemon not running after install. Check: sudo systemctl status docker and logs." else fail "Docker is installed but not usable. Try: sudo systemctl start docker" fi fi info "Docker ready. Using command: $DOCKER_CMD" if [ "$DOCKER_CMD" = "sudo docker" ]; then warn "docker requires sudo in this shell. Run: newgrp docker OR log out/in to use docker without sudo." fi # ---------- DOCKER MYSQL SETUP ---------- info "Setting up MySQL Docker container..." if $DOCKER_CMD ps -a --format '{{.Names}}' | grep -q "^${MYSQL_CONTAINER}$"; then info "Removing existing MySQL container..." $DOCKER_CMD stop "$MYSQL_CONTAINER" 2>/dev/null || true $DOCKER_CMD rm "$MYSQL_CONTAINER" 2>/dev/null || true fi info "Pulling MySQL 8.0 image..." $DOCKER_CMD pull mysql:8.0 info "Starting MySQL container..." $DOCKER_CMD run -d \ --name "$MYSQL_CONTAINER" \ -e MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASSWORD" \ -e MYSQL_DATABASE="$MYSQL_DATABASE" \ -e MYSQL_ROOT_HOST='%' \ -p "$MYSQL_PORT:3306" \ -v "$ROOT_DIR/$BACKEND_DIR/init.sql:/docker-entrypoint-initdb.d/init.sql:ro" \ mysql:8.0 \ --default-authentication-plugin=mysql_native_password info "Waiting for MySQL to be ready..." for i in {1..60}; do if $DOCKER_CMD exec "$MYSQL_CONTAINER" mysqladmin ping -h localhost -uroot -p"$MYSQL_ROOT_PASSWORD" >/dev/null 2>&1; then info "MySQL ready." break fi sleep 1 if [ "$i" -eq 60 ]; then fail "MySQL failed to start. Check logs: $DOCKER_CMD logs $MYSQL_CONTAINER" fi done sleep 3 # ---------- BACKEND PREP: ensure python3-venv installed, create venv, install requirements & gunicorn ---------- info "Preparing backend virtualenv and dependencies..." info "Ensuring python3-venv and pip are installed..." sudo apt update -y sudo apt install -y python3-venv python3-pip # create venv if missing (use absolute path) if [ ! -d "$VENV_DIR_ABS" ]; then info "Creating virtualenv at $VENV_DIR_ABS" $PYTHON_BIN -m venv "$VENV_DIR_ABS" || fail "Failed to create virtualenv at $VENV_DIR_ABS" fi # Activate and install requirements inside venv # shellcheck source=/dev/null . "$VENV_DIR_ABS/bin/activate" || fail "Failed to activate virtualenv at $VENV_DIR_ABS" info "Upgrading pip & installing backend requirements..." pip install --upgrade pip setuptools wheel >/dev/null if [ -f "$ROOT_DIR/$BACKEND_DIR/requirements.txt" ]; then pip install -r "$ROOT_DIR/$BACKEND_DIR/requirements.txt" || warn "pip install -r returned non-zero" else warn "requirements.txt not found in $BACKEND_DIR" fi info "Installing gunicorn..." pip install --upgrade gunicorn >/dev/null || warn "pip install gunicorn returned non-zero" deactivate || true # compute gunicorn binary absolute path and verify GUNICORN_BIN="$VENV_DIR_ABS/bin/gunicorn" if [ ! -x "$GUNICORN_BIN" ]; then fail "gunicorn binary not found at $GUNICORN_BIN (venv may be broken)" fi # ---------- DETECT app.py location and set APP_MODULE ---------- # prefer backend/app.py, fallback to backend/backend/app.py if [ -f "$ROOT_DIR/$BACKEND_DIR/app.py" ]; then APP_MODULE="backend.app:app" START_CWD="$ROOT_DIR" elif [ -f "$ROOT_DIR/$BACKEND_DIR/backend/app.py" ]; then APP_MODULE="backend.backend.app:app" START_CWD="$ROOT_DIR" else # if neither exists, fail early with helpful message fail "Could not find app.py under $BACKEND_DIR. Please ensure backend/app.py exists." fi info "Using APP_MODULE=$APP_MODULE" # ---------- START BACKEND (Flask dev server, no Gunicorn) ---------- info "Starting backend with Flask development server (no Gunicorn)..." BACKEND_APP="app.py" # your backend/app.py if sudo ss -ltnp 2>/dev/null | grep -q ':5000'; then warn "Port 5000 already in use; skipping backend start." else BACKEND_CMD="bash -lc 'cd \"$ROOT_DIR/$BACKEND_DIR\" && \ source \"$VENV_DIR_ABS/bin/activate\" && \ FLASK_APP=$BACKEND_APP FLASK_ENV=development flask run --host=0.0.0.0 --port=5000'" BACKEND_PIDFILE="$ROOT_DIR/backend.pid" start_bg "$BACKEND_CMD" "$ROOT_DIR/$BACKEND_LOG" "$BACKEND_PIDFILE" fi # # ---------- FRONTEND ---------- # info "Starting frontend..." # if [ -f "$ROOT_DIR/$FRONTEND_DIR/package.json" ]; then # cd "$ROOT_DIR/$FRONTEND_DIR" # if [[ "$(pwd)" == /mnt/* ]]; then # info "Detected WSL/Windows FS. Cleaning node_modules..." # rm -rf node_modules package-lock.json # npm install # else # if [ -f "package-lock.json" ]; then # if ! npm ci; then # rm -rf node_modules # npm install # fi # else # npm install # fi # fi # FRONTEND_START_CMD=${FRONTEND_START_CMD:-"npm run dev"} # FRONTEND_PIDFILE="$ROOT_DIR/frontend.pid" # start_bg "$FRONTEND_START_CMD" "$ROOT_DIR/$FRONTEND_LOG" "$FRONTEND_PIDFILE" # cd "$ROOT_DIR" # else # warn "No package.json in frontend/ — skipping frontend start." # fi # ---------- FINISH ---------- echo "" echo "=========================================" echo "🚀 Application Started (or attempted)!" echo "=========================================" echo "$(timestamp) MySQL (Docker) → localhost:$MYSQL_PORT" echo " Container: $MYSQL_CONTAINER" echo " Database: $MYSQL_DATABASE" echo "" echo "$(timestamp) Backend → http://localhost:5000" echo " Log: $BACKEND_LOG" echo " PID: $(cat backend.pid 2>/dev/null || echo 'N/A')" echo "" # echo "$(timestamp) Frontend → http://localhost:5173" # echo " Log: $FRONTEND_LOG" # echo " PID: $(cat frontend.pid 2>/dev/null || echo 'N/A')" echo "=========================================" echo "" echo "Useful commands:" echo " tail -f $BACKEND_LOG" # echo " tail -f $FRONTEND_LOG" echo " $DOCKER_CMD logs -f $MYSQL_CONTAINER" echo " $DOCKER_CMD exec -it $MYSQL_CONTAINER mysql -uroot -p$MYSQL_ROOT_PASSWORD $MYSQL_DATABASE" echo " kill \$(cat backend.pid 2>/dev/null || echo '') && kill \$(cat frontend.pid 2>/dev/null || echo '') && $DOCKER_CMD stop $MYSQL_CONTAINER" echo "========================================="