# Per-repo harbor task Dockerfile for potion-wp-site (potion-polyglot PHP member). # # HAND-AUTHORED, not generated: PHP is not a runtime kind gen-harbor-dockerfiles.ts knows, # and adding one for a single member would be a larger change than it earns. The generator # gets a new kind when a second first-party PHP member turns up; the other estates' # members are Rust/JVM/.NET/Node. Precedent: strongsuit-client and strongsuit-server are also # hand-authored and sit outside MEMBERS. # # The repo is a full WordPress 5.9.2 checkout, but the first-party code is one directory: # wp-content/themes/potion, an Automattic _s derivative with real templates, an SCSS # pipeline and JS. WordPress core and the stock plugins around it are vendored. # # What makes this member gradable without a test suite is that the theme ships genuine # deterministic offline checks, and they are the reason to bake the toolchain rather than # just the source: # composer lint:php -> php-parallel-lint across the theme # composer lint:wpcs -> phpcs against the WordPress-theme ruleset the repo commits # itself in phpcs.xml.dist # npm run lint:js -> wp-scripts lint-js over js/*.js # npm run lint:scss -> wp-scripts lint-style over sass/**/*.scss # # No database. Grading needs none: every check above is static. *Running* the site would # need MySQL plus an import of one of the two committed SQL dumps — a documented run-app # limitation, the same shape as potion-api needing Mongo, and why this member is # runtime:'none' in TOOLKIT_GROUPS rather than bootable in Explore. FROM php:8.1-cli-bookworm ARG TOOLKIT_BUILD_ID=dev # Node 16 for the theme's node-sass/wp-scripts toolchain. node-sass builds native bindings # against a specific ABI and has no prebuilt binary for current Node, so a modern major # would fail to install rather than merely warn. ENV NODE_VERSION=16.20.2 RUN apt-get update && apt-get install -y --no-install-recommends \ git curl unzip ca-certificates xz-utils libzip-dev \ && rm -rf /var/lib/apt/lists/* \ && curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-arm64.tar.xz" \ -o /tmp/node.tar.xz \ && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \ && rm /tmp/node.tar.xz \ && node --version && npm --version # Python >=3.10 for the reduced-toolset agent's str_replace_editor (dataclass kw_only). # This block used to ASSERT the base already had it, on the claim that php:8.1-bookworm # ships python3.11. It does not — the assertion failed with exit 127 (python3: not found), # so the image could not build and this member could not be graded. Install a managed # interpreter via uv, the same way every generated per-member Dockerfile does. RUN curl -fsSL https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh \ && uv python install 3.10 \ && ln -sf "$(uv python find 3.10)" /usr/local/bin/python3 \ && python3 --version # composer, pinned by installer checksum rather than piping the web to php. RUN curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php \ && php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer \ && rm /tmp/composer-setup.php \ && composer --version # --- Playwright + Chromium, when the task opts in ---------------------------- # Installed only when task.toml sets `[metadata] browser = true`. A Dockerfile cannot read # task.toml, so build-workspace.sh writes that answer to environment/browser-optin. # Self-contained under /opt — the member's own runtime is untouched. ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright COPY browser-optin /tmp/browser-optin RUN set -eu; \ if [ "$(cat /tmp/browser-optin)" != "1" ]; then echo "browser: task did not opt in; skipping Playwright"; exit 0; fi; \ set -x; \ apt-get update -qq; \ apt-get install -y -qq --no-install-recommends \ xz-utils \ libxcomposite1 \ libxdamage1 \ libxfixes3 \ libxrandr2 \ libasound2 \ libatk1.0-0 \ libatk-bridge2.0-0 \ libatspi2.0-0 \ libcups2 \ libdbus-1-3 \ libgbm1 \ libnspr4 \ libnss3 \ libxkbcommon0 \ libpango-1.0-0 \ libcairo2 \ libxshmfence1 \ libx11-xcb1 \ libxcb-dri3-0 \ libdrm2; \ rm -rf /var/lib/apt/lists/*; \ arch="$(dpkg --print-architecture)"; \ case "$arch" in amd64) nodearch=x64;; arm64) nodearch=arm64;; *) echo "unsupported arch: $arch" >&2; exit 1;; esac; \ curl -fsSL "https://nodejs.org/dist/v20.19.5/node-v20.19.5-linux-${nodearch}.tar.xz" -o /tmp/pw-node.tar.xz; \ mkdir -p /opt/pw-node; \ tar -xJf /tmp/pw-node.tar.xz -C /opt/pw-node --strip-components=1; \ rm /tmp/pw-node.tar.xz; \ export npm_config_prefix=/opt/pw-node PATH="/opt/pw-node/bin:$PATH"; \ /opt/pw-node/bin/npm install -g playwright@1.56.0; \ test -d /opt/pw-node/lib/node_modules/playwright; \ /opt/pw-node/bin/node /opt/pw-node/lib/node_modules/playwright/cli.js install chromium; \ printf '#!/bin/sh\nNODE_PATH=/opt/pw-node/lib/node_modules exec /opt/pw-node/bin/node "$@"\n' > /usr/local/bin/pw; \ chmod +x /usr/local/bin/pw; \ printf 'const{chromium}=require("playwright");(async()=>{const b=await chromium.launch();const p=await b.newPage();await p.setContent("

ok

");if(await p.textContent("#t")!=="ok")throw new Error("bad render");await b.close();console.log("chromium OK");})()\n' > /tmp/pw-check.js; \ pw /tmp/pw-check.js; \ rm -f /tmp/pw-check.js WORKDIR /workspace # `COPY workspace/ .`, matching every generated per-member Dockerfile: build-workspace.sh # materialises the task tree at environment/workspace/, so a `COPY repo ...` here cannot # resolve and the image fails to build with "/repo: not found" — i.e. the member could not # be graded at all. Caught by authoring a task against this member rather than only # against the default one. COPY workspace/ . # Bake the theme's dev dependencies so the checks run offline. Both are best-effort: the # source plus a working toolchain is the substrate, and a registry hiccup at build time # must not make the whole member ungradable. # composer >=2.9 refuses by default to install any package carrying a security advisory, # and the theme's own linter (wp-coding-standards/wpcs 2.x, via wptrt/wpthemereview) is # flagged — so `composer install` resolved to nothing, vendor/bin stayed empty, and BOTH # deterministic checks failed with "Could not open input file" rather than with a verdict. # The advisory is on a dev-only linter that runs offline against this repo's own source, so # the block is turned off for this vendored toolchain rather than pinning the checks away. # Second half of the same story: composer 2 ABORTS a non-interactive install unless the # phpcodesniffer-composer-installer plugin is explicitly allowed, which is why the vendor # tree came out half-populated (packages downloaded, none installed, no vendor/bin entries). # That plugin is also what registers the WordPress standard with phpcs, so lint:wpcs cannot # work without it. RUN cd /workspace/wp-content/themes/potion \ && composer config --no-plugins policy.advisories.block false \ && composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true \ && (composer install --no-interaction --no-progress || \ echo "warning: composer install incomplete — lint:php / lint:wpcs may be unavailable") \ && (npm install --no-audit --no-fund || \ echo "warning: npm install incomplete — lint:js / lint:scss may be unavailable") ENV TOOLKIT_BUILD_ID=${TOOLKIT_BUILD_ID} # Install the Codex CLI at BUILD time, for the same reason claude is: the agent-setup # install needs the network, which the trial DNS jail blocks. Hard-fail rather than let a # codex-less image cache and break every trial on that repo at agent-setup. RUN for i in 1 2 3; do \ if curl -fsSL https://chatgpt.com/codex/install.sh -o /tmp/codex-install.sh \ && CODEX_INSTALL_DIR=/usr/local/bin CODEX_NON_INTERACTIVE=true sh /tmp/codex-install.sh; then break; fi; \ echo "WARNING: codex install attempt $i failed; retrying in 5s" >&2; sleep 5; \ done; \ rm -f /tmp/codex-install.sh; \ if ! command -v codex >/dev/null 2>&1 && [ -x "$HOME/.local/bin/codex" ]; then \ ln -sf "$HOME/.local/bin/codex" /usr/local/bin/codex; \ fi; \ if ! command -v codex >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then \ npm install -g @openai/codex@latest || true; \ fi; \ command -v codex >/dev/null 2>&1 \ && echo "codex installed at $(command -v codex)" \ || echo "WARNING: codex CLI not installed (see the install output above)" >&2 # Resolver for the trial DNS allowlist (scripts/lib/dns-jail.sh); if this # does not land, trials just run unjailed. RUN (command -v apk >/dev/null 2>&1 && apk add --no-cache dnsmasq bind-tools) \ || (apt-get update && apt-get install -y --no-install-recommends dnsmasq-base dnsutils \ && rm -rf /var/lib/apt/lists/*) \ || true # Restrict DNS to the model endpoint when DNSJAIL_ALLOW is set (the agent supplies it). # Source: scripts/lib/dns-jail-container.sh, staged here by build-workspace.sh. COPY dns-jail/ /opt/raccoon-dns-jail/ RUN if [ -f /opt/raccoon-dns-jail/dns-jail-container.sh ]; then \ install -m 0755 /opt/raccoon-dns-jail/dns-jail-container.sh /usr/local/bin/raccoon-dns-jail \ && sh -n /usr/local/bin/raccoon-dns-jail; \ else echo "NOTE: no DNS jail script staged; trials on this image run unjailed" >&2; fi