Enable HTTPS production deployment on Sinbad2 via Apache reverse proxy.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
.env.local
|
||||
@@ -17,6 +17,7 @@
|
||||
# - Backend ejecutado fuera de Docker (uvicorn --port 8000):
|
||||
# VITE_API_URL=http://localhost:8000/api
|
||||
#
|
||||
# - Producción (ya definido en .env):
|
||||
# VITE_API_URL=http://sinbad2.ujaen.es:8070/api
|
||||
# - Producción Sinbad2 (HTTPS, prefijo /deckofcards; build en docker-compose.prod.yaml):
|
||||
# VITE_BASE_PATH=/deckofcards/
|
||||
# VITE_API_URL=/deckofcards/api
|
||||
VITE_API_URL=http://localhost:8070/api
|
||||
|
||||
+33
-2
@@ -1,5 +1,36 @@
|
||||
FROM node:20-alpine
|
||||
FROM node:20-alpine AS development
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
|
||||
RUN npm install
|
||||
CMD ["npm", "run", "dev", "--", "--host"]
|
||||
|
||||
CMD ["npm", "run", "dev", "--", "--host"]
|
||||
|
||||
FROM node:20-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG VITE_BASE_PATH=/deckofcards/
|
||||
ARG VITE_API_URL=/deckofcards/api
|
||||
|
||||
ENV VITE_BASE_PATH=$VITE_BASE_PATH
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27-alpine AS production
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="%BASE_URL%favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Deck of Cards</title>
|
||||
</head>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Apache en Sinbad2 termina TLS en https://sinbad2.ujaen.es/deckofcards
|
||||
# y reenvía el tráfico a este contenedor (puerto 8071) sin el prefijo público:
|
||||
# ProxyPass /deckofcards http://host.docker.internal:8071/
|
||||
# El navegador sigue viendo /deckofcards/...; aquí llegan rutas como /, /api/, /assets/.
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8000/api/;
|
||||
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 $http_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export default function Header() {
|
||||
className="flex items-center gap-3 whitespace-nowrap transition-opacity hover:opacity-80"
|
||||
>
|
||||
<img
|
||||
src="/favicon.svg"
|
||||
src={`${import.meta.env.BASE_URL}favicon.svg`}
|
||||
alt="Deck of Cards Logo"
|
||||
className="h-10 w-10 rounded-xl object-contain shadow-sm"
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_URL;
|
||||
import { APP_BASE_PATH } from './lib/paths';
|
||||
|
||||
const configuredApiUrl = import.meta.env.VITE_API_URL;
|
||||
export const API_BASE_URL = configuredApiUrl || (APP_BASE_PATH ? `${APP_BASE_PATH}/api` : '/api');
|
||||
|
||||
export const CHART_COLORS = [
|
||||
'#ef4444', '#f59e0b', '#10b981', '#3b82f6',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Axios from 'axios';
|
||||
import { API_BASE_URL } from '../config';
|
||||
import { isLoginPath, toAppPath } from './paths';
|
||||
|
||||
const api = Axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
@@ -28,8 +29,8 @@ api.interceptors.response.use(
|
||||
localStorage.removeItem('user');
|
||||
|
||||
// SOLUCIÓN: Solo recargamos y redirigimos si NO estamos ya en /login
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login';
|
||||
if (!isLoginPath(window.location.pathname)) {
|
||||
window.location.href = toAppPath('/login');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
const normalizeBasePath = (value) => {
|
||||
if (!value || value === '/') {
|
||||
return '';
|
||||
}
|
||||
const withLeadingSlash = value.startsWith('/') ? value : `/${value}`;
|
||||
return withLeadingSlash.replace(/\/$/, '');
|
||||
};
|
||||
|
||||
export const APP_BASE_PATH = normalizeBasePath(import.meta.env.VITE_BASE_PATH);
|
||||
|
||||
export const toAppPath = (path) => {
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||||
return `${APP_BASE_PATH}${normalizedPath}` || normalizedPath;
|
||||
};
|
||||
|
||||
export const isLoginPath = (pathname) => {
|
||||
const loginPath = toAppPath('/login');
|
||||
return pathname === loginPath || pathname === '/login';
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { APP_BASE_PATH } from '../lib/paths';
|
||||
import MainLayout from '../components/layout/MainLayout';
|
||||
import DocEditor from '../pages/DocEditor';
|
||||
import Login from '../pages/Login';
|
||||
@@ -18,7 +19,7 @@ function ProtectedHistoryRoute() {
|
||||
|
||||
export default function AppRouter() {
|
||||
return (
|
||||
<Router>
|
||||
<Router basename={APP_BASE_PATH || undefined}>
|
||||
<MainLayout>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/editor" replace />} />
|
||||
|
||||
@@ -2,8 +2,11 @@ import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
const basePath = process.env.VITE_BASE_PATH || '/'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: basePath,
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
|
||||
Reference in New Issue
Block a user