#!/usr/bin/env bash # start-server.sh — arranque del servidor web de yt-scraper en macOS/Linux. # Espejo de scripts/start-server.ps1 (Windows); mismo formato de .run/server.info # ("PUERTO PID FECHA"), asi ambos conviven sobre el mismo proyecto. # # Uso: # bash scripts/start-server.sh (o `make serve`) # PYTHON_EXE=/ruta/python3 bash scripts/start-server.sh # igual que -PythonExe en el .ps1 # # Compatible con el bash 3.2 de macOS y con POSIX sh: sin arrays asociativos, # sin ${var,,}, sin mapfile. set -u # --- raiz del proyecto (carpeta padre de scripts/) --- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" || exit 1 ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" || exit 1 cd "$ROOT" || exit 1 RUN_DIR=".run" INFO_FILE="$RUN_DIR/server.info" LOG_FILE="$RUN_DIR/server.log" ERR_LOG_FILE="$RUN_DIR/server.err.log" HOST="127.0.0.1" PORT_MIN=8000 PORT_MAX=8100 READY_TIMEOUT=30 # segundos maximos esperando /healthz say() { printf '%s\n' "$*"; } # --- interprete Python: venv del proyecto si existe, si no python3 --- if [ -n "${PYTHON_EXE:-}" ]; then PY="$PYTHON_EXE" elif [ -x ".venv/bin/python" ]; then PY=".venv/bin/python" else PY="$(command -v python3 || command -v python || true)" if [ -z "$PY" ]; then say " [error] no encontre python3. Instalalo con: brew install python@3.12" exit 1 fi fi # --- GET a /healthz; exit 0 si responde 'ok' (curl, con fallback a python) --- healthz_ok() { # $1 = puerto if command -v curl >/dev/null 2>&1; then curl -fsS --max-time 2 "http://$HOST:$1/healthz" 2>/dev/null | grep -q ok else "$PY" -c "import urllib.request,sys; sys.exit(0 if b'ok' in urllib.request.urlopen('http://$HOST:$1/healthz', timeout=2).read() else 1)" >/dev/null 2>&1 fi } # --- abre el navegador segun el SO; si no puede, imprime la URL --- open_browser() { case "$(uname)" in Darwin) if command -v open >/dev/null 2>&1; then open "$1" && say " [ok] navegador abierto -> $1" && return 0 fi ;; *) if command -v xdg-open >/dev/null 2>&1; then xdg-open "$1" && say " [ok] navegador abierto -> $1" && return 0 fi ;; esac say " [info] abre esta URL en tu navegador: $1" } mkdir -p "$RUN_DIR" say "" say " [yt-scraper] iniciando servidor local..." # --- (1) servidor ya registrado y vivo? -> reutilizarlo --- # El puerto respondiendo es la verdad: si /healthz contesta, ese ES el # servidor corriendo, aunque el PID registrado haya muerto (el proceso # padre puede terminar y dejar el hijo vivo). if [ -f "$INFO_FILE" ]; then reg_port="" first_line="$(head -n 1 "$INFO_FILE" 2>/dev/null || true)" # primera linea: "PUERTO PID FECHA" set -- $first_line reg_port="${1:-}" if [ -n "$reg_port" ] && healthz_ok "$reg_port"; then say " [ok] servidor ya activo en puerto $reg_port" say " URL: http://localhost:$reg_port" say " log: $LOG_FILE" say " stop: bash scripts/stop-server.sh" open_browser "http://$HOST:$reg_port" say "" exit 0 fi say " [info] Habia un registro viejo de otra sesion; limpiandolo..." rm -f "$INFO_FILE" fi # --- (2) puertos libres en 8000-8100 (prueba de bind con Python) --- FREE_PORTS="$("$PY" - <<'PYEOF' 2>/dev/null import socket free = [] for port in range(8000, 8101): s = socket.socket() try: s.bind(("127.0.0.1", port)) except OSError: s.close() continue s.close() free.append(str(port)) if len(free) >= 8: break print(" ".join(free)) PYEOF )" if [ -z "$FREE_PORTS" ]; then say " [error] no se encontro ningun puerto libre ($PORT_MIN-$PORT_MAX)" exit 2 fi # --- (3) lanzar uvicorn en cada candidato hasta que uno responda --- launched=0 for PORT in $FREE_PORTS; do rm -f "$LOG_FILE" "$ERR_LOG_FILE" say " [...] Encendiendo el servidor (intento $PORT)..." nohup "$PY" -m uvicorn yt_scraper.webapp.app:app \ --host "$HOST" --port "$PORT" --log-level info \ >>"$LOG_FILE" 2>>"$ERR_LOG_FILE" & SERVER_PID=$! say " El servidor corre en segundo plano. Registro: $LOG_FILE" # Esperar /healthz (max ~30 s); si el proceso muere, probar otro puerto. waited=0 ready=0 while [ "$waited" -lt "$READY_TIMEOUT" ]; do if ! kill -0 "$SERVER_PID" 2>/dev/null; then say " [warn] el proceso murio al arrancar; probando el siguiente puerto..." break fi if healthz_ok "$PORT"; then ready=1 break fi sleep 1 waited=$((waited + 1)) done if [ "$ready" -ne 1 ]; then # Mostrar el motivo probable del fallo para poder diagnosticarlo. if [ -f "$ERR_LOG_FILE" ]; then say " (ultimas lineas de $ERR_LOG_FILE):" tail -n 6 "$ERR_LOG_FILE" 2>/dev/null | sed 's/^/ /' fi kill "$SERVER_PID" 2>/dev/null continue fi printf '%s %s %s\n' "$PORT" "$SERVER_PID" "$(date '+%Y-%m-%dT%H:%M:%S%z')" > "$INFO_FILE" say " [ok] Servidor encendido." say " URL: http://localhost:$PORT" say " log: $LOG_FILE" say " stop: bash scripts/stop-server.sh" open_browser "http://$HOST:$PORT" launched=1 break done if [ "$launched" -ne 1 ]; then say " [error] no se pudo iniciar el servidor en ninguno de los puertos candidatos." if [ -f "$LOG_FILE" ]; then say " --- ultimo log del servidor ---" tail -n 25 "$LOG_FILE" 2>/dev/null | sed 's/^/ /' else say ' instala dependencias: pip install -e ".[web]"' fi say "" exit 3 fi say "" exit 0