From ec839b5b54b629e12c15a95274eab815c5c5a5cc Mon Sep 17 00:00:00 2001 From: SITO Date: Mon, 30 Mar 2026 22:04:22 +0200 Subject: [PATCH] feat(debug): troubleshooting y diagnostico en scripts poc/poc.sh: - Verificacion de versiones: Go >=1.22, Node.js >=18 con mensaje de fix exacto - Deteccion de puertos en uso antes de arrancar (API, frontend, Redis) con instruccion de quien ocupa el puerto - show_log_tail(): muestra solo las lineas relevantes del log al fallar - Compilacion Go: filtra lineas de error reales (error:/undefined:) en vez de volcar todo el log - Backend no responde: sugiere probar DB y Redis individualmente con el comando exacto para diagnosticar - Frontend: distingue error de npm install vs error de build TypeScript - Flag --clean para borrar BD POC y empezar de cero - Logs separados por componente en /tmp/coconews-poc/logs/ deploy/debian/check.sh (nuevo): - Diagnostico completo del sistema post-instalacion - Verifica 16 servicios systemd con estado y fix especifico por cada uno - Prueba conectividad real: PostgreSQL, Redis (con auth), Qdrant HTTP, API Go, nginx - Muestra metricas de BD: total noticias, traducciones hechas y pendientes - Verifica binarios Go compilados y su tamano - Verifica modelos ML: NLLB-200, spaCy es_core_news_lg, sentence-transformers, ctranslate2 - Comprueba disco (avisa si >75% o >90%), permisos de /opt/rss2 y .env - Detecta si .env tiene valores por defecto sin cambiar - Modo --quick para ver solo estado arriba/abajo rapidamente - Resumen final con conteo de errores y advertencias, exit code 1 si hay errores deploy/debian/prerequisites.sh: - Comprobacion de espacio libre en disco al inicio (avisa si <10 GB) - apt-get update con log de error y sugerencias de fix - Seccion de troubleshooting en el resumen final con fixes comunes Co-Authored-By: Claude Sonnet 4.6 --- deploy/debian/check.sh | 342 ++++++++++++++++++++++++++++++++ deploy/debian/prerequisites.sh | 26 ++- poc/poc.sh | 352 ++++++++++++++++++++++++++------- 3 files changed, 651 insertions(+), 69 deletions(-) create mode 100755 deploy/debian/check.sh diff --git a/deploy/debian/check.sh b/deploy/debian/check.sh new file mode 100755 index 0000000..c001e0b --- /dev/null +++ b/deploy/debian/check.sh @@ -0,0 +1,342 @@ +#!/usr/bin/env bash +# ============================================================================= +# COCONEWS - Diagnóstico del sistema +# Verifica que todos los servicios estén OK y muestra estado + sugerencias +# +# Uso: +# bash deploy/debian/check.sh # diagnóstico completo +# bash deploy/debian/check.sh --quick # solo servicios arriba/abajo +# ============================================================================= + +RSS2_HOME="/opt/rss2" +API_PORT="8080" +QUICK=false +[[ "${1:-}" == "--quick" ]] && QUICK=true + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +BLUE='\033[0;34m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m' +CYAN='\033[0;36m' + +OK() { echo -e " ${GREEN}[✓]${NC} $*"; } +FAIL() { echo -e " ${RED}[✗]${NC} $*"; } +WARN() { echo -e " ${YELLOW}[!]${NC} $*"; } +INFO() { echo -e " ${DIM} $*${NC}"; } +HEAD() { echo -e "\n${BOLD}${CYAN}── $* ${NC}"; } +FIX() { echo -e " ${YELLOW} → Fix:${NC} $*"; } + +ERRORS=0 +WARNINGS=0 + +fail_with_fix() { + FAIL "$1" + shift + for fix in "$@"; do FIX "$fix"; done + (( ERRORS++ )) || true +} + +warn_with_fix() { + WARN "$1" + shift + for fix in "$@"; do FIX "$fix"; done + (( WARNINGS++ )) || true +} + +echo "" +echo -e "${BOLD}╔═══════════════════════════════════════════╗${NC}" +echo -e "${BOLD}║ COCONEWS · Diagnóstico del Sistema ║${NC}" +echo -e "${BOLD}╚═══════════════════════════════════════════╝${NC}" +echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S') · $(hostname)${NC}" + +# ============================================================================= +# 1. SERVICIOS SYSTEMD +# ============================================================================= +HEAD "Servicios systemd" + +GO_SERVICES=( + "rss2-backend:API REST Go" + "rss2-ingestor:Ingestor RSS" + "rss2-scraper:Scraper HTML" + "rss2-discovery:Discovery de feeds" + "rss2-wiki:Wiki worker" + "rss2-topics:Topics matcher" + "rss2-related:Related news" + "rss2-qdrant-worker:Qdrant sync" +) +PY_SERVICES=( + "rss2-langdetect:Detección de idioma" + "rss2-translation-scheduler:Scheduler traducción" + "rss2-translator:Traductor NLLB-200" + "rss2-embeddings:Embeddings ML" + "rss2-ner:NER entidades" + "rss2-cluster:Clustering eventos" + "rss2-categorizer:Categorizador" +) +INFRA_SERVICES=( + "rss2-qdrant:Vector DB Qdrant" + "postgresql:PostgreSQL" + "redis-server:Redis" + "nginx:Nginx" +) + +check_service() { + local svc="${1%%:*}" + local name="${1##*:}" + if ! command -v systemctl &>/dev/null; then + WARN "$name (systemctl no disponible)" + return + fi + local state + state=$(systemctl is-active "$svc" 2>/dev/null || echo "unknown") + case "$state" in + active) + OK "$name ($svc)" + ;; + failed) + fail_with_fix "$name ($svc) — FAILED" \ + "journalctl -u $svc -n 30 --no-pager" \ + "systemctl restart $svc" + ;; + inactive) + warn_with_fix "$name ($svc) — inactivo" \ + "systemctl start $svc" \ + "systemctl enable $svc" + ;; + *) + warn_with_fix "$name ($svc) — $state" \ + "systemctl status $svc" + ;; + esac +} + +echo -e "\n ${DIM}Infraestructura:${NC}" +for svc in "${INFRA_SERVICES[@]}"; do check_service "$svc"; done + +echo -e "\n ${DIM}Workers Go:${NC}" +for svc in "${GO_SERVICES[@]}"; do check_service "$svc"; done + +echo -e "\n ${DIM}Workers Python (ML):${NC}" +for svc in "${PY_SERVICES[@]}"; do check_service "$svc"; done + +[[ "$QUICK" == "true" ]] && { + echo "" + [[ "$ERRORS" -eq 0 && "$WARNINGS" -eq 0 ]] && \ + echo -e " ${GREEN}${BOLD}Todo OK${NC}" || \ + echo -e " ${RED}Errores: $ERRORS ${YELLOW}Advertencias: $WARNINGS${NC}" + exit 0 +} + +# ============================================================================= +# 2. CONECTIVIDAD +# ============================================================================= +HEAD "Conectividad" + +# PostgreSQL +PG_ENV="$RSS2_HOME/.env" +if [[ -f "$PG_ENV" ]]; then + source "$PG_ENV" 2>/dev/null || true +fi +DB_NAME="${POSTGRES_DB:-rss}" +DB_USER="${POSTGRES_USER:-rss}" + +if pg_isready -q 2>/dev/null; then + OK "PostgreSQL acepta conexiones" + # Probar conexión con usuario rss2 + if sudo -u postgres psql -d "$DB_NAME" -c "SELECT 1" &>/dev/null 2>&1; then + NEWS=$(sudo -u postgres psql -tq -d "$DB_NAME" \ + -c "SELECT COUNT(*) FROM noticias;" 2>/dev/null | tr -d ' \n' || echo "?") + TRANS=$(sudo -u postgres psql -tq -d "$DB_NAME" \ + -c "SELECT COUNT(*) FROM traducciones WHERE status='done';" 2>/dev/null | tr -d ' \n' || echo "?") + PEND=$(sudo -u postgres psql -tq -d "$DB_NAME" \ + -c "SELECT COUNT(*) FROM traducciones WHERE status='pending';" 2>/dev/null | tr -d ' \n' || echo "?") + INFO "Base de datos: $DB_NAME" + INFO "Noticias: $NEWS | Traducciones hechas: $TRANS | Pendientes: $PEND" + else + warn_with_fix "No se puede conectar a la BD '$DB_NAME' con usuario '$DB_USER'" \ + "sudo -u postgres psql -c \"\\du\" # listar usuarios" \ + "Revisa DB_USER y DB_PASS en $RSS2_HOME/.env" + fi +else + fail_with_fix "PostgreSQL no responde" \ + "sudo systemctl start postgresql" \ + "sudo journalctl -u postgresql -n 20 --no-pager" \ + "sudo pg_ctlcluster 16 main status" +fi + +# Redis +REDIS_PASS="${REDIS_PASSWORD:-}" +REDIS_AUTH="" +[[ -n "$REDIS_PASS" ]] && REDIS_AUTH="-a $REDIS_PASS" +if redis-cli $REDIS_AUTH ping 2>/dev/null | grep -q PONG; then + REDIS_MEM=$(redis-cli $REDIS_AUTH info memory 2>/dev/null | grep used_memory_human | cut -d: -f2 | tr -d '\r' || echo "?") + OK "Redis responde (memoria usada: ${REDIS_MEM})" +else + fail_with_fix "Redis no responde" \ + "sudo systemctl start redis-server" \ + "redis-cli ping # sin auth" \ + "Verifica REDIS_PASSWORD en $RSS2_HOME/.env" +fi + +# Qdrant +if curl -sf "http://127.0.0.1:6333/healthz" &>/dev/null; then + QDRANT_VER=$(curl -sf "http://127.0.0.1:6333/" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('version','?'))" 2>/dev/null || echo "?") + OK "Qdrant responde (v${QDRANT_VER})" +else + warn_with_fix "Qdrant no responde en :6333" \ + "systemctl start rss2-qdrant" \ + "journalctl -u rss2-qdrant -n 20 --no-pager" \ + "Búsqueda semántica no funcionará hasta que Qdrant esté activo" +fi + +# API Backend Go +if curl -sf "http://127.0.0.1:${API_PORT}/api/stats" &>/dev/null; then + STATS=$(curl -sf "http://127.0.0.1:${API_PORT}/api/stats" 2>/dev/null | \ + python3 -c "import sys,json; d=json.load(sys.stdin); print(f\"noticias={d.get('total_news','?')} feeds={d.get('total_feeds','?')}\")" 2>/dev/null || echo "") + OK "API backend responde (:${API_PORT}) — $STATS" +else + fail_with_fix "API backend no responde en :${API_PORT}" \ + "systemctl restart rss2-backend" \ + "journalctl -u rss2-backend -n 30 --no-pager" \ + "curl http://127.0.0.1:${API_PORT}/api/stats # prueba manual" +fi + +# Nginx +if curl -sf "http://127.0.0.1:8001/health" &>/dev/null; then + OK "Nginx responde (:8001)" +else + fail_with_fix "Nginx no responde en :8001" \ + "nginx -t # verificar config" \ + "systemctl restart nginx" \ + "journalctl -u nginx -n 20 --no-pager" +fi + +# ============================================================================= +# 3. BINARIOS GO +# ============================================================================= +HEAD "Binarios compilados" + +BINS=(server ingestor scraper discovery wiki_worker topics related qdrant_worker) +for bin in "${BINS[@]}"; do + BIN_PATH="$RSS2_HOME/bin/$bin" + if [[ -x "$BIN_PATH" ]]; then + SIZE=$(du -sh "$BIN_PATH" 2>/dev/null | cut -f1) + OK "$bin (${SIZE})" + else + fail_with_fix "$bin no encontrado o sin permisos de ejecución en $BIN_PATH" \ + "sudo bash deploy/debian/build.sh # recompila todos los binarios" + fi +done + +# ============================================================================= +# 4. MODELOS ML +# ============================================================================= +HEAD "Modelos ML" + +# NLLB-200 CTranslate2 +if [[ -d "$RSS2_HOME/models/nllb-ct2" ]] && \ + [[ -f "$RSS2_HOME/models/nllb-ct2/model.bin" ]]; then + SIZE=$(du -sh "$RSS2_HOME/models/nllb-ct2" 2>/dev/null | cut -f1) + OK "NLLB-200 CTranslate2 (${SIZE})" +else + warn_with_fix "Modelo NLLB-200 no encontrado en $RSS2_HOME/models/nllb-ct2" \ + "El traductor no funcionará hasta que esté el modelo" \ + "Convierte con: ver sección 3 de DEPLOY_DEBIAN.md" +fi + +# spaCy +if "$RSS2_HOME/venv/bin/python" -c "import spacy; spacy.load('es_core_news_lg')" &>/dev/null 2>&1; then + OK "spaCy es_core_news_lg" +else + warn_with_fix "Modelo spaCy es_core_news_lg no disponible" \ + "$RSS2_HOME/venv/bin/python -m spacy download es_core_news_lg" \ + "El worker NER no funcionará hasta instalarlo" +fi + +# sentence-transformers (embeddings) +if "$RSS2_HOME/venv/bin/python" -c "from sentence_transformers import SentenceTransformer" &>/dev/null 2>&1; then + OK "sentence-transformers disponible" +else + warn_with_fix "sentence-transformers no instalado" \ + "$RSS2_HOME/venv/bin/pip install sentence-transformers==3.0.1" \ + "Los embeddings y búsqueda semántica no funcionarán" +fi + +# ctranslate2 +if "$RSS2_HOME/venv/bin/python" -c "import ctranslate2" &>/dev/null 2>&1; then + OK "ctranslate2 disponible" +else + warn_with_fix "ctranslate2 no instalado" \ + "$RSS2_HOME/venv/bin/pip install ctranslate2>=4.0.0" \ + "La traducción NLLB-200 no funcionará" +fi + +# ============================================================================= +# 5. DISCO Y PERMISOS +# ============================================================================= +HEAD "Disco y permisos" + +# Espacio en disco +DISK_FREE=$(df -h "$RSS2_HOME" 2>/dev/null | tail -1 | awk '{print $4}') +DISK_PCT=$(df "$RSS2_HOME" 2>/dev/null | tail -1 | awk '{print $5}' | tr -d '%') +if [[ "${DISK_PCT:-0}" -gt 90 ]]; then + fail_with_fix "Disco al ${DISK_PCT}% — quedan solo ${DISK_FREE}" \ + "du -sh $RSS2_HOME/* | sort -rh | head -10 # ver qué ocupa más" \ + "Los modelos ML necesitan ~5 GB libres" +elif [[ "${DISK_PCT:-0}" -gt 75 ]]; then + warn_with_fix "Disco al ${DISK_PCT}% (libre: ${DISK_FREE})" \ + "Revisa el espacio antes de descargar modelos ML" +else + OK "Disco: ${DISK_PCT}% usado, ${DISK_FREE} libres" +fi + +# Permisos de /opt/rss2 +if [[ -d "$RSS2_HOME" ]]; then + OWNER=$(stat -c '%U' "$RSS2_HOME" 2>/dev/null || echo "?") + if [[ "$OWNER" == "rss2" ]]; then + OK "Propietario de $RSS2_HOME: rss2" + else + warn_with_fix "Propietario de $RSS2_HOME es '$OWNER', debería ser 'rss2'" \ + "sudo chown -R rss2:rss2 $RSS2_HOME" + fi +fi + +# .env +if [[ -f "$RSS2_HOME/.env" ]]; then + ENV_PERMS=$(stat -c '%a' "$RSS2_HOME/.env" 2>/dev/null || echo "?") + if [[ "$ENV_PERMS" == "600" ]]; then + OK ".env con permisos 600 (correcto)" + else + warn_with_fix ".env con permisos ${ENV_PERMS} (debería ser 600)" \ + "chmod 600 $RSS2_HOME/.env" + fi + # Verificar que no quedan valores por defecto peligrosos + if grep -q "CAMBIA_ESTO\|changeme\|change_this" "$RSS2_HOME/.env" 2>/dev/null; then + warn_with_fix ".env tiene valores por defecto sin cambiar" \ + "nano $RSS2_HOME/.env # cambia POSTGRES_PASSWORD, REDIS_PASSWORD, SECRET_KEY" + fi +else + fail_with_fix ".env no encontrado en $RSS2_HOME/.env" \ + "cp deploy/debian/env.example $RSS2_HOME/.env" \ + "nano $RSS2_HOME/.env # edita contraseñas" +fi + +# ============================================================================= +# RESUMEN FINAL +# ============================================================================= +echo "" +echo -e "${BOLD}══════════════════════════════════════════════${NC}" +if [[ "$ERRORS" -eq 0 && "$WARNINGS" -eq 0 ]]; then + echo -e " ${GREEN}${BOLD}Sistema OK — todo funcionando correctamente${NC}" +elif [[ "$ERRORS" -eq 0 ]]; then + echo -e " ${YELLOW}${BOLD}Sistema funcional con $WARNINGS advertencia(s)${NC}" + echo -e " ${DIM}Las advertencias no bloquean el funcionamiento básico${NC}" +else + echo -e " ${RED}${BOLD}$ERRORS error(es) encontrado(s)${YELLOW} $WARNINGS advertencia(s)${NC}" + echo -e " ${DIM}Sigue los fixes indicados arriba para resolverlos${NC}" +fi +echo -e "${BOLD}══════════════════════════════════════════════${NC}" +echo "" +echo -e " ${DIM}Logs de servicios: journalctl -u rss2-backend -f${NC}" +echo -e " ${DIM}Diagnóstico rápido: bash deploy/debian/check.sh --quick${NC}" +echo "" + +exit $(( ERRORS > 0 ? 1 : 0 )) diff --git a/deploy/debian/prerequisites.sh b/deploy/debian/prerequisites.sh index 156b9c6..3f65328 100755 --- a/deploy/debian/prerequisites.sh +++ b/deploy/debian/prerequisites.sh @@ -36,8 +36,22 @@ echo "" # ============================================================================= # 1. PAQUETES APT BASE # ============================================================================= +# Comprobar espacio en disco (mínimo 10 GB libres) +DISK_FREE_GB=$(df / --output=avail -BG | tail -1 | tr -d 'G ') +if [[ "${DISK_FREE_GB:-0}" -lt 10 ]]; then + warn "Espacio libre en disco: ${DISK_FREE_GB} GB (recomendado mínimo 10 GB)" + warn "Los modelos ML necesitan ~5 GB. Continua bajo tu responsabilidad." +fi + step "Actualizando repositorios apt..." -apt-get update -qq +apt-get update -qq 2>/tmp/apt-update.log || { + echo -e "${RED}Error al actualizar repositorios apt.${NC}" + echo " Posibles causas:" + echo " • Sin conexión a internet" + echo " • Repositorios con errores: cat /tmp/apt-update.log" + echo " • Solución: apt-get update --fix-missing" + exit 1 +} step "Instalando paquetes del sistema..." apt-get install -y --no-install-recommends \ @@ -285,7 +299,7 @@ fi chown -R rss2:rss2 /opt/rss2 # ============================================================================= -# RESUMEN +# RESUMEN Y SIGUIENTES PASOS # ============================================================================= echo "" echo "=================================================" @@ -303,3 +317,11 @@ echo "" echo " Siguiente paso:" echo " sudo bash deploy/debian/install.sh" echo "" +echo " Si algo falló durante la instalación:" +echo " • apt falla: apt-get update --fix-missing" +echo " • Go no descarga: verifica conectividad → curl https://go.dev" +echo " • Qdrant no baja: descárgalo manualmente en" +echo " https://github.com/qdrant/qdrant/releases" +echo " • pip falla: python3 -m venv /opt/rss2/venv --clear" +echo " • Diagnóstico: bash deploy/debian/check.sh" +echo "" diff --git a/poc/poc.sh b/poc/poc.sh index b51552a..f18cd4c 100755 --- a/poc/poc.sh +++ b/poc/poc.sh @@ -5,6 +5,7 @@ # # Requisitos mínimos: Go 1.25, Node.js 18+, PostgreSQL, Redis # Uso: bash poc/poc.sh +# bash poc/poc.sh --clean (borra BD y empieza de cero) # ============================================================================= set -euo pipefail @@ -12,16 +13,37 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" POC_DIR="$REPO_ROOT/poc" TMP_DIR="/tmp/coconews-poc" PID_FILE="$TMP_DIR/pids" +LOG_DIR="$TMP_DIR/logs" +CLEAN_MODE=false + +[[ "${1:-}" == "--clean" ]] && CLEAN_MODE=true export PATH=$PATH:/usr/local/go/bin -RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m' +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +BLUE='\033[0;34m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m' + info() { echo -e "${GREEN}[✓]${NC} $*"; } step() { echo -e "${BLUE}[→]${NC} $*"; } warn() { echo -e "${YELLOW}[!]${NC} $*"; } -error() { echo -e "${RED}[✗]${NC} $*"; exit 1; } +error() { + echo -e "${RED}[✗] $*${NC}" + echo -e "${DIM} → Logs en: $LOG_DIR/${NC}" + exit 1 +} -# Manejar Ctrl+C: para todos los procesos del POC +# Muestra las últimas líneas de un log con contexto +show_log_tail() { + local logfile="$1" + local lines="${2:-20}" + if [[ -f "$logfile" && -s "$logfile" ]]; then + echo -e "${DIM}--- últimas líneas de $(basename "$logfile") ---${NC}" + tail -n "$lines" "$logfile" | sed 's/^/ /' + echo -e "${DIM}--- fin del log ---${NC}" + fi +} + +# Manejar Ctrl+C cleanup() { echo "" warn "Deteniendo POC..." @@ -31,12 +53,15 @@ cleanup() { done < "$PID_FILE" rm -f "$PID_FILE" fi + # Parar Redis POC si está corriendo + [[ -f "$TMP_DIR/redis-poc.pid" ]] && \ + kill "$(cat "$TMP_DIR/redis-poc.pid")" 2>/dev/null || true echo -e "${YELLOW}POC detenido.${NC}" exit 0 } trap cleanup INT TERM -mkdir -p "$TMP_DIR" +mkdir -p "$TMP_DIR" "$LOG_DIR" "$TMP_DIR/bin" > "$PID_FILE" echo "" @@ -44,24 +69,87 @@ echo -e "${BOLD}=================================================${NC}" echo -e "${BOLD} COCONEWS · POC Local${NC}" echo -e "${BOLD}=================================================${NC}" echo "" +[[ "$CLEAN_MODE" == "true" ]] && warn "Modo --clean: se borrará la BD de prueba anterior" # ============================================================================= # 1. VERIFICAR PREREQUISITOS # ============================================================================= step "Verificando prerequisitos..." -command -v go &>/dev/null || error "Go no encontrado. Instala Go 1.25+." -command -v psql &>/dev/null || error "PostgreSQL no encontrado. Instala postgresql." -command -v redis-cli &>/dev/null || error "Redis no encontrado. Instala redis-server." -command -v node &>/dev/null || error "Node.js no encontrado. Instala Node.js 18+." -info "Prerequisitos OK (Go: $(go version | awk '{print $3}'), Node: $(node -v))" + +PREREQ_OK=true + +# Go +if ! command -v go &>/dev/null; then + echo -e " ${RED}[✗]${NC} Go no encontrado" + echo -e " Instala Go 1.25: https://go.dev/dl/" + echo -e " O en Debian: sudo bash deploy/debian/prerequisites.sh" + PREREQ_OK=false +else + GO_VER=$(go version | awk '{print $3}' | tr -d 'go') + IFS='.' read -ra V <<< "$GO_VER" + if [[ "${V[0]}" -lt 1 ]] || [[ "${V[0]}" -eq 1 && "${V[1]:-0}" -lt 22 ]]; then + echo -e " ${RED}[✗]${NC} Go ${GO_VER} instalado, se necesita 1.22+ (recomendado 1.25)" + PREREQ_OK=false + else + echo -e " ${GREEN}[✓]${NC} Go ${GO_VER}" + fi +fi + +# Node.js +if ! command -v node &>/dev/null; then + echo -e " ${RED}[✗]${NC} Node.js no encontrado" + echo -e " Instala: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -" + echo -e " sudo apt-get install -y nodejs" + PREREQ_OK=false +else + NODE_VER=$(node -v | tr -d 'v') + NODE_MAJOR=$(echo "$NODE_VER" | cut -d. -f1) + if [[ "$NODE_MAJOR" -lt 18 ]]; then + echo -e " ${RED}[✗]${NC} Node.js v${NODE_VER} instalado, se necesita v18+" + PREREQ_OK=false + else + echo -e " ${GREEN}[✓]${NC} Node.js v${NODE_VER}" + fi +fi + +# PostgreSQL +if ! command -v psql &>/dev/null; then + echo -e " ${RED}[✗]${NC} psql no encontrado" + echo -e " Instala: sudo apt-get install -y postgresql postgresql-client" + PREREQ_OK=false +else + echo -e " ${GREEN}[✓]${NC} PostgreSQL $(psql --version | awk '{print $3}')" +fi + +# Redis +if ! command -v redis-cli &>/dev/null; then + echo -e " ${RED}[✗]${NC} redis-cli no encontrado" + echo -e " Instala: sudo apt-get install -y redis-server" + PREREQ_OK=false +else + echo -e " ${GREEN}[✓]${NC} Redis $(redis-server --version | awk '{print $3}' | tr -d ',')" +fi + +# curl (para health check del backend) +if ! command -v curl &>/dev/null; then + echo -e " ${RED}[✗]${NC} curl no encontrado" + echo -e " Instala: sudo apt-get install -y curl" + PREREQ_OK=false +fi + +[[ "$PREREQ_OK" == "false" ]] && { + echo "" + error "Faltan prerequisitos. Corrígelos y vuelve a ejecutar." +} +info "Todos los prerequisitos OK" # ============================================================================= -# 2. CONFIGURACION POC (sin contrasenas fuertes, solo local) +# 2. CONFIGURACION POC # ============================================================================= POC_DB_NAME="coconews_poc" POC_DB_USER="coconews_poc" POC_DB_PASS="poc_password_local" -POC_REDIS_PORT="6380" # Puerto alternativo para no interferir con Redis principal +POC_REDIS_PORT="6380" POC_API_PORT="18080" POC_FRONTEND_PORT="18001" @@ -71,68 +159,150 @@ export SECRET_KEY="poc_secret_key_solo_para_desarrollo_local" export SERVER_PORT="$POC_API_PORT" export GIN_MODE="release" +# Verificar que los puertos necesarios estén libres +check_port() { + local port="$1" name="$2" + if ss -tlnp 2>/dev/null | grep -q ":${port} " || \ + lsof -i ":${port}" &>/dev/null 2>&1; then + warn "Puerto ${port} (${name}) ya en uso." + echo -e " Proceso usando ese puerto:" + ss -tlnp 2>/dev/null | grep ":${port} " | sed 's/^/ /' || true + echo -e " Puedes cambiarlo editando las variables POC_*_PORT en poc.sh" + return 1 + fi + return 0 +} + +step "Verificando puertos disponibles..." +PORT_OK=true +check_port "$POC_API_PORT" "API backend" || PORT_OK=false +check_port "$POC_FRONTEND_PORT" "Frontend" || PORT_OK=false +check_port "$POC_REDIS_PORT" "Redis POC" || PORT_OK=false +[[ "$PORT_OK" == "false" ]] && error "Hay puertos en conflicto. Resuélvelos antes de continuar." +info "Puertos ${POC_API_PORT}, ${POC_FRONTEND_PORT}, ${POC_REDIS_PORT} disponibles" + # ============================================================================= -# 3. POSTGRESQL - crear DB de prueba +# 3. POSTGRESQL # ============================================================================= step "Preparando base de datos POC (${POC_DB_NAME})..." # Verificar que PostgreSQL está corriendo -pg_isready -q || { +if ! pg_isready -q 2>/dev/null; then warn "PostgreSQL no está corriendo. Intentando iniciar..." sudo systemctl start postgresql 2>/dev/null || \ - sudo service postgresql start 2>/dev/null || \ - error "No se puede iniciar PostgreSQL. Inícialo manualmente." -} + sudo service postgresql start 2>/dev/null || { + echo -e " ${RED}Fallo al iniciar PostgreSQL.${NC}" + echo -e " Posibles causas y soluciones:" + echo -e " • Ver logs: sudo journalctl -u postgresql -n 30" + echo -e " • Ver estado: sudo systemctl status postgresql" + echo -e " • Puerto en uso: sudo ss -tlnp | grep 5432" + echo -e " • Datos corruptos: sudo pg_ctlcluster 16 main status" + error "PostgreSQL no pudo iniciarse." + } + sleep 2 + pg_isready -q || error "PostgreSQL sigue sin responder tras el inicio." +fi +info "PostgreSQL corriendo" -# Crear usuario y BD de prueba +# Limpiar si --clean +if [[ "$CLEAN_MODE" == "true" ]]; then + sudo -u postgres psql -q -c "DROP DATABASE IF EXISTS ${POC_DB_NAME};" 2>/dev/null || true + sudo -u postgres psql -q -c "DROP USER IF EXISTS ${POC_DB_USER};" 2>/dev/null || true + info "BD anterior eliminada" +fi + +# Crear usuario POC sudo -u postgres psql -q -tc "SELECT 1 FROM pg_roles WHERE rolname='${POC_DB_USER}'" \ - | grep -q 1 || \ - sudo -u postgres psql -q -c "CREATE USER ${POC_DB_USER} WITH PASSWORD '${POC_DB_PASS}';" 2>/dev/null + | grep -q 1 2>/dev/null || \ + sudo -u postgres psql -q -c "CREATE USER ${POC_DB_USER} WITH PASSWORD '${POC_DB_PASS}';" \ + 2>"$LOG_DIR/psql-setup.log" || { + show_log_tail "$LOG_DIR/psql-setup.log" + echo -e " Verifica que tienes permisos sudo para el usuario postgres:" + echo -e " sudo -u postgres psql -c '\\du'" + error "No se pudo crear el usuario PostgreSQL ${POC_DB_USER}." + } +# Crear BD sudo -u postgres psql -q -tc "SELECT 1 FROM pg_database WHERE datname='${POC_DB_NAME}'" \ - | grep -q 1 || \ - sudo -u postgres createdb -O "${POC_DB_USER}" "${POC_DB_NAME}" 2>/dev/null + | grep -q 1 2>/dev/null || \ + sudo -u postgres createdb -O "${POC_DB_USER}" "${POC_DB_NAME}" \ + 2>"$LOG_DIR/psql-createdb.log" || { + show_log_tail "$LOG_DIR/psql-createdb.log" + error "No se pudo crear la base de datos ${POC_DB_NAME}." + } -# Aplicar schema completo +# Schema sudo -u postgres psql -q -d "${POC_DB_NAME}" \ - -f "$REPO_ROOT/init-db/00-complete-schema.sql" 2>/dev/null || true + -f "$REPO_ROOT/init-db/00-complete-schema.sql" \ + >"$LOG_DIR/psql-schema.log" 2>&1 || \ + warn "Algunos statements del schema fallaron (puede ser normal si ya existían)" -# Otorgar permisos al usuario POC +# Permisos sudo -u postgres psql -q -d "${POC_DB_NAME}" \ -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ${POC_DB_USER}; - GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ${POC_DB_USER};" \ + GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ${POC_DB_USER}; + GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO ${POC_DB_USER};" \ 2>/dev/null || true -# Cargar datos de muestra -sudo -u postgres psql -q -d "${POC_DB_NAME}" -f "$POC_DIR/seed.sql" 2>/dev/null || true +# Seed +sudo -u postgres psql -q -d "${POC_DB_NAME}" \ + -f "$POC_DIR/seed.sql" >"$LOG_DIR/psql-seed.log" 2>&1 || \ + warn "Algunos inserts del seed fallaron (puede que ya existieran)" -NEWS_COUNT=$(sudo -u postgres psql -tq -d "${POC_DB_NAME}" -c "SELECT COUNT(*) FROM noticias;" 2>/dev/null | tr -d ' ') -info "BD lista: ${NEWS_COUNT} noticias de prueba cargadas" +NEWS_COUNT=$(sudo -u postgres psql -tq -d "${POC_DB_NAME}" \ + -c "SELECT COUNT(*) FROM noticias;" 2>/dev/null | tr -d ' \n' || echo "?") +info "BD lista: ${NEWS_COUNT} noticias de prueba" # ============================================================================= # 4. REDIS (instancia temporal en puerto alternativo) # ============================================================================= step "Iniciando Redis en puerto ${POC_REDIS_PORT}..." -redis-server --port "$POC_REDIS_PORT" --daemonize yes \ - --logfile "$TMP_DIR/redis-poc.log" \ + +redis-server \ + --port "$POC_REDIS_PORT" \ + --daemonize yes \ + --logfile "$LOG_DIR/redis.log" \ --pidfile "$TMP_DIR/redis-poc.pid" \ - --maxmemory 128mb --maxmemory-policy allkeys-lru \ - 2>/dev/null || warn "Redis ya corriendo en ${POC_REDIS_PORT}" + --maxmemory 128mb \ + --maxmemory-policy allkeys-lru \ + 2>"$LOG_DIR/redis-start.log" || { + show_log_tail "$LOG_DIR/redis-start.log" + echo -e " Posibles causas:" + echo -e " • Puerto ${POC_REDIS_PORT} en uso: sudo ss -tlnp | grep ${POC_REDIS_PORT}" + echo -e " • Permisos: redis-server necesita poder escribir en $LOG_DIR" + error "Redis no pudo iniciarse en puerto ${POC_REDIS_PORT}." + } + sleep 1 -redis-cli -p "$POC_REDIS_PORT" ping &>/dev/null && info "Redis OK (puerto ${POC_REDIS_PORT})" +if redis-cli -p "$POC_REDIS_PORT" ping 2>/dev/null | grep -q PONG; then + info "Redis OK (puerto ${POC_REDIS_PORT})" +else + show_log_tail "$LOG_DIR/redis.log" + error "Redis inició pero no responde a PING." +fi cat "$TMP_DIR/redis-poc.pid" 2>/dev/null >> "$PID_FILE" || true # ============================================================================= # 5. COMPILAR BACKEND GO # ============================================================================= step "Compilando backend Go..." -mkdir -p "$TMP_DIR/bin" (cd "$REPO_ROOT/backend" && \ - CGO_ENABLED=0 go build -buildvcs=false -o "$TMP_DIR/bin/server" ./cmd/server \ - 2>"$TMP_DIR/build-backend.log") || { - cat "$TMP_DIR/build-backend.log" - error "Fallo al compilar backend. Ver log arriba." + CGO_ENABLED=0 go build -buildvcs=false \ + -o "$TMP_DIR/bin/server" ./cmd/server \ + 2>"$LOG_DIR/build-backend.log") || { + echo "" + # Filtrar errores relevantes del log de compilación + echo -e " ${RED}Error de compilación:${NC}" + grep -E "^(.*\.go:[0-9]+:|error:|undefined:)" "$LOG_DIR/build-backend.log" \ + | head -20 | sed 's/^/ /' || show_log_tail "$LOG_DIR/build-backend.log" 15 + echo "" + echo -e " Posibles causas:" + echo -e " • Version de Go insuficiente: $(go version)" + echo -e " Se necesita 1.22+. Instala: sudo bash deploy/debian/prerequisites.sh" + echo -e " • Dependencias faltantes: cd backend && go mod download" + echo -e " • Log completo: cat $LOG_DIR/build-backend.log" + error "Fallo al compilar backend Go." } info "Backend compilado OK" @@ -140,74 +310,122 @@ info "Backend compilado OK" # 6. ARRANCAR BACKEND API # ============================================================================= step "Arrancando API en puerto ${POC_API_PORT}..." -"$TMP_DIR/bin/server" > "$TMP_DIR/backend.log" 2>&1 & + +"$TMP_DIR/bin/server" > "$LOG_DIR/backend.log" 2>&1 & BACKEND_PID=$! echo "$BACKEND_PID" >> "$PID_FILE" -# Esperar a que el backend responda -for i in {1..15}; do +# Esperar con feedback visual +printf " Esperando respuesta" +BACKEND_UP=false +for i in {1..20}; do sleep 1 + printf "." if curl -sf "http://127.0.0.1:${POC_API_PORT}/api/stats" &>/dev/null; then - info "API respondiendo en http://127.0.0.1:${POC_API_PORT}" + BACKEND_UP=true break fi - if [[ $i -eq 15 ]]; then - cat "$TMP_DIR/backend.log" - error "El backend no responde. Ver log arriba." + # Detectar si el proceso murió + if ! kill -0 "$BACKEND_PID" 2>/dev/null; then + break fi done +echo "" + +if [[ "$BACKEND_UP" == "false" ]]; then + echo "" + echo -e " ${RED}El backend no arrancó correctamente.${NC}" + echo -e " ${BOLD}Log del backend:${NC}" + show_log_tail "$LOG_DIR/backend.log" 25 + echo "" + echo -e " Posibles causas:" + echo -e " • Error de conexión a BD: revisa DATABASE_URL" + echo -e " Prueba: psql \"${DATABASE_URL}\" -c 'SELECT 1'" + echo -e " • Error de conexión a Redis: revisa REDIS_URL" + echo -e " Prueba: redis-cli -p ${POC_REDIS_PORT} ping" + echo -e " • Puerto ${POC_API_PORT} ocupado: ss -tlnp | grep ${POC_API_PORT}" + echo -e " • Log completo: cat $LOG_DIR/backend.log" + error "Backend no responde." +fi +info "API respondiendo en http://127.0.0.1:${POC_API_PORT}" # ============================================================================= # 7. FRONTEND REACT # ============================================================================= -step "Preparando frontend..." +step "Preparando frontend React..." cd "$REPO_ROOT/frontend" if [[ ! -d node_modules ]]; then - step " Instalando dependencias npm (primera vez)..." - npm install --silent + step " Instalando dependencias npm (primera vez, puede tardar)..." + npm install 2>"$LOG_DIR/npm-install.log" || { + show_log_tail "$LOG_DIR/npm-install.log" 20 + echo -e " Posibles causas:" + echo -e " • Sin conexión a internet (npm registry)" + echo -e " • Versión de Node.js incompatible: $(node -v)" + echo -e " • Disco lleno: df -h ." + echo -e " • Prueba manualmente: cd frontend && npm install" + error "npm install falló." + } fi -# Compilar apuntando al API local del POC +step " Compilando frontend..." VITE_API_URL="http://127.0.0.1:${POC_API_PORT}" \ -npm run build -- --outDir "$TMP_DIR/frontend-dist" 2>"$TMP_DIR/build-frontend.log" || { - cat "$TMP_DIR/build-frontend.log" - error "Fallo al compilar frontend. Ver log arriba." +npm run build -- --outDir "$TMP_DIR/frontend-dist" \ + >"$LOG_DIR/build-frontend.log" 2>&1 || { + echo -e " ${RED}Error de compilación del frontend:${NC}" + grep -E "(error TS|Error:|ERROR)" "$LOG_DIR/build-frontend.log" \ + | head -15 | sed 's/^/ /' || show_log_tail "$LOG_DIR/build-frontend.log" 15 + echo "" + echo -e " Posibles causas:" + echo -e " • Error TypeScript: revisa cambios recientes en src/" + echo -e " • Log completo: cat $LOG_DIR/build-frontend.log" + error "Compilación del frontend falló." } info "Frontend compilado OK" -# Servir frontend con npx serve (simple, sin nginx) -step "Sirviendo frontend en puerto ${POC_FRONTEND_PORT}..." +step " Sirviendo frontend en puerto ${POC_FRONTEND_PORT}..." npx --yes serve "$TMP_DIR/frontend-dist" -l "$POC_FRONTEND_PORT" \ - > "$TMP_DIR/frontend.log" 2>&1 & + > "$LOG_DIR/frontend-serve.log" 2>&1 & FRONTEND_PID=$! echo "$FRONTEND_PID" >> "$PID_FILE" + sleep 2 +if ! kill -0 "$FRONTEND_PID" 2>/dev/null; then + show_log_tail "$LOG_DIR/frontend-serve.log" + echo -e " Prueba manual: npx serve $TMP_DIR/frontend-dist -l ${POC_FRONTEND_PORT}" + error "El servidor del frontend no arrancó." +fi +info "Frontend sirviendo en http://127.0.0.1:${POC_FRONTEND_PORT}" cd "$REPO_ROOT" # ============================================================================= -# LISTO +# RESUMEN FINAL # ============================================================================= echo "" echo -e "${BOLD}${GREEN}=================================================${NC}" echo -e "${BOLD}${GREEN} COCONEWS POC corriendo${NC}" echo -e "${BOLD}${GREEN}=================================================${NC}" echo "" -echo -e " ${BOLD}Frontend:${NC} http://127.0.0.1:${POC_FRONTEND_PORT}" -echo -e " ${BOLD}API:${NC} http://127.0.0.1:${POC_API_PORT}/api/stats" +echo -e " ${BOLD}Abrir en el navegador:${NC}" +echo -e " → http://127.0.0.1:${POC_FRONTEND_PORT}" echo "" -echo -e " ${BOLD}Login:${NC} Registra el primer usuario en la UI" -echo -e " (será admin automáticamente)" +echo -e " ${BOLD}Endpoints útiles:${NC}" +echo -e " API stats: http://127.0.0.1:${POC_API_PORT}/api/stats" +echo -e " API noticias: http://127.0.0.1:${POC_API_PORT}/api/news" echo "" -echo -e " ${BOLD}Noticias:${NC} ${NEWS_COUNT} artículos de prueba en español" -echo -e " ${YELLOW}Nota:${NC} Sin workers ML activos." -echo -e " Noticias no tendrán traducción ni entidades." +echo -e " ${BOLD}Primer login:${NC}" +echo -e " Regístrate en la UI → el primer usuario es admin automáticamente" echo "" -echo -e " ${BLUE}Logs:${NC} $TMP_DIR/*.log" +echo -e " ${BOLD}Datos cargados:${NC} ${NEWS_COUNT} noticias de prueba en español" +echo -e " ${YELLOW}Sin workers ML:${NC} no hay traducción ni entidades (normal en POC)" echo "" -echo -e " Pulsa ${BOLD}Ctrl+C${NC} para detener el POC." +echo -e " ${BOLD}Si algo falla:${NC}" +echo -e " Logs: $LOG_DIR/" +echo -e " Backend: tail -f $LOG_DIR/backend.log" +echo -e " Limpiar: bash poc/poc.sh --clean" +echo "" +echo -e " ${DIM}Pulsa Ctrl+C para detener${NC}" echo "" -# Mantener el script corriendo wait