#!/usr/bin/env bash # start-stack.sh — avvia ASF Core, ASF Web (Next.js), e opzionalmente ASF Worker. # Usage: # ./scripts/start-stack.sh # core + web, reload + dev mode # ./scripts/start-stack.sh --no-reload # core senza --reload (productionish) # ./scripts/start-stack.sh --with-worker # avvia anche asf-worker (7 code) # ./scripts/start-stack.sh --web-only # solo frontend Next.js # ./scripts/start-stack.sh --core-only # solo backend Core (FastAPI) # Bindings default: 0.0.0.0 (core 8000, web 3000). set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" usage() { sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//' exit 0 } [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]] && usage WITH_WORKER=0 RELOAD=1 CORE=1 WEB=1 for arg in "$@"; do case "$arg" in --with-worker|-w) WITH_WORKER=1 ;; --no-reload) RELOAD=0 ;; --web-only) CORE=0; WITH_WORKER=0 ;; --core-only) WEB=0 ;; -h|--help) usage ;; *) echo "Unknown option: $arg"; usage ;; esac done # ------------------------------------------------------------ # 1) Preflight checks # ------------------------------------------------------------ VENV_DIR="$ROOT_DIR/.venv" if [ ! -d "$VENV_DIR" ]; then echo "[start-stack] ❌ .venv non trovata in $VENV_DIR. Crea prima la venv: python -m venv .venv && source .venv/bin/activate" >&2 exit 1 fi # shellcheck disable=SC1091 source "$VENV_DIR/bin/activate" ENV_FILE="$ROOT_DIR/.env" if [ ! -f "$ENV_FILE" ]; then echo "[start-stack] ⚠️ .env non trovato, uso .env.example (se esiste)." if [ -f "$ROOT_DIR/.env.example" ]; then cp "$ROOT_DIR/.env.example" "$ROOT_DIR/.env" fi fi # shellcheck disable=SC1090 set -a; [ -f "$ENV_FILE" ] && source "$ENV_FILE"; set +a export ASF_ROOT="$ROOT_DIR" export PYTHONPATH="\ $ROOT_DIR/packages/asf-domain:\ $ROOT_DIR/services/asf-events:\ $ROOT_DIR/services/asf-connectors:\ $ROOT_DIR/services/asf-core:\ $ROOT_DIR/services/asf-worker:\ $ROOT_DIR/services/asf-ai:\ $ROOT_DIR/services/asf-context\ ${PYTHONPATH:+:$PYTHONPATH}" export NEXT_PUBLIC_ASF_CORE_URL="${NEXT_PUBLIC_ASF_CORE_URL:-http://localhost:8000}" export NEXT_PUBLIC_DEV_TENANT_ID="${NEXT_PUBLIC_DEV_TENANT_ID:-t-demo-001}" export NEXT_PUBLIC_DEV_USER_ID="${NEXT_PUBLIC_DEV_USER_ID:-u-admin}" LOG_DIR="$ROOT_DIR/var/log/asf" PID_DIR="$ROOT_DIR/var/run" mkdir -p "$LOG_DIR" "$PID_DIR" # ------------------------------------------------------------ # 2) Helpers # ------------------------------------------------------------ pids=() names=() start_in_dir() { local name="$1"; shift local dir="$1"; shift local log="$LOG_DIR/$name.log" echo " starting $name ($(basename "$dir")) ..." ( cd "$dir" # shellcheck disable=SC2093 exec "$@" >"$log" 2>&1 ) & local pid=$! pids+=("$pid") names+=("$name") echo " pid=$pid log=$log" } print_header() { local COLS COLS=$(tput cols 2>/dev/null || echo 80) printf '═%.0s' $(seq 1 "$COLS"); echo printf " ASF local stack · %s · tenant=%s user=%s\n" \ "$(date '+%F %T %Z')" \ "$NEXT_PUBLIC_DEV_TENANT_ID" \ "$NEXT_PUBLIC_DEV_USER_ID" printf '═%.0s' $(seq 1 "$COLS"); echo } graceful_stop() { echo echo "[start-stack] SIGINT/SIGTERM received — stopping ${#pids[@]} processes (${names[*]})." for i in "${!pids[@]}"; do local pid="${pids[$i]}" local name="${names[$i]}" if kill -0 "$pid" 2>/dev/null; then echo " stopping $name (pid=$pid)" kill -TERM "$pid" 2>/dev/null || true fi done # wait them out local waited=0 while [ $waited -lt 10 ]; do local alive=0 for pid in "${pids[@]}"; do kill -0 "$pid" 2>/dev/null && alive=1; done [ $alive -eq 0 ] && break sleep 1; waited=$((waited+1)) done # force kill any stragglers for pid in "${pids[@]}"; do kill -0 "$pid" 2>/dev/null && { echo " force kill pid=$pid"; kill -9 "$pid" 2>/dev/null || true; } done echo "[start-stack] stopped." exit 0 } trap graceful_stop INT TERM # ------------------------------------------------------------ # 3) Dependency quick liveness (Mongo + Rabbit — best effort) # ------------------------------------------------------------ print_header echo "[start-stack] python=$(python -V 2>&1 | cut -d' ' -f2) node=$(node -v) npm=v$(npm -v 2>/dev/null)" echo "[start-stack] PYTHONPATH[0:80]=${PYTHONPATH:0:80}…" echo echo "[deps] Mongo / Rabbit best-effort reachability (5s timeout)…" python - </dev/null || true import os,socket,time for host,port,name in [("172.19.40.102",27017,"Mongo rs0 PRIMARY"),("172.19.40.103",27017,"Mongo rs0 SEED"),("172.19.40.201",5672,"RabbitMQ AMQP"),("172.19.40.201",15672,"RabbitMQ HTTP mgmt")]: s=socket.socket(); s.settimeout(2) try: s.connect((host,port)); print(f" ✅ {name:22s} {host}:{port} reachable") except Exception as e: print(f" ⚠️ {name:22s} {host}:{port} unreachable ({e})") finally: s.close() PY echo # ------------------------------------------------------------ # 4) Start processes # ------------------------------------------------------------ echo "[start-stack] launching services (bind 0.0.0.0)…" # Core (FastAPI) if [ $CORE -eq 1 ]; then CORE_ARGS=(uvicorn app.main:app --host 0.0.0.0 --port 8000) [ $RELOAD -eq 1 ] && CORE_ARGS+=(--reload) start_in_dir "asf-core" "$ROOT_DIR/services/asf-core" "${CORE_ARGS[@]}" echo " → http://0.0.0.0:8000/docs (OpenAPI)" fi # Web (Next.js dev) if [ $WEB -eq 1 ]; then start_in_dir "asf-web" "$ROOT_DIR/frontend/asf-web" npm run dev -- --hostname 0.0.0.0 --port 3000 echo " → http://0.0.0.0:3000 (UI)" fi # Worker if [ $WITH_WORKER -eq 1 ]; then start_in_dir "asf-worker" "$ROOT_DIR/services/asf-worker" python -m app.worker echo " → queues=$ASF_WORKER_JOB_QUEUES" fi echo # Save PIDs on disk for `stop-stack.sh` : > "$PID_DIR/asf-stack.pids" for i in "${!pids[@]}"; do echo "${names[$i]} ${pids[$i]}" >> "$PID_DIR/asf-stack.pids" done echo "[start-stack] PIDs salvati in $PID_DIR/asf-stack.pids" echo echo "Tipi 'Ctrl+C' per arrestare TUTTI i processi. Oppure usa: ./scripts/stop-stack.sh" echo # ------------------------------------------------------------ # 5) Smoke test (first 20s) # ------------------------------------------------------------ deadline=$((SECONDS + 25)) ok_core=0; ok_web=0 while [ $SECONDS -lt $deadline ]; do if [ $CORE -eq 1 ] && [ $ok_core -eq 0 ]; then code=$(curl -s -m 4 -o /dev/null -w '%{http_code}' http://127.0.0.1:8000/api/v1/health || true) [ "$code" = "200" ] && { ok_core=1; echo " ✅ asf-core up /health HTTP=$code"; } fi if [ $WEB -eq 1 ] && [ $ok_web -eq 0 ]; then code=$(curl -s -m 4 -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/ || true) [ "$code" = "200" ] && { ok_web=1; echo " ✅ asf-web up / HTTP=$code"; } fi need=0 [ $CORE -eq 1 ] && [ $ok_core -eq 0 ] && need=1 [ $WEB -eq 1 ] && [ $ok_web -eq 0 ] && need=1 [ $need -eq 0 ] && break sleep 2 done [ $CORE -eq 1 ] && [ $ok_core -eq 0 ] && echo " ⚠️ asf-core non ha risposto /health in tempo (guarda $LOG_DIR/asf-core.log)" [ $WEB -eq 1 ] && [ $ok_web -eq 0 ] && echo " ⚠️ asf-web non ha risposto / in tempo (Next.js 14 first compile slow: attendi / guarda $LOG_DIR/asf-web.log)" echo echo "ASF stack ready. In attesa (foreground)… Ctrl+C = stop." # wait for pid in "${pids[@]}"; do wait "$pid" || true done graceful_stop