# syntax=docker/dockerfile:1

# KeepOrg synchronization server.
#
# The server is a static binary with no cgo — its only third-party code is
# the two pure-Go database drivers — so the runtime image carries the
# binary, a certificate bundle for talking to an OIDC provider or a
# PostgreSQL server over TLS, and nothing else. There is no shell in it: the binary
# probes its own health.
#
#   podman build --format docker -t keeporgd:<version> --build-arg VERSION=<version> .
#   docker build -t keeporgd:<version> --build-arg VERSION=<version> .
#
# podman needs --format docker, because the OCI image format has no
# HEALTHCHECK field and drops it silently.
#
# or `make docker`, which takes the version from versions.mk.

FROM golang:1.26-alpine AS build

ARG VERSION=dev
# The platform being built for, so `go build` cross-compiles to it rather than to
# the builder's own architecture.
ARG TARGETOS
ARG TARGETARCH
WORKDIR /src

# Only what the server is built from, so a change to the clients, the
# Android app or the docs does not invalidate this layer.
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download
COPY cmd/keeporgd ./cmd/keeporgd
COPY internal ./internal

RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath \
    -ldflags="-s -w -X main.version=${APP_VERSION}" \
        -o /out/keeporgd ./cmd/keeporgd

# Empty directories to copy into the scratch image with the right owner.
# There is no shell there to create them later.
RUN mkdir -p /out/var/lib/keeporg /out/etc/keeporg


FROM scratch

ARG VERSION=dev
LABEL org.opencontainers.image.title="KeepOrg server" \
      org.opencontainers.image.description="Synchronization server for collaborative KeePass databases" \
      org.opencontainers.image.version="${VERSION}" \
      org.opencontainers.image.source="https://garage.rcmd.space/keeporg" \
      org.opencontainers.image.licenses="AGPL-3.0-or-later" \
      cc.tiredsysadmin.application="cc.tiredsysadmin.linux.keeporgd"

# Needed to reach an OIDC provider, or PostgreSQL, over TLS. Nothing else from the builder
# comes along.
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

# The data directory holds the SQLite database, unless KEEPORG_DATABASE_URL
# points at PostgreSQL. Every row in it is sealed under the master key. The
# configuration directory holds server.json and, unless the key is supplied
# otherwise, that master key. They are separate on purpose: whoever holds
# both can read everything, and either alone is useless.
#
# Give the master key somewhere durable, one of these three ways:
#
#   --secret keeporg_master_key -e KEEPORG_MASTER_KEY_FILE=/run/secrets/keeporg_master_key
#   -e KEEPORG_MASTER_KEY="$(head -c 32 /dev/urandom | base64)"
#   -v keeporg-config:/etc/keeporg
#
# The first is preferred: the key is never in the process environment and
# never on a disk this container can write. The file may hold base64 or 32
# raw bytes. Neither environment variable is ever created by the server, so
# a secret that failed to mount is reported instead of being replaced.
#
# With none of the three, the server mints a key into the container's own
# layer on first start and loses it on the next `run`, after which it
# refuses to start: the database does not open with a new key. Nothing is
# overwritten; mount the original key and it starts.
COPY --from=build --chown=65532:65532 /out/var/lib/keeporg /var/lib/keeporg
COPY --from=build --chown=65532:65532 /out/etc/keeporg /etc/keeporg
VOLUME ["/var/lib/keeporg", "/etc/keeporg"]

COPY --from=build /out/keeporgd /keeporgd

# A fixed unprivileged id, matching the convention used by distroless.
USER 65532:65532
EXPOSE 8765

# The image has no shell, so the binary checks itself.
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
    CMD ["/keeporgd", "-healthcheck", "-addr", ":8765"]

ENTRYPOINT ["/keeporgd"]
CMD ["-addr", ":8765", "-data", "/var/lib/keeporg"]
