# syntax=docker/dockerfile:1.7
#
# Polylogue OCI image. Daemon-first multi-stage build.
#
# Stages:
#   1. builder    — uses `uv` to build the wheel from the checkout
#   2. runtime    — slim python image; default; entrypoint = polylogued
#   3. distroless — gcr.io/distroless/python3-debian12; minimal, no shell
#
# Build the default (slim) image:
#   docker buildx build -f packaging/Containerfile --target runtime    -t polylogue:slim       .
# Build the distroless image:
#   docker buildx build -f packaging/Containerfile --target distroless -t polylogue:distroless .
#
# Console scripts on PATH in the runtime stage:
#   polylogue, polylogued, polylogue-mcp  (CLI is included for `docker exec` debug)
#
# Operators are expected to mount durable state:
#   -v polylogue-archive:/data/archive   (POLYLOGUE_ARCHIVE_ROOT — SQLite + blobs)
#   -v polylogue-config:/etc/polylogue   (POLYLOGUE_CONFIG_DIR — polylogue.toml, secrets)
#
# See docs/installation.md and docs/docker-compose.yaml for example invocations.

# ---- builder ------------------------------------------------------------
FROM python:3.13-slim-bookworm AS builder

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    UV_NO_PROGRESS=1 \
    UV_LINK_MODE=copy

# uv is the canonical build driver in this repo.
COPY --from=ghcr.io/astral-sh/uv:0.5.4 /uv /usr/local/bin/uv

WORKDIR /src
COPY . /src

# Build wheel into /dist. Hatchling embeds polylogue/_build_info.py via
# packaging/hatch_build.py, so the wheel knows its own version + commit.
ARG POLYLOGUE_BUILD_COMMIT=container-build
ARG POLYLOGUE_BUILD_DIRTY=False
RUN python - <<PY
from pathlib import Path

commit = "${POLYLOGUE_BUILD_COMMIT}"
dirty = "${POLYLOGUE_BUILD_DIRTY}".strip().lower() in {"1", "true", "yes"}
Path("polylogue/_build_info.py").write_text(
    'from __future__ import annotations\n\n'
    f'BUILD_COMMIT = "{commit}"\n'
    f'BUILD_DIRTY = {dirty}\n',
    encoding="utf-8",
)
PY
RUN uv build --wheel --out-dir /dist /src

# ---- runtime (default) --------------------------------------------------
FROM python:3.13-slim-bookworm AS runtime

ARG POLYLOGUE_UID=10001
ARG POLYLOGUE_GID=10001

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    POLYLOGUE_FORCE_PLAIN=1 \
    POLYLOGUE_ARCHIVE_ROOT=/data/archive \
    POLYLOGUE_CONFIG_DIR=/etc/polylogue \
    XDG_DATA_HOME=/data \
    XDG_CONFIG_HOME=/etc/polylogue \
    XDG_CACHE_HOME=/var/cache/polylogue \
    POLYLOGUE_DAEMON_API_HOST=0.0.0.0 \
    POLYLOGUE_DAEMON_API_PORT=8766

# Runtime deps:
#   ca-certificates — outbound HTTPS (PyPI, Voyage, Drive)
#   tini             — PID 1 for clean SIGTERM/SIGINT signalling
#   curl             — used by HEALTHCHECK against the daemon /api/health endpoint
RUN apt-get update \
 && apt-get install -y --no-install-recommends ca-certificates tini curl \
 && rm -rf /var/lib/apt/lists/*

# Install the built wheel without dev/extras. Dependencies resolve from PyPI
# against the wheel's declared metadata. The resulting site-packages tree is
# copied unchanged into the distroless stage below.
COPY --from=builder /dist/*.whl /tmp/
RUN pip install --no-cache-dir /tmp/*.whl && rm /tmp/*.whl

# Non-root runtime user (UID 10001 by AC). Archive writes happen under
# /data/archive, config under /etc/polylogue, cache under /var/cache/polylogue.
# Operators that bind-mount host paths must chown those to UID 10001 or
# override --user explicitly.
RUN groupadd --system --gid ${POLYLOGUE_GID} polylogue \
 && useradd  --system --uid ${POLYLOGUE_UID} --gid polylogue \
       --home /home/polylogue --create-home polylogue \
 && mkdir -p /data/archive /etc/polylogue /var/cache/polylogue \
 && chown -R polylogue:polylogue /data /etc/polylogue /var/cache/polylogue

USER polylogue
WORKDIR /home/polylogue
VOLUME ["/data/archive", "/etc/polylogue"]
EXPOSE 8766

# Healthcheck hits the daemon HTTP API. The daemon must be started with
# --enable-api (the default container CMD does this) for this to succeed.
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
  CMD curl --fail --silent --show-error \
      "http://127.0.0.1:${POLYLOGUE_DAEMON_API_PORT}/api/health" \
      > /dev/null || exit 1

# tini reaps zombies and forwards signals; the daemon binary is PID 2.
# --enable-api turns on the HTTP surface used by HEALTHCHECK.
# --api-host 0.0.0.0 lets the host reach the API across the container boundary;
# operators that want loopback-only should override the CMD.
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["polylogued", "run", \
     "--enable-api", \
     "--api-host", "0.0.0.0", \
     "--api-port", "8766"]

# OCI image annotations. The release workflow overrides these from
# docker/metadata-action, but bake conservative defaults so a manual
# `docker build .` still produces an annotated image.
LABEL org.opencontainers.image.title="polylogue" \
      org.opencontainers.image.description="Polylogue AI session archive daemon (polylogued) + CLI" \
      org.opencontainers.image.source="https://github.com/Sinity/polylogue" \
      org.opencontainers.image.licenses="MIT" \
      org.opencontainers.image.vendor="Sinity"

# ---- distroless (optional) ----------------------------------------------
# Distroless variant for security-conscious deployments. Reuses the wheel
# installed into /usr/local in the runtime stage. No shell, no package
# manager — `docker exec` debugging is not possible against this image.
#
# Build with:  docker buildx build -f packaging/Containerfile --target distroless -t polylogue:distroless .
#
# Note: distroless does not ship curl, so HEALTHCHECK is omitted at the
# image level. Operators using Kubernetes / compose should configure
# probes against the same /api/health endpoint at the orchestrator layer.
FROM gcr.io/distroless/python3-debian12:nonroot AS distroless

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    POLYLOGUE_FORCE_PLAIN=1 \
    POLYLOGUE_ARCHIVE_ROOT=/data/archive \
    POLYLOGUE_CONFIG_DIR=/etc/polylogue \
    XDG_DATA_HOME=/data \
    XDG_CONFIG_HOME=/etc/polylogue \
    XDG_CACHE_HOME=/var/cache/polylogue \
    POLYLOGUE_DAEMON_API_HOST=0.0.0.0 \
    POLYLOGUE_DAEMON_API_PORT=8766 \
    PATH=/usr/local/bin:/usr/bin:/bin

# Copy the installed Python environment and the console scripts from the
# runtime stage. site-packages lives under /usr/local/lib/python3.13.
COPY --from=runtime /usr/local/bin/python        /usr/local/bin/python
COPY --from=runtime /usr/local/bin/python3       /usr/local/bin/python3
COPY --from=runtime /usr/local/bin/python3.13    /usr/local/bin/python3.13
COPY --from=runtime /usr/local/lib/libpython3.13.so.1.0 /usr/local/lib/libpython3.13.so.1.0
COPY --from=runtime /usr/local/lib/python3.13 /usr/local/lib/python3.13
COPY --from=runtime /usr/local/bin/polylogue       /usr/local/bin/polylogue
COPY --from=runtime /usr/local/bin/polylogued      /usr/local/bin/polylogued
COPY --from=runtime /usr/local/bin/polylogue-mcp   /usr/local/bin/polylogue-mcp

# Distroless ships /etc/passwd with the `nonroot` user at UID 65532. The
# `:nonroot` tag selects that user by default; we also chown'd the data
# directories in the runtime stage but distroless can't run chown, so
# operators must ensure host bind-mounts allow writes by UID 65532.
COPY --from=runtime --chown=65532:65532 /data           /data
COPY --from=runtime --chown=65532:65532 /etc/polylogue  /etc/polylogue
COPY --from=runtime --chown=65532:65532 /var/cache/polylogue /var/cache/polylogue

USER nonroot
WORKDIR /home/nonroot
VOLUME ["/data/archive", "/etc/polylogue"]
EXPOSE 8766

LABEL org.opencontainers.image.title="polylogue (distroless)" \
      org.opencontainers.image.description="Polylogue daemon + CLI on distroless python3" \
      org.opencontainers.image.source="https://github.com/Sinity/polylogue" \
      org.opencontainers.image.licenses="MIT" \
      org.opencontainers.image.vendor="Sinity"

ENTRYPOINT ["/usr/local/bin/polylogued"]
CMD ["run", "--enable-api", "--api-host", "0.0.0.0", "--api-port", "8766"]
