#!/usr/bin/env bash # ============================================================================= # RSS2 - Instalacion en Debian (sin Docker) # Ejecutar como root: bash install.sh # ============================================================================= set -euo pipefail RSS2_USER="rss2" RSS2_HOME="/opt/rss2" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' info() { echo -e "${GREEN}[INFO]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } [[ "$EUID" -ne 0 ]] && error "Ejecutar como root: sudo bash install.sh" # ============================================================================= # 1. DEPENDENCIAS DEL SISTEMA # ============================================================================= info "Instalando dependencias del sistema..." apt-get update -qq apt-get install -y --no-install-recommends \ curl wget git build-essential \ postgresql postgresql-client \ redis-server \ nginx \ python3 python3-pip python3-venv python3-dev \ nodejs npm \ ca-certificates tzdata \ libpq-dev # Go (rss-ingestor-go requiere Go 1.25) if ! command -v go &>/dev/null || [[ "$(go version | awk '{print $3}' | tr -d 'go')" < "1.25" ]]; then info "Instalando Go 1.25..." GO_VERSION="1.25.0" ARCH=$(dpkg --print-architecture) case "$ARCH" in amd64) GO_ARCH="amd64" ;; arm64) GO_ARCH="arm64" ;; *) error "Arquitectura no soportada: $ARCH" ;; esac curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" -o /tmp/go.tar.gz rm -rf /usr/local/go tar -C /usr/local -xzf /tmp/go.tar.gz echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh export PATH=$PATH:/usr/local/go/bin rm /tmp/go.tar.gz fi info "Go: $(go version)" # Qdrant (binario oficial) if [[ ! -f "$RSS2_HOME/qdrant/qdrant" ]]; then info "Descargando Qdrant..." QDRANT_VERSION="v1.12.1" ARCH=$(dpkg --print-architecture) case "$ARCH" in amd64) QDRANT_ARCH="x86_64-unknown-linux-musl" ;; arm64) QDRANT_ARCH="aarch64-unknown-linux-musl" ;; *) error "Arquitectura no soportada para Qdrant: $ARCH" ;; esac mkdir -p "$RSS2_HOME/qdrant" curl -fsSL "https://github.com/qdrant/qdrant/releases/download/${QDRANT_VERSION}/qdrant-${QDRANT_ARCH}.tar.gz" \ -o /tmp/qdrant.tar.gz tar -C "$RSS2_HOME/qdrant" -xzf /tmp/qdrant.tar.gz chmod +x "$RSS2_HOME/qdrant/qdrant" rm /tmp/qdrant.tar.gz fi # ============================================================================= # 2. USUARIO Y DIRECTORIOS # ============================================================================= info "Creando usuario $RSS2_USER y directorios..." id "$RSS2_USER" &>/dev/null || useradd -r -m -d "$RSS2_HOME" -s /bin/bash "$RSS2_USER" mkdir -p \ "$RSS2_HOME/bin" \ "$RSS2_HOME/src" \ "$RSS2_HOME/data/wiki_images" \ "$RSS2_HOME/data/qdrant_storage" \ "$RSS2_HOME/hf_cache" \ "$RSS2_HOME/models" \ "$RSS2_HOME/frontend/dist" \ "$RSS2_HOME/logs" # ============================================================================= # 3. CONFIGURACION ENTORNO # ============================================================================= if [[ ! -f "$RSS2_HOME/.env" ]]; then if [[ -f "$SCRIPT_DIR/env.example" ]]; then cp "$SCRIPT_DIR/env.example" "$RSS2_HOME/.env" warn "Copia env.example en $RSS2_HOME/.env - EDITA LAS CONTRASENAS antes de continuar" warn "Presiona Enter cuando hayas editado el .env, o Ctrl+C para salir" read -r else error "No se encontro env.example en $SCRIPT_DIR" fi fi # ============================================================================= # 4. POSTGRESQL # ============================================================================= info "Configurando PostgreSQL..." source "$RSS2_HOME/.env" 2>/dev/null || true DB_NAME="${POSTGRES_DB:-rss}" DB_USER="${POSTGRES_USER:-rss}" DB_PASS="${POSTGRES_PASSWORD:-changeme}" systemctl enable --now postgresql # Crear usuario y base de datos si no existen sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" | grep -q 1 || \ sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';" sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1 || \ sudo -u postgres createdb -O "$DB_USER" "$DB_NAME" # Ejecutar migraciones SQL if [[ -d "$REPO_ROOT/migrations" ]]; then info "Ejecutando migraciones..." for sql_file in "$REPO_ROOT/migrations"/*.sql; do [[ -f "$sql_file" ]] || continue info " Aplicando $(basename "$sql_file")..." sudo -u postgres psql -d "$DB_NAME" -f "$sql_file" 2>/dev/null || warn " (ya aplicada o error ignorado)" done fi # Ejecutar init-db scripts (schema inicial) if [[ -d "$REPO_ROOT/init-db" ]]; then info "Ejecutando scripts de init-db..." for sql_file in "$REPO_ROOT/init-db"/*.sql; do [[ -f "$sql_file" ]] || continue info " $(basename "$sql_file")..." sudo -u postgres psql -d "$DB_NAME" -f "$sql_file" 2>/dev/null || warn " (ya aplicada o error ignorado)" done fi # ============================================================================= # 5. REDIS # ============================================================================= info "Configurando Redis..." REDIS_PASS="${REDIS_PASSWORD:-changeme_redis}" # Agregar autenticacion y limites de memoria a redis.conf REDIS_CONF="/etc/redis/redis.conf" grep -q "requirepass $REDIS_PASS" "$REDIS_CONF" 2>/dev/null || { echo "requirepass $REDIS_PASS" >> "$REDIS_CONF" echo "maxmemory 512mb" >> "$REDIS_CONF" echo "maxmemory-policy allkeys-lru" >> "$REDIS_CONF" echo "appendonly yes" >> "$REDIS_CONF" } systemctl enable --now redis-server # ============================================================================= # 6. PYTHON VIRTUALENV + DEPENDENCIAS ML # ============================================================================= info "Creando virtualenv Python y instalando dependencias..." python3 -m venv "$RSS2_HOME/venv" "$RSS2_HOME/venv/bin/pip" install --upgrade pip -q if [[ -f "$REPO_ROOT/requirements.txt" ]]; then "$RSS2_HOME/venv/bin/pip" install -r "$REPO_ROOT/requirements.txt" -q fi # spaCy modelo en espaƱol "$RSS2_HOME/venv/bin/python" -m spacy download es_core_news_lg 2>/dev/null || \ warn "spaCy model es_core_news_lg no se pudo descargar, hazlo manualmente" # Copiar workers Python al directorio de trabajo info "Copiando workers Python..." rsync -a --delete "$REPO_ROOT/workers/" "$RSS2_HOME/src/workers/" cp "$REPO_ROOT/entity_config.json" "$RSS2_HOME/src/" 2>/dev/null || true # ============================================================================= # 7. COMPILAR GO (backend + workers) # ============================================================================= info "Compilando binarios Go..." export PATH=$PATH:/usr/local/go/bin export GOPATH=/tmp/go-build-rss2 # Backend API if [[ -d "$REPO_ROOT/backend" ]]; then (cd "$REPO_ROOT/backend" && \ CGO_ENABLED=0 GOOS=linux go build -buildvcs=false -o "$RSS2_HOME/bin/server" ./cmd/server && \ info " [OK] server") || warn " [FAIL] server" for cmd in scraper discovery wiki_worker topics related; do [[ -d "$REPO_ROOT/backend/cmd/$cmd" ]] || continue (cd "$REPO_ROOT/backend" && \ CGO_ENABLED=0 GOOS=linux go build -buildvcs=false -o "$RSS2_HOME/bin/$cmd" "./cmd/$cmd" && \ info " [OK] $cmd") || warn " [FAIL] $cmd" done # qdrant worker: output como qdrant_worker para coincidir con el service [[ -d "$REPO_ROOT/backend/cmd/qdrant" ]] && \ (cd "$REPO_ROOT/backend" && \ CGO_ENABLED=0 GOOS=linux go build -buildvcs=false -o "$RSS2_HOME/bin/qdrant_worker" "./cmd/qdrant" && \ info " [OK] qdrant_worker") || warn " [FAIL] qdrant_worker" fi # RSS Ingestor Go (repo separado, requiere Go 1.25) if [[ -d "$REPO_ROOT/rss-ingestor-go" ]]; then (cd "$REPO_ROOT/rss-ingestor-go" && \ GOTOOLCHAIN=local CGO_ENABLED=0 GOOS=linux go build -buildvcs=false -o "$RSS2_HOME/bin/ingestor" . && \ info " [OK] ingestor") || warn " [FAIL] ingestor" fi # ============================================================================= # 8. FRONTEND REACT # ============================================================================= info "Compilando frontend React..." if [[ -d "$REPO_ROOT/frontend" ]]; then (cd "$REPO_ROOT/frontend" && \ npm install --silent && \ VITE_API_URL=/api npm run build -- --outDir "$RSS2_HOME/frontend/dist" && \ info " [OK] frontend compilado") || warn " [FAIL] frontend" fi # ============================================================================= # 9. NGINX # ============================================================================= info "Configurando Nginx..." cp "$SCRIPT_DIR/nginx.conf" /etc/nginx/nginx.conf nginx -t && systemctl enable --now nginx && systemctl reload nginx # ============================================================================= # 10. SYSTEMD SERVICES # ============================================================================= info "Instalando servicios systemd..." SERVICES=( rss2-qdrant rss2-backend rss2-ingestor rss2-scraper rss2-discovery rss2-wiki rss2-topics rss2-related rss2-qdrant-worker rss2-langdetect rss2-translation-scheduler rss2-translator rss2-embeddings rss2-ner rss2-cluster rss2-categorizer ) for svc in "${SERVICES[@]}"; do svc_file="$SCRIPT_DIR/systemd/${svc}.service" if [[ -f "$svc_file" ]]; then cp "$svc_file" "/etc/systemd/system/${svc}.service" else warn "No se encontro $svc_file" fi done systemctl daemon-reload for svc in "${SERVICES[@]}"; do systemctl enable "$svc" 2>/dev/null || true done # ============================================================================= # 11. PERMISOS FINALES # ============================================================================= info "Ajustando permisos..." chown -R "$RSS2_USER:$RSS2_USER" "$RSS2_HOME" chmod 600 "$RSS2_HOME/.env" # ============================================================================= # 12. ARRANCAR SERVICIOS # ============================================================================= info "Arrancando servicios..." # Infraestructura primero systemctl start rss2-qdrant sleep 3 # API y workers Go for svc in rss2-backend rss2-ingestor rss2-scraper rss2-discovery rss2-wiki rss2-topics rss2-related rss2-qdrant-worker; do systemctl start "$svc" || warn "No se pudo arrancar $svc" done # Workers Python (modelos pesados, arrancan despues) for svc in rss2-langdetect rss2-translation-scheduler rss2-translator rss2-embeddings rss2-ner rss2-cluster rss2-categorizer; do systemctl start "$svc" || warn "No se pudo arrancar $svc" done # ============================================================================= echo "" info "=============================================" info " RSS2 instalado en $RSS2_HOME" info " Acceder en: http://$(hostname -I | awk '{print $1}'):8001" info "" info " Ver logs: journalctl -u rss2-backend -f" info " Ver estado: systemctl status rss2-backend" info " Editar env: nano $RSS2_HOME/.env" info "============================================="