diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b2140f..1b45631 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,8 +3,8 @@ stages: variables: APP_NAME: "generadorexamenesllms" - BACKEND_PORT: "8074" - FRONTEND_PORT: "8075" + BACKEND_PORT: "8068" + FRONTEND_PORT: "8069" deploy_to_sinbad2: stage: deploy diff --git a/README.md b/README.md index 6dd5402..82a00ef 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ docker compose up --build La API queda disponible en: ```text -http://sinbad2.ujaen.es:8074 +http://sinbad2.ujaen.es:8068 ``` ## Configuración diff --git a/backend/.env b/backend/.env index d0ed64e..69c2a60 100644 --- a/backend/.env +++ b/backend/.env @@ -1,23 +1,36 @@ -# --- Aplicación --- +# --- Aplicación (producción Sinbad2) --- APP_NAME=GenExamenes IA -ENVIRONMENT=local -# Clave legacy (reservada; las rutas /exam usan JWT de usuario). +ENVIRONMENT=production + +PUBLIC_BASE_URL=https://sinbad2.ujaen.es/generadorexamenesllm +TRUSTED_HOSTS=sinbad2.ujaen.es,localhost,127.0.0.1 +SECURITY_HSTS_SECONDS=31536000 + API_KEY=change-me-in-production-min-16-chars -# --- Base de datos (Docker: host "db") --- + DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes -# --- CORS (orígenes del frontend, separados por coma) --- -ALLOWED_ORIGINS=http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075 -# --- Rate limiting y tamaño de petición --- + +ALLOWED_ORIGINS=https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8069 + RATE_LIMIT_REQUESTS=60 RATE_LIMIT_WINDOW_SECONDS=60 -MAX_REQUEST_BYTES=1048576 -# --- JWT (login email/contraseña y sesión tras Google) --- +MAX_REQUEST_BYTES=25165824 + +UPLOAD_DIR=/app/uploads +MAX_UPLOAD_BYTES=20971520 +MAX_MATERIALS_PER_TEMPLATE=10 +MAX_REFERENCE_CHARS=12000 + +MAX_IMAGE_BYTES=5242880 +MAX_IMAGES_PER_TEMPLATE=20 +MAX_STORAGE_BYTES_PER_TEMPLATE=52428800 + JWT_SECRET_KEY=f3c9e7a1b4d8c2f6a9e1d3b7c5f2e8a4d1c7b9e3f6a2c4e8b1d7f3a9c6e2b4d8 JWT_ALGORITHM=HS256 JWT_EXPIRE_MINUTES=1440 -# --- Google Sign-In --- + GOOGLE_CLIENT_ID=123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com -# --- LLM (Sinbad2IA UJA — sin clave) --- + LLM_BASE_URL= LLM_MODEL=qwen3.5:35b LLM_TIMEOUT_SECONDS=180 diff --git a/backend/.env.example b/backend/.env.example index ac4821d..401f518 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -18,7 +18,7 @@ API_KEY=change-me-in-production-min-16-chars DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes # --- CORS (orígenes HTTPS del frontend; separados por coma) --- -ALLOWED_ORIGINS=https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075 +ALLOWED_ORIGINS=https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8069 # --- Rate limiting y tamaño de petición --- RATE_LIMIT_REQUESTS=60 diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 1eeb633..28c713f 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -13,7 +13,7 @@ class Settings(BaseSettings): api_prefix: str = "" api_key: str = Field(min_length=16) database_url: str = "postgresql+psycopg://genexamenes:genexamenes@localhost:5432/genexamenes" - allowed_origins: str = "https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075" + allowed_origins: str = "https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8069" rate_limit_requests: int = Field(default=60, ge=1) rate_limit_window_seconds: int = Field(default=60, ge=1) max_request_bytes: int = Field(default=1_048_576, ge=1_024) diff --git a/deploy/DESPLIEGUE_SINBAD2.md b/deploy/DESPLIEGUE_SINBAD2.md index d7a0bb1..a3caec0 100644 --- a/deploy/DESPLIEGUE_SINBAD2.md +++ b/deploy/DESPLIEGUE_SINBAD2.md @@ -15,8 +15,8 @@ El job `deploy_to_sinbad2` en `.gitlab-ci.yml`: 1. Copia el código por SSH a Sinbad2. 2. Ejecuta `docker compose build` y `docker compose up -d`. 3. Expone servicios en **HTTP** (sin certificados en Docker): - - Frontend → `:8075` - - Backend → `:8074` + - Frontend → `:8069` + - Backend → `:8068` Variables de despliegue: @@ -30,18 +30,28 @@ Variables de despliegue: ## 2. Apache (gestión UJA — no está en este repo) -Fragmento mínimo (`deploy/apache-reverse-proxy.conf`): +Configuración confirmada por sistemas (mismo estilo que `orcid2sword` → `:8073`): ```apache -ProxyPass /generadorexamenesllm http://host.docker.internal:8075/ -ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/ +ProxyPass /generadorexamenesllm http://host.docker.internal:8069/ +ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8069/ +``` + +**Importante:** esas líneas deben ir **antes** de las reglas de WordPress del sitio Sinbad2. +Si no, Apache sigue devolviendo la web del grupo (cabeceras `X-Powered-By: PHP` y `X-Redirect-By: WordPress`). + +Comprobación rápida tras recargar Apache: + +```bash +curl -I https://sinbad2.ujaen.es/generadorexamenesllm/ +# Debe mostrar Server: nginx (contenedor), NO PHP/WordPress ``` | Paso | Qué ocurre | |------|------------| | Certificado SSL | Lo proporciona el servidor institucional | | Entrada pública | `https://sinbad2.ujaen.es/generadorexamenesllm/` | -| Proxy | Apache reenvía a `:8075` (HTTP) y **quita** el prefijo | +| Proxy | Apache reenvía a `:8069` (HTTP) y **quita** el prefijo | | Nginx contenedor | Sirve SPA y hace proxy de `/auth/` y `/exam/` al backend | Si falta el `ProxyPass`, la ruta la atiende el CMS de Sinbad2 (home del grupo). @@ -51,7 +61,7 @@ Si falta el `ProxyPass`, la ruta la atiende el CMS de Sinbad2 (home del grupo). ### Frontend - Build con `VITE_APP_BASE_PATH=/generadorexamenesllm/`. -- `nginx.conf` entiende rutas **con prefijo** (acceso directo `:8075`) y **sin prefijo** (tras Apache). +- `nginx.conf` entiende rutas **con prefijo** (acceso directo `:8069`) y **sin prefijo** (tras Apache). - Proxy interno al backend; cabeceras `X-Forwarded-Proto` / `Host` propagadas desde Apache. ### Backend @@ -64,7 +74,7 @@ Si falta el `ProxyPass`, la ruta la atiende el CMS de Sinbad2 (home del grupo). ```bash # Directo al contenedor (HTTP, con prefijo) -curl -I http://sinbad2.ujaen.es:8075/generadorexamenesllm/ +curl -I http://sinbad2.ujaen.es:8069/generadorexamenesllm/ # Tras Apache (HTTPS) curl -I https://sinbad2.ujaen.es/generadorexamenesllm/ diff --git a/deploy/apache-reverse-proxy.conf b/deploy/apache-reverse-proxy.conf index 57beca5..9841eff 100644 --- a/deploy/apache-reverse-proxy.conf +++ b/deploy/apache-reverse-proxy.conf @@ -2,11 +2,12 @@ # Sin estas líneas, /generadorexamenesllm/ lo sirve el CMS de Sinbad2, no la app Docker. # # URL pública: https://sinbad2.ujaen.es/generadorexamenesllm/ -# Contenedor frontend (HTTP): host.docker.internal:8075 -# Contenedor backend (HTTP, solo interno): :8074 +# Contenedor frontend (HTTP): host.docker.internal:8069 +# Contenedor backend (HTTP en host): :8068 (red Docker interna sigue en 8074) -ProxyPass /generadorexamenesllm http://host.docker.internal:8075/ -ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/ +# Colocar ANTES de las reglas de WordPress del VirtualHost HTTPS. +ProxyPass /generadorexamenesllm http://host.docker.internal:8069/ +ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8069/ # Opcional: reenviar cabeceras de proxy (Apache 2.4+) # RequestHeader set X-Forwarded-Proto "https" diff --git a/docker-compose.yml b/docker-compose.yml index f2c2fdd..942ba7f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,12 +10,12 @@ services: PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-https://sinbad2.ujaen.es/generadorexamenesllm} TRUSTED_HOSTS: ${TRUSTED_HOSTS:-sinbad2.ujaen.es,localhost,127.0.0.1} # Sobrescribe backend/.env con el origen público del frontend en despliegue. - ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075} + ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8069} LLM_BASE_URL: LLM_MODEL: qwen3.5:35b LLM_TIMEOUT_SECONDS: "180" ports: - - "${BACKEND_PORT:-8074}:8074" + - "${BACKEND_PORT:-8068}:8074" depends_on: db: condition: service_healthy @@ -31,7 +31,7 @@ services: VITE_API_URL: ${VITE_API_URL:-} VITE_GOOGLE_CLIENT_ID: ${VITE_GOOGLE_CLIENT_ID:-} ports: - - "${FRONTEND_PORT:-8075}:80" + - "${FRONTEND_PORT:-8069}:80" depends_on: - backend restart: unless-stopped diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..83b7b6c --- /dev/null +++ b/frontend/.env @@ -0,0 +1,3 @@ +VITE_APP_BASE_PATH=/generadorexamenesllm/ +VITE_API_URL= +VITE_GOOGLE_CLIENT_ID= diff --git a/frontend/README.md b/frontend/README.md index 8ee1bc9..c983eaf 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -25,7 +25,7 @@ src/ ## Desarrollo local -Requisitos: Node 20+ y el backend corriendo en `http://sinbad2.ujaen.es:8074`. +Requisitos: Node 20+ y el backend corriendo en `http://sinbad2.ujaen.es:8068`. ```bash cd frontend @@ -72,7 +72,7 @@ Resumen: | Capa | Responsabilidad | |------|-----------------| -| Apache (UJA) | Certificado SSL, `ProxyPass` a `:8075` | +| Apache (UJA) | Certificado SSL, `ProxyPass` a `:8069` | | GitLab CI | `docker compose up`, build con base path | | Nginx contenedor | SPA + proxy `/auth/` y `/exam/` al backend | | Backend Uvicorn | `--proxy-headers`, HSTS, CORS HTTPS | @@ -82,8 +82,8 @@ URL pública: **`https://sinbad2.ujaen.es/generadorexamenesllm/`** Apache (fragmento): ```apache -ProxyPass /generadorexamenesllm http://host.docker.internal:8075/ -ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/ +ProxyPass /generadorexamenesllm http://host.docker.internal:8069/ +ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8069/ ``` Build: diff --git a/frontend/nginx.conf b/frontend/nginx.conf index a4cc3ba..9bc3c7c 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,11 +1,11 @@ -# Nginx del contenedor frontend (HTTP interno, puerto 80 → publicado en 8075). +# Nginx del contenedor frontend (HTTP interno, puerto 80 → publicado en 8069 en Sinbad2). # # Flujo HTTPS (igual que orcid2sword en Sinbad2): # 1. Usuario → https://sinbad2.ujaen.es/generadorexamenesllm/ -# 2. Apache termina TLS y hace ProxyPass al puerto 8075 (HTTP). -# 3. Con ProxyPass ... http://host:8075/ Apache QUITA el prefijo /generadorexamenesllm +# 2. Apache termina TLS y hace ProxyPass al puerto 8069 (HTTP). +# 3. Con ProxyPass ... http://host:8069/ Apache QUITA el prefijo /generadorexamenesllm # y el contenedor recibe /, /assets/, /auth/, etc. -# 4. Acceso directo al puerto 8075 (sin Apache) usa el prefijo /generadorexamenesllm/ +# 4. Acceso directo al puerto 8069 (sin Apache) usa el prefijo /generadorexamenesllm/ # porque el build de Vite lleva VITE_APP_BASE_PATH=/generadorexamenesllm/ map $http_x_forwarded_proto $forwarded_proto { @@ -54,7 +54,7 @@ server { try_files $uri $uri/ /index.html; } - # --- Mismo contenido/API bajo prefijo público (acceso directo :8075 o si Apache no quita prefijo) --- + # --- Mismo contenido/API bajo prefijo público (acceso directo :8069 o si Apache no quita prefijo) --- location ^~ /generadorexamenesllm/auth/ { proxy_pass http://backend:8074/auth/; include /etc/nginx/snippets/proxy_params.conf;