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:
parent
e3c682a36f
commit
ec839b5b54
3 changed files with 651 additions and 69 deletions
342
deploy/debian/check.sh
Executable file
342
deploy/debian/check.sh
Executable 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 ))
|
||||
|
|
@ -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 ""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue