155 lines
6.8 KiB
Docker
155 lines
6.8 KiB
Docker
# Per-repo harbor task Dockerfile for stocks-in-the-future (rubyforgood, Rails 8.1 / Ruby 3.4.4).
|
|
#
|
|
# Postgres + Redis; importmap; Minitest; system specs use Selenium + headless Chrome (the
|
|
# repo's own driver config passes --no-sandbox + --disable-dev-shm-usage, so no chromium
|
|
# wrapper). Bakes the workspace + Claude Code (grader); git-commits a baseline.
|
|
FROM ruby:3.4.4
|
|
ARG TOOLKIT_BUILD_ID=dev
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
build-essential pkg-config libpq-dev \
|
|
postgresql postgresql-client redis-server \
|
|
chromium chromium-driver \
|
|
libvips libyaml-dev locales \
|
|
python3 \
|
|
git sudo curl ca-certificates gnupg xz-utils jq \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
RUN ln -sf /usr/bin/chromium /usr/local/bin/google-chrome \
|
|
&& ln -sf /usr/bin/chromium /usr/local/bin/google-chrome-stable
|
|
|
|
# en_US.UTF-8 Postgres collation (matches CI's postgres image; avoids locale-ordering issues).
|
|
RUN sed -i 's/^# *en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
|
|
&& locale-gen \
|
|
&& PG_VERSION=$(ls /etc/postgresql) \
|
|
&& pg_dropcluster "${PG_VERSION}" main \
|
|
&& LANG=en_US.UTF-8 pg_createcluster --locale en_US.UTF-8 "${PG_VERSION}" main
|
|
ENV LANG=en_US.UTF-8
|
|
ENV LC_ALL=en_US.UTF-8
|
|
|
|
RUN PG_VERSION=$(ls /etc/postgresql) \
|
|
&& printf 'local all all trust\nhost all all 127.0.0.1/32 trust\nhost all all ::1/128 trust\nhost all all 0.0.0.0/0 trust\n' > "/etc/postgresql/${PG_VERSION}/main/pg_hba.conf" \
|
|
&& echo "listen_addresses='*'" >> "/etc/postgresql/${PG_VERSION}/main/postgresql.conf"
|
|
|
|
# database.yml.sample has no host/user; pin PGHOST/PGUSER → connect as postgres over TCP (trust).
|
|
ENV PGHOST=localhost
|
|
ENV PGUSER=postgres
|
|
ENV REDIS_URL=redis://localhost:6379/1
|
|
|
|
RUN gem install bundler -v 2.6.7
|
|
|
|
# Install Claude Code globally (grader runs `claude`); hard-gate on presence.
|
|
RUN for i in 1 2 3; do \
|
|
if curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh; then break; fi; \
|
|
echo "WARNING: claude install attempt $i failed; retrying in 5s" >&2; sleep 5; \
|
|
done; \
|
|
rm -f /tmp/claude-install.sh; \
|
|
for p in /root/.claude-code/claude /root/.local/bin/claude "$(find /root -name claude -type f 2>/dev/null | head -1)"; do \
|
|
[ -n "$p" ] && [ -x "$p" ] && ln -sf "$p" /usr/local/bin/claude && break; \
|
|
done; \
|
|
command -v claude >/dev/null 2>&1 || { echo "FATAL: claude CLI not installed — the grader needs it" >&2; exit 1; }; \
|
|
echo "claude installed at $(command -v claude)"
|
|
|
|
USER root
|
|
|
|
# --- 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("<h1 id=t>ok</h1>");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/ .
|
|
|
|
# config/database.yml is gitignored; materialize it from the committed sample.
|
|
RUN if [ -f config/database.yml.sample ] && [ ! -f config/database.yml ]; then \
|
|
cp config/database.yml.sample config/database.yml; \
|
|
fi
|
|
|
|
RUN mkdir -p .claude && \
|
|
echo '{"permissions":{"deny":["WebFetch","WebSearch"]}}' > .claude/settings.json
|
|
|
|
RUN git init -q && \
|
|
git config user.email "dev@agent" && \
|
|
git config user.name "Dev" && \
|
|
git add -A && \
|
|
git commit -m "initial" --quiet
|
|
|
|
RUN bundle config set --local frozen false \
|
|
&& bundle lock --add-platform x86_64-linux \
|
|
&& bundle lock --add-platform aarch64-linux \
|
|
&& bundle install --jobs 4 --retry 3
|
|
|
|
# Same command Explore's post-create runs, so the trial renders the app the way its author
|
|
# saw it: app/assets/builds/ is gitignored, so without this the app renders unstyled here
|
|
# but styled in Explore, and a browser task would be judged against a page its author
|
|
# never saw. Not assets:precompile — that bakes a manifest which pins the server to stale
|
|
# assets, so an agent's CSS edit would never be served.
|
|
RUN bin/rails tailwindcss:build
|
|
|
|
RUN for t in ruby bundle psql redis-server chromium chromedriver claude python3; do \
|
|
command -v "$t" >/dev/null 2>&1 || { echo "FATAL: required tool '$t' missing from image" >&2; exit 1; }; \
|
|
done; \
|
|
echo "toolchain OK: ruby=$(ruby --version) claude=$(command -v claude)"
|
|
|
|
RUN git add -A && git commit --amend --no-edit --quiet
|
|
|
|
# Start Postgres + Redis, wait, create + schema-load the dev + test databases.
|
|
RUN cat > /usr/local/bin/start-services.sh <<'EOF'
|
|
#!/bin/bash
|
|
set -e
|
|
service postgresql start
|
|
service redis-server start || redis-server --daemonize yes
|
|
until pg_isready -h localhost -p 5432 -U postgres >/dev/null 2>&1; do sleep 0.5; done
|
|
cd /workspace && bin/rails db:create db:schema:load 2>/dev/null || true
|
|
cd /workspace && RAILS_ENV=test bin/rails db:create db:schema:load 2>/dev/null || true
|
|
exec "$@"
|
|
EOF
|
|
RUN chmod +x /usr/local/bin/start-services.sh
|
|
|
|
ENTRYPOINT ["/usr/local/bin/start-services.sh"]
|
|
CMD ["sleep", "infinity"]
|