Files
asf/scripts/start-stack.sh
2026-09-08 07:21:34 -04:00

226 lines
7.3 KiB
Bash
Executable File

#!/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 - <<PY 2>/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