# syntax=docker/dockerfile:1.7
#
# engine-cyberia — DEVELOPMENT image (underpost/engine-cyberia-dev).
#
# CYBERIA_SOURCE selects where the engine, the deployment and the content come from:
#   clone  the *_REPOSITORY repositories; the content at the revision the deployment lock pins.
#   local  the source/ directory of the build context, staged by `cyberia run-workflow dev-env`.
ARG CYBERIA_SOURCE=clone

# ---------------------------------------------------------------------------
# Stage 1 — builder: take the sources, build the deploy, then scrub secrets.
# ---------------------------------------------------------------------------
FROM rockylinux/rockylinux:9 AS base
ARG UNDERPOST_VERSION=3.5.0
# Pin Node to an exact patch: dnf's nodejs:24 module lags (24.14.1) while
# underpost's dependencies require >=24.15.0, so install the official binary.
ARG NODE_VERSION=24.15.0
ENV NODE_ENV=development

RUN set -eux; \
    # `sudo` is required because underpost's build steps shell out to
    # `sudo cp`/`sudo rm` (src/server/runtime/start.js); the build runs as root so sudo
    # is just a passthrough, but the binary must exist.
    dnf install -y git openssl tar xz sudo; \
    arch="$(uname -m)"; \
    case "$arch" in \
      x86_64) node_arch=x64 ;; \
      aarch64) node_arch=arm64 ;; \
      *) echo "unsupported arch: $arch" >&2; exit 1 ;; \
    esac; \
    curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${node_arch}.tar.xz" \
      | tar -xJ -C /usr/local --strip-components=1; \
    node --version; npm --version; \
    dnf clean all; rm -rf /var/cache/dnf; \
    npm cache clean --force


# The builder's own `underpost clone` calls below run on this same CLI, and the runtime stage
# inherits it through `COPY --from=builder /usr/local/`.
COPY underpost-cli.tgz /tmp/underpost-cli.tgz
RUN set -eux; \
    npm install -g /tmp/underpost-cli.tgz --omit=dev; \
    rm -f /tmp/underpost-cli.tgz; \
    npm cache clean --force; \
    underpost --version; \
    # The staged package must be the pinned release: a drift here would ship a CLI the
    # deployment manifests were not built against.
    underpost --version | grep -Fq "${UNDERPOST_VERSION}"

WORKDIR /home/dd

# Only the stage CYBERIA_SOURCE names is built. The sources enter by COPY, not by a bind mount:
# SELinux denies a build container the host labels of the context.
FROM base AS source-local
COPY source/ /home/dd/source/

FROM base AS source-clone

FROM source-${CYBERIA_SOURCE} AS builder
ARG CYBERIA_SOURCE
ARG ENGINE_REPOSITORY=underpostnet/engine-cyberia
ARG DEPLOYMENT_REPOSITORY=underpostnet/cyberia-deployment
ARG CONTENT_REPOSITORY=underpostnet/cyberia-content

RUN --mount=type=secret,id=github_token \
    set -eu; \
    # A private repository needs the token; a public one reads without it.
    GITHUB_TOKEN="$(cat /run/secrets/github_token 2>/dev/null || true)"; \
    export GITHUB_TOKEN; \
    mkdir -p /home/dd/engine; \
    cd /home/dd; \
    case "$CYBERIA_SOURCE" in \
      clone) \
        ENGINE_CYBERIA_REPO="${ENGINE_REPOSITORY##*/}"; \
        underpost clone "$ENGINE_REPOSITORY"; \
        cp -a ./"$ENGINE_CYBERIA_REPO"/. /home/dd/engine/; \
        rm -rf ./"$ENGINE_CYBERIA_REPO"; \
        underpost clone "$DEPLOYMENT_REPOSITORY"; \
        DEPLOYMENT_DIR="/home/dd/${DEPLOYMENT_REPOSITORY##*/}" ;; \
      local) \
        cp -a /home/dd/source/engine/. /home/dd/engine/; \
        DEPLOYMENT_DIR=/home/dd/source/cyberia-deployment ;; \
    esac; \
    rm -rf /home/dd/engine/engine-private; \
    mkdir -p /home/dd/engine/engine-private/conf/dd-cyberia /home/dd/engine/cyberia-content; \
    cp -a "$DEPLOYMENT_DIR"/conf/dd-cyberia/. /home/dd/engine/engine-private/conf/dd-cyberia/.; \
    cp -a /home/dd/engine/engine-private/conf/dd-cyberia/package.json /home/dd/engine/package.json; \
    cd /home/dd/engine; \
    npm install; \
    if [ "$CYBERIA_SOURCE" = clone ]; then \
      # The content artifact the deployment lock pins: cyberia-content at its locked revision, packed
      # by cyberia-content itself, then checked against the lock. The packed copy holds data only.
      CONTENT_REVISION="$(CYBERIA_DEPLOYMENT_ROOT="$DEPLOYMENT_DIR" node bin/cyberia release list --locked \
        | awk '$1 == "cyberia-content" { print $4 }')"; \
      CONTENT_DIR="/home/dd/${CONTENT_REPOSITORY##*/}"; \
      ( cd /home/dd && underpost clone "$CONTENT_REPOSITORY" \
        && git -C "$CONTENT_DIR" checkout --detach "$CONTENT_REVISION" \
        && cd "$CONTENT_DIR" && npm ci --include=dev && node bin/cyberia-content.js pack ); \
      tar -xzf "$CONTENT_DIR"/artifacts/cyberia-content-*.tgz -C /home/dd/engine/cyberia-content --strip-components=1; \
      CYBERIA_DEPLOYMENT_ROOT="$DEPLOYMENT_DIR" node bin/cyberia release verify; \
      rm -rf "$CONTENT_DIR" "$DEPLOYMENT_DIR"; \
    else \
      # The working content artifact, unpinned: a local stack tests what the workspace holds.
      cp -a /home/dd/source/cyberia-content/. /home/dd/engine/cyberia-content/; \
    fi; \
    node bin app load --env development --args deploy-id=dd-cyberia; \
    ( cd /home/dd/engine/hardhat && npm install --include=dev ); \
    # --- build the client bundle (assets are now in place) --------------------
    node bin client dd-cyberia; \
    # --- CREDENTIAL SCRUB (defense in depth, while secrets are still in scope) -
    # 1) Redact the token from any file it may have reached.
    if [ -n "$GITHUB_TOKEN" ]; then \
      grep -rlF --exclude-dir=node_modules --exclude-dir=.git "$GITHUB_TOKEN" /home/dd/engine 2>/dev/null \
        | xargs -r sed -i "s#${GITHUB_TOKEN}#__REDACTED__#g" || true; \
    fi; \
    unset GITHUB_TOKEN; \
    # 2) Every .git dir: the private history must not ship.
    find /home/dd/engine -type d -name .git -prune -exec rm -rf {} + 2>/dev/null || true; \
    # 3) User-level credential stores possibly written during the build.
    rm -rf /root/.gitconfig /root/.git-credentials /root/.config/git \
           /root/.netrc /root/.ssh /root/.npm /root/.cache /root/.node-gyp; \
    # 4) The underpost global env store may hold GITHUB_TOKEN.
    rm -f "$(underpost root)/underpost/.env" 2>/dev/null || true; \
    # 5) Build caches, temp, and build-only toolchains. hardhat is contract
    #    build/CLI tooling (used only to build the coverage docs above); the
    #    runtime server never imports it, so its dev node_modules is pure bloat.
    npm cache clean --force 2>/dev/null || true; \
    rm -rf /home/dd/engine/hardhat/node_modules \
           /home/dd/engine/.npm /tmp/* /var/tmp/* /var/cache/dnf

# ---------------------------------------------------------------------------
# Stage 2 — runtime: clean base + pinned Node toolchain + scrubbed app tree.
# ---------------------------------------------------------------------------
FROM rockylinux/rockylinux:9 AS runtime
ENV NODE_ENV=development

# Node + npm + the underpost CLI (exact pinned version) from the builder — no
# re-download, no git/build toolchain. libstdc++ is the only runtime lib the
# prebuilt Node binary needs beyond the base image.
COPY --from=builder /usr/local/ /usr/local/
RUN dnf install -y libstdc++ git sudo && dnf clean all && rm -rf /var/cache/dnf && \
    useradd --create-home --home-dir /home/dd --shell /usr/sbin/nologin dd && \
    # The runtime runs as `dd`, but `underpost start --build` shells out to `sudo cp`/`sudo rm`
    # (src/server/runtime/start.js). Grant passwordless sudo so those succeed without a TTY/password.
    printf 'dd ALL=(ALL) NOPASSWD:ALL\n' > /etc/sudoers.d/dd && chmod 0440 /etc/sudoers.d/dd && \
    # underpost persists its global env and container state under
    # `$(npm root -g)/underpost` (getUnderpostRootPath). That path is a STORE directory, not
    # the CLI package tree: the staged package installs under its own package name, and
    # `underpost start --build` re-links the bin at the pulled checkout without moving the
    # store (src/server/runtime/start.js#linkRuntimeCli). Create it here — the non-root
    # runtime user cannot mkdir inside /usr/local itself (EACCES on the first state write).
    mkdir -p "$(npm root -g)/underpost" && chown -R dd:dd "$(npm root -g)/underpost"

# Copy ONLY the scrubbed runtime tree — no .git, no token, no caches.
COPY --from=builder --chown=dd:dd /home/dd/engine /home/dd/engine

RUN chmod +x /home/dd/engine/scripts/rpmfusion-ffmpeg-setup.sh && \
    /home/dd/engine/scripts/rpmfusion-ffmpeg-setup.sh

# Explicit re-chown: PVC-backed mounts under src/client/public land here at
# pod start and may override the COPY --chown above with root ownership.
RUN chown -R dd:dd /home/dd/engine/src/client/public

USER dd
WORKDIR /home/dd/engine

# One port per host of conf.server.json, from PORT+1 upward, plus the gRPC data
# service. Compose publishes the same block (ENGINE_CYBERIA_PORT_RANGE).
EXPOSE 4005-4019
EXPOSE 50051

CMD ["/bin/sh", "-c", "underpost start --run dd-cyberia development"]
