# Lint stage
#
# This FROM line is the single source of truth for the linter version:
# script/lint parses the image reference out of it and runs that exact
# image, so a local `make lint` and CI use the same linter. Bump the
# linter here (tag AND digest) and nowhere else.
#
# golangci/golangci-lint:v2.12.2-alpine, 2026-08-07
FROM golangci/golangci-lint:v2.12.2-alpine@sha256:91b27804074a0bacea298707f016911e60cf0cdbc6c7bf5ccacb5f0606d18d60 AS lint

RUN apk add --no-cache make build-base

WORKDIR /src

# Copy go mod files first for better layer caching
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Run formatting check and linter.
#
# CHECK_EPOCH must stay immediately above these RUNs. These layers are
# keyed on its value, so they are cache-eligible only for a value
# already built against this same tree. script/cibuild passes a fresh
# value on every invocation, which is what makes its green mean the
# checks really executed.
#
# The guarantee is conditional on that fresh value, not absolute. A
# build that omits --build-arg -- a bare `docker build .` -- gets an
# empty CHECK_EPOCH, and an empty string is a constant: the first such
# build runs the checks, and every one after it on an unchanged tree
# replays these layers from cache, never executing a check and still
# exiting 0, a green nothing earned. Gate through script/cibuild.
# Making the missing-arg case fail loudly instead is tracked in #91.
#
# CHECK_EPOCH is deliberately not referenced by the commands below: a
# declared-but-unreferenced ARG does enter BuildKit's cache key, which
# is measured on this host rather than assumed (PR #89). Upstream
# sneak/prompts #26 prefers expanding the value into the command so
# that the miss is contractual rather than dependent on that behavior
# staying as it is; adopting that here is tracked in #91. Do not delete
# this ARG as dead code -- the gate depends on it.
#
# ARG scope is per-stage, so the builder stage declares its own.
# Everything above this line (apk, go.mod, `go mod download`) is
# deliberately outside the busted range and keeps caching.
ARG CHECK_EPOCH
RUN make fmt-check
RUN make lint

# Build stage
# golang:1.26.1-alpine, 2026-03-17
FROM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder

# Depend on lint stage passing
COPY --from=lint /src/go.sum /dev/null

ARG VERSION=dev

# Install build dependencies for CGO (mattn/go-sqlite3) and sqlite3 CLI (tests)
RUN apk add --no-cache make build-base sqlite

WORKDIR /src

# Copy go mod files first for better layer caching
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Run tests. See the CHECK_EPOCH comment in the lint stage, including
# the conditions the guarantee depends on; ARG scope is per-stage, so
# this stage needs its own declaration, and it must stay immediately
# above the check RUN.
ARG CHECK_EPOCH
RUN make test

# Build (pure Go, no CGO required since we use modernc.org/sqlite)
RUN CGO_ENABLED=0 go build -ldflags "-X 'sneak.berlin/go/vaultik/internal/globals.Version=${VERSION}' -X 'sneak.berlin/go/vaultik/internal/globals.Commit=$(git rev-parse HEAD 2>/dev/null || echo unknown)' -X 'sneak.berlin/go/vaultik/internal/globals.CommitDate=$(git show -s --format=%cs HEAD 2>/dev/null || echo unknown)'" -o /vaultik ./cmd/vaultik

# Runtime stage
# alpine:3.21, 2026-02-25
FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709

RUN apk add --no-cache ca-certificates sqlite

# Copy binary from builder
COPY --from=builder /vaultik /usr/local/bin/vaultik

# Create non-root user
RUN adduser -D -H -s /sbin/nologin vaultik

USER vaultik

ENTRYPOINT ["/usr/local/bin/vaultik"]
