quito comentarios
This commit is contained in:
parent
68a5528f2f
commit
937da3f90b
8 changed files with 48 additions and 496 deletions
|
|
@ -1,16 +0,0 @@
|
|||
id,nombre
|
||||
1,Ciencia
|
||||
2,Cultura
|
||||
3,Deportes
|
||||
4,Economía
|
||||
5,Educación
|
||||
6,Entretenimiento
|
||||
7,Internacional
|
||||
8,Medio Ambiente
|
||||
9,Moda
|
||||
10,Opinión
|
||||
11,Política
|
||||
12,Salud
|
||||
13,Sociedad
|
||||
14,Tecnología
|
||||
15,Viajes
|
||||
|
|
|
@ -31,30 +31,41 @@ EVENT_BATCH_IDS = int(os.environ.get("EVENT_BATCH_IDS", "200"))
|
|||
EVENT_SLEEP_IDLE = float(os.environ.get("EVENT_SLEEP_IDLE", "5.0"))
|
||||
EVENT_DIST_THRESHOLD = float(os.environ.get("EVENT_DIST_THRESHOLD", "0.25"))
|
||||
|
||||
EMB_MODEL = os.environ.get(
|
||||
"EMB_MODEL",
|
||||
"sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
|
||||
)
|
||||
|
||||
|
||||
def get_conn():
|
||||
return psycopg2.connect(**DB)
|
||||
|
||||
|
||||
def ensure_schema(conn):
|
||||
"""
|
||||
Asegura que la tabla de eventos y las columnas necesarias existen.
|
||||
Aquí se asume el esquema original de eventos con centroid JSONB.
|
||||
"""
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS eventos (
|
||||
id SERIAL PRIMARY KEY,
|
||||
creado_en TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
creado_en TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
actualizado_en TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
centroid JSONB NOT NULL,
|
||||
centroid JSONB NOT NULL,
|
||||
total_traducciones INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
ALTER TABLE traducciones
|
||||
ADD COLUMN IF NOT EXISTS evento_id INTEGER REFERENCES eventos(id);
|
||||
"""
|
||||
)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS idx_traducciones_evento
|
||||
|
|
@ -67,6 +78,7 @@ def ensure_schema(conn):
|
|||
ON traducciones(evento_id, noticia_id);
|
||||
"""
|
||||
)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE OR REPLACE FUNCTION actualizar_evento_modificado()
|
||||
|
|
@ -91,43 +103,55 @@ def ensure_schema(conn):
|
|||
|
||||
|
||||
def fetch_pending_traducciones(conn) -> List[int]:
|
||||
"""
|
||||
Traducciones con status 'done', sin evento asignado
|
||||
y que ya tienen embedding en traduccion_embeddings para EMB_MODEL.
|
||||
"""
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT t.id
|
||||
FROM traducciones t
|
||||
JOIN embeddings e ON e.traduccion_id = t.id
|
||||
JOIN traduccion_embeddings e
|
||||
ON e.traduccion_id = t.id
|
||||
AND e.model = %s
|
||||
WHERE t.status = 'done'
|
||||
AND t.evento_id IS NULL
|
||||
AND t.lang_to = ANY(%s)
|
||||
ORDER BY t.id DESC
|
||||
LIMIT %s;
|
||||
""",
|
||||
(EVENT_LANGS, EVENT_BATCH_IDS),
|
||||
(EMB_MODEL, EVENT_LANGS, EVENT_BATCH_IDS),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [r[0] for r in rows]
|
||||
|
||||
|
||||
def fetch_embeddings_for(conn, tr_ids: List[int]) -> Dict[int, np.ndarray]:
|
||||
"""
|
||||
Devuelve un diccionario {traduccion_id: vector_numpy}
|
||||
leyendo de traduccion_embeddings.embedding para el EMB_MODEL.
|
||||
"""
|
||||
if not tr_ids:
|
||||
return {}
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT traduccion_id, vec
|
||||
FROM embeddings
|
||||
WHERE traduccion_id = ANY(%s);
|
||||
SELECT traduccion_id, embedding
|
||||
FROM traduccion_embeddings
|
||||
WHERE traduccion_id = ANY(%s)
|
||||
AND model = %s;
|
||||
""",
|
||||
(tr_ids,),
|
||||
(tr_ids, EMB_MODEL),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
|
||||
out: Dict[int, np.ndarray] = {}
|
||||
for tr_id, vec in rows:
|
||||
if not vec:
|
||||
for tr_id, emb in rows:
|
||||
if not emb:
|
||||
continue
|
||||
arr = np.array([float(x or 0.0) for x in vec], dtype="float32")
|
||||
arr = np.array([float(x or 0.0) for x in emb], dtype="float32")
|
||||
if arr.size == 0:
|
||||
continue
|
||||
out[int(tr_id)] = arr
|
||||
|
|
@ -135,6 +159,9 @@ def fetch_embeddings_for(conn, tr_ids: List[int]) -> Dict[int, np.ndarray]:
|
|||
|
||||
|
||||
def fetch_centroids(conn) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Carga todos los centroides actuales desde eventos.
|
||||
"""
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
|
|
@ -180,6 +207,10 @@ def assign_to_event(
|
|||
vec: np.ndarray,
|
||||
centroids: List[Dict[str, Any]],
|
||||
) -> None:
|
||||
"""
|
||||
Asigna una traducción a un evento existente (si distancia <= umbral)
|
||||
o crea un evento nuevo con este vector como centroide.
|
||||
"""
|
||||
from psycopg2.extras import Json
|
||||
|
||||
if vec is None or vec.size == 0:
|
||||
|
|
@ -260,11 +291,12 @@ def assign_to_event(
|
|||
def main():
|
||||
log.info(
|
||||
"Iniciando cluster_worker eventos "
|
||||
"(EVENT_LANGS=%s, BATCH_IDS=%s, DIST_THRESHOLD=%.3f, SLEEP=%.1fs)",
|
||||
"(EVENT_LANGS=%s, BATCH_IDS=%s, DIST_THRESHOLD=%.3f, SLEEP=%.1fs, EMB_MODEL=%s)",
|
||||
",".join(EVENT_LANGS),
|
||||
EVENT_BATCH_IDS,
|
||||
EVENT_DIST_THRESHOLD,
|
||||
EVENT_SLEEP_IDLE,
|
||||
EMB_MODEL,
|
||||
)
|
||||
|
||||
while True:
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
id,nombre
|
||||
1,África
|
||||
2,América
|
||||
3,Asia
|
||||
4,Europa
|
||||
5,Oceanía
|
||||
6,Antártida
|
||||
|
|
|
@ -2,7 +2,6 @@ import nltk
|
|||
import logging
|
||||
import ssl
|
||||
|
||||
# Soluciona problemas de certificado SSL en algunas configuraciones de sistema al descargar
|
||||
try:
|
||||
_create_unverified_https_context = ssl._create_unverified_context
|
||||
except AttributeError:
|
||||
|
|
@ -12,31 +11,22 @@ else:
|
|||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
|
||||
|
||||
# Lista de paquetes de NLTK que newspaper3k puede necesitar.
|
||||
# 'punkt' y 'punkt_tab' son para tokenización, 'stopwords' para el resumen.
|
||||
PACKAGES = ['punkt', 'punkt_tab', 'stopwords']
|
||||
|
||||
|
||||
def download_nltk_data():
|
||||
"""
|
||||
Descarga los paquetes de NLTK necesarios para newspaper3k.
|
||||
"""
|
||||
for package in PACKAGES:
|
||||
try:
|
||||
logging.info(f"Verificando si el paquete '{package}' de NLTK está disponible...")
|
||||
# Determina la ruta correcta para la verificación
|
||||
if package.startswith('punkt'):
|
||||
path = f'tokenizers/{package}'
|
||||
else:
|
||||
path = f'corpora/{package}'
|
||||
|
||||
nltk.data.find(path)
|
||||
logging.info(f"El paquete '{package}' ya está descargado.")
|
||||
|
||||
except LookupError:
|
||||
logging.info(f"El paquete '{package}' no se encontró. Iniciando descarga...")
|
||||
try:
|
||||
# El parámetro quiet=True evita el diálogo interactivo
|
||||
nltk.download(package, quiet=True)
|
||||
logging.info(f"Paquete '{package}' descargado con éxito.")
|
||||
except Exception as e:
|
||||
|
|
@ -44,6 +34,7 @@ def download_nltk_data():
|
|||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
download_nltk_data()
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import torch
|
|||
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s')
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# ---------- Configuración DB ----------
|
||||
DB = dict(
|
||||
host=os.environ.get("DB_HOST", "localhost"),
|
||||
port=int(os.environ.get("DB_PORT", 5432)),
|
||||
|
|
@ -22,31 +21,22 @@ DB = dict(
|
|||
password=os.environ.get("DB_PASS", "x"),
|
||||
)
|
||||
|
||||
# ---------- Parámetros de worker ----------
|
||||
# Modelo por defecto: multilingüe, bueno para muchas lenguas
|
||||
EMB_MODEL = os.environ.get(
|
||||
"EMB_MODEL",
|
||||
"sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
|
||||
)
|
||||
EMB_BATCH = int(os.environ.get("EMB_BATCH", "128"))
|
||||
SLEEP_IDLE = float(os.environ.get("EMB_SLEEP_IDLE", "5.0"))
|
||||
# Filtrado por idiomas destino (coma-separado). Por defecto sólo 'es'
|
||||
EMB_LANGS = [s.strip() for s in os.environ.get("EMB_LANGS", "es").split(",") if s.strip()]
|
||||
|
||||
# DEVICE_ENV: 'auto' | 'cpu' | 'cuda'
|
||||
DEVICE_ENV = os.environ.get("DEVICE", "auto").lower()
|
||||
|
||||
# Límite por iteración (para no tragar toda la tabla de golpe)
|
||||
EMB_LIMIT = int(os.environ.get("EMB_LIMIT", "1000"))
|
||||
|
||||
|
||||
# ---------- Utilidades ----------
|
||||
def get_conn():
|
||||
return psycopg2.connect(**DB)
|
||||
|
||||
|
||||
def ensure_schema(conn):
|
||||
"""Crea la tabla de embeddings para traducciones si no existe."""
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
|
|
@ -61,21 +51,12 @@ def ensure_schema(conn):
|
|||
);
|
||||
"""
|
||||
)
|
||||
# Alineado con init-db/08-embeddings.sql
|
||||
cur.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_tr_emb_model ON traduccion_embeddings(model);"
|
||||
)
|
||||
cur.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_tr_emb_traduccion_id ON traduccion_embeddings(traduccion_id);"
|
||||
)
|
||||
cur.execute("CREATE INDEX IF NOT EXISTS idx_tr_emb_model ON traduccion_embeddings(model);")
|
||||
cur.execute("CREATE INDEX IF NOT EXISTS idx_tr_emb_traduccion_id ON traduccion_embeddings(traduccion_id);")
|
||||
conn.commit()
|
||||
|
||||
|
||||
def fetch_batch_pending(conn) -> List[psycopg2.extras.DictRow]:
|
||||
"""
|
||||
Devuelve un lote de traducciones 'done' del/los idioma(s) objetivo
|
||||
que no tienen embedding aún para el EMB_MODEL indicado.
|
||||
"""
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
|
|
@ -96,15 +77,10 @@ def fetch_batch_pending(conn) -> List[psycopg2.extras.DictRow]:
|
|||
""",
|
||||
(EMB_MODEL, EMB_LANGS, EMB_LIMIT),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return rows
|
||||
return cur.fetchall()
|
||||
|
||||
|
||||
def texts_from_rows(rows: List[psycopg2.extras.DictRow]) -> List[str]:
|
||||
"""
|
||||
Compone el texto a vectorizar por cada traducción:
|
||||
'titulo_trad' + '\n' + 'resumen_trad'. Si alguno falta, usa lo disponible.
|
||||
"""
|
||||
texts: List[str] = []
|
||||
for r in rows:
|
||||
title = (r["titulo_trad"] or "").strip()
|
||||
|
|
@ -117,15 +93,11 @@ def texts_from_rows(rows: List[psycopg2.extras.DictRow]) -> List[str]:
|
|||
|
||||
|
||||
def upsert_embeddings(conn, rows, embs: np.ndarray, model_name: str):
|
||||
"""
|
||||
Inserta/actualiza embeddings por traducción en lote (batch insert).
|
||||
"""
|
||||
if embs.size == 0 or not rows:
|
||||
return
|
||||
|
||||
dim = int(embs.shape[1])
|
||||
|
||||
# Preparamos los datos para execute_values
|
||||
data = [
|
||||
(
|
||||
int(r["traduccion_id"]),
|
||||
|
|
@ -152,7 +124,6 @@ def upsert_embeddings(conn, rows, embs: np.ndarray, model_name: str):
|
|||
conn.commit()
|
||||
|
||||
|
||||
# ---------- Main loop ----------
|
||||
def main():
|
||||
log.info("Arrancando embeddings_worker para TRADUCCIONES")
|
||||
log.info(
|
||||
|
|
|
|||
223
install.sh
223
install.sh
|
|
@ -1,223 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
APP_NAME="rss"
|
||||
DB_NAME="rss"
|
||||
DB_USER="rss"
|
||||
APP_USER="x"
|
||||
APP_DIR=$(pwd)
|
||||
PYTHON_ENV="$APP_DIR/venv"
|
||||
WSGI_APP_ENTRY="app:app"
|
||||
WEB_PORT=8000
|
||||
|
||||
echo "🟢 Paso 0: Verificaciones y confirmación de seguridad"
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "❌ Este script debe ser ejecutado como root (usa sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
echo "⚠️ ADVERTENCIA: Este script realizará las siguientes acciones DESTRUCTIVAS:"
|
||||
echo " - Eliminará TODOS los servicios systemd que empiecen por '$APP_NAME'."
|
||||
echo " - Eliminará PERMANENTEMENTE la base de datos '$DB_NAME'."
|
||||
echo " - Eliminará PERMANENTEMENTE el usuario de base de datos '$DB_USER'."
|
||||
echo "------------------------------------------------------------------"
|
||||
read -p "Estás seguro de que quieres continuar? (escribe 'si' para confirmar): " CONFIRM
|
||||
if [ "$CONFIRM" != "si" ]; then
|
||||
echo "Operación cancelada por el usuario."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
read -sp "🔑 Introduce la contraseña para el usuario de la base de datos '$DB_USER' (se creará de nuevo): " DB_PASS
|
||||
echo
|
||||
if [ -z "$DB_PASS" ]; then
|
||||
echo "❌ La contraseña no puede estar vacía. Abortando."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🧹 Paso 0.5: Limpiando instalación anterior..."
|
||||
echo " -> Buscando y eliminando servicios systemd antiguos..."
|
||||
for service in $(systemctl list-unit-files | grep "^$APP_NAME" | cut -d' ' -f1); do
|
||||
echo " -> Deteniendo y deshabilitando $service"
|
||||
systemctl stop "$service" || true
|
||||
systemctl disable "$service" || true
|
||||
done
|
||||
rm -f /etc/systemd/system/$APP_NAME*
|
||||
systemctl daemon-reload
|
||||
echo " -> Servicios systemd limpiados."
|
||||
|
||||
echo "🟢 Paso 1: Instalando dependencias del sistema..."
|
||||
apt-get update
|
||||
apt-get install -y wget ca-certificates postgresql postgresql-contrib python3-venv python3-pip python3-dev libpq-dev gunicorn
|
||||
|
||||
echo "🔥 Paso 2: Eliminando y recreando la base de datos y el usuario..."
|
||||
## CORRECCIÓN: Se exporta PGPASSWORD para evitar exponer la contraseña en la línea de comandos
|
||||
## y se agrupa con la del paso 4 para mayor eficiencia.
|
||||
export PGPASSWORD="$DB_PASS"
|
||||
|
||||
sudo -u postgres psql -c "DROP DATABASE IF EXISTS $DB_NAME;"
|
||||
sudo -u postgres psql -c "DROP USER IF EXISTS $DB_USER;"
|
||||
echo " -> Entidades de BD anteriores eliminadas."
|
||||
sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';"
|
||||
sudo -u postgres psql -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;"
|
||||
echo "✅ Base de datos y usuario recreados con éxito."
|
||||
|
||||
echo "🐍 Paso 3: Configurando el entorno de la aplicación..."
|
||||
if ! id "$APP_USER" &>/dev/null; then
|
||||
echo "👤 Creando usuario del sistema '$APP_USER'..."
|
||||
useradd -m -s /bin/bash "$APP_USER"
|
||||
else
|
||||
echo "✅ Usuario del sistema '$APP_USER' ya existe."
|
||||
fi
|
||||
chown -R "$APP_USER":"$APP_USER" "$APP_DIR"
|
||||
|
||||
sudo -u "$APP_USER" bash <<EOF
|
||||
set -e
|
||||
cd "$APP_DIR"
|
||||
echo " -> Creando entorno virtual en $PYTHON_ENV"
|
||||
rm -rf "$PYTHON_ENV"
|
||||
python3 -m venv "$PYTHON_ENV"
|
||||
echo " -> Instalando dependencias desde requirements.txt..."
|
||||
"$PYTHON_ENV/bin/python" -m pip install --upgrade pip
|
||||
if [ -f "requirements.txt" ]; then
|
||||
"$PYTHON_ENV/bin/python" -m pip install -r "requirements.txt"
|
||||
else
|
||||
echo "⚠️ ADVERTENCIA: No se encontró requirements.txt."
|
||||
fi
|
||||
EOF
|
||||
|
||||
echo "🧠 Paso 3.5: Descargando modelos de lenguaje para Newspaper3k..."
|
||||
if [ -f "download_models.py" ]; then
|
||||
sudo -u "$APP_USER" "$PYTHON_ENV/bin/python" "$APP_DIR/download_models.py"
|
||||
echo "✅ Modelos NLP verificados/descargados."
|
||||
else
|
||||
echo "⚠️ ADVERTENCIA: No se encontró download_models.py. El scraping de URLs puede fallar."
|
||||
fi
|
||||
|
||||
echo "📐 Paso 4: Creando esquema de BD y sembrando datos..."
|
||||
# La variable PGPASSWORD ya está exportada desde el paso 2
|
||||
|
||||
psql -U "$DB_USER" -h localhost -d "$DB_NAME" <<SQL
|
||||
CREATE TABLE IF NOT EXISTS continentes (id SERIAL PRIMARY KEY, nombre VARCHAR(50) NOT NULL UNIQUE);
|
||||
CREATE TABLE IF NOT EXISTS categorias (id SERIAL PRIMARY KEY, nombre VARCHAR(100) NOT NULL UNIQUE);
|
||||
CREATE TABLE IF NOT EXISTS paises (id SERIAL PRIMARY KEY, nombre VARCHAR(100) NOT NULL UNIQUE, continente_id INTEGER REFERENCES continentes(id) ON DELETE SET NULL);
|
||||
CREATE TABLE IF NOT EXISTS feeds (id SERIAL PRIMARY KEY, nombre VARCHAR(255), descripcion TEXT, url TEXT NOT NULL UNIQUE, categoria_id INTEGER REFERENCES categorias(id) ON DELETE SET NULL, pais_id INTEGER REFERENCES paises(id) ON DELETE SET NULL, idioma CHAR(2), activo BOOLEAN DEFAULT TRUE, fallos INTEGER DEFAULT 0, last_etag TEXT, last_modified TEXT);
|
||||
CREATE TABLE IF NOT EXISTS fuentes_url (id SERIAL PRIMARY KEY, nombre VARCHAR(255) NOT NULL, url TEXT NOT NULL UNIQUE, categoria_id INTEGER REFERENCES categorias(id) ON DELETE SET NULL, pais_id INTEGER REFERENCES paises(id) ON DELETE SET NULL, idioma CHAR(2) DEFAULT 'es');
|
||||
CREATE TABLE IF NOT EXISTS noticias (id VARCHAR(32) PRIMARY KEY, titulo TEXT, resumen TEXT, url TEXT NOT NULL UNIQUE, fecha TIMESTAMP, imagen_url TEXT, fuente_nombre VARCHAR(255), categoria_id INTEGER REFERENCES categorias(id) ON DELETE SET NULL, pais_id INTEGER REFERENCES paises(id) ON DELETE SET NULL, tsv tsvector);
|
||||
ALTER TABLE noticias ADD COLUMN IF NOT EXISTS tsv tsvector;
|
||||
CREATE OR REPLACE FUNCTION noticias_tsv_trigger() RETURNS trigger AS \$\$ BEGIN new.tsv := setweight(to_tsvector('spanish', coalesce(new.titulo,'')), 'A') || setweight(to_tsvector('spanish', coalesce(new.resumen,'')), 'B'); return new; END \$\$ LANGUAGE plpgsql;
|
||||
DROP TRIGGER IF EXISTS tsvectorupdate ON noticias;
|
||||
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON noticias FOR EACH ROW EXECUTE PROCEDURE noticias_tsv_trigger();
|
||||
CREATE INDEX IF NOT EXISTS noticias_tsv_idx ON noticias USING gin(tsv);
|
||||
SQL
|
||||
|
||||
echo " -> Buscando archivos .sql para sembrar datos..."
|
||||
if [ -f "continentes.sql" ]; then
|
||||
echo " -> Cargando continentes.sql..."
|
||||
psql -U "$DB_USER" -h localhost -d "$DB_NAME" -f "continentes.sql"
|
||||
fi
|
||||
if [ -f "categorias.sql" ]; then
|
||||
echo " -> Cargando categorias.sql..."
|
||||
psql -U "$DB_USER" -h localhost -d "$DB_NAME" -f "categorias.sql"
|
||||
fi
|
||||
if [ -f "paises.sql" ]; then
|
||||
echo " -> Cargando paises.sql..."
|
||||
psql -U "$DB_USER" -h localhost -d "$DB_NAME" -f "paises.sql"
|
||||
fi
|
||||
|
||||
echo " -> Actualizando contadores de secuencias de la base de datos..."
|
||||
psql -U "$DB_USER" -h localhost -d "$DB_NAME" <<SQL
|
||||
SELECT setval('categorias_id_seq', (SELECT MAX(id) FROM categorias), true) WHERE (SELECT MAX(id) FROM categorias) IS NOT NULL;
|
||||
SELECT setval('continentes_id_seq', (SELECT MAX(id) FROM continentes), true) WHERE (SELECT MAX(id) FROM continentes) IS NOT NULL;
|
||||
SELECT setval('paises_id_seq', (SELECT MAX(id) FROM paises), true) WHERE (SELECT MAX(id) FROM paises) IS NOT NULL;
|
||||
SQL
|
||||
|
||||
unset PGPASSWORD
|
||||
echo "✅ Esquema de base de datos y datos iniciales configurados."
|
||||
|
||||
## CORRECCIÓN: Se elimina el Paso 5, ya no es necesario crear el worker.py dinámicamente.
|
||||
|
||||
echo "⚙️ Paso 6: Creando nuevos archivos de servicio systemd..."
|
||||
cat <<EOF > /etc/systemd/system/$APP_NAME.service
|
||||
[Unit]
|
||||
Description=Gunicorn instance to serve $APP_NAME
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=$APP_USER
|
||||
Group=$APP_USER
|
||||
WorkingDirectory=$APP_DIR
|
||||
Environment="PATH=$PYTHON_ENV/bin"
|
||||
Environment="SECRET_KEY=$(python3 -c 'import os; print(os.urandom(24).hex())')"
|
||||
Environment="DB_HOST=localhost"
|
||||
Environment="DB_PORT=5432"
|
||||
Environment="DB_NAME=$DB_NAME"
|
||||
Environment="DB_USER=$DB_USER"
|
||||
Environment="DB_PASS=$DB_PASS"
|
||||
ExecStart=$PYTHON_ENV/bin/gunicorn --workers 3 --bind 0.0.0.0:$WEB_PORT --timeout 120 $WSGI_APP_ENTRY
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
## CORRECCIÓN: Se elimina la creación de rss-worker.service y rss-worker.timer.
|
||||
## Se añade el nuevo servicio para el planificador persistente scheduler.py.
|
||||
cat <<EOF > /etc/systemd/system/$APP_NAME-scheduler.service
|
||||
[Unit]
|
||||
Description=$APP_NAME Scheduler Worker
|
||||
After=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$APP_USER
|
||||
Group=$APP_USER
|
||||
WorkingDirectory=$APP_DIR
|
||||
Environment="PATH=$PYTHON_ENV/bin"
|
||||
Environment="SECRET_KEY=$(python3 -c 'import os; print(os.urandom(24).hex())')"
|
||||
Environment="DB_HOST=localhost"
|
||||
Environment="DB_PORT=5432"
|
||||
Environment="DB_NAME=$DB_NAME"
|
||||
Environment="DB_USER=$DB_USER"
|
||||
Environment="DB_PASS=$DB_PASS"
|
||||
ExecStart=$PYTHON_ENV/bin/python $APP_DIR/scheduler.py
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
echo "✅ Archivos de servicio creados."
|
||||
|
||||
echo "🚀 Paso 7: Recargando, habilitando, arrancando servicios y configurando firewall..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable $APP_NAME.service
|
||||
systemctl start $APP_NAME.service
|
||||
|
||||
## CORRECCIÓN: Se habilitan e inician el nuevo servicio del planificador.
|
||||
systemctl enable $APP_NAME-scheduler.service
|
||||
systemctl start $APP_NAME-scheduler.service
|
||||
|
||||
if command -v ufw &> /dev/null && ufw status | grep -q 'Status: active'; then
|
||||
echo " -> Firewall UFW detectado. Abriendo puerto $WEB_PORT..."
|
||||
ufw allow $WEB_PORT/tcp
|
||||
ufw reload
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 ¡REINSTALACIÓN COMPLETADA!"
|
||||
echo "--------------------------------"
|
||||
echo ""
|
||||
echo "✅ La aplicación web está ahora accesible en:"
|
||||
echo " http://<IP_DE_TU_SERVIDOR>:$WEB_PORT"
|
||||
echo ""
|
||||
echo "Puedes verificar el estado de los servicios con:"
|
||||
echo "sudo systemctl status $APP_NAME.service"
|
||||
## CORRECCIÓN: Se actualiza el mensaje para reflejar el nuevo nombre del servicio del worker.
|
||||
echo "sudo systemctl status $APP_NAME-scheduler.service"
|
||||
echo ""
|
||||
echo "Para ver los logs de la aplicación web:"
|
||||
echo "sudo journalctl -u $APP_NAME.service -f"
|
||||
echo "Para ver los logs del planificador de noticias:"
|
||||
echo "sudo journalctl -u $APP_NAME-scheduler.service -f"
|
||||
196
paises.csv
196
paises.csv
|
|
@ -1,196 +0,0 @@
|
|||
id,nombre,continente_id
|
||||
1,Afganistán,3
|
||||
2,Albania,4
|
||||
3,Alemania,4
|
||||
4,Andorra,4
|
||||
5,Angola,1
|
||||
6,Antigua y Barbuda,2
|
||||
7,Arabia Saudita,3
|
||||
8,Argelia,1
|
||||
9,Argentina,2
|
||||
10,Armenia,3
|
||||
11,Australia,5
|
||||
12,Austria,4
|
||||
13,Azerbaiyán,3
|
||||
14,Bahamas,2
|
||||
15,Bangladés,3
|
||||
16,Barbados,2
|
||||
17,Baréin,3
|
||||
19,Belice,2
|
||||
20,Benín,1
|
||||
21,Bielorrusia,4
|
||||
22,Birmania,3
|
||||
23,Bolivia,2
|
||||
24,Bosnia y Herzegovina,4
|
||||
25,Botsuana,1
|
||||
26,Brasil,2
|
||||
27,Brunéi,3
|
||||
28,Bulgaria,4
|
||||
29,Burkina Faso,1
|
||||
30,Burundi,1
|
||||
31,Bután,3
|
||||
18,Bélgica,4
|
||||
32,Cabo Verde,1
|
||||
33,Camboya,3
|
||||
34,Camerún,1
|
||||
35,Canadá,2
|
||||
36,Catar,3
|
||||
37,Chad,1
|
||||
38,Chile,2
|
||||
39,China,3
|
||||
40,Chipre,3
|
||||
41,Colombia,2
|
||||
42,Comoras,1
|
||||
43,Corea del Norte,3
|
||||
44,Corea del Sur,3
|
||||
46,Costa Rica,2
|
||||
45,Costa de Marfil,1
|
||||
47,Croacia,4
|
||||
48,Cuba,2
|
||||
49,Dinamarca,4
|
||||
50,Dominica,2
|
||||
51,Ecuador,2
|
||||
52,Egipto,1
|
||||
53,El Salvador,2
|
||||
54,Emiratos Árabes Unidos,3
|
||||
55,Eritrea,1
|
||||
56,Eslovaquia,4
|
||||
57,Eslovenia,4
|
||||
58,España,4
|
||||
59,Estados Unidos,2
|
||||
60,Estonia,4
|
||||
61,Esuatini,1
|
||||
62,Etiopía,1
|
||||
63,Filipinas,3
|
||||
64,Finlandia,4
|
||||
65,Fiyi,5
|
||||
66,Francia,4
|
||||
67,Gabón,1
|
||||
68,Gambia,1
|
||||
69,Georgia,3
|
||||
70,Ghana,1
|
||||
71,Granada,2
|
||||
72,Grecia,4
|
||||
73,Guatemala,2
|
||||
74,Guinea,1
|
||||
76,Guinea Ecuatorial,1
|
||||
75,Guinea-Bisáu,1
|
||||
77,Guyana,2
|
||||
78,Haití,2
|
||||
79,Honduras,2
|
||||
80,Hungría,4
|
||||
81,India,3
|
||||
82,Indonesia,3
|
||||
83,Irak,3
|
||||
85,Irlanda,4
|
||||
84,Irán,3
|
||||
86,Islandia,4
|
||||
87,Islas Marshall,5
|
||||
88,Islas Salomón,5
|
||||
89,Israel,3
|
||||
90,Italia,4
|
||||
91,Jamaica,2
|
||||
92,Japón,3
|
||||
93,Jordania,3
|
||||
94,Kazajistán,3
|
||||
95,Kenia,1
|
||||
96,Kirguistán,3
|
||||
97,Kiribati,5
|
||||
98,Kuwait,3
|
||||
99,Laos,3
|
||||
100,Lesoto,1
|
||||
101,Letonia,4
|
||||
103,Liberia,1
|
||||
104,Libia,1
|
||||
105,Liechtenstein,4
|
||||
106,Lituania,4
|
||||
107,Luxemburgo,4
|
||||
102,Líbano,3
|
||||
108,Macedonia del Norte,4
|
||||
109,Madagascar,1
|
||||
110,Malasia,3
|
||||
111,Malaui,1
|
||||
112,Maldivas,3
|
||||
114,Malta,4
|
||||
113,Malí,1
|
||||
115,Marruecos,1
|
||||
116,Mauricio,1
|
||||
117,Mauritania,1
|
||||
119,Micronesia,5
|
||||
120,Moldavia,4
|
||||
122,Mongolia,3
|
||||
123,Montenegro,4
|
||||
124,Mozambique,1
|
||||
118,México,2
|
||||
121,Mónaco,4
|
||||
125,Namibia,1
|
||||
126,Nauru,5
|
||||
127,Nepal,3
|
||||
128,Nicaragua,2
|
||||
130,Nigeria,1
|
||||
131,Noruega,4
|
||||
132,Nueva Zelanda,5
|
||||
129,Níger,1
|
||||
133,Omán,3
|
||||
135,Pakistán,3
|
||||
136,Palaos,5
|
||||
137,Palestina,3
|
||||
138,Panamá,2
|
||||
139,Papúa Nueva Guinea,5
|
||||
140,Paraguay,2
|
||||
134,Países Bajos,4
|
||||
141,Perú,2
|
||||
142,Polonia,4
|
||||
143,Portugal,4
|
||||
144,Reino Unido,4
|
||||
145,República Centroafricana,1
|
||||
146,República Checa,4
|
||||
148,República Democrática del Congo,1
|
||||
149,República Dominicana,2
|
||||
147,República del Congo,1
|
||||
150,Ruanda,1
|
||||
151,Rumanía,4
|
||||
152,Rusia,3
|
||||
153,Samoa,5
|
||||
154,San Cristóbal y Nieves,2
|
||||
155,San Marino,4
|
||||
156,San Vicente y las Granadinas,2
|
||||
157,Santa Lucía,2
|
||||
158,Santo Tomé y Príncipe,1
|
||||
159,Senegal,1
|
||||
160,Serbia,4
|
||||
161,Seychelles,1
|
||||
162,Sierra Leona,1
|
||||
163,Singapur,3
|
||||
164,Siria,3
|
||||
165,Somalia,1
|
||||
166,Sri Lanka,3
|
||||
167,Sudáfrica,1
|
||||
168,Sudán,1
|
||||
169,Sudán del Sur,1
|
||||
170,Suecia,4
|
||||
171,Suiza,4
|
||||
172,Surinam,2
|
||||
173,Tailandia,3
|
||||
174,Tanzania,1
|
||||
175,Tayikistán,3
|
||||
176,Timor Oriental,3
|
||||
177,Togo,1
|
||||
178,Tonga,5
|
||||
179,Trinidad y Tobago,2
|
||||
181,Turkmenistán,3
|
||||
182,Turquía,3
|
||||
183,Tuvalu,5
|
||||
180,Túnez,1
|
||||
184,Ucrania,4
|
||||
185,Uganda,1
|
||||
186,Uruguay,2
|
||||
187,Uzbekistán,3
|
||||
188,Vanuatu,5
|
||||
189,Vaticano,4
|
||||
190,Venezuela,2
|
||||
191,Vietnam,3
|
||||
192,Yemen,3
|
||||
193,Yibuti,1
|
||||
194,Zambia,1
|
||||
195,Zimbabue,1
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue