Phase 5 of docs/agents/GITEA-SETUP.md, plus a correction to Phase 2 that the runbook could not have known it needed. gitea-mcp v1.7.0 goes into both images, pinned by the sha256 the release publishes and smoke-tested with `--version` at build time, so a bad pin fails the build instead of the agent. Each entrypoint registers it at user scope for that container's own identity, remove-then-add so a restart is idempotent. The token is passed BY PATH. `-e GITEA_ACCESS_TOKEN=$(cat …)` would write it in cleartext into ~/.claude.json, which every session in the container reads; GITEA_ACCESS_TOKEN_FILE is new in the pinned version and leaves the secret in its read-only mount. Verified against the binary's own --help, not assumed. The tool filter stops being an experiment. The names are in the release README: each agent gets issues, notifications, labels, milestones and pull requests, and NOT `pull_request_review_write`. That one matters because separate identities open a hole the runbook did not name: Gitea refuses to let an author approve their own pull request, and does nothing about sylph-decoder approving sylph-port's. Two agents could satisfy `required_approvals = 1` between themselves and then merge, since branch protection blocks pushes to main and never blocked merges. Withholding the tool is defence in depth; the controls are in branch protection, and both docs now say so: approvals whitelisted to the human so an agent's approval does not count, merges whitelisted to the human so an approved PR is still merged by a person. Phase 2's check gains the step that actually tests it -- approve the throwaway PR yourself, then confirm the agent STILL has no merge button. Without that step, the check passes on an instance where the agents can merge each other's work. Also settles two entries on the runbook's own "not verified" list: the tool filter names, and the Gitea version (1.25.5, whose API schema carries enable_merge_whitelist and enable_approvals_whitelist under those names). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
126 lines
6.6 KiB
Docker
126 lines
6.6 KiB
Docker
# Autonomous port agent for the Sylpheed Godot menu shell.
|
|
#
|
|
# DELIBERATELY SMALL. The reverse-engineering container next door is 4.36 GB
|
|
# because it builds Xenia Canary and drives it under a software Vulkan stack.
|
|
# This agent has no emulator, no oracle and no C++ build: it converts already-
|
|
# decoded assets and drives Godot. Keeping it light is what lets both containers
|
|
# run on one 12-core / 15 GB box without the memory pressure that has crashed it.
|
|
#
|
|
# What it needs, and nothing else: Rust (the exporter), Godot 4 (the runtime),
|
|
# ffmpeg (the transcode), and a headless display to screenshot Godot for
|
|
# comparison against the reference renderer.
|
|
|
|
FROM ubuntu:24.04
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive \
|
|
LANG=C.UTF-8 \
|
|
TZ=Etc/UTC
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
# toolchain for the exporter and for building sylpheed-cli from /reborn
|
|
build-essential pkg-config git curl ca-certificates \
|
|
libssl-dev \
|
|
# Godot 4 needs these even headless; the windowed run needs the X libs
|
|
libx11-6 libxcursor1 libxinerama1 libxrandr2 libxi6 libgl1 \
|
|
libasound2t64 libpulse0 libfontconfig1 \
|
|
# the transcode target (libtheora + libvorbis ship in Ubuntu's ffmpeg)
|
|
ffmpeg \
|
|
# headless display + the screenshot path, for diffing Godot's output
|
|
# against `sylpheed-cli screen render`
|
|
xvfb x11-utils openbox imagemagick \
|
|
# everyday
|
|
python3 jq ripgrep unzip file less nano tini sudo procps \
|
|
# expect drives Claude Code's one-time interactive gates
|
|
expect \
|
|
# PulseAudio, for capturing audio without a sound card. `module-null-sink`
|
|
# is a real device as far as any application is concerned, so the emulator
|
|
# and Godot open it normally and `parec` records what they play. Without
|
|
# it, "does this actually sound right" is unanswerable in a container --
|
|
# and the cue-to-event bindings stay a name match rather than a
|
|
# measurement. See docs/port/AUDIO-VERIFICATION.md.
|
|
pulseaudio pulseaudio-utils \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# ── Godot 4 ──────────────────────────────────────────────────────────────────
|
|
# Pinned. An engine version bump changes rendering, and this project compares
|
|
# screenshots against a reference renderer — so an upgrade must be a deliberate,
|
|
# stated act rather than a silent drift.
|
|
ARG GODOT_VERSION=4.7.2
|
|
RUN cd /tmp \
|
|
&& curl -fsSLO "https://github.com/godotengine/godot/releases/download/${GODOT_VERSION}-stable/Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip" \
|
|
&& unzip -q "Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip" \
|
|
&& mv "Godot_v${GODOT_VERSION}-stable_linux.x86_64" /usr/local/bin/godot \
|
|
&& chmod +x /usr/local/bin/godot \
|
|
&& printf '#!/bin/sh\nexec /usr/local/bin/godot --headless "$@"\n' > /usr/local/bin/godot-headless \
|
|
&& chmod +x /usr/local/bin/godot-headless \
|
|
&& rm -f "Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip"
|
|
|
|
# ── Node + Claude Code ───────────────────────────────────────────────────────
|
|
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
|
&& apt-get install -y --no-install-recommends nodejs \
|
|
&& npm install -g @anthropic-ai/claude-code \
|
|
&& npm cache clean --force \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# ── gitea-mcp ────────────────────────────────────────────────────────────────
|
|
# The agent's hands on issues, pull requests and notifications — Gitea's own MCP
|
|
# server, so there is no second store of truth to drift out of sync with the
|
|
# first.
|
|
#
|
|
# PINNED AND CHECKSUMMED, not "whatever is at that URL today": this binary is
|
|
# handed a token that can write to the repository. The checksum is the one
|
|
# published in `gitea-mcp_1.7.0_checksums.txt` for the Linux x86_64 asset.
|
|
ARG GITEA_MCP_VERSION=1.7.0
|
|
ARG GITEA_MCP_SHA256=bbc9a7b462facd3c56b1558ee6054e91f2fca27a2878b5599afddcf57d446b8d
|
|
RUN curl -fsSL -o /tmp/gitea-mcp.tar.gz \
|
|
"https://gitea.com/gitea/gitea-mcp/releases/download/v${GITEA_MCP_VERSION}/gitea-mcp_Linux_x86_64.tar.gz" \
|
|
&& echo "${GITEA_MCP_SHA256} /tmp/gitea-mcp.tar.gz" | sha256sum -c - \
|
|
&& tar -xzf /tmp/gitea-mcp.tar.gz -C /usr/local/bin gitea-mcp \
|
|
&& chmod +x /usr/local/bin/gitea-mcp \
|
|
&& rm -f /tmp/gitea-mcp.tar.gz \
|
|
&& gitea-mcp --version
|
|
|
|
# ── The agent user ───────────────────────────────────────────────────────────
|
|
# NOT root: Claude Code refuses --dangerously-skip-permissions with root
|
|
# privileges. Ubuntu 24.04 ships its own `ubuntu` account at uid 1000, so the
|
|
# common case — matching a host user who is also 1000 — collides with it.
|
|
ARG AGENT_UID=1000
|
|
ARG AGENT_GID=1000
|
|
RUN if getent passwd "${AGENT_UID}" >/dev/null; then \
|
|
userdel -r "$(getent passwd "${AGENT_UID}" | cut -d: -f1)" 2>/dev/null || true; \
|
|
fi; \
|
|
if getent group "${AGENT_GID}" >/dev/null; then \
|
|
groupdel "$(getent group "${AGENT_GID}" | cut -d: -f1)" 2>/dev/null || true; \
|
|
fi; \
|
|
groupadd -g "${AGENT_GID}" agent \
|
|
&& useradd -m -u "${AGENT_UID}" -g "${AGENT_GID}" -s /bin/bash -d /sylph-home/port agent \
|
|
&& mkdir -p /sylph-home/port /work /exchange /reborn \
|
|
&& chown -R "${AGENT_UID}:${AGENT_GID}" /sylph-home /work /exchange \
|
|
&& echo 'agent ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/agent
|
|
|
|
COPY bin/ /usr/local/bin/
|
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
RUN chmod +x /usr/local/bin/* /usr/local/bin/entrypoint.sh
|
|
|
|
USER agent
|
|
WORKDIR /work
|
|
|
|
# CARGO_TARGET_DIR points OUTSIDE the bind-mounted repo so the host and the
|
|
# container do not invalidate each other's incremental state on every switch.
|
|
ENV RUSTUP_HOME=/sylph-home/port/.rustup \
|
|
CARGO_HOME=/sylph-home/port/.cargo \
|
|
CARGO_TARGET_DIR=/sylph-home/port/target-container \
|
|
PATH=/work/tools:/sylph-home/port/.cargo/bin:/usr/local/bin:/usr/bin:/bin
|
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
|
| sh -s -- -y --default-toolchain stable --profile minimal --component clippy --component rustfmt
|
|
|
|
RUN mkdir -p /sylph-home/port/target-container /sylph-home/port/.claude
|
|
|
|
ENV HOME=/sylph-home/port \
|
|
DISPLAY=:97 \
|
|
SCREEN_GEOMETRY=1280x720x24 \
|
|
PROJECT_DIR=/work
|
|
|
|
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
|
|
CMD ["bash"]
|