diff --git a/Dockerfile b/Dockerfile index 89ee973..daa05cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,23 +14,26 @@ FROM python:3.13-slim WORKDIR /app RUN apt-get update \ - && apt-get install -y --no-install-recommends nginx git ffmpeg \ + && apt-get install -y --no-install-recommends ffmpeg curl ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN pip install uv COPY pyproject.toml uv.lock ./ -RUN uv pip install --system . gunicorn +RUN uv pip install --system . gunicorn \ + && rm -rf /root/.cache -RUN git clone https://github.com/DrizzleTime/FoxelUpgrade /app/migrate +RUN curl -L https://github.com/DrizzleTime/FoxelUpgrade/archive/refs/heads/main.tar.gz -o /tmp/migrate.tgz \ + && mkdir -p /app/migrate \ + && tar -xzf /tmp/migrate.tgz --strip-components=1 -C /app/migrate \ + && rm -rf /tmp/migrate.tgz COPY --from=frontend-builder /app/web/dist /app/web/dist COPY . . -COPY nginx.conf /etc/nginx/nginx.conf - RUN mkdir -p data/db data/mount && \ - chmod 777 data/db data/mount + chmod 777 data/db data/mount && \ + rm -rf /var/log/apt /var/cache/apt/archives EXPOSE 80 diff --git a/entrypoint.sh b/entrypoint.sh index 588fb76..cb09ebf 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,5 +1,4 @@ #!/bin/bash set -e python migrate/run.py -nginx -g 'daemon off;' & -exec gunicorn -k uvicorn.workers.UvicornWorker -w 1 -b 0.0.0.0:8000 main:app \ No newline at end of file +exec gunicorn -k uvicorn.workers.UvicornWorker -w 1 -b 0.0.0.0:80 main:app diff --git a/main.py b/main.py index e04e8df..f5d146a 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,15 @@ import os +from pathlib import Path +from contextlib import asynccontextmanager + from domain.config.service import ConfigService, VERSION from domain.adapters.registry import runtime_registry -from fastapi.middleware.cors import CORSMiddleware -from contextlib import asynccontextmanager from db.session import close_db, init_db from api.routers import include_routers -from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse +from fastapi import FastAPI, HTTPException, Request from fastapi.exceptions import RequestValidationError from middleware.exception_handler import ( global_exception_handler, @@ -20,6 +24,31 @@ from domain.tasks.task_queue import task_queue_service load_dotenv() +class SPAStaticFiles(StaticFiles): + async def get_response(self, path, scope): + response = await super().get_response(path, scope) + if response.status_code == 404: + return await super().get_response("index.html", scope) + return response + + +INDEX_FILE = Path("web/dist/index.html") +SPA_EXCLUDE_PREFIXES = ("/api", "/docs", "/openapi.json", "/webdav", "/s3") + + +async def spa_fallback_middleware(request: Request, call_next): + response = await call_next(request) + if ( + response.status_code == 404 + and request.method == "GET" + and "text/html" in request.headers.get("accept", "") + and not request.url.path.startswith(SPA_EXCLUDE_PREFIXES) + and INDEX_FILE.exists() + ): + return FileResponse(INDEX_FILE) + return response + + @asynccontextmanager async def lifespan(app: FastAPI): os.makedirs("data/db", exist_ok=True) @@ -40,6 +69,7 @@ def create_app() -> FastAPI: description="A highly extensible private cloud storage solution for individuals and teams", lifespan=lifespan, ) + app.middleware("http")(spa_fallback_middleware) include_routers(app) app.add_exception_handler(HTTPException, http_exception_handler) app.add_exception_handler(RequestValidationError, validation_exception_handler) @@ -56,6 +86,7 @@ app.add_middleware( allow_methods=["*"], allow_headers=["*"], ) +app.mount("/", SPAStaticFiles(directory="web/dist", html=True, check_dir=False), name="static") if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/nginx.conf b/nginx.conf deleted file mode 100644 index 83a504b..0000000 --- a/nginx.conf +++ /dev/null @@ -1,45 +0,0 @@ -user www-data; -worker_processes auto; -pid /run/nginx.pid; -include /etc/nginx/modules-enabled/*.conf; - -events { - worker_connections 768; -} - -http { - sendfile on; - tcp_nopush on; - types_hash_max_size 2048; - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - gzip on; - gzip_disable "msie6"; - client_max_body_size 4G; - - server { - listen 80; - server_name _; - - location ~ ^/(api|webdav|s3|docs|openapi\.json$) { - proxy_pass http://127.0.0.1:8000; - 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 / { - root /app/web/dist; - index index.html; - try_files $uri $uri/ /index.html; - } - } -} \ No newline at end of file