diff --git a/Dockerfile b/Dockerfile index 5ca3702..179ac72 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,14 @@ # syntax=docker/dockerfile:1 -FROM python:3.13-slim-trixie -# --- Base tooling + Node + pnpm (unchanged, but without apt ffmpeg) --- +# ----------------------------------------------------- +# Build stage: install tooling, dependencies and build +# ----------------------------------------------------- +FROM python:3.13-slim-trixie AS builder + +# Base tooling + Node + pnpm + build deps RUN apt-get update && apt-get install -y \ gcc wget curl gnupg bash git ca-certificates \ unzip p7zip-full \ - # build deps for ffmpeg from source build-essential yasm nasm pkg-config \ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y nodejs \ @@ -14,44 +17,59 @@ RUN apt-get update && apt-get install -y \ WORKDIR /app -# --- Build and install FFmpeg from source --- -# Pin to a release for reproducibility; change version if you need newer. +# Build and install FFmpeg from source ARG FFMPEG_VERSION=6.1.2 RUN set -eux; \ curl -L "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz" -o /tmp/ffmpeg.tar.xz; \ tar -xJf /tmp/ffmpeg.tar.xz -C /tmp; \ cd "/tmp/ffmpeg-${FFMPEG_VERSION}"; \ - # Minimal config: no docs, no ffplay; native AAC encoder is included by default ./configure --prefix=/usr/local --disable-debug --disable-doc --disable-ffplay; \ make -j"$(nproc)"; \ make install; \ - # Make sure binaries are trivially discoverable - ln -sf /usr/local/bin/ffmpeg /usr/bin/ffmpeg; \ - ln -sf /usr/local/bin/ffprobe /usr/bin/ffprobe; \ - # Clean build artifacts - rm -rf /tmp/ffmpeg*; + rm -rf /tmp/ffmpeg* -# Python deps first for better caching +# Python dependencies COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt -# App source +# Frontend dependencies and build +COPY frontend/pnpm-lock.yaml frontend/package.json frontend/ +RUN cd frontend && pnpm install +COPY frontend/ frontend/ +RUN cd frontend && pnpm build + +# Copy application source COPY . . -# Optional env copy (your original step) -RUN [ -f .env.linux ] && cp -f .env.linux .env && rm -f .env.linux || true +# Remove dev files to shrink final image +RUN rm -rf tests \ + frontend/node_modules frontend/.svelte-kit frontend/.vite \ + frontend/src frontend/static frontend/tsconfig.json \ + frontend/vite.config.ts frontend/svelte.config.js \ + frontend/eslint.config.js frontend/package.json \ + frontend/pnpm-lock.yaml frontend/README.md \ + frontend/vitest-setup-client.ts -# Frontend deps (cache pnpm install) -COPY frontend/package.json frontend/pnpm-lock.yaml frontend/ -RUN cd frontend && pnpm install -# Vite dev server env -ENV VITE_API_BASE=/api -EXPOSE 5173 +# ----------------------------------------------------- +# Final stage: minimal runtime image +# ----------------------------------------------------- +FROM python:3.13-slim-trixie + +WORKDIR /app + +# Bring in Python deps and FFmpeg +COPY --from=builder /usr/local /usr/local -# Entrypoint -COPY entrypoint.sh ./entrypoint.sh +# Bring in application code and built frontend +COPY --from=builder /app /app + +ARG PORT=8001 +ENV PORT=${PORT} +EXPOSE ${PORT} + +# Ensure entrypoint is executable RUN chmod +x entrypoint.sh -# Use bash so wait -n works if you rely on it CMD ["bash", "./entrypoint.sh"] + diff --git a/README.md b/README.md index 3d2196b..8f7a891 100644 --- a/README.md +++ b/README.md @@ -88,3 +88,137 @@ key via the `X-API-Key` header (REST) or `api_key` query parameter (WebSocket). ## 👨‍💻 Who Is It For? * Personal project, specifically tailored for my needs + +## ⚙️ Configuration + +The application is configured through environment variables. Frequently used settings +include: + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `8001` | HTTP port for the API and static frontend | +| `DB_HOST` | – | Database host name | +| `DB_PORT` | `3306` | Database port | +| `DB_USER` | – | Database user | +| `DB_PASS` | – | Database password | +| `DB_DB` | – | Database name | +| `API_KEY` | – | Optional shared secret required by clients | +| `CORS_ORIGINS` | `*` | Comma‑separated list of allowed origins | + +Other optional variables exist for specific downloaders such as Discogs, Spotify or +Telegram; consult the source if you need those integrations. + +## 🐳 Running with Docker + +Build the production image and expose it on the desired port: + +```bash +docker build -t music-importer . +docker run -p 8001:8001 \ + -e DB_HOST=db -e DB_PORT=3306 -e DB_USER=music-importer \ + -e DB_PASS=music-importer -e DB_DB=music-importer \ + music-importer +``` + +### docker-compose example + +```yaml +version: "3.9" +services: + db: + image: mariadb:11 + environment: + MARIADB_ROOT_PASSWORD: rootpass + MARIADB_DATABASE: music-importer + MARIADB_USER: music-importer + MARIADB_PASSWORD: music-importer + volumes: + - db_data:/var/lib/mysql + + music-importer: + build: . + depends_on: + - db + environment: + DB_HOST: db + DB_PORT: 3306 + DB_USER: music-importer + DB_PASS: music-importer + DB_DB: music-importer + # API_KEY: choose-a-secret + ports: + - "${PORT:-8001}:${PORT:-8001}" + +volumes: + db_data: +``` + +Use the `PORT` variable to run multiple instances side-by-side and override any other +variables as needed for your setup. + +## ⚙️ Configuration + +The application is configured through environment variables. Frequently used settings +include: + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `8001` | HTTP port for the API and static frontend | +| `DB_HOST` | – | Database host name | +| `DB_PORT` | `3306` | Database port | +| `DB_USER` | – | Database user | +| `DB_PASS` | – | Database password | +| `DB_DB` | – | Database name | +| `API_KEY` | – | Optional shared secret required by clients | +| `CORS_ORIGINS` | `*` | Comma‑separated list of allowed origins | + +Other optional variables exist for specific downloaders such as Discogs, Spotify or +Telegram; consult the source if you need those integrations. + +## 🐳 Running with Docker + +Build the production image and expose it on the desired port: + +```bash +docker build -t music-importer . +docker run -p 8001:8001 \ + -e DB_HOST=db -e DB_PORT=3306 -e DB_USER=music-importer \ + -e DB_PASS=music-importer -e DB_DB=music-importer \ + music-importer +``` + +### docker-compose example + +```yaml +version: "3.9" +services: + db: + image: mariadb:11 + environment: + MARIADB_ROOT_PASSWORD: rootpass + MARIADB_DATABASE: music-importer + MARIADB_USER: music-importer + MARIADB_PASSWORD: music-importer + volumes: + - db_data:/var/lib/mysql + + music-importer: + build: . + depends_on: + - db + environment: + DB_HOST: db + DB_PORT: 3306 + DB_USER: music-importer + DB_PASS: music-importer + DB_DB: music-importer + # API_KEY: choose-a-secret + ports: + - "${PORT:-8001}:${PORT:-8001}" + +volumes: + db_data: +``` + +Use the `PORT` variable to run multiple instances side-by-side and override any other +variables as needed for your setup. diff --git a/api/server.py b/api/server.py index b904992..02bc82f 100644 --- a/api/server.py +++ b/api/server.py @@ -17,6 +17,7 @@ ) from fastapi.middleware.cors import CORSMiddleware from fastapi.concurrency import run_in_threadpool +from fastapi.staticfiles import StaticFiles from .steps import step_map from .db_init import ensure_tables_exist @@ -293,3 +294,6 @@ async def jobs_ws(ws: WebSocket): pass finally: clients.discard(ws) + +# Serve the pre-built frontend as static files +app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend") diff --git a/docker-compose.yml b/docker-compose.yml index a7996fe..f2d4cc0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,8 +36,7 @@ services: DB_PASS: music-importer DB_DB: music-importer ports: - - "8001:8001" - - "5173:5173" + - "${PORT:-8001}:${PORT:-8001}" volumes: - .:/app volumes: diff --git a/entrypoint.sh b/entrypoint.sh index c1a53f5..e0b3c9f 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -2,16 +2,6 @@ set -e export PATH="/usr/local/bin:/usr/bin:/bin:${PATH}" command -v ffmpeg || echo "ffmpeg missing from PATH at runtime" -# Start backend -uvicorn api.server:app --host 0.0.0.0 --port 8001 & -BACK_PID=$! - -# Start frontend -cd frontend -pnpm dev:docker --host 0.0.0.0 --port 5173 & -FRONT_PID=$! - -# Wait for both processes -wait -n $BACK_PID $FRONT_PID -kill -TERM $BACK_PID $FRONT_PID 2>/dev/null || true -wait \ No newline at end of file +# Start backend only (frontend assets are pre-built) +PORT="${PORT:-8001}" +uvicorn api.server:app --host 0.0.0.0 --port "$PORT" diff --git a/frontend/package.json b/frontend/package.json index dc5d483..9b73e99 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,8 +4,8 @@ "version": "0.0.1", "type": "module", "scripts": { - "dev": "VITE_API_BASE=/api VITE_DEV_API_TARGET=http://192.168.1.178:8001 vite dev", - "dev:docker": "VITE_API_BASE=/api VITE_DEV_API_TARGET=http://192.168.1.27:8001 vite dev", + "dev": "VITE_API_BASE=/api VITE_DEV_API_TARGET=${VITE_DEV_API_TARGET:-http://localhost:8001} vite dev", + "dev:docker": "VITE_API_BASE=/api VITE_DEV_API_TARGET=${VITE_DEV_API_TARGET:-http://localhost:8001} vite dev", "build": "vite build", "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", @@ -20,6 +20,7 @@ "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@sveltejs/adapter-auto": "^4.0.0", + "@sveltejs/adapter-static": "^3.0.0", "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/vite": "^4.0.0", diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js index 1295460..33b51b2 100644 --- a/frontend/svelte.config.js +++ b/frontend/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-auto'; +import adapter from '@sveltejs/adapter-static'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ @@ -7,12 +7,9 @@ const config = { // for more information about preprocessors preprocess: vitePreprocess(), - kit: { - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter() - } + kit: { + adapter: adapter({ pages: 'dist', assets: 'dist', fallback: 'index.html' }) + } }; export default config; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index b45ffce..1346dd2 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -3,7 +3,7 @@ import tailwindcss from '@tailwindcss/vite'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; -const devProxyTarget = process.env.VITE_DEV_API_TARGET ?? 'http://192.168.1.178:8001'; +const devProxyTarget = process.env.VITE_DEV_API_TARGET ?? 'http://localhost:8001'; export default defineConfig({ plugins: [tailwindcss(), sveltekit()],