# Stage 1: Build web UI
FROM node:26-alpine AS web-builder
WORKDIR /app/web
COPY web/package.json web/package-lock.json ./
RUN npm ci
COPY web/ ./
RUN npm run build

# Stage 2: Build Go binary
FROM golang:1.27-alpine AS go-builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
COPY --from=web-builder /app/web/build ./web/build

# Build metadata. All three are caller-passed via --build-arg (see
# pad-cloud/scripts/build-pad.sh for the production wrapper that
# resolves them from the host's pad checkout).
#
# Why all three are passed in vs. computed inside the container:
#
#   - .dockerignore intentionally excludes .git/, so an in-container
#     `git rev-parse` substitution returns empty (with `2>/dev/null`
#     swallowing the error) — the previous Dockerfile shipped "dev"
#     forever because of this. We don't want to add .git/ to the
#     context just for this; pre-computing on the host is the
#     standard pattern.
#   - `date` would work in-container but a fresh `date` value on
#     every build invalidates layer caching for this RUN. Passing
#     as ARG lets the caller decide cache semantics.
#
# Defaults are deliberately ugly-but-honest so a `docker build .`
# without args produces a binary whose pad_version makes the
# misconfiguration obvious ("dev (unknown)") rather than hiding it.
ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILD_TIME=
RUN CGO_ENABLED=0 go build \
    -ldflags="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.buildTime=${BUILD_TIME}" \
    -o pad ./cmd/pad

# Stage 3: Runtime
FROM alpine:3.24
# ca-certificates: TLS roots for outbound HTTPS (e.g. Maileroo email).
# tzdata: timezone names for Go's time package.
# shadow: provides usermod / groupmod (BusyBox's adduser/addgroup don't
#         ship modify equivalents — needed by docker-entrypoint.sh).
# su-exec: lightweight alpine equivalent of gosu — execs the target in
#          the same process so SIGTERM propagates correctly. Used by the
#          entrypoint shim AND by the conditional healthcheck below.
RUN apk add --no-cache ca-certificates tzdata shadow su-exec
COPY --from=go-builder /app/pad /usr/local/bin/pad
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# In-image `pad` user/group at uid/gid 1000 — the entrypoint shim remaps
# these to match PUID/PGID at container start (defaults 99/100, the
# Unraid `nobody:users` convention). The actual numeric IDs can be
# anything; 1000 is just the historical default.
RUN addgroup -g 1000 pad \
    && adduser -D -u 1000 -G pad -h /home/pad pad \
    && mkdir -p /data \
    && chown -R pad:pad /data
ENV PAD_DATA_DIR=/data
ENV PAD_HOST=0.0.0.0

# IMPORTANT: NO `USER pad` directive — container starts as root so the
# entrypoint shim can chown /data + remap user/group ids before
# dropping privileges via `su-exec pad`. See TASK-1168 / PLAN-1166.

EXPOSE 7777
VOLUME /data

# Healthcheck adapts to the caller's chosen execution model:
#
#   • Default invocation (container starts as root, entrypoint drops to pad):
#     healthcheck runs as ROOT (Docker uses the image USER, which is root
#     because we removed the USER directive). Wrap with su-exec so it
#     matches the main process's unprivileged UID.
#
#   • `docker run --user 1234` invocation: healthcheck ALSO runs as 1234.
#     `su-exec pad ...` would fail because 1234 lacks the privilege to
#     switch to user `pad`. Just run wget directly in that branch.
#
# start-period bumped 10s → 60s. The always-chown-R policy (D4) means a
# user restoring a backup with a large attachment store can legitimately
# spend tens of seconds in the entrypoint before pad starts listening;
# 60s covers ~600k files at 10k files/sec on local SSD.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
    CMD sh -c 'if [ "$(id -u)" = "0" ]; then exec su-exec pad wget -q --spider http://localhost:7777/api/v1/health; fi; exec wget -q --spider http://localhost:7777/api/v1/health'

ENTRYPOINT ["docker-entrypoint.sh", "pad"]
CMD ["server", "start"]
