# syntax=docker/dockerfile:1
#
# ChipBench — self-contained reverse-engineering environment for 8-bit
# (and small embedded) chip dumps. Bundles: Ghidra 11.3.2 + GhidraMCP 1.4,
# radare2, binwalk v3, binutils for several targets, sdcc, gputils,
# capstone/unicorn/keystone, plus a firmware-hacker toolset (flashrom,
# avrdude, openocd, srecord, simavr, unpacking tools, serial consoles).
# Optional VNC/noVNC desktop so the Ghidra GUI also runs headlessly.
#
# Nothing runs as root; runtime user is mapped to host uid/gid so
# bind-mounted artifacts keep their owner.
#
# This project is AGPLv3 (see LICENSE). It builds tooling from public
# sources; no binaries are hosted by the project.

# ---- Stage 1: build binwalk v3 (Rust) in an isolated builder ----------------
FROM rust:1-slim-bookworm AS binwalk-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
        pkg-config libfontconfig1-dev ca-certificates \
    && rm -rf /var/lib/apt/lists/*
RUN cargo install --root /out --version 3.1.0 binwalk

# ---- Stage 2: main image ----------------------------------------------------
ARG BUILD_UID=1001
ARG BUILD_GID=1001

FROM ubuntu:24.04

# ARGs above do not cross FROM boundaries; re-declare with defaults for this
# stage (overridable at build time: --build-arg BUILD_UID=$(id -u) etc.)
ARG BUILD_UID=1001
ARG BUILD_GID=1001

ENV DEBIAN_FRONTEND=noninteractive \
    TZ=UTC \
    LANG=C.UTF-8 \
    LC_ALL=C.UTF-8 \
    DISPLAY=:0 \
    JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 \
    GHIDRA_HOME=/opt/ghidra \
    GHIDRAMCP_HOME=/opt/ghidramcp \
    VENV_HOME=/opt/venv \
    GHIDRA_SCRIPTS=/opt/ghidra-scripts \
    PATH="${JAVA_HOME}/bin:/opt/ghidra:/opt/ghidra/support:/opt/venv/bin:/usr/local/bin:${PATH}"

# -----------------------------------------------------------------------------
# 1. System packages
#    - GUI/VNC stack (optional desktop for the Ghidra GUI)
#    - core RE CLI: radare2, binutils-multiarch, binutils-avr, sdcc, gputils
#    - chip-programming & debug: flashrom, avrdude, openocd, stlink-tools
#    - EPROM/SREC/HEX wrangling: srecord
#    - simulators: simavr (AVR), gpsim (PIC)
#    - firmware-unpacking: binwalk deps (7z, cabextract, lzma, cpio, squashfs,
#      unar), u-boot-tools (mkimage), device-tree-compiler (dtc)
#    - serial: tio, picocom
#    - misc: esptool, z80dasm, vbindiff
#    NOTE: openjdk-21-jdk is UNPINNED — in-process javac for Ghidra .java
#    user scripts has broken before on point-release drift. The supported
#    script path here is Jython (.py); the .java helper scripts are not shipped.
# -----------------------------------------------------------------------------
RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates wget git unzip xz-utils \
        openjdk-21-jdk \
        xvfb x11vnc fluxbox supervisor \
        novnc websockify \
        xterm dbus-x11 \
        libfontconfig1 fonts-dejavu-core \
        python3 python3-venv python3-pip \
        file xxd hexyl binutils \
        binutils-avr \
        binutils-multiarch \
        gputils \
        sdcc \
        radare2 \
        net-tools iproute2 procps less vim-tiny \
        flashrom avrdude openocd stlink-tools \
        srecord \
        simavr gpsim \
        z80dasm \
        squashfs-tools p7zip-full cabextract lzma cpio unar \
        u-boot-tools device-tree-compiler \
        tio picocom \
        esptool \
        vbindiff \
    && rm -rf /var/lib/apt/lists/*

# binwalk v3 binary (Rust) from the builder stage.
COPY --from=binwalk-builder /out/bin/binwalk /usr/local/bin/binwalk

# -----------------------------------------------------------------------------
# 2. Ghidra 11.3.2 (pinned: latest version the GhidraMCP plugin supports)
# -----------------------------------------------------------------------------
ARG GHIDRA_VERSION=11.3.2
ARG GHIDRA_ZIP=ghidra_11.3.2_PUBLIC_20250415.zip
ARG GHIDRA_URL=https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.3.2_build/${GHIDRA_ZIP}
ARG GHIDRA_SHA256=99d45035bdcc3d6627e7b1232b7b379905a9fad76c772c920602e2b5d8b2dac2

RUN cd /tmp \
    && wget -q -O ghidra.zip "${GHIDRA_URL}" \
    && echo "${GHIDRA_SHA256}  ghidra.zip" | sha256sum -c - \
    && unzip -q ghidra.zip -d /opt \
    && rm -f ghidra.zip \
    && mv /opt/ghidra_* "${GHIDRA_HOME}"

# -----------------------------------------------------------------------------
# 3. GhidraMCP 1.4 plugin (optional REST server inside the Ghidra GUI).
#    The GitHub release ships a NESTED zip; unpack both layers.
#    (Harmless "Module manifest file error" warnings at headless startup are
#     known noise from this plugin's manifest formatting.)
# -----------------------------------------------------------------------------
ARG GHIDRAMCP_VERSION=1.4
ARG GHIDRAMCP_ZIP=GhidraMCP-release-1-4.zip
ARG GHIDRAMCP_URL=https://github.com/LaurieWired/GhidraMCP/releases/download/${GHIDRAMCP_VERSION}/${GHIDRAMCP_ZIP}
ARG GHIDRAMCP_SHA256=b81ca5240fdde57ea4e899170dcd9fdee6ed29246280c74263a509bfcfc7e734

RUN cd /tmp \
    && wget -q -O mcp-outer.zip "${GHIDRAMCP_URL}" \
    && echo "${GHIDRAMCP_SHA256}  mcp-outer.zip" | sha256sum -c - \
    && mkdir -p outer && unzip -q mcp-outer.zip -d outer \
    && inner_zip="$(find outer -name 'GhidraMCP-*.zip' | head -n1)" \
    && test -n "$inner_zip" \
    && mkdir -p inner && unzip -q "$inner_zip" -d inner \
    && extdir="$(dirname "$(find inner -name extension.properties | head -n1)")" \
    && test -n "$extdir" \
    && mkdir -p "${GHIDRA_HOME}/Ghidra/Extensions" \
    && cp -r "$extdir" "${GHIDRA_HOME}/Ghidra/Extensions/GhidraMCP" \
    && rm -rf /tmp/*

ARG BRIDGE_URL=https://raw.githubusercontent.com/LaurieWired/GhidraMCP/1.4/bridge_mcp_ghidra.py
# Non-fatal: the MCP bridge is optional (GUI-only convenience). GitHub raw
# occasionally rate-limits (429); retries + a warning keep builds reproducible.
RUN mkdir -p "${GHIDRAMCP_HOME}" \
    && ( wget -q --tries=3 --timeout=30 -O "${GHIDRAMCP_HOME}/bridge_mcp_ghidra.py" "${BRIDGE_URL}" \
         || echo "[warn] GhidraMCP bridge download failed; GUI MCP bridge disabled (headless workflow unaffected)" )

# -----------------------------------------------------------------------------
# 4. Python tooling (isolated venv; respects PEP 668)
# -----------------------------------------------------------------------------
RUN python3 -m venv "${VENV_HOME}" \
    && "${VENV_HOME}/bin/pip" install --no-cache-dir --upgrade pip \
    && "${VENV_HOME}/bin/pip" install --no-cache-dir \
        "mcp==1.5.0" "requests==2.32.3" "pyserial>=3.5" \
        capstone unicorn \
    && ( "${VENV_HOME}/bin/pip" install --no-cache-dir keystone-engine \
         || echo "[warn] keystone-engine unavailable on this platform; skipping (non-fatal)" )

# -----------------------------------------------------------------------------
# 5. Runtime user matching host uid/gid (no root needed at runtime)
# -----------------------------------------------------------------------------
RUN groupadd -g "${BUILD_GID}" chip \
    && useradd -m -u "${BUILD_UID}" -g "${BUILD_GID}" -s /bin/bash chip

COPY scripts/ghidra-scripts/ "${GHIDRA_SCRIPTS}/"
COPY conf/supervisord.conf /opt/conf/supervisord.conf
COPY conf/profile.d/chipbench.sh /etc/profile.d/chipbench.sh
COPY scripts/entrypoint.sh /opt/entrypoint.sh
RUN chmod +x /opt/entrypoint.sh \
    && chown -R chip:chip "${GHIDRA_SCRIPTS}" /opt/entrypoint.sh /opt/conf

USER chip:chip
WORKDIR /data

EXPOSE 5900 6080 8081

ENTRYPOINT ["/opt/entrypoint.sh"]
CMD ["supervisor"]
