# Multi-stage Dockerfile for the Attune injected agent binaries
#
# Builds statically-linked `attune-agent` and `attune-sensor-agent` binaries
# using musl, suitable for injection into arbitrary runtime containers.
#
# Stages:
#   builder      - Cross-compile with musl for a fully static binary
#   agent-binary - Minimal scratch image containing just the binary
#   agent-init   - BusyBox-based image for use as a Kubernetes init container
#                  or Docker Compose volume-populating service (has `cp`)
#
# Usage:
#   # Build the minimal binary-only image:
#   DOCKER_BUILDKIT=1 docker buildx build --target agent-binary -f docker/Dockerfile.agent -t attune-agent:binary .
#
#   # Build the init container image (for volume population via `cp`):
#   DOCKER_BUILDKIT=1 docker buildx build --target agent-init -f docker/Dockerfile.agent -t attune-agent:latest .
#
#   # Use in docker-compose.yaml to populate a shared volume:
#   # agent-init:
#   #   image: attune-agent:latest
#   #   command: ["cp", "/usr/local/bin/attune-agent", "/shared/attune-agent"]
#   #   volumes:
#   #     - agent_binary:/shared
#
# Note: `attune-agent` lives in the worker crate and `attune-sensor-agent`
# lives in the sensor crate.

ARG RUST_VERSION=1.92
ARG DEBIAN_VERSION=bookworm
ARG RUST_TARGET=x86_64-unknown-linux-musl

# ============================================================================
# Stage 1: Builder - Cross-compile a statically-linked binary with musl
# ============================================================================
FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS builder

ARG RUST_TARGET

# Install musl toolchain for static linking
RUN apt-get update && apt-get install -y \
    musl-tools \
    pkg-config \
    libssl-dev \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Add the requested musl target for fully static binaries
RUN rustup target add ${RUST_TARGET}

WORKDIR /build

# Increase rustc stack size to prevent SIGSEGV during release builds
ENV RUST_MIN_STACK=67108864

# Enable SQLx offline mode — compile-time query checking without a live database
ENV SQLX_OFFLINE=true

# ---------------------------------------------------------------------------
# Dependency caching layer
# Copy only Cargo metadata first so `cargo fetch` is cached when only source
# code changes. This follows the same selective-copy optimization pattern as
# the other active Dockerfiles in this directory.
# ---------------------------------------------------------------------------
COPY Cargo.toml Cargo.lock ./
COPY crates/common/Cargo.toml ./crates/common/Cargo.toml
COPY crates/api/Cargo.toml ./crates/api/Cargo.toml
COPY crates/executor/Cargo.toml ./crates/executor/Cargo.toml
COPY crates/sensor/Cargo.toml ./crates/sensor/Cargo.toml
COPY crates/core-timer-sensor/Cargo.toml ./crates/core-timer-sensor/Cargo.toml
COPY crates/worker/Cargo.toml ./crates/worker/Cargo.toml
COPY crates/notifier/Cargo.toml ./crates/notifier/Cargo.toml
COPY crates/cli/Cargo.toml ./crates/cli/Cargo.toml

# Create minimal stub sources so cargo can resolve the workspace and fetch deps.
# These are ONLY used for `cargo fetch` — never compiled.
# NOTE: The worker crate has TWO binary targets and the sensor crate now has
# two binary targets as well, so we create stubs for all of them.
RUN mkdir -p crates/common/src && echo "" > crates/common/src/lib.rs && \
    mkdir -p crates/api/src && echo "fn main(){}" > crates/api/src/main.rs && \
    mkdir -p crates/executor/src && echo "fn main(){}" > crates/executor/src/main.rs && \
    mkdir -p crates/executor/benches && echo "fn main(){}" > crates/executor/benches/context_clone.rs && \
    mkdir -p crates/sensor/src && echo "fn main(){}" > crates/sensor/src/main.rs && \
    echo "fn main(){}" > crates/sensor/src/agent_main.rs && \
    mkdir -p crates/core-timer-sensor/src && echo "fn main(){}" > crates/core-timer-sensor/src/main.rs && \
    mkdir -p crates/worker/src && echo "fn main(){}" > crates/worker/src/main.rs && \
    echo "fn main(){}" > crates/worker/src/agent_main.rs && \
    mkdir -p crates/notifier/src && echo "fn main(){}" > crates/notifier/src/main.rs && \
    mkdir -p crates/cli/src && echo "fn main(){}" > crates/cli/src/main.rs

# Download all dependencies (cached unless Cargo.toml/Cargo.lock change)
# registry/git use sharing=shared — cargo handles concurrent reads safely
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
    --mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
    cargo fetch

# ---------------------------------------------------------------------------
# Build layer
# Copy real source code and compile only the agent binary with musl
# ---------------------------------------------------------------------------
COPY migrations/ ./migrations/
COPY crates/ ./crates/

# Build the injected agent binaries, statically linked with musl.
# Uses a dedicated cache ID (agent-target) so the musl target directory
# doesn't collide with the glibc target cache used by other Dockerfiles.
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
    --mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
    --mount=type=cache,id=agent-target,target=/build/target,sharing=locked \
    cargo build --release --target ${RUST_TARGET} --bin attune-agent --bin attune-sensor-agent && \
    cp /build/target/${RUST_TARGET}/release/attune-agent /build/attune-agent && \
    cp /build/target/${RUST_TARGET}/release/attune-sensor-agent /build/attune-sensor-agent

# Strip the binaries to minimize size
RUN strip /build/attune-agent && strip /build/attune-sensor-agent

# Verify the binaries are statically linked and functional
RUN ls -lh /build/attune-agent /build/attune-sensor-agent && \
    file /build/attune-agent && \
    file /build/attune-sensor-agent && \
    ldd /build/attune-agent 2>&1 || true && \
    ldd /build/attune-sensor-agent 2>&1 || true

# ============================================================================
# Stage 2: agent-binary - Minimal image with just the static binary
# ============================================================================
# This is the smallest possible image — a single static binary on scratch.
# Useful when you only need to extract the binary (e.g., via COPY --from).
FROM scratch AS agent-binary

COPY --from=builder /build/attune-agent /usr/local/bin/attune-agent
COPY --from=builder /build/attune-sensor-agent /usr/local/bin/attune-sensor-agent

ENTRYPOINT ["/usr/local/bin/attune-agent"]

# ============================================================================
# Stage 3: agent-init - Init container for volume population
# ============================================================================
# Uses busybox so we have `cp`, `sh`, etc. for use as a Docker Compose or
# Kubernetes init container that copies the agent binary into a shared volume.
#
# Example docker-compose.yaml usage:
#   agent-init:
#     image: attune-agent:latest
#     command: ["cp", "/usr/local/bin/attune-agent", "/shared/attune-agent"]
#     volumes:
#       - agent_binary:/shared
#
#   my-worker-container:
#     image: python:3.12
#     command: ["/agent/attune-agent"]
#     volumes:
#       - agent_binary:/agent:ro
#     depends_on:
#       agent-init:
#         condition: service_completed_successfully
FROM busybox:1.36 AS agent-init

COPY --from=builder /build/attune-agent /usr/local/bin/attune-agent
COPY --from=builder /build/attune-sensor-agent /usr/local/bin/attune-sensor-agent

ENTRYPOINT ["/usr/local/bin/attune-agent"]
