# syntax=docker/dockerfile:1

# ---------------------------------------------------------------------------
# KenApi — GitDataAI 主业务后端（cargo workspace，二进制 app：serve / event / migrate / openapi）
#
#   docker build -t kenapi:local .
#   docker run --rm -p 7051:7051 ... kenapi:local                 # app serve
#   docker run --rm -p 7031:7031 -p 7032:7032 ... kenapi:local app event   # 事件系统
#   docker run --rm -p 7051:7051 \
#     -e APP_DATABASE__URL=postgres://... kenapi:local
#
# 与仓库内其它镜像共用的约定：
#   * builder 只负责编译，runtime 只带二进制 + 运行期依赖；
#   * BuildKit cache mount 复用 cargo registry / git / target，重复构建不重编依赖；
#   * 非 root 运行（uid/gid 10001）、tini 作 PID 1、SIGTERM 优雅退出；
#   * 配置从 /app/config 读取，密钥只走环境变量或挂载文件，不烘进镜像层。
# ---------------------------------------------------------------------------

ARG RUST_IMAGE=rust:1-bookworm
ARG RUNTIME_IMAGE=debian:bookworm-slim

FROM node:26-bookworm-slim AS identity-web
WORKDIR /web
# 前端由 pnpm 管理（pnpm-workspace.yaml / pnpm-lock.yaml）；Node 25 起不再自带 corepack。
RUN npm install -g pnpm@12
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY view ./view
COPY packages ./packages
COPY openapi.json ./
COPY scripts ./scripts
COPY vite.config.ts tsconfig.json tsconfig.app.json tsconfig.node.json \
     orval.config.ts biome.json ./
# 契约闸门：生成客户端必须与提交的 packages/apiclient 一致。
RUN bash scripts/contract-check.sh --client-only
RUN pnpm build

# ---------------------------------------------------------------------------
# builder
# ---------------------------------------------------------------------------
FROM ${RUST_IMAGE} AS builder

# protoc：lib/proto 的 build.rs 会编译 .proto。
# libssl-dev / pkg-config：native-tls 链（reqwest 等处）需要 OpenSSL 头文件。
ENV PROTOC_INCLUDE=/usr/include \
    CARGO_TERM_COLOR=always

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        pkg-config \
        libssl-dev \
        cmake \
        perl \
        git \
        curl \
        ca-certificates \
        unzip \
        protobuf-compiler \
        libprotobuf-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /build

# 工具链由 rust-toolchain.toml 固定（nightly-2026-10-27）；rustup 在这一层装好，
# 后面所有 cargo 调用都用这个版本，不受基础镜像自带 stable 影响。
COPY rust-toolchain.toml ./
RUN rustup show > /dev/null

COPY Cargo.toml Cargo.lock openapi.json ./
COPY lib ./lib
COPY app ./app
COPY --from=identity-web /web/view/dist ./view/dist

RUN --mount=type=cache,id=kenapi-cargo-registry,target=/usr/local/cargo/registry \
    --mount=type=cache,id=kenapi-cargo-git,target=/usr/local/cargo/git \
    --mount=type=cache,id=kenapi-target,target=/build/target \
    cargo build --release --locked --workspace --bins \
    && mkdir -p /out \
    && cp target/release/app /out/ \
    && /out/app openapi --check \
    && strip /out/app

# ---------------------------------------------------------------------------
# runtime
# ---------------------------------------------------------------------------
FROM ${RUNTIME_IMAGE} AS runtime

ARG APP_UID=10001
ARG APP_GID=10001

# libssl3：native-tls 运行期依赖；git：仓库维护/排障用；
# curl 只服务 HEALTHCHECK，tini 负责转发 SIGTERM 并回收子进程。
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        git \
        ca-certificates \
        libssl3 \
        curl \
        tini \
    && rm -rf /var/lib/apt/lists/* \
    && useradd --system --create-home --uid ${APP_UID} --user-group gitdata \
    && mkdir -p /opt/gitdata/repos /opt/gitdata/objects \
    && chown -R gitdata:gitdata /opt/gitdata

WORKDIR /app

# 只带默认配置；config/local/ 与各类密钥由部署方挂载或注入。
COPY --chown=gitdata:gitdata config/default config/default
COPY --from=builder /out/app /usr/local/bin/

LABEL org.opencontainers.image.title="KenApi" \
      org.opencontainers.image.description="GitDataAI monolith: business API and login web" \
      org.opencontainers.image.version="1.0.0"

ENV RUST_BACKTRACE=0

USER gitdata

EXPOSE 7051 7031 7032
STOPSIGNAL SIGTERM

# 默认 CMD 是 apihost：/healthz 可用。`app event` 角色没有 HTTP /healthz，
# 部署时由 k8s 用 tcpSocket 探针覆盖镜像 HEALTHCHECK。
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
    CMD curl -fsS "http://127.0.0.1:${KENAPI_HEALTH_PORT:-7051}/healthz" >/dev/null || exit 1

ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["app", "serve"]
