Use Sinbad2 production ports 8068 (backend) and 8069 (frontend).

Apache ProxyPass targets host port 8069; update compose, CI, CORS defaults, deployment docs, and production .env files for https://sinbad2.ujaen.es/generadorexamenesllm/.
This commit is contained in:
Mireya Cueto Garrido
2026-06-03 12:13:02 +02:00
parent 7dcc7dc0e1
commit d06b961a73
11 changed files with 67 additions and 40 deletions
+2 -2
View File
@@ -3,8 +3,8 @@ stages:
variables: variables:
APP_NAME: "generadorexamenesllms" APP_NAME: "generadorexamenesllms"
BACKEND_PORT: "8074" BACKEND_PORT: "8068"
FRONTEND_PORT: "8075" FRONTEND_PORT: "8069"
deploy_to_sinbad2: deploy_to_sinbad2:
stage: deploy stage: deploy
+1 -1
View File
@@ -31,7 +31,7 @@ docker compose up --build
La API queda disponible en: La API queda disponible en:
```text ```text
http://sinbad2.ujaen.es:8074 http://sinbad2.ujaen.es:8068
``` ```
## Configuración ## Configuración
+24 -11
View File
@@ -1,23 +1,36 @@
# --- Aplicación --- # --- Aplicación (producción Sinbad2) ---
APP_NAME=GenExamenes IA APP_NAME=GenExamenes IA
ENVIRONMENT=local ENVIRONMENT=production
# Clave legacy (reservada; las rutas /exam usan JWT de usuario).
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 API_KEY=change-me-in-production-min-16-chars
# --- Base de datos (Docker: host "db") ---
DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes 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 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 RATE_LIMIT_REQUESTS=60
RATE_LIMIT_WINDOW_SECONDS=60 RATE_LIMIT_WINDOW_SECONDS=60
MAX_REQUEST_BYTES=1048576 MAX_REQUEST_BYTES=25165824
# --- JWT (login email/contraseña y sesión tras Google) ---
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_SECRET_KEY=f3c9e7a1b4d8c2f6a9e1d3b7c5f2e8a4d1c7b9e3f6a2c4e8b1d7f3a9c6e2b4d8
JWT_ALGORITHM=HS256 JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=1440 JWT_EXPIRE_MINUTES=1440
# --- Google Sign-In ---
GOOGLE_CLIENT_ID=123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com GOOGLE_CLIENT_ID=123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com
# --- LLM (Sinbad2IA UJA — sin clave) ---
LLM_BASE_URL= LLM_BASE_URL=
LLM_MODEL=qwen3.5:35b LLM_MODEL=qwen3.5:35b
LLM_TIMEOUT_SECONDS=180 LLM_TIMEOUT_SECONDS=180
+1 -1
View File
@@ -18,7 +18,7 @@ API_KEY=change-me-in-production-min-16-chars
DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes
# --- CORS (orígenes HTTPS del frontend; separados por coma) --- # --- 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 limiting y tamaño de petición ---
RATE_LIMIT_REQUESTS=60 RATE_LIMIT_REQUESTS=60
+1 -1
View File
@@ -13,7 +13,7 @@ class Settings(BaseSettings):
api_prefix: str = "" api_prefix: str = ""
api_key: str = Field(min_length=16) api_key: str = Field(min_length=16)
database_url: str = "postgresql+psycopg://genexamenes:genexamenes@localhost:5432/genexamenes" 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_requests: int = Field(default=60, ge=1)
rate_limit_window_seconds: 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) max_request_bytes: int = Field(default=1_048_576, ge=1_024)
+18 -8
View File
@@ -15,8 +15,8 @@ El job `deploy_to_sinbad2` en `.gitlab-ci.yml`:
1. Copia el código por SSH a Sinbad2. 1. Copia el código por SSH a Sinbad2.
2. Ejecuta `docker compose build` y `docker compose up -d`. 2. Ejecuta `docker compose build` y `docker compose up -d`.
3. Expone servicios en **HTTP** (sin certificados en Docker): 3. Expone servicios en **HTTP** (sin certificados en Docker):
- Frontend → `:8075` - Frontend → `:8069`
- Backend → `:8074` - Backend → `:8068`
Variables de despliegue: Variables de despliegue:
@@ -30,18 +30,28 @@ Variables de despliegue:
## 2. Apache (gestión UJA — no está en este repo) ## 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 ```apache
ProxyPass /generadorexamenesllm http://host.docker.internal:8075/ ProxyPass /generadorexamenesllm http://host.docker.internal:8069/
ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/ 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 | | Paso | Qué ocurre |
|------|------------| |------|------------|
| Certificado SSL | Lo proporciona el servidor institucional | | Certificado SSL | Lo proporciona el servidor institucional |
| Entrada pública | `https://sinbad2.ujaen.es/generadorexamenesllm/` | | 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 | | 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). 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 ### Frontend
- Build con `VITE_APP_BASE_PATH=/generadorexamenesllm/`. - 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. - Proxy interno al backend; cabeceras `X-Forwarded-Proto` / `Host` propagadas desde Apache.
### Backend ### Backend
@@ -64,7 +74,7 @@ Si falta el `ProxyPass`, la ruta la atiende el CMS de Sinbad2 (home del grupo).
```bash ```bash
# Directo al contenedor (HTTP, con prefijo) # 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) # Tras Apache (HTTPS)
curl -I https://sinbad2.ujaen.es/generadorexamenesllm/ curl -I https://sinbad2.ujaen.es/generadorexamenesllm/
+5 -4
View File
@@ -2,11 +2,12 @@
# Sin estas líneas, /generadorexamenesllm/ lo sirve el CMS de Sinbad2, no la app Docker. # Sin estas líneas, /generadorexamenesllm/ lo sirve el CMS de Sinbad2, no la app Docker.
# #
# URL pública: https://sinbad2.ujaen.es/generadorexamenesllm/ # URL pública: https://sinbad2.ujaen.es/generadorexamenesllm/
# Contenedor frontend (HTTP): host.docker.internal:8075 # Contenedor frontend (HTTP): host.docker.internal:8069
# Contenedor backend (HTTP, solo interno): :8074 # Contenedor backend (HTTP en host): :8068 (red Docker interna sigue en 8074)
ProxyPass /generadorexamenesllm http://host.docker.internal:8075/ # Colocar ANTES de las reglas de WordPress del VirtualHost HTTPS.
ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/ ProxyPass /generadorexamenesllm http://host.docker.internal:8069/
ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8069/
# Opcional: reenviar cabeceras de proxy (Apache 2.4+) # Opcional: reenviar cabeceras de proxy (Apache 2.4+)
# RequestHeader set X-Forwarded-Proto "https" # RequestHeader set X-Forwarded-Proto "https"
+3 -3
View File
@@ -10,12 +10,12 @@ services:
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-https://sinbad2.ujaen.es/generadorexamenesllm} PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-https://sinbad2.ujaen.es/generadorexamenesllm}
TRUSTED_HOSTS: ${TRUSTED_HOSTS:-sinbad2.ujaen.es,localhost,127.0.0.1} TRUSTED_HOSTS: ${TRUSTED_HOSTS:-sinbad2.ujaen.es,localhost,127.0.0.1}
# Sobrescribe backend/.env con el origen público del frontend en despliegue. # 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_BASE_URL:
LLM_MODEL: qwen3.5:35b LLM_MODEL: qwen3.5:35b
LLM_TIMEOUT_SECONDS: "180" LLM_TIMEOUT_SECONDS: "180"
ports: ports:
- "${BACKEND_PORT:-8074}:8074" - "${BACKEND_PORT:-8068}:8074"
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
@@ -31,7 +31,7 @@ services:
VITE_API_URL: ${VITE_API_URL:-} VITE_API_URL: ${VITE_API_URL:-}
VITE_GOOGLE_CLIENT_ID: ${VITE_GOOGLE_CLIENT_ID:-} VITE_GOOGLE_CLIENT_ID: ${VITE_GOOGLE_CLIENT_ID:-}
ports: ports:
- "${FRONTEND_PORT:-8075}:80" - "${FRONTEND_PORT:-8069}:80"
depends_on: depends_on:
- backend - backend
restart: unless-stopped restart: unless-stopped
+3
View File
@@ -0,0 +1,3 @@
VITE_APP_BASE_PATH=/generadorexamenesllm/
VITE_API_URL=
VITE_GOOGLE_CLIENT_ID=
+4 -4
View File
@@ -25,7 +25,7 @@ src/
## Desarrollo local ## 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 ```bash
cd frontend cd frontend
@@ -72,7 +72,7 @@ Resumen:
| Capa | Responsabilidad | | 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 | | GitLab CI | `docker compose up`, build con base path |
| Nginx contenedor | SPA + proxy `/auth/` y `/exam/` al backend | | Nginx contenedor | SPA + proxy `/auth/` y `/exam/` al backend |
| Backend Uvicorn | `--proxy-headers`, HSTS, CORS HTTPS | | Backend Uvicorn | `--proxy-headers`, HSTS, CORS HTTPS |
@@ -82,8 +82,8 @@ URL pública: **`https://sinbad2.ujaen.es/generadorexamenesllm/`**
Apache (fragmento): Apache (fragmento):
```apache ```apache
ProxyPass /generadorexamenesllm http://host.docker.internal:8075/ ProxyPass /generadorexamenesllm http://host.docker.internal:8069/
ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/ ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8069/
``` ```
Build: Build:
+5 -5
View File
@@ -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): # Flujo HTTPS (igual que orcid2sword en Sinbad2):
# 1. Usuario → https://sinbad2.ujaen.es/generadorexamenesllm/ # 1. Usuario → https://sinbad2.ujaen.es/generadorexamenesllm/
# 2. Apache termina TLS y hace ProxyPass al puerto 8075 (HTTP). # 2. Apache termina TLS y hace ProxyPass al puerto 8069 (HTTP).
# 3. Con ProxyPass ... http://host:8075/ Apache QUITA el prefijo /generadorexamenesllm # 3. Con ProxyPass ... http://host:8069/ Apache QUITA el prefijo /generadorexamenesllm
# y el contenedor recibe /, /assets/, /auth/, etc. # 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/ # porque el build de Vite lleva VITE_APP_BASE_PATH=/generadorexamenesllm/
map $http_x_forwarded_proto $forwarded_proto { map $http_x_forwarded_proto $forwarded_proto {
@@ -54,7 +54,7 @@ server {
try_files $uri $uri/ /index.html; 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/ { location ^~ /generadorexamenesllm/auth/ {
proxy_pass http://backend:8074/auth/; proxy_pass http://backend:8074/auth/;
include /etc/nginx/snippets/proxy_params.conf; include /etc/nginx/snippets/proxy_params.conf;