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 <noreply@anthropic.com>
This commit is contained in:
SITO 2026-03-30 22:04:22 +02:00
parent e3c682a36f
commit ec839b5b54
3 changed files with 651 additions and 69 deletions

342
deploy/debian/check.sh Executable file
View file

@ -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 ))

View file

@ -36,8 +36,22 @@ echo ""
# ============================================================================= # =============================================================================
# 1. PAQUETES APT BASE # 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..." 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..." step "Instalando paquetes del sistema..."
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
@ -285,7 +299,7 @@ fi
chown -R rss2:rss2 /opt/rss2 chown -R rss2:rss2 /opt/rss2
# ============================================================================= # =============================================================================
# RESUMEN # RESUMEN Y SIGUIENTES PASOS
# ============================================================================= # =============================================================================
echo "" echo ""
echo "=================================================" echo "================================================="
@ -303,3 +317,11 @@ echo ""
echo " Siguiente paso:" echo " Siguiente paso:"
echo " sudo bash deploy/debian/install.sh" echo " sudo bash deploy/debian/install.sh"
echo "" 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 ""

View file

@ -5,6 +5,7 @@
# #
# Requisitos mínimos: Go 1.25, Node.js 18+, PostgreSQL, Redis # Requisitos mínimos: Go 1.25, Node.js 18+, PostgreSQL, Redis
# Uso: bash poc/poc.sh # Uso: bash poc/poc.sh
# bash poc/poc.sh --clean (borra BD y empieza de cero)
# ============================================================================= # =============================================================================
set -euo pipefail set -euo pipefail
@ -12,16 +13,37 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
POC_DIR="$REPO_ROOT/poc" POC_DIR="$REPO_ROOT/poc"
TMP_DIR="/tmp/coconews-poc" TMP_DIR="/tmp/coconews-poc"
PID_FILE="$TMP_DIR/pids" 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 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} $*"; } info() { echo -e "${GREEN}[✓]${NC} $*"; }
step() { echo -e "${BLUE}[→]${NC} $*"; } step() { echo -e "${BLUE}[→]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${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() { cleanup() {
echo "" echo ""
warn "Deteniendo POC..." warn "Deteniendo POC..."
@ -31,12 +53,15 @@ cleanup() {
done < "$PID_FILE" done < "$PID_FILE"
rm -f "$PID_FILE" rm -f "$PID_FILE"
fi 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}" echo -e "${YELLOW}POC detenido.${NC}"
exit 0 exit 0
} }
trap cleanup INT TERM trap cleanup INT TERM
mkdir -p "$TMP_DIR" mkdir -p "$TMP_DIR" "$LOG_DIR" "$TMP_DIR/bin"
> "$PID_FILE" > "$PID_FILE"
echo "" echo ""
@ -44,24 +69,87 @@ echo -e "${BOLD}=================================================${NC}"
echo -e "${BOLD} COCONEWS · POC Local${NC}" echo -e "${BOLD} COCONEWS · POC Local${NC}"
echo -e "${BOLD}=================================================${NC}" echo -e "${BOLD}=================================================${NC}"
echo "" echo ""
[[ "$CLEAN_MODE" == "true" ]] && warn "Modo --clean: se borrará la BD de prueba anterior"
# ============================================================================= # =============================================================================
# 1. VERIFICAR PREREQUISITOS # 1. VERIFICAR PREREQUISITOS
# ============================================================================= # =============================================================================
step "Verificando 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." PREREQ_OK=true
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+." # Go
info "Prerequisitos OK (Go: $(go version | awk '{print $3}'), Node: $(node -v))" 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_NAME="coconews_poc"
POC_DB_USER="coconews_poc" POC_DB_USER="coconews_poc"
POC_DB_PASS="poc_password_local" 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_API_PORT="18080"
POC_FRONTEND_PORT="18001" 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 SERVER_PORT="$POC_API_PORT"
export GIN_MODE="release" 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})..." step "Preparando base de datos POC (${POC_DB_NAME})..."
# Verificar que PostgreSQL está corriendo # Verificar que PostgreSQL está corriendo
pg_isready -q || { if ! pg_isready -q 2>/dev/null; then
warn "PostgreSQL no está corriendo. Intentando iniciar..." warn "PostgreSQL no está corriendo. Intentando iniciar..."
sudo systemctl start postgresql 2>/dev/null || \ sudo systemctl start postgresql 2>/dev/null || \
sudo service postgresql start 2>/dev/null || \ sudo service postgresql start 2>/dev/null || {
error "No se puede iniciar PostgreSQL. Inícialo manualmente." 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"
# 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 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 usuario y BD de prueba # Crear BD
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
sudo -u postgres psql -q -tc "SELECT 1 FROM pg_database WHERE datname='${POC_DB_NAME}'" \ sudo -u postgres psql -q -tc "SELECT 1 FROM pg_database WHERE datname='${POC_DB_NAME}'" \
| grep -q 1 || \ | grep -q 1 2>/dev/null || \
sudo -u postgres createdb -O "${POC_DB_USER}" "${POC_DB_NAME}" 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}" \ 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}" \ sudo -u postgres psql -q -d "${POC_DB_NAME}" \
-c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ${POC_DB_USER}; -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 2>/dev/null || true
# Cargar datos de muestra # Seed
sudo -u postgres psql -q -d "${POC_DB_NAME}" -f "$POC_DIR/seed.sql" 2>/dev/null || true 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 ' ') NEWS_COUNT=$(sudo -u postgres psql -tq -d "${POC_DB_NAME}" \
info "BD lista: ${NEWS_COUNT} noticias de prueba cargadas" -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) # 4. REDIS (instancia temporal en puerto alternativo)
# ============================================================================= # =============================================================================
step "Iniciando Redis en puerto ${POC_REDIS_PORT}..." 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" \ --pidfile "$TMP_DIR/redis-poc.pid" \
--maxmemory 128mb --maxmemory-policy allkeys-lru \ --maxmemory 128mb \
2>/dev/null || warn "Redis ya corriendo en ${POC_REDIS_PORT}" --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 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 cat "$TMP_DIR/redis-poc.pid" 2>/dev/null >> "$PID_FILE" || true
# ============================================================================= # =============================================================================
# 5. COMPILAR BACKEND GO # 5. COMPILAR BACKEND GO
# ============================================================================= # =============================================================================
step "Compilando backend Go..." step "Compilando backend Go..."
mkdir -p "$TMP_DIR/bin"
(cd "$REPO_ROOT/backend" && \ (cd "$REPO_ROOT/backend" && \
CGO_ENABLED=0 go build -buildvcs=false -o "$TMP_DIR/bin/server" ./cmd/server \ CGO_ENABLED=0 go build -buildvcs=false \
2>"$TMP_DIR/build-backend.log") || { -o "$TMP_DIR/bin/server" ./cmd/server \
cat "$TMP_DIR/build-backend.log" 2>"$LOG_DIR/build-backend.log") || {
error "Fallo al compilar backend. Ver log arriba." 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" info "Backend compilado OK"
@ -140,74 +310,122 @@ info "Backend compilado OK"
# 6. ARRANCAR BACKEND API # 6. ARRANCAR BACKEND API
# ============================================================================= # =============================================================================
step "Arrancando API en puerto ${POC_API_PORT}..." 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=$! BACKEND_PID=$!
echo "$BACKEND_PID" >> "$PID_FILE" echo "$BACKEND_PID" >> "$PID_FILE"
# Esperar a que el backend responda # Esperar con feedback visual
for i in {1..15}; do printf " Esperando respuesta"
BACKEND_UP=false
for i in {1..20}; do
sleep 1 sleep 1
printf "."
if curl -sf "http://127.0.0.1:${POC_API_PORT}/api/stats" &>/dev/null; then 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 break
fi fi
if [[ $i -eq 15 ]]; then # Detectar si el proceso murió
cat "$TMP_DIR/backend.log" if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
error "El backend no responde. Ver log arriba." break
fi fi
done 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 # 7. FRONTEND REACT
# ============================================================================= # =============================================================================
step "Preparando frontend..." step "Preparando frontend React..."
cd "$REPO_ROOT/frontend" cd "$REPO_ROOT/frontend"
if [[ ! -d node_modules ]]; then if [[ ! -d node_modules ]]; then
step " Instalando dependencias npm (primera vez)..." step " Instalando dependencias npm (primera vez, puede tardar)..."
npm install --silent 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 fi
# Compilar apuntando al API local del POC step " Compilando frontend..."
VITE_API_URL="http://127.0.0.1:${POC_API_PORT}" \ 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" || { npm run build -- --outDir "$TMP_DIR/frontend-dist" \
cat "$TMP_DIR/build-frontend.log" >"$LOG_DIR/build-frontend.log" 2>&1 || {
error "Fallo al compilar frontend. Ver log arriba." 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" 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" \ 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=$! FRONTEND_PID=$!
echo "$FRONTEND_PID" >> "$PID_FILE" echo "$FRONTEND_PID" >> "$PID_FILE"
sleep 2 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" cd "$REPO_ROOT"
# ============================================================================= # =============================================================================
# LISTO # RESUMEN FINAL
# ============================================================================= # =============================================================================
echo "" echo ""
echo -e "${BOLD}${GREEN}=================================================${NC}" echo -e "${BOLD}${GREEN}=================================================${NC}"
echo -e "${BOLD}${GREEN} COCONEWS POC corriendo${NC}" echo -e "${BOLD}${GREEN} COCONEWS POC corriendo${NC}"
echo -e "${BOLD}${GREEN}=================================================${NC}" echo -e "${BOLD}${GREEN}=================================================${NC}"
echo "" echo ""
echo -e " ${BOLD}Frontend:${NC} http://127.0.0.1:${POC_FRONTEND_PORT}" echo -e " ${BOLD}Abrir en el navegador:${NC}"
echo -e " ${BOLD}API:${NC} http://127.0.0.1:${POC_API_PORT}/api/stats" echo -e " → http://127.0.0.1:${POC_FRONTEND_PORT}"
echo "" echo ""
echo -e " ${BOLD}Login:${NC} Registra el primer usuario en la UI" echo -e " ${BOLD}Endpoints útiles:${NC}"
echo -e " (será admin automáticamente)" 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 ""
echo -e " ${BOLD}Noticias:${NC} ${NEWS_COUNT} artículos de prueba en español" echo -e " ${BOLD}Primer login:${NC}"
echo -e " ${YELLOW}Nota:${NC} Sin workers ML activos." echo -e " Regístrate en la UI → el primer usuario es admin automáticamente"
echo -e " Noticias no tendrán traducción ni entidades."
echo "" 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 ""
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 "" echo ""
# Mantener el script corriendo
wait wait