FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim

WORKDIR /app

# The uv base image bundles uv, so no `pip install uv` needed. Copy the
# manifest + lockfile first as their own layer so editing source files
# doesn't bust the dep cache. ``uv sync --frozen`` refuses to mutate
# uv.lock at build time — if pyproject.toml drifts from uv.lock, the
# build fails loudly instead of silently floating versions, which is
# the whole #158 fix. ``uv.lock*`` keeps the COPY glob permissive so
# the build error fires from `uv sync` (clearer message) rather than
# from COPY.
COPY pyproject.toml uv.lock* ./
RUN uv sync --frozen --no-cache

# #803 SECURITY/MED + #851 STABLE-BLOCKER (a29): create a non-root
# user (uid 1000) AND install gosu so a runtime entrypoint can drop
# privileges *after* the persistent volume is mounted.
#
# Pre-#851: ``USER parbaked`` was set at image-build time, and the
# build-time ``chown -R parbaked:parbaked /data`` succeeded against
# the image's empty /data dir. But Fly mounts the persistent volume
# at ``/data`` at RUNTIME, shadowing the build-time chown — so any
# pre-existing volume (every upgrade-path deploy) kept its prior
# root ownership and uid 1000 couldn't write to it. SQLite then
# raised ``attempt to write a readonly database`` on every
# state-changing request.
#
# Post-fix: stay as root through ENTRYPOINT, run ``chown -R 1000:1000
# /data`` *after* the mount, then ``exec gosu parbaked …`` to drop
# privileges before uvicorn boots. Standard non-root +
# persistent-volume container pattern. The runtime user is still
# uid 1000 by the time CMD runs — #803's security goal (no root
# inside the request handler) is preserved.
RUN useradd -u 1000 -ms /bin/bash parbaked \
    && apt-get update \
    && apt-get install -y --no-install-recommends gosu \
    && rm -rf /var/lib/apt/lists/* \
    && mkdir -p /data \
    && chown -R parbaked:parbaked /app /data

COPY --chown=parbaked:parbaked . .

# Re-assert /app ownership after COPY — host-side files may carry
# uid 0 modes, and dropping to parbaked at runtime needs to read
# them. /data is intentionally left for the entrypoint to chown at
# runtime so the fix is idempotent across re-deploys against a
# pre-populated volume.
RUN chown -R parbaked:parbaked /app

# Entrypoint shim (#851): runs as root, fixes /data ownership
# against whatever the mounted volume's prior state was (no-op on
# a fresh volume), then drops to parbaked via gosu before exec'ing
# CMD. Inline via printf so consumers ejecting the Dockerfile get
# one self-contained file.
RUN printf '%s\n' \
        '#!/bin/sh' \
        'set -e' \
        'chown -R parbaked:parbaked /data 2>/dev/null || true' \
        'exec gosu parbaked "$@"' \
    > /usr/local/bin/parbaked-entrypoint \
    && chmod +x /usr/local/bin/parbaked-entrypoint

# NOTE: no ``USER parbaked`` here — the entrypoint above drops to
# parbaked via gosu at runtime instead. Switching at image-build
# time was the #851 root cause.

ENV PORT=8000
EXPOSE 8000

# Kernel runtime — parbaked finds your routes/ files and wires the
# auth/admin/health routers itself. No main.py needed. ``uv sync``
# installs into ``.venv`` (no --system flag exists on sync), so invoke
# uvicorn from that venv rather than expecting a system install.
#
# ``--no-proxy-headers`` (#722): parbaked owns the X-Forwarded-For
# trust decision via ``ParbakedConfig.trust_proxy_headers`` — letting
# uvicorn pre-mutate ``request.client.host`` opens a rate-limit-bucket
# bypass on any deploy whose immediate peer is loopback (``nginx →
# 127.0.0.1:8000``).
#
# WARNING (#794): ``trust_proxy_headers=True`` is only safe when the
# upstream proxy OVERWRITES inbound XFF. Fly's edge APPENDS rather
# than overwriting (see #726), so the generated fly.toml leaves
# PARBAKED_TRUST_PROXY_HEADERS unset and the safe-default
# ``trust_proxy_headers=False`` applies. The runtime emits a stderr
# WARN at boot whenever the knob is on.
ENTRYPOINT ["/usr/local/bin/parbaked-entrypoint"]
CMD [".venv/bin/uvicorn", "parbaked.runtime:create_app", "--factory", "--no-proxy-headers", "--host", "0.0.0.0", "--port", "8000"]
