# Frontend: built first, it is the part that changes most often
FROM node:22-slim AS frontend
WORKDIR /build

# Dependencies in their own layer, so a source-only change does not reinstall them
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci

COPY frontend/ ./
# The checks run here rather than in a separate CI job: the build then fails on
# the runner for the same reason it would fail locally, and there is only one
# environment to keep working.
RUN npx eslint . --max-warnings=0
RUN npm run build          # type-check + vite build


# Backend: compiled against the sqlx offline data, so no database is needed here
FROM rust:1.97-slim AS backend
WORKDIR /build

ENV SQLX_OFFLINE=true
# Traceability: `build.rs` falls back on this when there is no git repository
ARG GIT_HASH=unknown
ENV GIT_HASH=${GIT_HASH}

# Warm the dependency layer with a dummy main, so editing src/ does not rebuild
# every crate we depend on
COPY Cargo.toml Cargo.lock build.rs ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs \
    && cargo build --release \
    && rm -rf src

COPY .sqlx/ .sqlx/
COPY src/ src/
# cargo skips a rebuild when only mtime changed: force it for our own crate
RUN touch src/main.rs \
    && cargo build --release --locked \
    && cargo test --release --locked


# Runtime
FROM debian:trixie-slim
LABEL org.opencontainers.image.title="vm-selector"

# virsh is how the app talks to libvirt. openssh-client is only needed if you
# point vm.uri at a qemu+ssh:// uri instead of the mounted socket; curl serves
# the healthcheck.
RUN apt-get update \
    && apt-get install -y --no-install-recommends libvirt-clients openssh-client curl \
    && rm -rf /var/lib/apt/lists/*

# The migrations travel with the image, so the stack needs nothing from the
# repository: the `migrate` service runs this same image with another entrypoint.
COPY --from=ghcr.io/amacneil/dbmate:2 /usr/local/bin/dbmate /usr/local/bin/dbmate
COPY db/migrations/ /app/db/migrations/

# debian:*-slim ships without /etc/nsswitch.conf, and glibc then cannot turn a
# uid back into a user record. libvirt does exactly that lookup before connecting
# and gives up with "Failed to find user record for uid", whatever the socket
# permissions are. Restore the plain file-based lookup.
RUN printf 'passwd: files\ngroup: files\nshadow: files\nhosts: files dns\n' \
    > /etc/nsswitch.conf

# Unprivileged, but it must land in the host's libvirt group to reach the
# socket: `group_add` in the compose file does that at run time.
RUN useradd --system --create-home --uid 10001 app

WORKDIR /app
COPY --from=backend /build/target/release/app-template /usr/local/bin/vm-selector
COPY --from=frontend /build/dist/ /app/frontend/
COPY docker/config.yml /etc/app-template/config.yml

USER app
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -fsS http://localhost:3000/api/version || exit 1

# No shell: signals reach the binary directly, so the graceful shutdown works
ENTRYPOINT ["/usr/local/bin/vm-selector"]
