321 lines
11 KiB
Bash
321 lines
11 KiB
Bash
#!/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 "========================================="
|