409 lines
25 KiB
Docker
409 lines
25 KiB
Docker
FROM coder-devbox-base:latest AS base-default
|
|
|
|
USER root
|
|
|
|
# socat: bridges kilo acp's stdio-only Agent Client Protocol onto a plain TCP
|
|
# socket for peer-workspace collaboration (see peer-mcp-bridge.py / README's
|
|
# "Peer workspaces" section) — kilo serve has no MCP server endpoint of its
|
|
# own, ACP is the only real path to another workspace's live agent session.
|
|
RUN apt-get update -qq \
|
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends socat \
|
|
&& apt-get clean \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# ENV NVM_DIR=/home/coder/.nvm
|
|
ENV NVM_DIR=/usr/local/nvm
|
|
# Exposes every global npm install (not just a hardcoded allowlist) without
|
|
# per-binary symlinks — same pattern as dotnet/ruby's flavor Dockerfiles.
|
|
ENV PATH="$NVM_DIR/versions/node/active/bin:${PATH}"
|
|
|
|
RUN mkdir -p "$NVM_DIR" \
|
|
&& chown -R coder:coder "$NVM_DIR"
|
|
|
|
USER coder
|
|
|
|
# Install NVM
|
|
RUN curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/refs/heads/master/install.sh" | bash
|
|
|
|
|
|
# Sanity check that it actually landed where expected
|
|
RUN test -s "$NVM_DIR/nvm.sh" && echo "nvm.sh found at $NVM_DIR" || (echo "nvm install failed" && exit 1)
|
|
|
|
RUN bash -c "source $NVM_DIR/nvm.sh \
|
|
&& nvm install 20 \
|
|
&& nvm alias default 20 \
|
|
&& nvm use default \
|
|
&& npm install -g @kilocode/cli \
|
|
&& npm install -g @openai/codex \
|
|
&& ln -sfn \$NVM_DIR/versions/node/\$(nvm version default) \$NVM_DIR/versions/node/active"
|
|
|
|
RUN echo "[ Installing Claude Code (native installer) ]" \
|
|
&& curl -fsSL https://claude.ai/install.sh | bash \
|
|
&& "$HOME/.local/bin/claude" --version
|
|
|
|
# uv/uvx (Astral's Python package manager) — needed for MiniMax's own MCP
|
|
# servers (minimax-mcp, minimax-coding-plan-mcp; see the "MiniMax multimodal
|
|
# MCP servers" section of devenv/README.md for why these Python packages were
|
|
# picked over the community JS port). Installed here as `coder` (same as the
|
|
# Claude Code native installer just above) so the binaries land in
|
|
# $HOME/.local/bin, then copied out to /usr/local/bin below — same
|
|
# outside-/home/coder reasoning as the claude binary copy further down.
|
|
RUN echo "[ Installing uv (for MiniMax's official Python MCP servers) ]" \
|
|
&& curl -LsSf https://astral.sh/uv/install.sh | sh \
|
|
&& "$HOME/.local/bin/uv" --version
|
|
|
|
# Bake plugins + skills into the image rather than installing them from every
|
|
# workspace's startup_script: they land under /home/coder/.claude, and since
|
|
# /home/coder is a per-workspace *named* Docker volume (see devenv/main.tf's
|
|
# docker_volume.home_volume) that's empty the first time any container mounts
|
|
# it, Docker copies this image layer's content into it on that first mount —
|
|
# so every freshly created workspace gets these pre-installed for free, with
|
|
# zero network calls or plugin-CLI invocations at startup. (Pre-existing
|
|
# workspaces' volumes are already non-empty and won't pick this up
|
|
# automatically; those need a one-time manual install.)
|
|
#
|
|
# `claude plugin marketplace update` first: the marketplace ships with a
|
|
# baked-in snapshot that's stale relative to GitHub by the time this image
|
|
# builds (frontend-design/superpowers/code-review 404'd against it in a real
|
|
# workspace — the CLI's own error suggested this exact fix), so an explicit
|
|
# refresh is required before install, `marketplace add` alone isn't enough.
|
|
# typescript-lsp installed here too (plugin registration only — it needs
|
|
# typescript-language-server on PATH to actually do anything, installed a
|
|
# few steps down as root; the plugin CLI doesn't touch/require the LSP
|
|
# binary at install time, only at use time). Installed here rather than
|
|
# alongside it because this whole block runs as `coder`, so it lands
|
|
# correctly under /home/coder/.claude with the right ownership — the
|
|
# typescript-language-server RUN step below runs as root, and this same
|
|
# `claude plugin install` there would write root-owned files into a
|
|
# directory the `coder` user needs to write to at runtime (marketplace
|
|
# cache updates, enabling/disabling plugins). `superpowers` is left enabled
|
|
# (no `plugin disable` call) — on by default for every workspace.
|
|
#
|
|
# `impeccable` (pbakaus/impeccable) is installed from its own marketplace
|
|
# here too, not via its CLI installer: as of v4.0.2 it ships a real
|
|
# `.claude-plugin/marketplace.json` + `plugin/.claude-plugin/plugin.json`
|
|
# (confirmed against the live repo — this didn't exist when the CLI
|
|
# installer was first wired up), so it's a genuine first-party-shaped
|
|
# plugin for Claude Code now, same install shape as the official plugins
|
|
# above. Marketplace name and plugin name are both `impeccable` (per its
|
|
# own marketplace.json), hence `impeccable@impeccable`. Codex still gets it
|
|
# via the separate CLI-installer step further down — it has no
|
|
# `.codex-plugin/` manifest, so this marketplace only covers Claude Code.
|
|
#
|
|
# `context7` (upstash/context7) similarly ships a real
|
|
# `.claude-plugin/marketplace.json` (marketplace name `context7-marketplace`,
|
|
# confirmed via GitHub API), bundling a `context7-mcp` skill and a `/docs`
|
|
# command on top of the context7 MCP server registration devenv/main.tf
|
|
# already wires up by hand — installing it here is a strict addition, not a
|
|
# replacement (Claude Code's MCP precedence is CLI-flag config > plugin
|
|
# config, so the plugin's own duplicate "context7" MCP entry is silently
|
|
# ignored, no conflict). Purpose: push the agent to actually pull current
|
|
# library docs before guessing/trial-and-error-scripting an API surface,
|
|
# rather than relying on the bare MCP entry alone. Note its repo layout
|
|
# splits Claude's plugin (`plugins/claude/context7`) and Codex's
|
|
# (`plugins/codex/context7`) into separate directories neither one's
|
|
# `marketplace.json` cross-references, unlike the shared same-directory
|
|
# dual-manifest shape claude-plugins-official uses above — so unlike that
|
|
# marketplace, `codex plugin add` can't discover this one; Codex keeps using
|
|
# the hand-wired MCP entry only, same as before this change.
|
|
#
|
|
# `playwright` needs no separate `marketplace add` — it's published straight
|
|
# from the same `claude-plugins-official` marketplace already added above.
|
|
# Confirmed against that marketplace's live marketplace.json: entry `"name":
|
|
# "playwright", "source": "./external_plugins/playwright"`, whose
|
|
# `.claude-plugin/plugin.json` + `.mcp.json` (`npx @playwright/mcp@latest`)
|
|
# duplicate the hand-wired `playwright`/`playwright-visible` MCP entries
|
|
# devenv/main.tf already renders. CORRECTION (found live on
|
|
# chris/portal-rework, see devenv/README.md's playwright section): "CLI-flag
|
|
# config wins over plugin config" only holds when a session is actually
|
|
# invoked with `--mcp-config` — any invocation that skips it (a headless/
|
|
# background job, an IDE-launched session) falls through to this plugin's
|
|
# broken un-flagged default (real Chrome, never installed) instead. Fixed via
|
|
# a project-root `.mcp.json` render in main.tf (`claude_project_mcp_config`),
|
|
# not anything in this Dockerfile. No `.codex-plugin` manifest exists for
|
|
# playwright (confirmed against the repo, same situation as impeccable), so
|
|
# Codex keeps using only its own hand-wired entry.
|
|
#
|
|
# `ralph-loop`, `playground`, `feature-dev`, `serena`, `figma` — all 5
|
|
# confirmed real via a direct fetch of claude-plugins-official's own
|
|
# marketplace.json (not just trusting search results/subagent summaries,
|
|
# same verification standard as every plugin above). `ralph-loop`/
|
|
# `playground`/`feature-dev` are pure skill/command/agent plugins with no
|
|
# `.mcp.json` at all (confirmed via each plugin's own directory listing).
|
|
# Unlike impeccable (whose OWN standalone CLI installer drops plain
|
|
# SKILL.md content into ~/.agents/skills/, a directory Kilo also scans by
|
|
# default per its own docs — see devenv/README.md's impeccable section for
|
|
# the live-confirmed correction), a Claude Code *marketplace plugin*'s
|
|
# content stays nested under ~/.claude/plugins/cache/..., never flattened
|
|
# into ~/.claude/skills/ or ~/.agents/skills/ — so there's genuinely nothing
|
|
# for Kilo's compatibility scan to find here. Claude Code only, no Kilo/
|
|
# Codex equivalent exists to wire up, same conclusion as superpowers.
|
|
# `serena` (semantic code
|
|
# analysis, `uvx --from git+https://github.com/oraios/serena serena
|
|
# start-mcp-server` — `uvx` already installed below) and `figma` (Figma's
|
|
# own remote MCP, `https://mcp.figma.com/mcp`) both do bundle a real MCP
|
|
# server, hand-wired separately into kilo.jsonc.tftpl and
|
|
# codex_serena_mcp_toml/codex_figma_mcp_toml in devenv/main.tf for
|
|
# cross-harness parity — see those files' comments. Figma's remote MCP
|
|
# authenticates via an interactive OAuth browser flow with no documented
|
|
# headless alternative; installed anyway for config parity, but not yet
|
|
# confirmed to actually complete that handshake inside a display-less
|
|
# container. None of these 5 ship a `.codex-plugin` manifest either
|
|
# (confirmed against each plugin's directory listing), so `codex plugin add`
|
|
# can't discover any of them regardless.
|
|
#
|
|
# `code-simplifier`, `github`, `claude-security`, `security-guidance` — same
|
|
# marketplace, same verification standard as above. Two of these ship hooks
|
|
# worth knowing about before debugging surprising behavior: `claude-security`'s
|
|
# hook only fires on its own `/claude-security` slash-command menu (a banner,
|
|
# no side effects), but `security-guidance` genuinely runs on every session
|
|
# unconditionally (SessionStart/UserPromptSubmit/PostToolUse/Stop) — pattern-
|
|
# based warnings on every Edit/Write and an LLM-based diff review on Stop and
|
|
# on `git commit`/`git push`, which can force Claude to keep going (Stop hook
|
|
# exit code 2) until it addresses findings. `github` bundles its own MCP
|
|
# server against GitHub's Copilot MCP endpoint, needs the
|
|
# `github-personal-access-token` Coder secret before it actually authenticates
|
|
# (see devenv/README.md's "Secrets" section) — installed and enabled either
|
|
# way, just 401s until that secret exists. None of these 4 ship a
|
|
# `.codex-plugin` manifest either, Claude Code only.
|
|
RUN CLAUDE="$HOME/.local/bin/claude" \
|
|
&& echo "[ Installing Claude Code plugins: frontend-design, superpowers, code-review, typescript-lsp, impeccable, context7, playwright, ralph-loop, playground, feature-dev, serena, figma, code-simplifier, github, claude-security, security-guidance ]" \
|
|
&& ( "$CLAUDE" plugin marketplace update claude-plugins-official \
|
|
|| "$CLAUDE" plugin marketplace add anthropics/claude-plugins-official ) \
|
|
&& "$CLAUDE" plugin install frontend-design@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install superpowers@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install code-review@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install typescript-lsp@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install playwright@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install ralph-loop@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install playground@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install feature-dev@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install serena@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install figma@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install code-simplifier@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install github@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install claude-security@claude-plugins-official \
|
|
&& "$CLAUDE" plugin install security-guidance@claude-plugins-official \
|
|
&& "$CLAUDE" plugin marketplace add pbakaus/impeccable \
|
|
&& "$CLAUDE" plugin install impeccable@impeccable \
|
|
&& "$CLAUDE" plugin marketplace add upstash/context7 \
|
|
&& "$CLAUDE" plugin install context7@context7-marketplace
|
|
|
|
# Codex CLI plugins — same claude-plugins-official marketplace as the Claude
|
|
# Code block above. Its plugins ship a `.codex-plugin/plugin.json` manifest
|
|
# alongside `.claude-plugin/plugin.json` — confirmed live: `codex plugin add
|
|
# frontend-design@claude-plugins-official` installs cleanly, and
|
|
# typescript-lsp's `.codex-plugin/plugin.json` declares a native
|
|
# `lspServers` entry pointing at the same typescript-language-server this
|
|
# image installs below — this is a real first-party cross-agent
|
|
# marketplace, not something scraped or repurposed. Lands under
|
|
# /home/coder/.codex, the same per-workspace named volume as Claude's
|
|
# ~/.claude — same bake-into-the-image reasoning as the Claude block above.
|
|
# `codex` resolves via $NVM_DIR/versions/node/active/bin already on PATH
|
|
# (see ENV PATH at the top of this file), no full path needed the way
|
|
# Claude's native installer requires "$HOME/.local/bin/claude".
|
|
#
|
|
# No `codex plugin disable` subcommand exists (confirmed against `codex
|
|
# plugin --help`/`add --help`/`remove --help`) — a plugin's enabled state
|
|
# lives entirely in config.toml's `[plugins."x@y"].enabled` key, which
|
|
# devenv/main.tf's codex_config re-renders on every codex_config_version
|
|
# bump anyway (same enabledPlugins-style fix already applied for Claude
|
|
# Code), so superpowers' enabled state is set there instead of here (on by
|
|
# default, same as Claude Code).
|
|
RUN echo "[ Installing Codex CLI plugins: frontend-design, superpowers, code-review, typescript-lsp ]" \
|
|
&& ( codex plugin marketplace upgrade claude-plugins-official \
|
|
|| codex plugin marketplace add anthropics/claude-plugins-official ) \
|
|
&& codex plugin add frontend-design@claude-plugins-official \
|
|
&& codex plugin add superpowers@claude-plugins-official \
|
|
&& codex plugin add code-review@claude-plugins-official \
|
|
&& codex plugin add typescript-lsp@claude-plugins-official
|
|
|
|
# mcp-builder / web-artifacts-builder: plain SKILL.md directories from the
|
|
# public anthropics/skills repo, not plugins, so fetched via tarball straight
|
|
# into ~/.claude/skills/ instead of through the plugin CLI. Same
|
|
# baked-into-the-image reasoning as the plugins above.
|
|
RUN mkdir -p "$HOME/.claude/skills" \
|
|
&& curl -sL "https://github.com/anthropics/skills/archive/refs/heads/main.tar.gz" -o /tmp/anthropic-skills.tar.gz \
|
|
&& tar -xzf /tmp/anthropic-skills.tar.gz -C "$HOME/.claude/skills" --strip-components=2 \
|
|
skills-main/skills/mcp-builder skills-main/skills/web-artifacts-builder \
|
|
&& rm -f /tmp/anthropic-skills.tar.gz
|
|
|
|
# impeccable (pbakaus/impeccable) for Codex only — its own official CLI
|
|
# installer, not a plugin marketplace: unlike Claude Code (installed as a
|
|
# real marketplace plugin above), it still ships no `.codex-plugin/`
|
|
# manifest (confirmed against the repo), so `codex plugin add` can't see it
|
|
# the way frontend-design/superpowers/etc. above can; the CLI installer is
|
|
# the only path for Codex. `--scope=global` writes plain SKILL.md content to
|
|
# ~/.agents/skills/impeccable (confirmed live — Codex CLI reads skills from
|
|
# $HOME/.agents/skills globally, per its own docs), landing in the same
|
|
# per-workspace home volume as everything else in this block, so it's free
|
|
# on first mount. `--providers=codex` only: Claude Code would also accept
|
|
# `claude` here, but that'd double-install the skill content the
|
|
# `impeccable@impeccable` marketplace plugin above already provides
|
|
# (its plugin.json bundles its own `./.claude/skills/`) — no need for both.
|
|
# `--no-hooks` skips the optional per-project PostToolUse/Stop design-check
|
|
# hook (`.codex/hooks.json`) — it's scoped to whatever directory the
|
|
# installer runs in, which here is the image build context, not any real
|
|
# future project, and Codex's hook additionally needs an interactive
|
|
# per-project `/hooks` trust approval that doesn't fit a headless build
|
|
# step; the skill itself (the `/impeccable` commands) works fully without
|
|
# it. Package declares `engines.node >= 22.12.0`; this image's default Node
|
|
# is 20 (see `nvm install 20` above) — confirmed live that this is only an
|
|
# npm engine warning, not a hard failure, so no separate Node version is
|
|
# provisioned just for this one build-time install.
|
|
RUN bash -c "source $NVM_DIR/nvm.sh \
|
|
&& npx --yes impeccable@latest install --providers=codex --scope=global --yes --no-hooks"
|
|
|
|
USER root
|
|
|
|
# Copy (not symlink — the home copy may get overwritten by auto-update,
|
|
# self-update, or a stale/older volume) into image-owned territory so a
|
|
# fresh or stale workspace always has a working claude binary immediately
|
|
RUN cp "/home/coder/.local/bin/claude" /usr/local/bin/claude \
|
|
&& chmod +x /usr/local/bin/claude \
|
|
&& /usr/local/bin/claude --version
|
|
|
|
# Same copy-out reasoning as the claude binary above, for uv/uvx.
|
|
RUN cp "/home/coder/.local/bin/uv" /usr/local/bin/uv \
|
|
&& cp "/home/coder/.local/bin/uvx" /usr/local/bin/uvx \
|
|
&& chmod +x /usr/local/bin/uv /usr/local/bin/uvx \
|
|
&& /usr/local/bin/uv --version
|
|
|
|
# LSP servers for Kilo's "lsp": true config — bash and typescript are common
|
|
# enough across every devbox flavor to belong here rather than per-flavor.
|
|
# (typescript-lsp@claude-plugins-official, Claude Code's equivalent, is
|
|
# installed above in the USER-coder block, not here — see that comment.
|
|
# Language-specific "-lsp" plugins for ruby/php/csharp are installed the
|
|
# same way in their own flavor Dockerfiles instead, since those languages
|
|
# aren't in every image.)
|
|
RUN bash -c "source $NVM_DIR/nvm.sh \
|
|
&& npm install -g bash-language-server typescript-language-server typescript"
|
|
|
|
# Playwright MCP server (for kilo.jsonc's "playwright" MCP entry). Browsers go to
|
|
# /opt/ms-playwright (not ~/.cache/ms-playwright under /home/coder) since that
|
|
# home directory is a per-workspace persistent volume that shadows anything
|
|
# baked into the image after its first-ever creation — see kilo.jsonc.tftpl notes.
|
|
#
|
|
# Only @playwright/mcp is installed globally — NOT a separate top-level
|
|
# `playwright` package. @playwright/mcp pins its own playwright-core version
|
|
# (currently a pre-release, ahead of whatever `npm install -g playwright`
|
|
# resolves to "latest"), and the two disagree on browser revision numbers: a
|
|
# separately-installed playwright downloads a revision @playwright/mcp doesn't
|
|
# recognize, so the MCP server errors "Browser is not installed" even though a
|
|
# browser is very much on disk. Installing/installing-deps through
|
|
# @playwright/mcp's own bundled playwright-core CLI keeps both in lockstep.
|
|
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright
|
|
RUN bash -c "source $NVM_DIR/nvm.sh \
|
|
&& npm install -g @playwright/mcp \
|
|
&& PW_CORE_CLI=\$(find \$NVM_DIR/versions/node/active/lib/node_modules/@playwright/mcp -path '*playwright-core/cli.js' | head -1) \
|
|
&& node \"\$PW_CORE_CLI\" install-deps chromium \
|
|
&& node \"\$PW_CORE_CLI\" install chromium" \
|
|
&& chmod -R a+rX /opt/ms-playwright
|
|
|
|
# Chrome DevTools MCP server (Google's official chrome-devtools-mcp, for
|
|
# kilo.jsonc/claude-mcp.json's "chrome-devtools" MCP entry — DevTools
|
|
# protocol access: console, network, performance traces, not just
|
|
# navigate/click/screenshot like Playwright). Same non-home-directory
|
|
# reasoning as Playwright above: Chrome for Testing goes to
|
|
# /opt/puppeteer-cache (not ~/.cache/puppeteer under /home/coder), which
|
|
# chrome-devtools-mcp picks up automatically via PUPPETEER_CACHE_DIR — no
|
|
# --executablePath needed, confirmed live (a real MCP `initialize` handshake
|
|
# over stdio against a container built from this exact image, headless
|
|
# Chrome launched and responded, no --no-sandbox required since it runs as
|
|
# the unprivileged `coder` user, not root).
|
|
ENV PUPPETEER_CACHE_DIR=/opt/puppeteer-cache
|
|
RUN bash -c "source $NVM_DIR/nvm.sh \
|
|
&& npm install -g chrome-devtools-mcp \
|
|
&& npx --yes puppeteer browsers install chrome --path /opt/puppeteer-cache" \
|
|
&& chmod -R a+rX /opt/puppeteer-cache
|
|
|
|
# mcp-telegram (MTProto-based Telegram MCP server) — installed so the binary
|
|
# is available on PATH, deliberately NOT wired into kilo.jsonc/mcp.json:
|
|
# using it for real needs a Telegram API ID/hash and an interactive
|
|
# device-login flow, which doesn't belong baked into a shared image or
|
|
# secrets file the way an API key does. Whoever wants it configures and adds
|
|
# the MCP entry themselves, per-workspace, when they actually need it.
|
|
# Caveat: pulls in `telegram` (gramjs), which upstream has archived in favor
|
|
# of the `teleproto` fork — fine for "available but unused," worth
|
|
# re-evaluating if this actually gets configured for real use.
|
|
RUN bash -c "source $NVM_DIR/nvm.sh && npm install -g mcp-telegram"
|
|
|
|
# MiniMax's official multimodal MCP servers — minimax-mcp (image/video/tts/
|
|
# voice-clone/voice-design/music generation) and minimax-coding-plan-mcp
|
|
# (Token-Plan-exclusive web_search + image understanding), both authoritative
|
|
# Python packages per MiniMax's own docs (see devenv/README.md's "MiniMax
|
|
# multimodal MCP servers" section for why these were picked over the
|
|
# community JS port). UV_TOOL_DIR/UV_TOOL_BIN_DIR keep the installed venvs
|
|
# and shims outside /home/coder — same reasoning as PLAYWRIGHT_BROWSERS_PATH/
|
|
# PUPPETEER_CACHE_DIR above: it's the only way a rebuilt image actually
|
|
# reaches workspaces whose /home/coder volume already exists.
|
|
ENV UV_TOOL_DIR=/opt/uv-tools \
|
|
UV_TOOL_BIN_DIR=/opt/uv-tools/bin
|
|
ENV PATH="/opt/uv-tools/bin:${PATH}"
|
|
RUN uv tool install minimax-mcp \
|
|
&& uv tool install minimax-coding-plan-mcp \
|
|
&& chmod -R a+rX /opt/uv-tools
|
|
|
|
# Dokku CLI — a small custom wrapper (not the official dokku/dokku
|
|
# contrib/dokku_client.sh) that lets any workspace run
|
|
# `dokku <command> --app <name>` directly against the Dokku PaaS on this
|
|
# same host (stacks/harry/dokku/), not just `git push dokku-demo`. The
|
|
# official client was tried first and rejected: it always shells out to a
|
|
# literal `ssh` binary, but this environment has no live ssh-agent socket
|
|
# and no on-disk SSH identity file for it to authenticate with — confirmed
|
|
# live in a real workspace (`ssh-add -l` and a bare `ssh -T git@github.com`
|
|
# both fail with no agent/no identity). The one thing that DOES
|
|
# authenticate here is Coder's own GIT_SSH_COMMAND proxy
|
|
# (`<coder-agent-binary> gitssh --`, injected as an agent-wide env var by
|
|
# Coder itself, fetching the real per-user key from the Coder control plane
|
|
# on demand) — that's what `git clone`/`git push dokku-demo` actually use
|
|
# under the hood, not a plain ssh-agent identity, contrary to what was
|
|
# originally assumed here. Vendoring the upstream script and patching its
|
|
# one `ssh` call was considered and rejected too: it's re-fetched fresh from
|
|
# `master` on every image rebuild, so a sed-based patch could silently stop
|
|
# matching upstream content on some future rebuild. dokku-cli.sh is a
|
|
# handful of lines, fully owned, and calls `coder gitssh` directly instead —
|
|
# confirmed live end-to-end against the real dokku container (`dokku
|
|
# apps:list` returned real app names through this exact path).
|
|
#
|
|
# App auto-detection from a git remote (the official client's main value
|
|
# beyond plain ssh) wouldn't have worked here regardless: this template's
|
|
# dokku-demo remote is deliberately written as "dokku-demo:<app>" with no
|
|
# literal "dokku@" prefix (auth comes from the `User dokku` directive on the
|
|
# dokku-demo SSH alias instead — see main.tf), which never matches a
|
|
# `dokku@host:app`-shaped parser. So every invocation needs an explicit
|
|
# --app <name> either way (the app name is always the workspace's own
|
|
# lowercased name) — documented in devbox-agents.md.
|
|
ENV DOKKU_HOST=dokku
|
|
COPY ./dokku-cli.sh /usr/local/bin/dokku
|
|
RUN chmod +x /usr/local/bin/dokku
|
|
|
|
# Lives outside /home/coder (like vnc-startup.sh) so it always reflects the
|
|
# current image, not whatever was baked into a volume on its first-ever
|
|
# creation.
|
|
COPY ./kilo/acp-bridge-listen.sh /usr/local/bin/acp-bridge-listen.sh
|
|
COPY ./kilo/acp-bridge-auth.py /usr/local/bin/acp-bridge-auth.py
|
|
COPY ./kilo/peer-mcp-bridge.py /usr/local/bin/peer-mcp-bridge.py
|
|
RUN chmod +x /usr/local/bin/acp-bridge-listen.sh /usr/local/bin/acp-bridge-auth.py /usr/local/bin/peer-mcp-bridge.py
|
|
|
|
# Claude Code's side of the peer bridge — see claude/claude-peer-listen.sh
|
|
# for why this is a headless `claude -p` runner rather than an ACP dance
|
|
# (Claude Code has no stdio agent-protocol server the way `kilo acp` does).
|
|
# Reuses acp-bridge-auth.py unchanged as the token gate for both bridges.
|
|
COPY ./claude/claude-peer-listen.sh /usr/local/bin/claude-peer-listen.sh
|
|
COPY ./claude/claude-peer-runner.py /usr/local/bin/claude-peer-runner.py
|
|
COPY ./claude/peer-mcp-bridge-claude.py /usr/local/bin/peer-mcp-bridge-claude.py
|
|
RUN chmod +x /usr/local/bin/claude-peer-listen.sh /usr/local/bin/claude-peer-runner.py /usr/local/bin/peer-mcp-bridge-claude.py
|
|
|
|
# Codex CLI's side of the peer bridge — see codex/codex-peer-listen.sh for
|
|
# why this one EXECs straight into `codex mcp-server` (no runner script
|
|
# needed, unlike Claude Code's): `codex mcp-server` already speaks
|
|
# newline-delimited JSON-RPC on stdio, the same shape `kilo acp` uses.
|
|
# Reuses acp-bridge-auth.py unchanged as the token gate for all three bridges.
|
|
COPY ./codex/codex-peer-listen.sh /usr/local/bin/codex-peer-listen.sh
|
|
COPY ./codex/peer-mcp-bridge-codex.py /usr/local/bin/peer-mcp-bridge-codex.py
|
|
RUN chmod +x /usr/local/bin/codex-peer-listen.sh /usr/local/bin/peer-mcp-bridge-codex.py
|
|
|
|
USER coder
|
|
|