From 5dd486844fcb11bc48ef972507b26df4cae0775b Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Thu, 30 Oct 2025 17:31:29 +1100 Subject: [PATCH] Make docker image work --- Dockerfile | 6 +++- backend/main.py | 70 +++++++++++++++++++++++++++++++++++++++++----- docker-compose.yml | 3 +- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 35bf427..d72d685 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,6 +47,7 @@ EXPOSE 8000 # Environment variables ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/app/backend ENV DATABASE_URL=sqlite+aiosqlite:///./data/youmusic.db ENV MUSIC_DIR=/app/data/music ENV UPLOAD_DIR=/app/data/uploads @@ -57,5 +58,8 @@ ENV BASE_DIR=/app HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/ || exit 1 +# Set working directory to backend +WORKDIR /app/backend + # Run the application -CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/main.py b/backend/main.py index 6c157e9..bc612a4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,8 +1,10 @@ import os from contextlib import asynccontextmanager +from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse from app.api import music, playlist, download, search, stream, artist from app.core.config import settings from app.db.session import init_db @@ -11,11 +13,34 @@ from app.db.session import init_db @asynccontextmanager async def lifespan(app: FastAPI): # Startup + print("=" * 80) + print("🎵 YouMusic Starting Up") + print("=" * 80) + print(f"📂 BASE_DIR: {settings.BASE_DIR}") + print(f"📂 MUSIC_DIR: {settings.MUSIC_DIR}") + print(f"📂 UPLOAD_DIR: {settings.UPLOAD_DIR}") + print(f"📂 TEMP_DIR: {settings.TEMP_DIR}") + print(f"📂 DATABASE: {settings.DATABASE_URL}") + + cache_dir = os.path.join(settings.BASE_DIR, "data", "cache") + artist_cache_dir = os.path.join(cache_dir, "artists") + artist_images_dir = os.path.join(cache_dir, "artist_images") + + print(f"📂 CACHE_DIR: {cache_dir}") + print(f"📂 ARTIST_CACHE: {artist_cache_dir}") + print(f"📂 ARTIST_IMAGES: {artist_images_dir}") + print(f"🎬 FFMPEG: {settings.FFMPEG_LOCATION}") + print("=" * 80) + os.makedirs(settings.MUSIC_DIR, exist_ok=True) os.makedirs(settings.UPLOAD_DIR, exist_ok=True) - os.makedirs(os.path.join(settings.BASE_DIR, "data", "cache"), exist_ok=True) - os.makedirs(os.path.join(settings.BASE_DIR, "data", "cache", "artist_images"), exist_ok=True) + os.makedirs(cache_dir, exist_ok=True) + os.makedirs(artist_cache_dir, exist_ok=True) + os.makedirs(artist_images_dir, exist_ok=True) await init_db() + + print("✅ All directories created and database initialized") + print("=" * 80) yield # Shutdown @@ -53,16 +78,47 @@ app.include_router(stream.router, prefix="/api", tags=["stream"]) app.include_router(artist.router, prefix="/api/artists", tags=["artists"]) -@app.get("/") -async def root(): - return {"message": "YouMusic API is running"} - - +# Health check endpoint (must be before catch-all route) @app.get("/health") async def health(): return {"status": "healthy"} +# Mount frontend static files (built React app) +static_dir = Path(__file__).parent / "static" +if static_dir.exists(): + app.mount("/assets", StaticFiles(directory=str(static_dir / "assets")), name="assets") + + @app.get("/") + async def serve_frontend(): + """Serve the React frontend""" + index_file = static_dir / "index.html" + if index_file.exists(): + return FileResponse(index_file) + return {"message": "YouMusic API is running (frontend not built)"} + + @app.get("/{full_path:path}") + async def serve_spa(full_path: str): + """Serve React app for all non-API routes (SPA routing)""" + # Skip API routes, health check, and static assets + if full_path.startswith(("api/", "music/", "cache/", "assets/", "health", "docs", "redoc", "openapi.json")): + return {"error": "Not found"} + + # For all other routes, serve index.html (React Router handles client-side routing) + index_file = static_dir / "index.html" + if index_file.exists(): + return FileResponse(index_file) + return {"error": "Frontend not found"} +else: + @app.get("/") + async def root(): + return {"message": "YouMusic API is running (frontend not built)"} + + @app.get("/health") + async def health_no_frontend(): + return {"status": "healthy"} + + if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/docker-compose.yml b/docker-compose.yml index 9d8c098..8ba4105 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,8 @@ services: - "8000:8000" volumes: - ./data:/app/data - - ./backend:/app/backend + # Note: Do not mount ./backend in production - it overwrites the built image + # For development, use ./dev.sh instead environment: - DATABASE_URL=sqlite+aiosqlite:////app/data/youmusic.db - MUSIC_DIR=/app/data/music