refactor nginx

Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
This commit is contained in:
Omar Sánchez Pizarro
2026-01-21 00:30:13 +01:00
parent ed2107086c
commit 53928328d4
11 changed files with 394 additions and 78 deletions

205
web/DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,205 @@
# Guía de Desarrollo
## Configuración de Puertos
### Desarrollo Local
| Servicio | Puerto | URL |
|----------|--------|-----|
| Dashboard (Vue) | 3000 | http://localhost:3000 |
| Backend (API) | 3001 | http://localhost:3001 |
| Landing (Astro) | 3002 | http://localhost:3002 |
### Producción (Docker)
| Servicio | Puerto Externo | Puerto Interno |
|----------|----------------|----------------|
| Nginx Proxy | 80 | - |
| Dashboard | - | 80 |
| Backend | - | 3001 |
| Landing | - | 80 |
## Ejecutar en Desarrollo Local
### 1. Backend (API)
```bash
cd web/backend
npm install
npm run dev
```
El backend estará disponible en `http://localhost:3001`
**Endpoints**:
- API: `http://localhost:3001/api/*`
- WebSocket: `ws://localhost:3001/ws`
### 2. Dashboard (Vue)
```bash
cd web/dashboard
npm install
npm run dev
```
El dashboard estará disponible en `http://localhost:3000`
**Nota**: Vite está configurado con proxy automático:
- `/api/*``http://localhost:3001/api/*`
- `/ws``ws://localhost:3001/ws`
Esto significa que puedes hacer peticiones a `/api/users` desde el dashboard y automáticamente se redirigirán al backend.
### 3. Landing (Astro)
```bash
cd web/landing
npm install
npm run dev
```
La landing estará disponible en `http://localhost:3002`
## Ejecutar con Docker (Producción)
```bash
# Construir imágenes
docker-compose build
# Iniciar servicios
docker-compose up -d
# Ver logs
docker-compose logs -f
# Detener servicios
docker-compose down
```
Todo estará disponible en `http://localhost`:
- Landing: `http://localhost/`
- Dashboard: `http://localhost/dashboard/`
- API: `http://localhost/api/`
## Reconstruir después de cambios
### Cambios en código (desarrollo)
No es necesario hacer nada, Vite y Node tienen hot-reload automático.
### Cambios en configuración o Docker
```bash
# Reconstruir servicio específico
docker-compose build dashboard
docker-compose build backend
docker-compose build landing
# Reconstruir todo
docker-compose build
# Reiniciar servicios
docker-compose up -d
```
## Diferencias entre Desarrollo y Producción
### Dashboard
- **Desarrollo**:
- Puerto 3000
- Vite proxy activo para `/api` y `/ws`
- Hot Module Replacement (HMR)
- Source maps
- **Producción**:
- Puerto 80 (interno)
- Sin proxy (nginx principal maneja todo)
- Código minificado y optimizado
- Assets con hash para cache
### Backend
- **Desarrollo**:
- Puerto 3001
- `node --watch` para auto-reload
- Variables de entorno por defecto
- **Producción**:
- Puerto 3001 (interno)
- `node` sin watch
- Variables de entorno de Docker
### Landing
- **Desarrollo**:
- Puerto 3002
- Servidor de desarrollo de Astro
- Hot reload
- **Producción**:
- Puerto 80 (interno)
- Archivos estáticos servidos por nginx
- Pre-renderizado
## Troubleshooting
### Puerto ya en uso
```bash
# Ver qué está usando el puerto
lsof -i :3000 # Dashboard
lsof -i :3001 # Backend
lsof -i :3002 # Landing
# Matar proceso
kill -9 <PID>
```
### Proxy no funciona en desarrollo
Asegúrate de que:
1. El backend está corriendo en el puerto 3001
2. Estás ejecutando `npm run dev` (modo desarrollo)
3. La configuración de proxy en `vite.config.js` está correcta
### Assets 404 en producción
Ver `NGINX_CONFIG.md` para detalles de configuración de nginx y assets.
## Variables de Entorno
### Backend (desarrollo local)
Crear archivo `.env` en `web/backend/`:
```env
PORT=3001
PROJECT_ROOT=/ruta/al/proyecto
MONGODB_HOST=localhost
MONGODB_PORT=27017
MONGODB_DATABASE=wallabicher
MONGODB_USERNAME=admin
MONGODB_PASSWORD=adminpassword
```
### Backend (Docker)
Las variables se configuran en `docker-compose.yml`:
- `PORT=3001`
- `PROJECT_ROOT=/data`
- Credenciales de MongoDB
## Base URLs
### Dashboard
La configuración de `base: '/dashboard/'` en `vite.config.js` hace que:
- Todos los assets se construyan con prefijo `/dashboard/`
- Vue Router use `/dashboard` como base
- Service Worker se registre en `/dashboard/sw.js`
**No cambiar** a menos que quieras cambiar la ruta del dashboard.
### Landing
Sin base (raíz por defecto). Se sirve desde `/`.
**No cambiar** a menos que quieras mover la landing a otra ruta.

139
web/NGINX_CONFIG.md Normal file
View File

@@ -0,0 +1,139 @@
# Configuración de Nginx y Assets
## Arquitectura
```
Usuario
nginx (proxy principal) :80
├─→ /api → backend:3001
├─→ /ws → backend:3001
├─→ /dashboard → dashboard:80
└─→ / → landing:80
```
## Configuración de rutas
### 1. Dashboard (Vue + Vite)
**Base URL**: `/dashboard/`
- **Vite config**: `base: '/dashboard/'` (siempre)
- **Vue Router**: `createWebHistory('/dashboard')`
- **Nginx interno**: Sirve desde `/usr/share/nginx/html/dashboard/`
- **Assets**: Se construyen con prefijo `/dashboard/` automáticamente por Vite
**Flujo de peticiones**:
```
Usuario → http://localhost/dashboard/
nginx proxy → http://dashboard:80/dashboard/
nginx dashboard → /usr/share/nginx/html/dashboard/index.html
```
**Assets**:
```
Usuario → http://localhost/dashboard/assets/index-abc123.js
nginx proxy → http://dashboard:80/dashboard/assets/index-abc123.js
nginx dashboard → /usr/share/nginx/html/dashboard/assets/index-abc123.js
```
### 2. Landing (Astro)
**Base URL**: `/`
- **Astro config**: Sin base (raíz por defecto)
- **Nginx interno**: Sirve desde `/usr/share/nginx/html/`
- **Assets**: Se construyen para la raíz
**Flujo de peticiones**:
```
Usuario → http://localhost/
nginx proxy → http://landing:80/
nginx landing → /usr/share/nginx/html/index.html
```
## Puertos
### Desarrollo local
```
Dashboard (Vue): http://localhost:3000
Backend (API): http://localhost:3001
Landing (Astro): http://localhost:3002
```
### Producción (Docker)
```
nginx (proxy): :80 (externo)
├─ dashboard: :80 (interno)
├─ backend: :3001 (interno)
└─ landing: :80 (interno)
```
## Desarrollo local
Para desarrollo local, el proxy de Vite está configurado solo para `mode === 'development'`:
```bash
# Terminal 1: Backend
cd web/backend
npm run dev
# → API en http://localhost:3001
# Terminal 2: Dashboard
cd web/dashboard
npm run dev
# → Dashboard en http://localhost:3000
# → Vite proxy activo: /api → localhost:3001, /ws → localhost:3001
# Terminal 3: Landing
cd web/landing
npm run dev
# → Landing en http://localhost:3002
# Producción (Docker)
docker-compose up -d
# → Todo en http://localhost:80
```
## Reconstruir después de cambios
Si cambias la configuración de nginx o los archivos de configuración:
```bash
# Reconstruir solo el dashboard
docker-compose build dashboard
# Reconstruir solo la landing
docker-compose build landing
# Reconstruir todo
docker-compose build
# Reiniciar servicios
docker-compose up -d
```
## Troubleshooting
### Assets 404 en dashboard
1. Verificar que Vite construyó con `base: '/dashboard/'`
2. Verificar que el Dockerfile copia a `/usr/share/nginx/html/dashboard`
3. Verificar que nginx-dashboard.conf tiene la location `/dashboard/`
### Assets 404 en landing
1. Verificar que Astro construyó sin base (raíz)
2. Verificar que el Dockerfile copia a `/usr/share/nginx/html/`
3. Verificar que nginx landing usa root `/usr/share/nginx/html`
### Service Worker no se registra
El Service Worker debe estar en `/dashboard/sw.js` y registrarse con scope `/dashboard/`

View File

@@ -11,14 +11,14 @@ RUN npm ci
# Copiar código fuente # Copiar código fuente
COPY . . COPY . .
# Construir aplicación # Construir aplicación con base /dashboard/
RUN npm run build RUN npm run build
# Stage de producción - servir con nginx # Stage de producción - servir con nginx
FROM nginx:alpine FROM nginx:alpine
# Copiar archivos construidos # Copiar archivos construidos (ya incluyen el prefijo /dashboard en las rutas)
COPY --from=builder /app/dist /usr/share/nginx/html COPY --from=builder /app/dist /usr/share/nginx/html/dashboard
# Copiar configuración de nginx (se puede sobrescribir con volumen) # Copiar configuración de nginx (se puede sobrescribir con volumen)
COPY nginx-dashboard.conf /etc/nginx/conf.d/default.conf COPY nginx-dashboard.conf /etc/nginx/conf.d/default.conf

View File

@@ -10,15 +10,22 @@ server {
gzip_min_length 1024; gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript; gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
# SPA routing - todas las rutas del dashboard # Dashboard assets con prefijo /dashboard/
location / { location /dashboard/ {
try_files $uri $uri/ /index.html;
}
# Cache static assets # Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y; expires 1y;
add_header Cache-Control "public, immutable"; add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# SPA routing - todas las rutas del dashboard
try_files $uri $uri/ /dashboard/index.html;
}
# Redirigir raíz al dashboard
location = / {
return 301 /dashboard/;
} }
} }

View File

@@ -8,42 +8,24 @@ server {
gzip on; gzip on;
gzip_vary on; gzip_vary on;
gzip_min_length 1024; gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json; gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
# SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# API proxy
location /api {
proxy_pass http://backend:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
}
# WebSocket proxy
location /ws {
proxy_pass http://backend:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
# Dashboard assets con prefijo /dashboard/
location /dashboard/ {
# Cache static assets # Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y; expires 1y;
add_header Cache-Control "public, immutable"; add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# SPA routing - todas las rutas del dashboard
try_files $uri $uri/ /dashboard/index.html;
}
# Redirigir raíz al dashboard
location = / {
return 301 /dashboard/;
} }
} }

View File

@@ -20,8 +20,8 @@ self.addEventListener('push', (event) => {
let notificationData = { let notificationData = {
title: 'Wallabicher', title: 'Wallabicher',
body: 'Tienes nuevas notificaciones', body: 'Tienes nuevas notificaciones',
icon: '/android-chrome-192x192.png', icon: '/dashboard/android-chrome-192x192.png',
badge: '/android-chrome-192x192.png', badge: '/dashboard/android-chrome-192x192.png',
tag: 'wallabicher-notification', tag: 'wallabicher-notification',
requireInteraction: false, requireInteraction: false,
data: {} data: {}
@@ -89,7 +89,7 @@ self.addEventListener('notificationclick', (event) => {
} else { } else {
// Si no hay URL, abrir la app // Si no hay URL, abrir la app
event.waitUntil( event.waitUntil(
clients.openWindow('/') clients.openWindow('/dashboard/')
); );
} }
}); });

View File

@@ -105,8 +105,8 @@ app.mount('#app');
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => { window.addEventListener('load', async () => {
try { try {
const registration = await navigator.serviceWorker.register('/sw.js', { const registration = await navigator.serviceWorker.register('/dashboard/sw.js', {
scope: '/' scope: '/dashboard/'
}); });
console.log('Service Worker registrado:', registration.scope); console.log('Service Worker registrado:', registration.scope);
} catch (error) { } catch (error) {

View File

@@ -2,7 +2,7 @@ import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { fileURLToPath, URL } from 'url'; import { fileURLToPath, URL } from 'url';
export default defineConfig({ export default defineConfig(({ mode }) => ({
plugins: [vue()], plugins: [vue()],
base: '/dashboard/', base: '/dashboard/',
resolve: { resolve: {
@@ -12,7 +12,9 @@ export default defineConfig({
}, },
server: { server: {
port: 3000, port: 3000,
proxy: { host: true,
// Proxy solo para desarrollo local
proxy: mode === 'development' ? {
'/api': { '/api': {
target: 'http://localhost:3001', target: 'http://localhost:3001',
changeOrigin: true, changeOrigin: true,
@@ -21,7 +23,7 @@ export default defineConfig({
target: 'ws://localhost:3001', target: 'ws://localhost:3001',
ws: true, ws: true,
}, },
} : undefined,
}, },
}, }));
});

View File

@@ -17,11 +17,8 @@ RUN npm run build
# Stage de producción - servir con nginx # Stage de producción - servir con nginx
FROM nginx:alpine FROM nginx:alpine
# Copiar archivos construidos # Copiar archivos construidos a la raíz
COPY --from=builder /app/dist /usr/share/nginx/html/landing COPY --from=builder /app/dist /usr/share/nginx/html
#change /usr/share/nginx/html to /usr/share/nginx/html/landing
RUN sed -i 's|/usr/share/nginx/html|/usr/share/nginx/html/landing|g' /etc/nginx/conf.d/default.conf
# Exponer puerto # Exponer puerto
EXPOSE 80 EXPOSE 80

View File

@@ -5,5 +5,9 @@ import tailwind from '@astrojs/tailwind';
export default defineConfig({ export default defineConfig({
integrations: [tailwind()], integrations: [tailwind()],
output: 'static', output: 'static',
server: {
port: 3002,
host: true,
},
}); });

View File

@@ -33,17 +33,7 @@ server {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
} }
# Login también va al dashboard # Dashboard Vue - pasar petición completa con prefijo /dashboard
location /login {
proxy_pass http://dashboard:80;
proxy_http_version 1.1;
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;
}
# Dashboard Vue
location /dashboard { location /dashboard {
proxy_pass http://dashboard:80; proxy_pass http://dashboard:80;
proxy_http_version 1.1; proxy_http_version 1.1;
@@ -51,10 +41,6 @@ server {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
# Para SPA routing dentro de /dashboard - quitar el prefijo /dashboard
rewrite ^/dashboard/(.*)$ /$1 break;
rewrite ^/dashboard$ / break;
} }
# Landing page (Astro) - raíz # Landing page (Astro) - raíz
@@ -66,11 +52,5 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
} }
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
} }