#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PIDS=() usage() { cat <<'EOF' Usage: ./scripts/dev.sh [start|stop|restart|status|--reset] Starts MySQL, Redis, MinIO, Qdrant, backend, admin frontend and legacy mobile fallback. start start local demo services stop stop backend and frontend dev processes restart stop backend and frontend, then start again status show local demo process status --reset rebuild local demo database before starting services EOF } cleanup() { trap - INT TERM EXIT for pid in "${PIDS[@]}"; do kill "$pid" >/dev/null 2>&1 || true done } ensure_docker() { if docker info >/dev/null 2>&1; then return fi if [[ "$(uname -s)" == "Darwin" ]] && [[ -d /Applications/Docker.app ]]; then echo "Docker daemon is not running. Opening Docker Desktop..." open -a Docker for _ in {1..90}; do if docker info >/dev/null 2>&1; then echo "Docker daemon is ready." return fi sleep 2 done fi echo "Docker daemon is not running. Start Docker Desktop, then run ./scripts/dev.sh again." >&2 exit 1 } ensure_port_free() { local port="$1" if lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then echo "Port $port is already in use:" >&2 lsof -nP -iTCP:"$port" -sTCP:LISTEN >&2 echo "Stop the existing process, then run ./scripts/dev.sh again." >&2 exit 1 fi } stop_port() { local port="$1" local pids pids="$(lsof -tiTCP:"$port" -sTCP:LISTEN || true)" if [[ -z "$pids" ]]; then echo "Port $port is not in use." return fi echo "Stopping port $port: $pids" kill $pids >/dev/null 2>&1 || true for _ in {1..20}; do if ! lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then return fi sleep 0.2 done kill -9 $pids >/dev/null 2>&1 || true } stop_dev() { stop_port 8080 stop_port 5173 stop_port 5174 } status() { docker compose -f docker-compose.dev.yml ps lsof -nP -iTCP:8080 -sTCP:LISTEN || true lsof -nP -iTCP:5173 -sTCP:LISTEN || true lsof -nP -iTCP:5174 -sTCP:LISTEN || true } if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then usage exit 0 fi COMMAND="${1:-start}" RESET=false case "$COMMAND" in start) ;; --reset) COMMAND="start" RESET=true ;; stop) cd "$ROOT_DIR" stop_dev exit 0 ;; restart) cd "$ROOT_DIR" stop_dev COMMAND="start" ;; status) cd "$ROOT_DIR" status exit 0 ;; *) usage exit 1 ;; esac cd "$ROOT_DIR" ensure_docker ensure_port_free 8080 ensure_port_free 5173 ensure_port_free 5174 if [[ "$RESET" == true ]]; then ./scripts/reset-dev-db.sh else docker compose -f docker-compose.dev.yml up -d mysql redis qdrant minio minio-init fi trap cleanup INT TERM EXIT ./scripts/dev-backend.sh & PIDS+=("$!") ./scripts/dev-frontend.sh & PIDS+=("$!") ./scripts/dev-mobile.sh & PIDS+=("$!") echo "Backend: http://127.0.0.1:8080/" echo "Admin: http://127.0.0.1:5173/" echo "Legacy mobile fallback: http://127.0.0.1:5174/h5/user" echo "Current user side: https://wygj-mobile-uni.localhost/h5/ (run: portless)" echo "Qdrant: http://127.0.0.1:6333/" echo "Press Ctrl+C to stop backend, admin frontend and legacy mobile frontend." while :; do for pid in "${PIDS[@]}"; do if ! kill -0 "$pid" >/dev/null 2>&1; then wait "$pid" exit $? fi done sleep 2 done