Skip to content
This repository was archived by the owner on Nov 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 42 additions & 24 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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 \
Expand All @@ -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"]

134 changes: 134 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 4 additions & 0 deletions api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Comment on lines +298 to +299

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Guard static mount when frontend artifacts absent

Mounting the frontend with StaticFiles(directory="frontend/dist", html=True) happens at import time and StaticFiles raises a RuntimeError if the directory does not exist. The repository ignores frontend/dist, so running the backend locally (uvicorn api.server:app) or executing tests that import api.server now crashes unless the frontend was built beforehand. Consider conditionally mounting the static files or falling back when frontend/dist is missing so development and non-Docker runs continue to work.

Useful? React with 👍 / 👎.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex fix comments

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Added a guard around the frontend static mount, using Path to ensure the directory exists before mounting and prevent runtime errors when frontend/dist is missing api/server.pyL1-L8 api/server.pyL299-L302
  • Reordered the Docker build steps to install frontend dependencies separately and build the frontend only after copying the project sources, ensuring a successful build DockerfileL35-L41

Testing

  • pytest -q

View task →

3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 3 additions & 13 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
# Start backend only (frontend assets are pre-built)
PORT="${PORT:-8001}"
uvicorn api.server:app --host 0.0.0.0 --port "$PORT"
5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 ''",
Expand All @@ -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",
Expand Down
11 changes: 4 additions & 7 deletions frontend/svelte.config.js
Original file line number Diff line number Diff line change
@@ -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} */
Expand All @@ -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;
2 changes: 1 addition & 1 deletion frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()],
Expand Down
Loading