# Deploy image for jray-server.
#
# §8 is explicit that neither Docker nor Compose should be *required* — the
# primary artifact is a single static binary plus one database file, and that is
# deliberately the lowest-friction thing a hobbyist operator can deploy. This
# image is the optional convenience, not the intended path.
#
# It builds against musl so the runtime stage can be `scratch`: no libc, no shell,
# no package manager, nothing to keep patched. rusqlite is built with `bundled`
# (SQLite compiled in) and reqwest with rustls rather than OpenSSL, so there is
# genuinely nothing left to link against.
#
#   docker build -t jray-server .
#   docker run --rm -p 8080:8080 -v jray-data:/data \
#     -e JRAY_TMDB_API_KEY=... jray-server

FROM rust:1.92-alpine AS builder

# `musl-dev` for the C toolchain rusqlite's bundled SQLite needs; `file` for the
# static-linkage assertion below.
RUN apk add --no-cache musl-dev file

WORKDIR /build

# Dependencies first, in their own layer, so editing source does not re-download
# and rebuild the entire tree.
COPY Cargo.toml Cargo.lock ./
RUN mkdir -p src \
    && echo 'fn main() {}' > src/main.rs \
    && echo '' > src/lib.rs \
    && cargo build --release --target x86_64-unknown-linux-musl \
    && rm -rf src

COPY src ./src

# `touch` defeats the cargo staleness check that the dummy-source trick above
# would otherwise leave in place.
RUN touch src/main.rs src/lib.rs \
    && cargo build --release --target x86_64-unknown-linux-musl \
    && strip target/x86_64-unknown-linux-musl/release/jray-server

# Verify the result is genuinely static. A dynamically-linked binary would fail at
# runtime on `scratch`, and failing here is far easier to diagnose.
#
# Asserted with `file`, not `ldd`: the musl target produces a **static-PIE**, and
# `ldd` prints the musl loader path for one, so an `ldd`-based check reports a
# static binary as dynamic. `file` reports "static-pie linked" and is unambiguous.
RUN file target/x86_64-unknown-linux-musl/release/jray-server | tee /tmp/linkage \
    && grep -qE 'static-pie linked|statically linked' /tmp/linkage \
    || (echo "binary is not statically linked; it will not run on scratch" && exit 1)

# Stage the data directory with the runtime uid's ownership. `scratch` has no
# shell, so this cannot be done in the final stage — and a bare `VOLUME` there
# would be created root-owned, leaving the non-root process unable to create the
# database at all.
RUN mkdir -p /staged-data && chown 65534:65534 /staged-data

# ---------------------------------------------------------------------------

FROM scratch

COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/jray-server /jray-server

# The database lives on a volume; §8 warns that a plain file copy of a live WAL
# database is not a valid backup, so back it up with `VACUUM INTO` from the host
# rather than by archiving this directory.
#
# Copied from the builder so it arrives owned by the runtime uid. Docker seeds a
# named volume from the image's directory, ownership included, so the server can
# create the database on first run. A bind mount is *not* seeded this way — the
# host directory keeps its own ownership, so it must be made writable by uid
# 65534 (`chown 65534:65534 /path/on/host`).
COPY --from=builder --chown=65534:65534 /staged-data /data
VOLUME ["/data"]

# Non-root. `scratch` has no /etc/passwd, so this is a bare uid — which is all the
# kernel needs, and the binary touches nothing outside /data.
USER 65534:65534

ENV JRAY_BIND=0.0.0.0:8080 \
    JRAY_DB=/data/jray.db

EXPOSE 8080

# No HEALTHCHECK: it would need a shell or curl, and `scratch` has neither.
# §4 provides `GET /health` (liveness) and `/ready` (database and migrations) for
# an orchestrator to probe externally, which is the right place for it.

ENTRYPOINT ["/jray-server"]
