# Canonical's maintained glibc-compatible Python runtime is kept as a named stage so the builder
# can preserve its passwd/group contract without adding a shell or package manager to the final
# image.
FROM ubuntu/python:3.12-24.04@sha256:d5b4c1e1da458c6e10da9d7f42276d8d640ed5a0d87b1cced734a9434c531a62 AS runtime-base

# The builder is deliberately separate from the runtime. The agent only needs the Python
# application packages at runtime; a full Debian build environment must not become part of the
# shipped image or its vulnerability surface.
FROM python:3.12-slim-trixie@sha256:e5c9fa26ffb76e11e0f054f30dc2523a2f9693f0c36c0cf1e39b27e152d899fc AS builder

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /build
COPY config/security/build-toolchain.lock /tmp/modelforge-build-toolchain.lock
COPY backend ./backend
COPY node-agent ./node-agent

# Install into a plain target directory so the final image can use Canonical's maintained,
# glibc-compatible chiselled Python runtime. Hash-locked build tooling is removed inside the
# builder only; no installer or build-backend metadata is carried into the runtime image. The
# target install ignores the builder venv so a build-only package cannot mask a runtime dependency.
# The import check exercises the same dependency closure that the final image receives without
# contacting the control plane or touching a model repository.
RUN python -m venv /opt/build-venv \
    && /opt/build-venv/bin/python -m pip install --no-cache-dir --disable-pip-version-check \
        --require-hashes --only-binary=:all: --index-url https://pypi.org/simple \
        -r /tmp/modelforge-build-toolchain.lock \
    && /opt/build-venv/bin/python -m pip install --no-cache-dir --disable-pip-version-check \
        --no-build-isolation --ignore-installed --no-compile \
        --target /opt/site-packages ./backend ./node-agent \
    && PYTHONPATH=/opt/site-packages /opt/build-venv/bin/python -m pip check \
    && PYTHONPATH=/opt/site-packages /opt/build-venv/bin/python -c \
        "import httpx, psutil, pynvml, modelforge_api, modelforge_node_agent" \
    && /opt/build-venv/bin/python -m pip uninstall --yes pip setuptools wheel editables hatchling pathspec \
        pluggy tomlkit trove-classifiers

# Prepare the fixed runtime contract without requiring a shell or package manager in the final
# image. The numeric IDs are intentional: Compose and existing volumes rely on UID 100/GID 101.
RUN mkdir -p /runtime/app /runtime/data/state /runtime/data/hf-cache \
        /runtime/data/artifacts /runtime/data/quarantine \
    && chown -R 100:101 /runtime/app /runtime/data

# Preserve the canonical runtime's passwd/group entries and add the existing ModelForge identity
# with its established home directory. The final image remains shell-less and runs numerically as
# UID 100/GID 101, while libraries that resolve the process user still see the same contract.
COPY --from=runtime-base /etc/passwd /runtime/etc/passwd
COPY --from=runtime-base /etc/group /runtime/etc/group
RUN python -c "from pathlib import Path; p=Path('/runtime/etc/passwd'); s=p.read_text(); p.write_text(s if '\\nmodelforge-agent:' in s else s.rstrip('\\n')+'\\nmodelforge-agent:x:100:101::/app:/usr/sbin/nologin\\n'); p=Path('/runtime/etc/group'); s=p.read_text(); p.write_text(s if '\\nmodelforge-agent:' in s else s.rstrip('\\n')+'\\nmodelforge-agent:x:101:\\n')"

FROM runtime-base

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PYTHONPATH=/opt/site-packages \
    HOME=/app

# Build identity. These are stamped in at build time so a running container can say exactly
# where it came from; an argument that is never passed stays empty and is reported as null
# rather than becoming a claimed commit.
ARG MODELFORGE_VERSION=0.0.0
ARG MODELFORGE_COMMIT=""
ARG MODELFORGE_BUILT_AT=""
ENV MODELFORGE_BUILD_COMMIT=${MODELFORGE_COMMIT}
ENV MODELFORGE_BUILD_TIMESTAMP=${MODELFORGE_BUILT_AT}
LABEL org.opencontainers.image.title="ITWorx ModelForge node agent"
LABEL org.opencontainers.image.description="Outbound-only compute node agent for ITWorx ModelForge"
LABEL org.opencontainers.image.version="${MODELFORGE_VERSION}"
LABEL org.opencontainers.image.revision="${MODELFORGE_COMMIT}"
LABEL org.opencontainers.image.created="${MODELFORGE_BUILT_AT}"
LABEL org.opencontainers.image.source="https://git.example.com/example/modelforge"
LABEL org.opencontainers.image.vendor="ITWorx"
LABEL org.opencontainers.image.licenses="AGPL-3.0-or-later"

COPY --from=builder --chown=100:101 /opt/site-packages /opt/site-packages
COPY --from=builder --chown=100:101 /runtime/app /app
COPY --from=builder --chown=100:101 /runtime/data /data
COPY --from=builder /runtime/etc/passwd /etc/passwd
COPY --from=builder /runtime/etc/group /etc/group

WORKDIR /app
USER 100:101

# Canonical's rock image has a Pebble entrypoint. The agent has no init supervisor requirement,
# so explicitly remove it and invoke the reviewed Python module directly.
ENTRYPOINT []
CMD ["python3", "-m", "modelforge_node_agent.main"]
