# Optional APK mirror override, empty = upstream default (dl-cdn.alpinelinux.org).
#
# dl-cdn is Fastly-fronted, and Fastly is simply unroutable from some networks —
# anya, our only Host build box, times out on BOTH the A (146.75.118.132) and
# AAAA record while github / docker hub / registry.brood.mom stay reachable.
# That killed the 0.5.13 build at `apk add` (exit 4, "python3 (no such
# package)"). Official mirrors answer fine, so the fix is a swappable mirror
# rather than a pinned vendor:
#   docker buildx build --build-arg ALPINE_MIRROR=https://dl-2.alpinelinux.org/alpine …
# `scripts/release.sh host` forwards $ALPINE_MIRROR when it is set in the env.
ARG ALPINE_MIRROR=""

FROM node:22-alpine AS base

# Install dependencies only when needed
FROM base AS deps
ARG ALPINE_MIRROR
# python3 / make / g++ are build-only deps for native modules:
#   - isolated-vm (Code node, ADR-062) — V8 isolate native binding
# Stage-local: NOT carried into the runner image (the .node binary
# is already compiled and copied via node_modules).
RUN if [ -n "$ALPINE_MIRROR" ]; then \
      sed -i "s|https://dl-cdn.alpinelinux.org/alpine|$ALPINE_MIRROR|g" /etc/apk/repositories 2>/dev/null || true; \
      for f in /etc/apk/repositories.d/*; do \
        [ -f "$f" ] && sed -i "s|https://dl-cdn.alpinelinux.org/alpine|$ALPINE_MIRROR|g" "$f"; \
      done 2>/dev/null || true; \
      echo "[dockerfile] APK mirror → $ALPINE_MIRROR"; \
    fi; \
    apk add --no-cache libc6-compat python3 make g++
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json package-lock.json ./
COPY apps/host/package.json ./apps/host/
COPY apps/mother/ui/package.json ./apps/mother/ui/
COPY apps/signer/package.json ./apps/signer/
COPY apps/spinneret/package.json ./apps/spinneret/
RUN npm ci

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED=1

# Build workspace package types (@brood/renderer, @brood/runtime-react,
# @brood/eject, @brood/data-tunnel-aead, …) BEFORE next build. Without
# this step, dist/*.d.ts is empty and `next build`'s typecheck pass
# fails on imports of brand-new exports (e.g. FG_ATTR added to
# @brood/renderer in PR #472 — host's FilterGroupClient.tsx couldn't
# resolve its types until renderer dist was regenerated). Same root
# cause behind the three 0.4.62 build failures (#476/#478/#481).
RUN npm run build:types

# Dummy env vars so Next.js can compile (ARG = not persisted in image layers)
ARG DATABASE_URL="postgres://x:x@localhost:5432/x"
ARG BOSS_SECRET_KEY="build-dummy"
ARG S3_ENDPOINT="http://localhost:3900"
ARG S3_REGION="us-east-1"
ARG BETTER_AUTH_SECRET="build-dummy-secret-not-used-at-runtime"
# CAUTION: env vars present during `next build` are INLINED as literals into
# the Turbopack middleware bundle (absent ones stay runtime lookups). This
# value therefore ships inside the image as the baked BETTER_AUTH_URL the
# proxy sees — it MUST stay a non-public host so primaryDomain's
# isPublicCanonicalHostname filter drops it from the site-URL chain. A
# public-looking dummy here becomes the canonical-301 target for every Host
# deployed without SITE_URL = the 0.4.77 altereco outage class. Same reason
# SITE_URL must never be added to the build env.
ARG BETTER_AUTH_URL="http://localhost:3000"
RUN DATABASE_URL="$DATABASE_URL" \
    BOSS_SECRET_KEY="$BOSS_SECRET_KEY" \
    S3_ENDPOINT="$S3_ENDPOINT" \
    S3_REGION="$S3_REGION" \
    BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
    BETTER_AUTH_URL="$BETTER_AUTH_URL" \
    npm run build:host

# Build the tunnel spinneret next to host-app so the runtime image carries both.
# Compose toggles which process runs per service (host vs spinneret) via `command:`.
RUN cd apps/spinneret && npx tsc -p tsconfig.json

# Bundle the Host Worker (cron jobs + async workflow/eject) next to host-app.
# Not in the Next standalone trace, so it needs its own esbuild step — compose
# runs it from the same image via `command:`. isolated-vm stays external and is
# copied into the runner stage below.
RUN cd apps/host && node scripts/build-worker.mjs

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Spinneret audit-log dir, pre-created and owned by the spinneret's uid. The
# canonical compose (docker-compose.host.yml) points BROOD_SPINNERET_AUDIT_LOG_DIR
# here and mounts a named volume at this path. A fresh named volume inherits
# the OWNERSHIP of the image dir it's seeded from — so without this line the
# volume would mount root-owned and the non-root spinneret (uid 1001) could not
# write its append-only audit trail (EACCES on /var/log/brood-spinneret/audit.log).
# (The default audit dir is /etc/brood, which is the read-only config mount —
# also not writable; the named volume + this chown is the supported path.)
RUN mkdir -p /var/log/brood-spinneret
RUN chown nextjs:nodejs /var/log/brood-spinneret

# Copy the standalone build
COPY --from=builder --chown=nextjs:nodejs /app/apps/host/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/host/.next/static ./apps/host/.next/static
# Copy init script for DB self-initialization
COPY --from=builder --chown=nextjs:nodejs /app/apps/host/scripts/init.mjs ./scripts/init.mjs
# ADR-135 Phase 4 — Host-local owner-operator enrollment (physical-access OOB anchor).
COPY --from=builder --chown=nextjs:nodejs /app/apps/host/scripts/enroll-owner.mjs ./scripts/enroll-owner.mjs
# Copy migrations for DB setup
COPY --from=builder --chown=nextjs:nodejs /app/apps/host/src/db/migrations ./migrations
# Install postgres for init script into separate dir
RUN mkdir -p /app/init-deps && cd /app/init-deps && npm init -y --silent 2>/dev/null && npm install --save-exact postgres@3.4.7 2>/dev/null

# Spinneret bundle — compose flips `command:` to launch it from the same image.
# Isolated node_modules for `ws` so we don't bloat the host-app standalone tree.
COPY --from=builder --chown=nextjs:nodejs /app/apps/spinneret/package.json /app/apps/spinneret/package.json
COPY --from=builder --chown=nextjs:nodejs /app/apps/spinneret/dist /app/apps/spinneret/dist
RUN cd /app/apps/spinneret && npm install --omit=dev --omit=optional --silent 2>/dev/null && chown -R nextjs:nodejs node_modules

# Host Worker bundle — compose flips `command:` to run it from this same image
# (cron: schedule_tick, recompute_cascade, refresh_oauth_tokens, reapers; async:
# execute/resume workflow, eject). isolated-vm is the bundle's only native dep;
# Node resolves it from /app/node_modules walking up from the bundle path.
COPY --from=builder --chown=nextjs:nodejs /app/apps/host/dist/worker ./apps/host/dist/worker
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/isolated-vm ./node_modules/isolated-vm

USER nextjs

EXPOSE 3000

# Default to production, override with BROOD_ENV=staging
ENV BROOD_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Init DB (create schemas/tables) → start server
CMD ["sh", "-c", "node scripts/init.mjs && node apps/host/server.js"]

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
  CMD wget -q --spider http://127.0.0.1:${PORT}/api/boss/status || exit 1
