# The API image. Built from the repository root, not from this directory:
#
#   docker build -f src/DodoSSH.Api/Dockerfile .
#
# Directory.Build.props, Directory.Packages.props, NuGet.config and global.json all sit at
# the root and all four are load-bearing here — central package management means a csproj
# alone does not name a single version, and a build that cannot see them resolves nothing.

# ---------------------------------------------------------------------------------------
# Build
# ---------------------------------------------------------------------------------------
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build

# Reproducible builds. Directory.Build.props turns ContinuousIntegrationBuild on when this
# is set, which is what normalises the source paths baked into the PDBs — without it two
# builds of the same commit differ by the directory they happened in.
ENV CI=true \
    DOTNET_NOLOGO=true \
    DOTNET_CLI_TELEMETRY_OPTOUT=true \
    DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true

WORKDIR /src

# The manifests first, and only the manifests. This layer is what makes an ordinary code
# change a ten-second rebuild instead of a full package restore: it is invalidated by a
# dependency change and by nothing else. Every project in DodoSSH.Api's reference closure
# has to be here — restore walks ProjectReference, and a missing csproj fails the graph
# rather than skipping a node.
COPY global.json NuGet.config Directory.Build.props Directory.Packages.props ./
COPY src/DodoSSH.Api/DodoSSH.Api.csproj                       src/DodoSSH.Api/
COPY src/DodoSSH.Api/packages.lock.json                       src/DodoSSH.Api/
COPY src/DodoSSH.Contracts/DodoSSH.Contracts.csproj           src/DodoSSH.Contracts/
COPY src/DodoSSH.Contracts/packages.lock.json                 src/DodoSSH.Contracts/
COPY src/DodoSSH.Crypto/DodoSSH.Crypto.csproj                 src/DodoSSH.Crypto/
COPY src/DodoSSH.Crypto/packages.lock.json                    src/DodoSSH.Crypto/
COPY src/DodoSSH.Domain/DodoSSH.Domain.csproj                 src/DodoSSH.Domain/
COPY src/DodoSSH.Domain/packages.lock.json                    src/DodoSSH.Domain/
COPY src/DodoSSH.Infrastructure/DodoSSH.Infrastructure.csproj src/DodoSSH.Infrastructure/
COPY src/DodoSSH.Infrastructure/packages.lock.json            src/DodoSSH.Infrastructure/

# Locked mode here for the same reason CI uses it: the lock files are committed, so a
# dependency that changed without its lock file being reviewed fails the build rather than
# quietly shipping. An image is the one place that matters most.
RUN dotnet restore src/DodoSSH.Api/DodoSSH.Api.csproj --locked-mode

# BannedSymbols.txt is an AdditionalFiles entry in Directory.Build.props. Without it the
# BannedApiAnalyzers rules silently pass, and with TreatWarningsAsErrors the whole point of
# the list is that it fails a build — so its absence would be invisible in exactly the way
# it is meant to prevent.
COPY BannedSymbols.txt .editorconfig ./
COPY src/ src/

RUN dotnet publish src/DodoSSH.Api/DodoSSH.Api.csproj \
        --no-restore \
        --configuration Release \
        --output /app \
        -p:UseAppHost=false

# ---------------------------------------------------------------------------------------
# Runtime
# ---------------------------------------------------------------------------------------
#
# Chiseled: no shell, no package manager, no libc utilities, and a non-root user (uid 1654)
# already set by the base image. That closes off `docker exec sh` on a process that holds a
# database connection and the cursor signing key, and it is affordable here specifically
# because Directory.Build.props sets InvariantGlobalization — the ICU and tzdata a normal
# base carries are exactly what this product has already decided it does not use.
#
# The cost is real and worth stating: there is no HEALTHCHECK below, because there is no
# curl and nothing to run one with. The health endpoints exist and are anonymous —
# /healthz/live, /healthz/ready, /healthz/startup — so the probe belongs in whatever runs
# the container. Readiness is not decoration on this API: it fails while an EF migration is
# pending and names the one it is waiting for, which is the intended way to discover that a
# deployment shipped ahead of its schema.
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled AS final

# Passed by CI; see .github/workflows/ci.yml. Declared with empty defaults so a local
# `docker build` with no arguments still succeeds.
ARG VERSION=""
ARG REVISION=""
ARG CREATED=""

LABEL org.opencontainers.image.title="DodoSSH API" \
      org.opencontainers.image.description="DodoSSH server: sync, identity, teams and relay authorization." \
      org.opencontainers.image.vendor="DodoTech" \
      org.opencontainers.image.licenses="MIT" \
      org.opencontainers.image.source="https://git.dodotech.cloud/DodoTech/DodoSSH" \
      org.opencontainers.image.version="${VERSION}" \
      org.opencontainers.image.revision="${REVISION}" \
      org.opencontainers.image.created="${CREATED}"

WORKDIR /app
COPY --from=build /app .

# 8080 is the .NET container default (ASPNETCORE_HTTP_PORTS in the base image), and plain
# HTTP is deliberate: Program.cs has no UseHttpsRedirection because the API is always behind
# a proxy that terminates TLS, and redirecting from here would loop.
EXPOSE 8080

# Configuration reaches the process two ways, both already wired in Program.cs: environment
# variables prefixed DODOSSH_, and files under /run/secrets for anything that should not be
# readable in `docker inspect`. The three the process will not start or run correctly
# without are DODOSSH_ConnectionStrings__Postgres, DODOSSH_Oidc__Authority and — on more
# than one node — DODOSSH_Sync__CursorSigningKey.
#
# Nothing migrates the database from in here. That is the API's own design: it fails
# /healthz/ready while a migration is pending and names it, so the schema is applied by
# `dotnet ef database update` alongside the deployment rather than by a racing container.
ENTRYPOINT ["dotnet", "DodoSSH.Api.dll"]
