# syntax=docker/dockerfile:1

# Steid as a container.
#
# Two stages: a Rust builder that compiles and bundles assets, and a slim Debian
# runtime that carries the binary, the asset bundle, and `git`.
#
# Debian, not Alpine, on both sides. `build.rs` runs the *standalone* Tailwind CLI,
# downloaded from GitHub at build time, and those Linux builds are glibc-linked —
# a musl builder fails at `cargo build`, not at runtime, which is at least loud.
# The runtime stays on the same libc so the binary needs no static-linking dance.


# --- builder ---------------------------------------------------------------

# rustc >= 1.95 is a hard floor: Topcoat 0.5 requires it, and on an older toolchain
# `topcoat` resolves to an empty `v0.0.0` placeholder rather than failing. Pinned
# rather than `rust:bookworm` so an image rebuilt in six months is the same build.
FROM rust:1.97-bookworm AS builder

# `topcoat asset bundle` needs the CLI. Installed before the source is copied so
# editing the app doesn't rebuild it. `--locked` keeps it reproducible.
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
    cargo install topcoat-cli --version 0.5.0 --locked

WORKDIR /src
COPY . .

# One command does both jobs: `topcoat asset bundle` runs `cargo build` itself and
# then scans the linked binary for the assets it declares, writing them plus a
# `manifest.toml` to `target/assets`. A plain `cargo build --release` is *not*
# enough — it produces the binary, but `AssetBundle::load()` looks for
# `assets/manifest.toml` beside the executable at startup and the app fails to boot
# without it.
#
# `target/` and the cargo registry are cache mounts, so a rebuild after a code edit
# reuses compiled dependencies. Nothing is faked to get that: no dummy `main.rs`,
# no split manifest copy. Those tricks interact badly with `build.rs`, which scans
# the real sources for Tailwind classes, and a stale stylesheet is a silent wrong
# answer rather than a build failure. Because a cache mount is not part of the
# image, the artefacts are copied to `/out` inside the same `RUN`.
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
    --mount=type=cache,target=/src/target,sharing=locked \
    topcoat asset bundle --release \
 && mkdir -p /out \
 && cp target/release/steid /out/steid \
 && cp -r target/assets /out/assets


# --- runtime ---------------------------------------------------------------

FROM debian:bookworm-slim

# `git` is not optional. Steid shells out to it for everything: `git init --bare`
# creates a repository and `git http-backend` (shipped inside the git package, at
# /usr/lib/git-core) serves clone and push. An image without it builds cleanly and
# then fails at the first repository the user creates.
# `ca-certificates` for outbound TLS.
RUN apt-get update \
 && apt-get install -y --no-install-recommends git ca-certificates \
 && rm -rf /var/lib/apt/lists/*

# Non-root. A fixed uid so a bind-mounted host directory can be chowned to match.
RUN useradd --system --create-home --home-dir /home/steid --uid 10001 steid

# The bundle must sit beside the binary: `AssetBundle::load()` walks up from the
# executable looking for `assets/manifest.toml`, so /app/steid finds /app/assets.
WORKDIR /app
COPY --from=builder --chown=root:root /out/steid /app/steid
COPY --from=builder --chown=root:root /out/assets /app/assets

# All persistent state under one directory, so one volume covers it: the SQLite
# database as a file in /data, the bare repositories under /data/repos. SQLite
# writes `-wal` and `-shm` siblings, so /data itself must be writable, not just the
# database file.
RUN mkdir -p /data/repos && chown -R steid:steid /data
VOLUME ["/data"]

ENV STEID_DATABASE_URL="sqlite:/data/steid.db?mode=rwc" \
    STEID_DATA_DIR="/data/repos" \
    HOST="0.0.0.0" \
    PORT="3000" \
    HOME="/home/steid"

# Deliberately not set: STEID_INSECURE_COOKIES. It strips `Secure` from the session
# cookie and belongs to plain-HTTP local development only.

EXPOSE 3000
USER steid
CMD ["/app/steid"]
