# Runlayer AI Watch scanner — container image (Detect only).
#
# Multi-stage: a manylinux_2_28 build stage produces the PyInstaller `aiwatch`
# onedir bundle with a glibc 2.28 floor (same approach as the .deb/.rpm build);
# a debian:12-slim runtime stage (glibc 2.36 ≥ 2.28) runs it. The entrypoint
# scan-host-users.sh fans out one privilege-dropped scan per bind-mounted host
# home (see that script's header).
#
# BUILD CONTEXT: the monorepo ROOT. `cli/` has an editable path dependency on
# the sibling `packages/python` (runlayer-hooks-sdk, `../packages/python` in
# cli/pyproject.toml), so both trees must be in the context. Build with:
#
#     docker build -f cli/packaging/container/Dockerfile -t ai-watch-linux .
#
# (All COPY paths below are relative to the repo root.) The manylinux build stage is
# amd64-only; the full image build is exercised in CI/release (chunk 4). The
# entrypoint logic is validated independently by the stub smoke test in
# packaging/container/tests/ (no manylinux/PyInstaller build required).
#
# RUN (standalone) — scan every host user's home, looping every 15 min:
#
#     docker run --rm \
#       -e RUNLAYER_API_KEY=rl_org_... \
#       -e RUNLAYER_HOST=https://api.runlayer.com \
#       -e RUNLAYER_HOST_HOME_PREFIX=/host \
#       -e RUNLAYER_MACHINE_ID_PATH=/host/etc/machine-id \
#       -v /:/host:ro \
#       ai-watch-linux
#
# SELinux caveat (RHEL/Fedora hosts): bind mounts are unreadable to the
# container without an opt-out. With the whole-root mount NEVER use `:z`/`:Z`
# — that relabels the bind SOURCE, i.e. the entire host filesystem, which can
# break host services (Docker's own docs warn against relabeling system dirs).
# Use `--security-opt label=disable` (per-container, leaves host labels
# untouched) or a custom SELinux policy. `:z` is only reasonable on narrow
# per-directory mounts you own.
#
# Read-only mounts also mean the scanner's per-user ~/.runlayer state (stored
# device-id fallback, logs) cannot persist — deliberate and tolerated: the
# device id comes from the mounted machine-id (RUNLAYER_MACHINE_ID_PATH) and
# container logs go to stdout, so nothing load-bearing is lost.

ARG AIWATCH_PLATFORM=linux/amd64

# ---- Build stage: glibc 2.28 floor onedir bundle -----------------------------
FROM --platform=${AIWATCH_PLATFORM} quay.io/pypa/manylinux_2_28_x86_64 AS build

ARG UV_VERSION=0.9.18
ENV UV_INSTALL_DIR=/usr/local/bin \
    UV_UNMANAGED_INSTALL=1 \
    UV_LINK_MODE=copy \
    PATH=/usr/local/bin:$PATH

# Pinned uv installer (matches .tool-versions).
RUN curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" | sh

# Resolve third-party dependencies before application source changes invalidate
# the layer. Preserve the sibling layout for cli's editable SDK dependency.
COPY cli/pyproject.toml cli/uv.lock cli/.python-version cli/README.md cli/LICENSE /src/cli/
COPY packages/python/pyproject.toml packages/python/README.md /src/packages/python/
WORKDIR /src/cli
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev --no-install-local

COPY cli/ /src/cli/
COPY packages/python/ /src/packages/python/

# Build the aiwatch onedir bundle exactly as the Linux package build does
# (cwd = cli/, spec resolves ../runlayer_cli/... relative to packaging/). uv
# provisions its own CPython, so no system python is required.
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev \
    && uv pip install pyinstaller \
    && .venv/bin/pyinstaller packaging/aiwatch.spec \
    && test -x /src/cli/dist/aiwatch/aiwatch

# ---- Runtime stage -----------------------------------------------------------
# Pinned linux/amd64: the aiwatch bundle is x86_64-only (manylinux_2_28_x86_64,
# same as the .deb/.rpm), so the whole image is amd64. Pinning keeps the runtime
# arch coherent with the build stage even when building on an arm64 host (the
# binary then runs under emulation, e.g. Rosetta/qemu). The argument keeps the
# default explicit while satisfying BuildKit's dynamic platform validation.
FROM --platform=${AIWATCH_PLATFORM} debian:12-slim AS runtime

LABEL org.opencontainers.image.title="Runlayer AI Watch scanner" \
      org.opencontainers.image.vendor="Runlayer Inc." \
      org.opencontainers.image.licenses="Apache-2.0" \
      org.opencontainers.image.source="https://github.com/Runlayer"

# util-linux -> setpriv + flock (numeric-uid privilege drop + overlap guard);
# coreutils -> timeout/readlink/date/mktemp; procps for operator debugging;
# ca-certificates for the HTTPS submission to the gateway.
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        util-linux \
        coreutils \
        procps \
        ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Install the onedir bundle and put the binary on PATH.
COPY --from=build /src/cli/dist/aiwatch/ /usr/lib/runlayer/aiwatch/
RUN ln -sf /usr/lib/runlayer/aiwatch/aiwatch /usr/bin/aiwatch

# Bake the Detect-only managed config into the image. Without it the scanner's
# check-ins read empty managed config, Sessions defaults ON, and every
# container device reports a spurious Sessions status instead of the promised
# Enforce/Sessions=Disabled. Detect-only is this image's identity, not a
# per-deploy choice — the scanner reads /etc/runlayer/aiwatch/config.json
# inside the container (host paths are only ever read under /host).
RUN mkdir -p /etc/runlayer/aiwatch \
    && printf '{\n  "Sessions": false,\n  "Enforcement": false\n}\n' \
        > /etc/runlayer/aiwatch/config.json \
    && chmod 0644 /etc/runlayer/aiwatch/config.json

# Fan-out entrypoint.
COPY cli/packaging/container/scan-host-users.sh /usr/lib/runlayer/scan-host-users.sh
RUN chmod 0755 /usr/lib/runlayer/scan-host-users.sh

ENTRYPOINT ["/usr/lib/runlayer/scan-host-users.sh"]
