# uj-mastering-master — web UI
#
# Multi-stage on purpose: the build stage compiles wheels (librosa pulls in
# scipy, numba and llvmlite, all of which have C extensions), and none of that
# toolchain belongs in the runtime image.
#
# The runtime image installs the *installed package*, not a source tree, so the
# same image works whether you built it from a checkout or a release tarball.
#
# Note on audio: the web UI stores uploaded audio in the browser (OPFS) and
# spools each request body to /tmp only for the duration of that request. There
# is no volume for audio, because the app never keeps any — see README.
#
# Note on the desktop app: this image is the *web* front end only. PyQt5 and
# pyqtgraph are deliberately not installed — there is no display in a container.

# ---- build -----------------------------------------------------------------
FROM python:3.11-slim AS build

ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
    PIP_NO_CACHE_DIR=1 \
    UV_LINK_MODE=copy

# numba/llvmlite and scipy need a compiler; libsndfile is what soundfile
# (librosa's decoder) links against at runtime, but the wheel needs it at build
# time to be detected.
RUN apt-get update && apt-get install -y --no-install-recommends \
      build-essential \
 && rm -rf /var/lib/apt/lists/*

COPY --from=ghcr.io/astral-sh/uv:0.12.5 /uv /usr/local/bin/uv

WORKDIR /src
# Copy the whole checkout minus .dockerignore. Deliberately not a hand-picked
# file list: `py-modules` in pyproject.toml enumerates every module the build
# backend must find, and a curated COPY that omits any of them fails the build
# with "file <module>.py not found". The exclusions that matter (venv, caches,
# .git, local state) are in .dockerignore, which is the right place for them.
COPY . .

# Build a wheel, then install it into a self-contained prefix to copy forward.
# `"uj-mastering-master[web]"` (not `--extra web`) is the syntax uv wants when
# installing a source tree — `--extra` needs a lockfile/setup.py it does not
# have here and fails with "Requesting extras requires a pylock.toml...".
RUN uv venv /opt/venv \
 && uv pip install --python /opt/venv/bin/python --no-cache \
      ".[web]"

# ---- runtime ---------------------------------------------------------------
FROM python:3.11-slim AS runtime

# libsndfile1 is the only runtime system dependency: librosa decodes through
# soundfile, which dlopens it. Everything else is a self-contained wheel.
# (FFmpeg is *not* needed — it only ever generated test fixtures.)
RUN apt-get update && apt-get install -y --no-install-recommends \
      libsndfile1 \
 && rm -rf /var/lib/apt/lists/*

# Non-root, with a home it can actually write to: librosa/numba want a cache
# dir, and the app spools request bodies under /tmp.
RUN useradd --create-home --uid 10001 --shell /usr/sbin/nologin ujm

COPY --from=build /opt/venv /opt/venv

ENV PATH=/opt/venv/bin:$PATH \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    HOME=/home/ujm \
    NUMBA_CACHE_DIR=/tmp/numba-cache \
    MPLCONFIGDIR=/tmp/matplotlib

# The app's temporary spools land here. Declared so a read-only root filesystem
# can still be used (`read_only: true` + `tmpfs: /tmp` in compose).
VOLUME ["/tmp"]

USER ujm
WORKDIR /home/ujm

EXPOSE 8000

# Baked-in healthcheck: hit the API rather than the page, so it fails when the
# *backend* is broken and not merely when a static asset is missing.
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
  CMD ["python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/api/metrics', timeout=4).status == 200 else 1)"]

# 0.0.0.0, not the CLI default of 127.0.0.1: loopback inside a container is
# unreachable from the host, which is the whole point of publishing a port.
CMD ["ujm-web", "--host", "0.0.0.0", "--port", "8000", "--log-level", "INFO"]
