# syntax=docker/dockerfile:1.7
#
# uai standard image — the ONE per-host workbench image (ADR-022).
#
# Built once per host at host startup and cached as `uai-standard:dev`.
# A task reuses this image unchanged unless a selected project declares an
# `extra` snippet, in which case task-up derives `uai-task-<id>` FROM this
# image. See ../../../docs/runtime.md.
#
# Design points:
#   - Debian bookworm-slim base, non-root `node` user (home /home/node).
#   - asdf at /opt/asdf with plugins for nodejs/python/golang/ruby/rust.
#     Concrete runtime versions are installed lazily at task-up by uai-init,
#     cached on the host-wide named volume mounted at /opt/asdf-data.
#   - A default ~/.tool-versions (asdf global fallback) pins a working Node +
#     Python so the agent CLIs and a 0-project scratchpad always have a runtime;
#     a project's own .tool-versions takes precedence (asdf dir walk).
#   - Claude Code + Codex + code-server installed globally via the root Node.
#
# This Dockerfile takes NO build args and substitutes NO placeholders — it is
# fully static. Per-task variation lives in the derived `uai-task-<id>` image.

FROM debian:bookworm-slim

# ---------------------------------------------------------------------------
# 0. Pinned versions (single source of truth for this image).
# ---------------------------------------------------------------------------

# asdf release to clone. Pinned for reproducible builds; bump deliberately.
ENV ASDF_VERSION=v0.14.1

# ---------------------------------------------------------------------------
# 1. System packages.
#
# build-essential + the -dev libs cover what asdf's nodejs/python/ruby/rust
# build plugins need to compile a runtime from source on first use. unzip is
# needed by several asdf plugins; gnupg + ca-certificates for the gh apt repo.
# ---------------------------------------------------------------------------

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
 && apt-get install -y --no-install-recommends \
      build-essential \
      ca-certificates \
      curl \
      direnv \
      git \
      gnupg \
      jq \
      libbz2-dev \
      libffi-dev \
      liblzma-dev \
      libncurses-dev \
      libreadline-dev \
      libsqlite3-dev \
      libssl-dev \
      libxml2-dev \
      libxmlsec1-dev \
      libyaml-dev \
      openssh-client \
      pkg-config \
      procps \
      ripgrep \
      tk-dev \
      tmux \
      unzip \
      xz-utils \
      zlib1g-dev \
      zsh \
 && rm -rf /var/lib/apt/lists/*

# GitHub CLI (`gh`) — used by the ship/PR flow inside the container.
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
      | tee /usr/share/keyrings/githubcli-archive-keyring.gpg >/dev/null \
 && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
 && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
      > /etc/apt/sources.list.d/github-cli.list \
 && apt-get update \
 && apt-get install -y --no-install-recommends gh \
 && rm -rf /var/lib/apt/lists/*

# uv — fast Python package + venv manager (used by uai-init's Python path).
# Installed system-wide so both root and node can run it.
RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh

# ---------------------------------------------------------------------------
# 2. Non-root `node` user.
#
# The container runs as `node`; uai-init and the agent CLIs expect
# /home/node. uid/gid 1000 matches the common host default so bind-mounted
# worktree files stay writable.
# ---------------------------------------------------------------------------

RUN groupadd --gid 1000 node \
 && useradd --uid 1000 --gid 1000 --create-home --home-dir /home/node --shell /bin/bash node

# ---------------------------------------------------------------------------
# 3. asdf.
#
# Clone to /opt/asdf (the code) and point the DATA dir at /opt/asdf-data
# (the host-wide cache volume mounts there). Both root and node must be able
# to read the install and write the data dir, so /opt/asdf-data is group
# owned by `node` and group-writable; the data volume inherits this.
# ---------------------------------------------------------------------------

ENV ASDF_DIR=/opt/asdf
ENV ASDF_DATA_DIR=/opt/asdf-data
# Leave the tool-versions filename at asdf's default (`.tool-versions`) so asdf
# walks UP from the cwd and a project's /workspace/<slug>/.tool-versions wins.
# The baked default lives at the node user's home (~/.tool-versions) as asdf's
# global fallback for tasks with no project file (agent CLIs, scratchpad).
# Do NOT set ASDF_DEFAULT_TOOL_VERSIONS_FILENAME to an absolute path — that
# overrides the search *filename* and bypasses project files entirely (it made
# asdf read only /etc/.tool-versions, ignoring per-project pins).

RUN git clone --depth 1 --branch "$ASDF_VERSION" https://github.com/asdf-vm/asdf.git "$ASDF_DIR" \
 && mkdir -p "$ASDF_DATA_DIR" \
 && chown -R node:node "$ASDF_DIR" "$ASDF_DATA_DIR" \
 && chmod -R g+rwX "$ASDF_DATA_DIR"

# Make asdf available on PATH + shell init for BOTH root and node, for login
# and non-login shells (uai-init runs as node via a non-interactive exec).
#   - asdf.sh sources the shim + completions and prepends the shims dir.
#   - The shims dir is also added to PATH directly so a bare `docker exec`
#     (which sources neither profile) still sees installed runtimes.
ENV PATH=/opt/asdf-data/shims:/opt/asdf/bin:$PATH

RUN printf '%s\n' \
      '. /opt/asdf/asdf.sh' \
      > /etc/profile.d/asdf.sh \
 && chmod 0644 /etc/profile.d/asdf.sh

# Source asdf from each user's bashrc as well (interactive non-login shells,
# e.g. the code-server terminal) and enable direnv.
RUN for rc in /root/.bashrc /home/node/.bashrc; do \
      printf '\n%s\n%s\n' \
        '. /opt/asdf/asdf.sh' \
        'eval "$(direnv hook bash)"' >> "$rc"; \
    done \
 && chown node:node /home/node/.bashrc

# zsh + oh-my-zsh as the interactive login shell for `node` (the code-server
# terminal and `docker exec -it`). Installed unattended; the installer writes a
# default ~/.zshrc (robbyrussell theme, git plugin) which we extend with the
# same asdf + direnv wiring as .bashrc. Non-interactive execs (uai-init, claude,
# codex, git) invoke their command directly, so the login-shell change does not
# affect them.
RUN su node -c 'sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended' \
 && printf '\n%s\n%s\n' \
      '. /opt/asdf/asdf.sh' \
      'eval "$(direnv hook zsh)"' >> /home/node/.zshrc \
 && chown node:node /home/node/.zshrc \
 && usermod -s /usr/bin/zsh node

# ---------------------------------------------------------------------------
# 4. asdf plugins + the root default runtimes.
#
# Plugins are added for all common runtimes; only the root defaults are
# *installed* at build time. Other versions are installed lazily by uai-init
# and cached on the /opt/asdf-data volume.
#
# Run as node so the install lands in /opt/asdf-data owned by node.
# ---------------------------------------------------------------------------

# Default .tool-versions at the node user's home = asdf's GLOBAL fallback, used
# only when no project /workspace/<slug>/.tool-versions is in scope (agent CLIs,
# scratchpad tasks). A project file takes precedence via asdf's dir walk. The
# `tool-versions` file in this directory is the AUTHORITATIVE source.
COPY tool-versions /home/node/.tool-versions
RUN chmod 0644 /home/node/.tool-versions \
 && chown node:node /home/node/.tool-versions

USER node

# Add plugins for all common runtimes, then install exactly the versions the
# default ~/.tool-versions pins (read straight from the file so there is one
# source of truth). Other versions install lazily at task-up via uai-init.
RUN bash -lc '\
  set -e; \
  . /opt/asdf/asdf.sh; \
  asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git; \
  asdf plugin add python https://github.com/asdf-community/asdf-python.git; \
  asdf plugin add golang https://github.com/asdf-community/asdf-golang.git; \
  asdf plugin add ruby   https://github.com/asdf-vm/asdf-ruby.git; \
  asdf plugin add rust   https://github.com/asdf-community/asdf-rust.git; \
  cd /home/node && asdf install nodejs && asdf install python; \
'

# ---------------------------------------------------------------------------
# 5. Global agent CLIs + code-server, via the root-default Node.
#
# These are Node programs and must be present even for a 0-project task, so
# they are installed globally against the asdf-default Node and then reshimmed
# so `claude` / `codex` / `code-server` resolve through the asdf shims dir
# (which is on PATH for a bare docker exec).
# ---------------------------------------------------------------------------

RUN bash -lc '\
  set -e; \
  . /opt/asdf/asdf.sh; \
  npm install -g @anthropic-ai/claude-code @openai/codex; \
  curl -fsSL https://code-server.dev/install.sh | sh -s -- --method standalone --prefix /home/node/.local; \
  asdf reshim nodejs; \
'

ENV PATH=/home/node/.local/bin:$PATH

# ---------------------------------------------------------------------------
# 6. uai-init baked in.
# ---------------------------------------------------------------------------

USER root

COPY --chown=root:root container/uai-init /usr/local/bin/uai-init
RUN chmod 0755 /usr/local/bin/uai-init

# Curated code-server defaults. uai-init seeds these into the per-task User
# settings dir when none exists (slim chrome, telemetry off, trust off).
COPY --chown=root:root container/code-server-settings.json /usr/local/share/uai/code-server-settings.json

# ---------------------------------------------------------------------------
# 7. Default working directory + entrypoint.
#
# The container is a long-lived workbench. uai-init (deps + code-server) and
# the agent CLIs are started by the host via `docker exec` after task-up.
# ---------------------------------------------------------------------------

USER node
WORKDIR /workspace

CMD ["sleep", "infinity"]
