Align Sinbad2 HTTPS deployment with orcid2sword reverse-proxy pattern.
This adds nginx dual-path routing, forwarded proxy headers, Uvicorn proxy-headers, production security settings, and deployment docs for https://sinbad2.ujaen.es/generadorexamenesllm/.
This commit is contained in:
@@ -46,6 +46,9 @@ deploy_to_sinbad2:
|
|||||||
|
|
||||||
export BACKEND_PORT=$BACKEND_PORT
|
export BACKEND_PORT=$BACKEND_PORT
|
||||||
export FRONTEND_PORT=$FRONTEND_PORT
|
export FRONTEND_PORT=$FRONTEND_PORT
|
||||||
|
export ENVIRONMENT=production
|
||||||
|
export PUBLIC_BASE_URL="https://sinbad2.ujaen.es/generadorexamenesllm"
|
||||||
|
export TRUSTED_HOSTS="sinbad2.ujaen.es,localhost,127.0.0.1"
|
||||||
export ALLOWED_ORIGINS="https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:$FRONTEND_PORT"
|
export ALLOWED_ORIGINS="https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:$FRONTEND_PORT"
|
||||||
export VITE_APP_BASE_PATH="/generadorexamenesllm/"
|
export VITE_APP_BASE_PATH="/generadorexamenesllm/"
|
||||||
export VITE_API_URL=""
|
export VITE_API_URL=""
|
||||||
|
|||||||
+11
-2
@@ -1,6 +1,15 @@
|
|||||||
# --- Aplicación ---
|
# --- Aplicación ---
|
||||||
APP_NAME=GenExamenes IA
|
APP_NAME=GenExamenes IA
|
||||||
ENVIRONMENT=local
|
ENVIRONMENT=production
|
||||||
|
|
||||||
|
# URL pública HTTPS (Apache termina TLS; contenedores en HTTP interno)
|
||||||
|
PUBLIC_BASE_URL=https://sinbad2.ujaen.es/generadorexamenesllm
|
||||||
|
|
||||||
|
# Hosts aceptados por TrustedHostMiddleware (sin esquema ni puerto)
|
||||||
|
TRUSTED_HOSTS=sinbad2.ujaen.es,localhost,127.0.0.1
|
||||||
|
|
||||||
|
# HSTS (segundos; 1 año por defecto)
|
||||||
|
SECURITY_HSTS_SECONDS=31536000
|
||||||
|
|
||||||
# Clave legacy (reservada; las rutas /exam usan JWT de usuario).
|
# Clave legacy (reservada; las rutas /exam usan JWT de usuario).
|
||||||
API_KEY=change-me-in-production-min-16-chars
|
API_KEY=change-me-in-production-min-16-chars
|
||||||
@@ -8,7 +17,7 @@ API_KEY=change-me-in-production-min-16-chars
|
|||||||
# --- Base de datos (Docker: host "db") ---
|
# --- 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) ---
|
# --- 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:8075
|
||||||
|
|
||||||
# --- Rate limiting y tamaño de petición ---
|
# --- Rate limiting y tamaño de petición ---
|
||||||
|
|||||||
+6
-1
@@ -25,4 +25,9 @@ USER app
|
|||||||
|
|
||||||
EXPOSE 8074
|
EXPOSE 8074
|
||||||
|
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8074"]
|
CMD ["uvicorn", "app.main:app", \
|
||||||
|
"--host", "0.0.0.0", \
|
||||||
|
"--port", "8074", \
|
||||||
|
"--proxy-headers", \
|
||||||
|
"--forwarded-allow-ips", "*", \
|
||||||
|
"--no-server-header"]
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
app_name: str = "GenExamenes IA"
|
app_name: str = "GenExamenes IA"
|
||||||
environment: str = "local"
|
environment: str = "local"
|
||||||
|
public_base_url: str = "https://sinbad2.ujaen.es/generadorexamenesllm"
|
||||||
|
trusted_hosts: str = "sinbad2.ujaen.es,localhost,127.0.0.1"
|
||||||
|
security_hsts_seconds: int = Field(default=31_536_000, ge=0)
|
||||||
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"
|
||||||
@@ -41,10 +44,18 @@ class Settings(BaseSettings):
|
|||||||
extra="ignore",
|
extra="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_production(self) -> bool:
|
||||||
|
return self.environment.lower() in {"production", "prod"}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cors_origins(self) -> list[str]:
|
def cors_origins(self) -> list[str]:
|
||||||
return [origin.strip() for origin in self.allowed_origins.split(",") if origin.strip()]
|
return [origin.strip() for origin in self.allowed_origins.split(",") if origin.strip()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def trusted_hosts_list(self) -> list[str]:
|
||||||
|
return [host.strip() for host in self.trusted_hosts.split(",") if host.strip()]
|
||||||
|
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache
|
||||||
def get_settings() -> Settings:
|
def get_settings() -> Settings:
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from fastapi import Request, Response
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
||||||
|
|
||||||
|
from app.core.config import Settings
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
||||||
|
"""Cabeceras de seguridad; HSTS en producción o cuando el proxy indica HTTPS."""
|
||||||
|
|
||||||
|
def __init__(self, app: object, settings: Settings) -> None:
|
||||||
|
super().__init__(app)
|
||||||
|
self._settings = settings
|
||||||
|
|
||||||
|
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
||||||
|
response = await call_next(request)
|
||||||
|
|
||||||
|
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
||||||
|
response.headers.setdefault("X-Frame-Options", "SAMEORIGIN")
|
||||||
|
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||||
|
|
||||||
|
forwarded_proto = request.headers.get("x-forwarded-proto", request.url.scheme)
|
||||||
|
is_https = forwarded_proto == "https" or request.url.scheme == "https"
|
||||||
|
|
||||||
|
if is_https or self._settings.is_production:
|
||||||
|
hsts = f"max-age={self._settings.security_hsts_seconds}"
|
||||||
|
if self._settings.is_production:
|
||||||
|
hsts += "; includeSubDomains"
|
||||||
|
response.headers.setdefault("Strict-Transport-Security", hsts)
|
||||||
|
|
||||||
|
return response
|
||||||
+6
-1
@@ -1,13 +1,15 @@
|
|||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
|
|
||||||
from fastapi import Depends, FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
||||||
|
|
||||||
from app.api.routes import auth, exports, generation, health, history, images, materials, questions, templates
|
from app.api.routes import auth, exports, generation, health, history, images, materials, questions, templates
|
||||||
from app.core.config import get_settings
|
from app.core.config import get_settings
|
||||||
from app.core.errors import register_exception_handlers
|
from app.core.errors import register_exception_handlers
|
||||||
from app.core.middleware import RateLimitMiddleware, RequestSizeLimitMiddleware
|
from app.core.middleware import RateLimitMiddleware, RequestSizeLimitMiddleware
|
||||||
|
from app.core.security_headers import SecurityHeadersMiddleware
|
||||||
from app.db.init_db import init_db
|
from app.db.init_db import init_db
|
||||||
|
|
||||||
|
|
||||||
@@ -25,6 +27,9 @@ def create_app() -> FastAPI:
|
|||||||
# las peticiones OPTIONS (preflight) respondan antes que rate limit, etc.
|
# las peticiones OPTIONS (preflight) respondan antes que rate limit, etc.
|
||||||
app.add_middleware(RequestSizeLimitMiddleware, settings=settings)
|
app.add_middleware(RequestSizeLimitMiddleware, settings=settings)
|
||||||
app.add_middleware(RateLimitMiddleware, settings=settings)
|
app.add_middleware(RateLimitMiddleware, settings=settings)
|
||||||
|
app.add_middleware(SecurityHeadersMiddleware, settings=settings)
|
||||||
|
if settings.trusted_hosts_list:
|
||||||
|
app.add_middleware(TrustedHostMiddleware, allowed_hosts=settings.trusted_hosts_list)
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=settings.cors_origins,
|
allow_origins=settings.cors_origins,
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Despliegue en Sinbad2 (HTTPS vía Apache)
|
||||||
|
|
||||||
|
Patrón equivalente a **orcid2sword**: TLS en Apache, contenedores en HTTP en puertos internos.
|
||||||
|
|
||||||
|
## URL pública
|
||||||
|
|
||||||
|
`https://sinbad2.ujaen.es/generadorexamenesllm/`
|
||||||
|
|
||||||
|
No usar `/deckofcars/` ni `/deckofcards/` (son otros proyectos).
|
||||||
|
|
||||||
|
## 1. GitLab CI (este repositorio)
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
|
Variables de despliegue:
|
||||||
|
|
||||||
|
| Variable | Valor |
|
||||||
|
|----------|--------|
|
||||||
|
| `VITE_APP_BASE_PATH` | `/generadorexamenesllm/` |
|
||||||
|
| `VITE_API_URL` | *(vacío: misma base HTTPS vía nginx)* |
|
||||||
|
| `ENVIRONMENT` | `production` |
|
||||||
|
| `PUBLIC_BASE_URL` | `https://sinbad2.ujaen.es/generadorexamenesllm` |
|
||||||
|
| `ALLOWED_ORIGINS` | `https://sinbad2.ujaen.es,...` |
|
||||||
|
|
||||||
|
## 2. Apache (gestión UJA — no está en este repo)
|
||||||
|
|
||||||
|
Fragmento mínimo (`deploy/apache-reverse-proxy.conf`):
|
||||||
|
|
||||||
|
```apache
|
||||||
|
ProxyPass /generadorexamenesllm http://host.docker.internal:8075/
|
||||||
|
ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/
|
||||||
|
```
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
| 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).
|
||||||
|
|
||||||
|
## 3. Adaptaciones en la aplicación
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- Build con `VITE_APP_BASE_PATH=/generadorexamenesllm/`.
|
||||||
|
- `nginx.conf` entiende rutas **con prefijo** (acceso directo `:8075`) y **sin prefijo** (tras Apache).
|
||||||
|
- Proxy interno al backend; cabeceras `X-Forwarded-Proto` / `Host` propagadas desde Apache.
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- Uvicorn con `--proxy-headers` y `--forwarded-allow-ips *`.
|
||||||
|
- `TrustedHostMiddleware` + cabeceras HSTS en producción.
|
||||||
|
- CORS con origen `https://sinbad2.ujaen.es`.
|
||||||
|
|
||||||
|
## 4. Comprobaciones
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Directo al contenedor (HTTP, con prefijo)
|
||||||
|
curl -I http://sinbad2.ujaen.es:8075/generadorexamenesllm/
|
||||||
|
|
||||||
|
# Tras Apache (HTTPS)
|
||||||
|
curl -I https://sinbad2.ujaen.es/generadorexamenesllm/
|
||||||
|
|
||||||
|
# API vía nginx del frontend
|
||||||
|
curl https://sinbad2.ujaen.es/generadorexamenesllm/health
|
||||||
|
```
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
# Fragmento para el VirtualHost de sinbad2.ujaen.es (HTTPS).
|
# Apache en sinbad2.ujaen.es (VirtualHost HTTPS) — gestión UJA.
|
||||||
# Sin esto, /generadorexamenesllm/ lo atiende el CMS de Sinbad2 y 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/
|
||||||
|
# Contenedor frontend (HTTP): host.docker.internal:8075
|
||||||
|
# Contenedor backend (HTTP, solo interno): :8074
|
||||||
|
|
||||||
ProxyPass /generadorexamenesllm http://host.docker.internal:8075/
|
ProxyPass /generadorexamenesllm http://host.docker.internal:8075/
|
||||||
ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/
|
ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/
|
||||||
|
|
||||||
# URL pública: https://sinbad2.ujaen.es/generadorexamenesllm/
|
# Opcional: reenviar cabeceras de proxy (Apache 2.4+)
|
||||||
|
# RequestHeader set X-Forwarded-Proto "https"
|
||||||
|
# RequestHeader set X-Forwarded-Host "sinbad2.ujaen.es"
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ services:
|
|||||||
- ./backend/.env
|
- ./backend/.env
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes
|
DATABASE_URL: postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes
|
||||||
|
ENVIRONMENT: ${ENVIRONMENT:-production}
|
||||||
|
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.
|
# 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:8075}
|
||||||
LLM_BASE_URL:
|
LLM_BASE_URL:
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ RUN npm run build
|
|||||||
# --- Serve stage ---
|
# --- Serve stage ---
|
||||||
FROM nginx:1.27-alpine AS serve
|
FROM nginx:1.27-alpine AS serve
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY proxy_params.conf /etc/nginx/snippets/proxy_params.conf
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
+14
-5
@@ -64,13 +64,22 @@ docker compose up --build
|
|||||||
Las variables `VITE_APP_BASE_PATH`, `VITE_API_URL` y `VITE_GOOGLE_CLIENT_ID` pueden pasarse como
|
Las variables `VITE_APP_BASE_PATH`, `VITE_API_URL` y `VITE_GOOGLE_CLIENT_ID` pueden pasarse como
|
||||||
variables de entorno al ejecutar `docker compose`.
|
variables de entorno al ejecutar `docker compose`.
|
||||||
|
|
||||||
## Despliegue HTTPS en subruta (Sinbad2)
|
## Despliegue HTTPS en Sinbad2 (patrón orcid2sword)
|
||||||
|
|
||||||
La app **no** va bajo `/deckofcars/` (eso es otro sitio). La URL pública es:
|
Documentación completa: [`deploy/DESPLIEGUE_SINBAD2.md`](../deploy/DESPLIEGUE_SINBAD2.md)
|
||||||
|
|
||||||
`https://sinbad2.ujaen.es/generadorexamenesllm/`
|
Resumen:
|
||||||
|
|
||||||
En Apache del servidor deben existir estas líneas (el prefijo se quita al llegar al contenedor en el puerto 8075):
|
| Capa | Responsabilidad |
|
||||||
|
|------|-----------------|
|
||||||
|
| Apache (UJA) | Certificado SSL, `ProxyPass` a `:8075` |
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
URL pública: **`https://sinbad2.ujaen.es/generadorexamenesllm/`**
|
||||||
|
|
||||||
|
Apache (fragmento):
|
||||||
|
|
||||||
```apache
|
```apache
|
||||||
ProxyPass /generadorexamenesllm http://host.docker.internal:8075/
|
ProxyPass /generadorexamenesllm http://host.docker.internal:8075/
|
||||||
@@ -84,7 +93,7 @@ VITE_APP_BASE_PATH=/generadorexamenesllm/
|
|||||||
VITE_API_URL=
|
VITE_API_URL=
|
||||||
```
|
```
|
||||||
|
|
||||||
Si Apache no tiene ese `ProxyPass`, el navegador verá la web principal de Sinbad2 en lugar de esta app.
|
El navegador habla solo con HTTPS (Apache → nginx → backend); no hay mixed content ni CORS cruzado entre puertos.
|
||||||
|
|
||||||
## Manejo de errores
|
## Manejo de errores
|
||||||
|
|
||||||
|
|||||||
+55
-19
@@ -1,46 +1,82 @@
|
|||||||
|
# Nginx del contenedor frontend (HTTP interno, puerto 80 → publicado en 8075).
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# y el contenedor recibe /, /assets/, /auth/, etc.
|
||||||
|
# 4. Acceso directo al puerto 8075 (sin Apache) usa el prefijo /generadorexamenesllm/
|
||||||
|
# porque el build de Vite lleva VITE_APP_BASE_PATH=/generadorexamenesllm/
|
||||||
|
|
||||||
|
map $http_x_forwarded_proto $forwarded_proto {
|
||||||
|
default $http_x_forwarded_proto;
|
||||||
|
"" $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
map $http_x_forwarded_host $forwarded_host {
|
||||||
|
default $http_x_forwarded_host;
|
||||||
|
"" $host;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
# Backend API dentro de la misma base HTTPS (evita mixed content).
|
gzip on;
|
||||||
|
gzip_types text/css application/javascript application/json image/svg+xml;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
|
||||||
|
# --- API: rutas sin prefijo (Apache quita /generadorexamenesllm) ---
|
||||||
location /auth/ {
|
location /auth/ {
|
||||||
proxy_pass http://backend:8074/auth/;
|
proxy_pass http://backend:8074/auth/;
|
||||||
proxy_http_version 1.1;
|
include /etc/nginx/snippets/proxy_params.conf;
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location /exam/ {
|
location /exam/ {
|
||||||
proxy_pass http://backend:8074/exam/;
|
proxy_pass http://backend:8074/exam/;
|
||||||
proxy_http_version 1.1;
|
include /etc/nginx/snippets/proxy_params.conf;
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /health {
|
location = /health {
|
||||||
proxy_pass http://backend:8074/health;
|
proxy_pass http://backend:8074/health;
|
||||||
proxy_http_version 1.1;
|
include /etc/nginx/snippets/proxy_params.conf;
|
||||||
proxy_set_header Host $host;
|
}
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
location /assets/ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
# SPA: cualquier ruta desconocida sirve index.html (React Router).
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache de assets con hash.
|
# --- Mismo contenido/API bajo prefijo público (acceso directo :8075 o si Apache no quita prefijo) ---
|
||||||
location /assets/ {
|
location ^~ /generadorexamenesllm/auth/ {
|
||||||
|
proxy_pass http://backend:8074/auth/;
|
||||||
|
include /etc/nginx/snippets/proxy_params.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /generadorexamenesllm/exam/ {
|
||||||
|
proxy_pass http://backend:8074/exam/;
|
||||||
|
include /etc/nginx/snippets/proxy_params.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /generadorexamenesllm/health {
|
||||||
|
proxy_pass http://backend:8074/health;
|
||||||
|
include /etc/nginx/snippets/proxy_params.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /generadorexamenesllm/assets/ {
|
||||||
|
alias /usr/share/nginx/html/assets/;
|
||||||
expires 1y;
|
expires 1y;
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
}
|
}
|
||||||
|
|
||||||
gzip on;
|
location ^~ /generadorexamenesllm/ {
|
||||||
gzip_types text/css application/javascript application/json image/svg+xml;
|
try_files $uri $uri/ /index.html;
|
||||||
gzip_min_length 1024;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $forwarded_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||||
|
proxy_set_header X-Forwarded-Host $forwarded_host;
|
||||||
|
proxy_redirect off;
|
||||||
Reference in New Issue
Block a user