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:
+2
-2
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
VITE_APP_BASE_PATH=/generadorexamenesllm/
|
||||||
|
VITE_API_URL=
|
||||||
|
VITE_GOOGLE_CLIENT_ID=
|
||||||
+4
-4
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user