# ────────────────────────────────────────────────────────────────────────────
# Baize — Official Dockerfile
#
# Build:  docker build -t baize .
# Run:    docker compose up -d   (see docker-compose.yml)
#
# This image installs Baize and its dependencies, then starts all PM2-managed
# services (scheduler, web-console, c4-dispatcher, activity-monitor, channels).
# The AI loop (Claude Code) runs inside a persistent tmux session so it can
# receive heartbeat / message commands through the c4-dispatcher bridge.
# ────────────────────────────────────────────────────────────────────────────

FROM node:22-slim

LABEL org.opencontainers.image.source="https://github.com/baize01-ai/baize-core"
LABEL org.opencontainers.image.description="Baize — autonomous AI agent infrastructure"
# Build flags: official sources by default (npmjs + Debian). Set to "aliyun"
# when building for the Aliyun ACR (mainland distribution) — npmmirror npm +
# Aliyun Debian mirror, baked into the image so docker run npm also uses the
# mainland source. ARG must precede the RUNs that reference them.
ARG BAIZE_APT_MIRROR=off
ARG BAIZE_NPM_MIRROR=off

# ── System packages ───────────────────────────────────────────────────────────
# Point apt at the Aliyun Debian mirror when building on mainland servers
# (deb.debian.org is unreachable/slow there; env flag lets non-CN builds keep
# the official source).
RUN if [ "${BAIZE_APT_MIRROR:-off}" = "aliyun" ]; then \
      sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources; \
      sed -i 's|security.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources; \
    fi
RUN apt-get update && apt-get install -y --no-install-recommends \
      git \
      curl \
      tmux \
      bash \
      ca-certificates \
      # Needed by some Claude Code operations
      procps \
      # For `baize doctor` network checks
      dnsutils \
      # Native-module build toolchain (better-sqlite3 etc.): prebuild download
      # can fail at build/runtime (GitHub Releases is a flaky source); with the
      # toolchain present npm falls back to source compile instead of leaving a
      # broken binary in the image (D19 container crash root cause).
      python3 \
      make \
      g++ \
    && rm -rf /var/lib/apt/lists/*

# Mainland: registry.npmjs.org is unreachable — point npm at npmmirror
# (build-arg BAIZE_NPM_MIRROR=off keeps the official registry).
RUN if [ "${BAIZE_NPM_MIRROR:-off}" = "aliyun" ]; then \
      npm config set registry https://registry.npmmirror.com --global; \
      # Also drop a system-wide npmrc so the runtime user (baize) and any
      # docker exec npm use the mainland mirror too.
      mkdir -p /usr/etc && printf 'registry=https://registry.npmmirror.com\n' > /usr/etc/npmrc; \
      # Container npm runs as USER baize with prefix /home/baize/.npm-global —
      # its globalconfig is NOT /usr/etc/npmrc, so also write the user-level
      # ~/.npmrc (highest precedence, always read) or npm stays on npmjs.
      mkdir -p /home/baize && printf 'registry=https://registry.npmmirror.com\n' > /home/baize/.npmrc; \
    fi
RUN npm install -g pm2@latest


# ── Create baize user (non-root) ──────────────────────────────────────────────
RUN useradd -m -s /bin/bash baize \
    && mkdir -p /home/baize/.local/bin /home/baize/.npm-global \
    && chown -R baize:baize /home/baize
USER baize
ENV HOME=/home/baize
ENV NPM_CONFIG_PREFIX=/home/baize/.npm-global
ENV PATH="/home/baize/.npm-global/bin:/home/baize/.local/bin:/usr/local/bin:${PATH}"
# Web console must listen on all interfaces for Docker port mapping to work
# (default is 127.0.0.1, which Docker Desktop cannot reach). PM2 fork mode
# inherits process env, so this reaches the web-console service even when the
# bundled ecosystem.config.cjs predates the WEB_CONSOLE_BIND field.
ENV WEB_CONSOLE_BIND=0.0.0.0

# ── Install baize-core from the npm package ─────────────────────────────────
# The image pulls the published @baize-ai/core package (no source code in the
# build context) — source stays private, the image matches the npm version
# exactly. Build with: docker build --build-arg BAZE_CORE_VERSION=0.2.0 .
ARG BAZE_CORE_VERSION=latest
# Local-source build: pass BAZE_CORE_TARBALL=/core.tgz (COPY'd into the
# build context) to install the workspace package instead of the registry
# version — lets us verify un-released fixes (e.g. D51) in the image.
ARG BAZE_CORE_TARBALL=
# Local-source build tarball — COPY'd BEFORE the install RUN so the file is
# present in that layer AND a tarball change invalidates the install layer.
# Registry builds provide an empty placeholder via docker-publish.sh's temp
# context (or omit BAZE_CORE_TARBALL → the COPY still needs a file present).
COPY core.tgz /tmp/core.tgz
WORKDIR /home/baize
RUN if [ -n "${BAZE_CORE_TARBALL}" ]; then npm install -g /tmp/${BAZE_CORE_TARBALL}; else npm install -g @baize-ai/core@${BAZE_CORE_VERSION}; fi \
    && baize --version \
    # ── Build-time skill dependency preinstall (reproducibility) ─────────
    # `baize init` reinstalls skill deps at runtime; doing it here bakes the
    # native modules (better-sqlite3 etc.) into the image with a known-good
    # result and the toolchain above as fallback. Same selection logic as
    # init.js installSkillDependencies (skills with a package.json that has
    # dependencies).
    && for skill_dir in /home/baize/.npm-global/lib/node_modules/@baize-ai/core/skills/*/; do \
         if [ -f "${skill_dir}package.json" ]; then \
           deps=$(node -e "const p=require('${skill_dir}package.json');console.log(p.dependencies?1:0)" 2>/dev/null || echo 0); \
           if [ "$deps" = "1" ]; then \
             (cd "$skill_dir" && npm install --production --no-audit --no-fund) || exit 1; \
           fi; \
         fi; \
       done \
    # ── Force local native-module compilation ──────────────────────────
    # better-sqlite3 12.x prebuild download can return the WRONG platform
    # binary (observed: macOS Mach-O inside linux build → ERR_DLOPEN_FAILED
    # at runtime). Rebuild forces node-gyp compile with the toolchain above,
    # guaranteeing a correct linux ELF regardless of prebuild availability.
    && for skill_dir in /home/baize/.npm-global/lib/node_modules/@baize-ai/core/skills/*/; do \
         if [ -d "${skill_dir}node_modules/better-sqlite3" ]; then \
           (cd "${skill_dir}node_modules/better-sqlite3" && npm rebuild better-sqlite3) || exit 1; \
         fi; \
       done \
    # ── Bake deps into the runtime skills dir ──────────────────────────
    # init's skill sync preserves existing node_modules and (since the
    # idempotent-dep check) skips reinstall when deps are loadable — so
    # pre-baking here makes runtime init never touch the compiled binaries.
    && mkdir -p /home/baize/baize/.claude/skills \
    && for skill_dir in /home/baize/.npm-global/lib/node_modules/@baize-ai/core/skills/*/; do \
         name=$(basename "$skill_dir"); \
         if [ -d "$skill_dir" ] && [ -f "$skill_dir/package.json" ]; then \
           mkdir -p "/home/baize/baize/.claude/skills/$name"; \
           cp -r "$skill_dir"/. "/home/baize/baize/.claude/skills/$name/"; \
         fi; \
       done \
    && node -e "const D=require('/home/baize/baize/.claude/skills/web-console/node_modules/better-sqlite3'); new D(':memory:')" \
    && echo "native modules OK (locally compiled, baked into runtime skills)"

# ── Workspace directories ─────────────────────────────────────────────────────
# ~/baize is mounted as a single volume in docker-compose.yml.
# Creating subdirectories here ensures correct ownership in the image.
RUN mkdir -p \
      /home/baize/baize/pm2 \
      /home/baize/.claude

# ── Copy PM2 ecosystem config ─────────────────────────────────────────────────
COPY --chown=baize:baize templates/pm2/ecosystem.config.cjs /home/baize/baize/pm2/ecosystem.config.cjs

# ── Copy entrypoint ───────────────────────────────────────────────────────────
COPY --chown=baize:baize docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# ── Ports ─────────────────────────────────────────────────────────────────────
# Web console (web-console service, default 3456)
EXPOSE 3456
# Caddy / reverse proxy (optional, enabled via .env)
EXPOSE 8080

# Healthcheck is defined in docker-compose.yml (start_period=600s for slow init).
# No HEALTHCHECK here to avoid a conflicting override.

ENTRYPOINT ["/entrypoint.sh"]
