diff --git a/Dockerfile b/Dockerfile index 87e8af2..80a3994 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,16 +34,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Create non-root user RUN useradd --create-home --shell /bin/bash foxy WORKDIR /app +RUN chown foxy:foxy /app # Python dependencies -COPY backend/requirements.txt ./ +COPY --chown=foxy:foxy backend/requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt # Backend source -COPY backend/app ./app +COPY --chown=foxy:foxy backend/app ./app # Frontend static files (served by FastAPI) -COPY --from=frontend-build /build/dist ./static +COPY --chown=foxy:foxy --from=frontend-build /build/dist ./static # Healthcheck HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ diff --git a/backend/app/main.py b/backend/app/main.py index 8f2d840..3cba4a7 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -9,6 +9,9 @@ from contextlib import asynccontextmanager from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse +import os from app.config import settings from app.database import init_db @@ -62,6 +65,23 @@ app.include_router(logs.router) app.include_router(workflows.router) app.include_router(config.router) +# ─── Static Files ───────────────────────────────────────────────────────────── + +if os.path.exists("static"): + app.mount("/assets", StaticFiles(directory="static/assets"), name="assets") + + @app.get("/{full_path:path}") + async def serve_spa(full_path: str): + # API requests should already be handled by routers + # If not an API request, serve index.html + if full_path.startswith("api/") or full_path.startswith("ws/"): + return None # Should not happen due to router order + + index_path = os.path.join("static", "index.html") + if os.path.exists(index_path): + return FileResponse(index_path) + return {"detail": "Frontend not found"} + # ─── WebSocket ─────────────────────────────────────────────────────────────────