# syntax=docker/dockerfile:1
#
# Only in One - 账号服务端镜像
#
# ---------- 基础镜像选型 ----------
# 用 node:22-alpine，**不是** node:22-slim，也不需要换 slim。理由：
#   1. package.json 的 `engines.node` 是 ">=22.5"，22-alpine 当前为 22.23.3，满足；
#   2. 运行期依赖只有 express + jsonwebtoken，全是纯 JS，没有需要 glibc 的原生模块；
#   3. `node:sqlite` 是 Node **内置**模块（SQLite 3.51.3 直接编译进二进制），
#      musl 版本一样能用 —— 已在容器内实测通过：
#        node -e "require('node:sqlite')" → 开内存库、建表、读写全部正常，
#        只打印一条 `ExperimentalWarning: SQLite is an experimental feature`。
#        这条 warning 是**预期输出，不是错误**，不会影响退出码。
#   4. alpine 体积约为 slim 的 1/4，用户本机拉取/重建都更快。
# 不选 distroless：没有 sh，HEALTHCHECK 和进容器排障都会变得很痛苦。
FROM node:22-alpine

# ---------- 运行期环境变量默认值 ----------
# JWT_SECRET 故意**不在这里设置**：
#   密钥只从宿主机 .env（compose env_file）注入，绝不写进镜像层。
#   一旦写进 ENV，任何能拿到镜像的人都能从 `docker image inspect` 里读到它并伪造 token。
ENV NODE_ENV=production \
    HOST=0.0.0.0 \
    PORT=3000 \
    DB_PATH=/app/data/onlyinone.db

WORKDIR /app

# ---------- 依赖（单独一层，利用构建缓存）----------
# 先只拷 manifest：改源码时这一层命中缓存，不会重装依赖。
# 用 `npm ci` 而不是 `npm install`：仓库里有 package-lock.json，可复现、更快。
# --omit=dev：本服务没有 devDependencies（测试用 node:test），装了纯属浪费体积。
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force

# ---------- 源码 ----------
# 哪些不进镜像由 .dockerignore 负责：node_modules / test / data / .env / *.log …
COPY . .

# ---------- 非 root 运行 ----------
# 直接用镜像自带的 `node` 用户（uid/gid 1000），不自建 uid 1001：
#   官方 node 镜像已经建好该用户，再造一个只是多一份需要同步的映射关系。
# 只 chown 数据目录：源码保持 root 所有、对 node 用户只读即可（最小权限）。
# 迁移（migrations/）只需要读权限，所以不必 chown。
RUN mkdir -p /app/data && chown -R node:node /app/data

USER node

EXPOSE 3000

# ---------- 健康检查 ----------
# 用 `/api/v1/health`：该路由与 `/api/health` 双注册，**不依赖 308 重定向**
# （server.js 注释明写「容器探针不应跟随重定向」），所以探针不需要 -L 之类的跟随逻辑。
#
# alpine 里**没有 curl**，两个候选：
#   a) busybox 自带的 `wget --spider` —— 存在，但语义弱：4xx 不一定回非零退出码，
#      失败原因也难区分；
#   b) `node -e "fetch(...)"` —— node 必然存在，用 r.ok 精确判定 2xx，
#      且与运行时同一个 fetch 实现，不存在版本差异。
# 选 b。
#
# --start-period=20s：容器启动后要先跑 001/002 两个迁移才 listen，
# 这段时间内的探测失败属正常现象，不计入 --retries。
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
    CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/api/v1/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"

# ---------- 启动 ----------
# exec 形式（JSON 数组）：node 直接接收信号，不需要经过 shell。
# 不写 `npm start` 的两个原因：
#   1. npm 会多一层包装进程，信号转发和退出码都会绕一圈；
#   2. npm start 里的 `--env-file-if-exists=.env` 在容器内没有意义 ——
#      .env 不进镜像，配置全部来自 compose 的 env_file / environment。
CMD ["node", "server.js"]
