Ctrl K
ComunidadGuías…

start.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# ──────────────────────────────────────────────
# Spendif.ai — Startup script (macOS / Linux)
# Conforme a SW_ENGINEERING_BLUEPRINT.md §16.
# ──────────────────────────────────────────────
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

info()  { echo -e "${GREEN}[INFO]${NC}  $*"; }
warn()  { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }

# ── App paths (assoluti = chiave univoca per pgrep, §16.6.1) ───────────────
APP_PATH_UI="$SCRIPT_DIR/app.py"
APP_PATH_API="$SCRIPT_DIR/api/main.py"

UI_PORT_BASE=8501
UI_PORT_MAX=8510
API_PORT_BASE=8000
API_PORT_MAX=8010

# ── Multi-instance management (§16.6) ──────────────────────────────────────
kill_previous_instance() {
    local pattern="$1"
    local label="${2:-istanza}"
    local pids
    pids=$(pgrep -f "$pattern" 2>/dev/null || true)
    [ -z "$pids" ] && return 0
    for pid in $pids; do
        if [ "$pid" = "$$" ] || [ "$pid" = "$PPID" ]; then
            continue
        fi
        warn "Killing previous $label (PID $pid)..."
        kill -TERM "$pid" 2>/dev/null || true
        for _ in 1 2 3 4 5; do
            kill -0 "$pid" 2>/dev/null || break
            sleep 0.5
        done
        kill -KILL "$pid" 2>/dev/null || true
    done
    return 0
}

find_free_port() {
    local base="$1"
    local max="$2"
    local port
    for port in $(seq "$base" "$max"); do
        if ! lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
            echo "$port"
            return 0
        fi
    done
    return 1
}

# ── stop mode — bypass del pre-flight (§16.6.4) ────────────────────────────
if [ "${1:-}" = "stop" ]; then
    kill_previous_instance "$APP_PATH_UI"  "UI"
    kill_previous_instance "$APP_PATH_API" "API"
    info "Stopped (or nothing to stop)."
    exit 0
fi

# ── Pre-flight checks ──────────────────────────

# Python 3.13+ — cerca il più recente compatibile
PYTHON=""
for candidate in python3.14 python3.13 python3; do
    if command -v "$candidate" &>/dev/null; then
        ver=$("$candidate" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
        major=$(echo "$ver" | cut -d. -f1)
        minor=$(echo "$ver" | cut -d. -f2)
        if [ "$major" -ge 3 ] && [ "$minor" -ge 13 ]; then
            PYTHON="$candidate"
            PY_VER="$ver"
            break
        fi
    fi
done
if [ -z "$PYTHON" ]; then
    error "Python >= 3.13 non trovato. Installa Python >= 3.13."
fi
info "Python $PY_VER OK ($PYTHON)"

# uv
if ! command -v uv &>/dev/null; then
    warn "uv non trovato. Installazione in corso..."
    curl -LsSf https://astral.sh/uv/install.sh | sh
    export PATH="$HOME/.local/bin:$PATH"
    if ! command -v uv &>/dev/null; then
        error "Installazione di uv fallita. Installa manualmente: https://docs.astral.sh/uv/"
    fi
fi
info "uv $(uv --version | head -1) OK"

# ── Setup ───────────────────────────────────────

# .env
if [ ! -f .env ]; then
    if [ -f .env.example ]; then
        cp .env.example .env
        info "File .env creato da .env.example"
    else
        warn "File .env.example non trovato — procedo senza .env"
    fi
fi

# Dipendenze (protegge build custom — vedi scripts/_lib/protect_custom.sh, §16.4.1)
info "Sincronizzazione dipendenze..."
SAFE_SYNC_MODE=non-interactive
# shellcheck source=scripts/_lib/protect_custom.sh
source "$SCRIPT_DIR/scripts/_lib/protect_custom.sh"
safe_sync_run

# Attivazione virtualenv
VENV_DIR="$SCRIPT_DIR/.venv"
if [ ! -d "$VENV_DIR" ]; then
    error "Virtualenv non trovato in $VENV_DIR. Esegui 'uv sync' manualmente."
fi
export PATH="$VENV_DIR/bin:$PATH"
export VIRTUAL_ENV="$VENV_DIR"
info "Virtualenv attivato ($VENV_DIR)"

# ── Avvio ───────────────────────────────────────

MODE="${1:-ui}"

case "$MODE" in
    ui)
        kill_previous_instance "$APP_PATH_UI" "UI"
        UI_PORT=$(find_free_port "$UI_PORT_BASE" "$UI_PORT_MAX") \
            || error "Nessuna porta libera in range $UI_PORT_BASE-$UI_PORT_MAX"
        [ "$UI_PORT" != "$UI_PORT_BASE" ] && warn "Porta $UI_PORT_BASE occupata (non nostra) — uso $UI_PORT"
        info "Avvio Streamlit UI su http://localhost:$UI_PORT"
        exec "$VENV_DIR/bin/streamlit" run "$APP_PATH_UI" --server.headless true --server.port "$UI_PORT"
        ;;
    api)
        kill_previous_instance "$APP_PATH_API" "API"
        API_PORT=$(find_free_port "$API_PORT_BASE" "$API_PORT_MAX") \
            || error "Nessuna porta libera in range $API_PORT_BASE-$API_PORT_MAX"
        [ "$API_PORT" != "$API_PORT_BASE" ] && warn "Porta $API_PORT_BASE occupata (non nostra) — uso $API_PORT"
        info "Avvio API server su http://localhost:$API_PORT"
        exec "$VENV_DIR/bin/uvicorn" api.main:app --host 0.0.0.0 --port "$API_PORT"
        ;;
    all)
        kill_previous_instance "$APP_PATH_UI"  "UI"
        kill_previous_instance "$APP_PATH_API" "API"
        UI_PORT=$(find_free_port "$UI_PORT_BASE" "$UI_PORT_MAX") \
            || error "Nessuna porta libera in range $UI_PORT_BASE-$UI_PORT_MAX"
        API_PORT=$(find_free_port "$API_PORT_BASE" "$API_PORT_MAX") \
            || error "Nessuna porta libera in range $API_PORT_BASE-$API_PORT_MAX"
        [ "$UI_PORT"  != "$UI_PORT_BASE"  ] && warn "Porta $UI_PORT_BASE occupata — uso $UI_PORT per UI"
        [ "$API_PORT" != "$API_PORT_BASE" ] && warn "Porta $API_PORT_BASE occupata — uso $API_PORT per API"
        info "Avvio UI + API..."
        "$VENV_DIR/bin/uvicorn" api.main:app --host 0.0.0.0 --port "$API_PORT" &
        API_PID=$!
        trap "kill $API_PID 2>/dev/null" EXIT
        info "API avviata (PID $API_PID) su http://localhost:$API_PORT"
        info "Avvio Streamlit UI su http://localhost:$UI_PORT"
        "$VENV_DIR/bin/streamlit" run "$APP_PATH_UI" --server.headless true --server.port "$UI_PORT"
        ;;
    *)
        echo "Uso: $0 [ui|api|all|stop]"
        echo "  ui    — Solo interfaccia Streamlit (default)"
        echo "  api   — Solo server API REST"
        echo "  all   — Entrambi"
        echo "  stop  — Termina istanze precedenti (UI + API) ed esce"
        exit 1
        ;;
esac