From 0118774e31ffb7b7be78d0188cd54c413e1abd10 Mon Sep 17 00:00:00 2001 From: Davidherran Date: Wed, 1 Apr 2026 16:57:22 +0000 Subject: [PATCH] production: docker fixes, nginx SSL config, generateStaticParams fallback --- .gitignore | 1 + Dockerfile | 1 + nginx/conf.d/flux-ssl.conf | 107 ------------------ nginx/conf.d/flux.conf | 47 +++++--- src/app/[locale]/applications/[slug]/page.tsx | 18 ++- src/app/[locale]/news/[slug]/page.tsx | 18 ++- 6 files changed, 64 insertions(+), 128 deletions(-) delete mode 100644 nginx/conf.d/flux-ssl.conf diff --git a/.gitignore b/.gitignore index f390d12..279c87d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ yarn-error.log* next-env.d.ts /src/generated/prisma +certbot/ diff --git a/Dockerfile b/Dockerfile index 2d3f4ad..ca66bf2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ RUN DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" npx prisma gene # Disable telemetry during build ENV NEXT_TELEMETRY_DISABLED=1 +ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" RUN npm run build diff --git a/nginx/conf.d/flux-ssl.conf b/nginx/conf.d/flux-ssl.conf deleted file mode 100644 index f8797cf..0000000 --- a/nginx/conf.d/flux-ssl.conf +++ /dev/null @@ -1,107 +0,0 @@ -# ═══════════════════════════════════════════════════════════════ -# FLUX SRL — Nginx SSL Configuration (rf-flux.com) -# Swap this in after running certbot: -# cp nginx/conf.d/flux-ssl.conf nginx/conf.d/flux.conf -# docker compose restart nginx -# ═══════════════════════════════════════════════════════════════ - -limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; -limit_req_zone $binary_remote_addr zone=login:10m rate=3r/m; - -upstream nextjs { - server app:3000; - keepalive 32; -} - -# HTTP → HTTPS redirect -server { - listen 80; - server_name rf-flux.com www.rf-flux.com; - - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - location / { - return 301 https://$host$request_uri; - } -} - -# HTTPS -server { - listen 443 ssl http2; - server_name rf-flux.com www.rf-flux.com; - - # SSL Certificates - ssl_certificate /etc/letsencrypt/live/rf-flux.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/rf-flux.com/privkey.pem; - - # SSL Hardening (A+ on SSL Labs) - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; - ssl_prefer_server_ciphers off; - ssl_session_timeout 1d; - ssl_session_cache shared:SSL:10m; - ssl_session_tickets off; - ssl_stapling on; - ssl_stapling_verify on; - - # HSTS (2 years) - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; - - # Content Security Policy - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob:; connect-src 'self' https://api.openai.com; media-src 'self'; object-src 'none'; frame-ancestors 'self';" always; - - location /_next/static/ { - proxy_pass http://nextjs; - expires 365d; - add_header Cache-Control "public, immutable"; - access_log off; - } - - location /footage/ { - proxy_pass http://nextjs; - expires 30d; - add_header Cache-Control "public"; - access_log off; - } - - location /hq-command/ { - limit_req zone=login burst=5 nodelay; - proxy_pass http://nextjs; - proxy_set_header Host $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 $scheme; - } - - location /api/chat { - limit_req zone=api burst=20 nodelay; - proxy_pass http://nextjs; - proxy_set_header Host $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 $scheme; - proxy_http_version 1.1; - proxy_set_header Connection ''; - proxy_buffering off; - proxy_cache off; - proxy_read_timeout 120s; - } - - location /api/health { - proxy_pass http://nextjs; - access_log off; - } - - location / { - proxy_pass http://nextjs; - proxy_set_header Host $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 $scheme; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -} diff --git a/nginx/conf.d/flux.conf b/nginx/conf.d/flux.conf index acf050f..599797f 100644 --- a/nginx/conf.d/flux.conf +++ b/nginx/conf.d/flux.conf @@ -1,10 +1,5 @@ -# ═══════════════════════════════════════════════════════════════ -# FLUX SRL — Nginx Site Configuration -# Phase 1: HTTP only (SSL added after certbot runs) -# ═══════════════════════════════════════════════════════════════ - limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; -limit_req_zone $binary_remote_addr zone=login:10m rate=3r/m; +limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s; upstream nextjs { server app:3000; @@ -15,12 +10,31 @@ server { listen 80; server_name rf-flux.com www.rf-flux.com; - # Let's Encrypt challenge (keep this even after SSL) location /.well-known/acme-challenge/ { root /var/www/certbot; } - # Static assets: Next.js hashed (immutable cache) + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + http2 on; + server_name rf-flux.com www.rf-flux.com; + + ssl_certificate /etc/letsencrypt/live/rf-flux.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/rf-flux.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; + location /_next/static/ { proxy_pass http://nextjs; expires 365d; @@ -28,7 +42,6 @@ server { access_log off; } - # Static assets: footage, GLB models location /footage/ { proxy_pass http://nextjs; expires 30d; @@ -36,9 +49,16 @@ server { access_log off; } - # CMS Admin (strict rate limiting) - location /hq-command/ { - limit_req zone=login burst=5 nodelay; + location /hq-command/login { + limit_req zone=login burst=10 nodelay; + proxy_pass http://nextjs; + proxy_set_header Host $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 $scheme; + } + + location /hq-command/ { proxy_pass http://nextjs; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -46,7 +66,6 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } - # FluxAI Chat API (streaming + rate limited) location /api/chat { limit_req zone=api burst=20 nodelay; proxy_pass http://nextjs; @@ -61,13 +80,11 @@ server { proxy_read_timeout 120s; } - # Health check location /api/health { proxy_pass http://nextjs; access_log off; } - # Default location / { proxy_pass http://nextjs; proxy_set_header Host $host; diff --git a/src/app/[locale]/applications/[slug]/page.tsx b/src/app/[locale]/applications/[slug]/page.tsx index 48e27c9..9b80035 100644 --- a/src/app/[locale]/applications/[slug]/page.tsx +++ b/src/app/[locale]/applications/[slug]/page.tsx @@ -30,8 +30,20 @@ function getApplicationImages(slug: string) { // GENERACIÓN DE RUTAS ESTÁTICAS DESDE LA BD export async function generateStaticParams() { - const apps = await prisma.application.findMany({ select: { slug: true } }); - return apps.map((app: { slug: string }) => ({ slug: app.slug })); + // In production Docker build, DB is not available. + // Pages are generated on-demand via SSR instead. + if (process.env.NODE_ENV === 'production' && !process.env.VERCEL) { + return []; + } + + try { + const apps = await prisma.application.findMany({ + select: { slug: true }, + }); + return apps.map((app: any) => ({ slug: app.slug })); + } catch { + return []; + } } export const revalidate = 60; @@ -76,4 +88,4 @@ export default async function ApplicationPage({ params }: { params: Promise<{ sl // Pasamos TODO al componente cliente interactivo (que ya viene traducido) return ; -} \ No newline at end of file +} diff --git a/src/app/[locale]/news/[slug]/page.tsx b/src/app/[locale]/news/[slug]/page.tsx index 13a7491..f3eb45d 100644 --- a/src/app/[locale]/news/[slug]/page.tsx +++ b/src/app/[locale]/news/[slug]/page.tsx @@ -10,8 +10,20 @@ import BreathingField from "@/components/visuals/BreathingField"; import { getLocalizedData } from "@/lib/i18nHelper"; export async function generateStaticParams() { - const articles = await prisma.newsArticle.findMany({ select: { slug: true } }); - return articles.map((a: any) => ({ slug: a.slug })); + // In production Docker build, DB is not available. + // Pages are generated on-demand via SSR instead. + if (process.env.NODE_ENV === 'production' && !process.env.VERCEL) { + return []; + } + + try { + const articles = await prisma.newsArticle.findMany({ + select: { slug: true }, + }); + return articles.map((a: any) => ({ slug: a.slug })); + } catch { + return []; + } } // ── SÚPER PARSER MARKDOWN (Con Tablas, Imágenes y Dark/Light Mode) ── @@ -271,4 +283,4 @@ export default async function ArticlePage({ params }: { params: Promise<{ slug: ); -} \ No newline at end of file +}