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