diff --git a/.gitignore b/.gitignore index 8ae687c..0a5d564 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ docs/plans node_modules .serena CLAUDE.md +.devcontainer diff --git a/README.md b/README.md index 3ce938d..759c2e4 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ Docker-based dev containers with Claude Code, MCP servers, and common tooling pr volumes: - ..:/workspace:cached + - home:/home + - /var/run/docker.sock:/var/run/docker.sock # Overrides default command so things don't # shut down after the process ends @@ -72,6 +74,9 @@ Docker-based dev containers with Claude Code, MCP servers, and common tooling pr ipc: host init: true + volumes: + home: + networks: default: driver: bridge @@ -89,6 +94,10 @@ Docker-based dev containers with Claude Code, MCP servers, and common tooling pr "service": "app", "workspaceFolder": "/workspace", + "mounts": [ + "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" + ], + "forwardPorts": [3000, 4983, 5173, 5174], "portsAttributes": { @@ -136,10 +145,12 @@ All images extend a shared base (`base/Dockerfile` — `debian:trixie`) and run | Image | Extra stack | |-------|-------------| +| `ghcr.io/recoskyler/devcontainer-base:latest` | — (base only) | | `ghcr.io/recoskyler/trixie-bun-nvm-uv-claude:latest` | Bun | | `ghcr.io/recoskyler/trixie-php-nvm-uv-claude:latest` | PHP 8.4, Composer | | `ghcr.io/recoskyler/trixie-rust-nvm-uv-claude:latest` | Rust toolchain | | `ghcr.io/recoskyler/trixie-vnc-nvm-uv-claude:latest` | x11vnc, Xvfb | +| `ghcr.io/recoskyler/trixie-vnc-flutter-rust-nvm-uv-claude:latest` | Flutter, Rust, Android SDK, VNC | ## What's Included @@ -151,8 +162,10 @@ All images extend a shared base (`base/Dockerfile` — `debian:trixie`) and run - **MCP servers**: Serena, Context7, Automem - **GSD** (Get Shit Done for Claude Code) - **Agent Browser** + Chrome +- **Docker** CLI + Compose plugin (`docker`, `docker compose`) — mount the host socket to use - **CLI tools**: git, curl, wget, vim, nano, jq, tmux, xclip, openssh-client, gnupg, cmake, less, unzip, gh, pnpm, tsx - **Search & file tools**: ripgrep, fd-find, fzf, bat, tree +- **PDF tools**: poppler-utils (pdftotext, pdfinfo, etc.) - **Networking & HTTP**: httpie, netcat - **Cloud & infra**: AWS CLI v2, Terraform, kubectl, Stripe CLI - **Utilities**: duf, git-delta, tldr @@ -181,6 +194,18 @@ All images extend a shared base (`base/Dockerfile` — `debian:trixie`) and run - **x11vnc**, **Xvfb**, xdg-utils +### Flutter (`trixie-vnc-flutter-rust-nvm-uv-claude`) + +Extends the VNC image with Flutter, Rust, and Android tooling. + +- **Flutter** via FVM (`flutter`, `dart`, `fvm`) +- **Rust** toolchain (rustup, rustfmt, clippy, cargo-watch, cargo-edit, cargo-nextest) +- **Android SDK**: cmdline-tools, platform-tools, build-tools (28.0.3 + 35.0.0), API 35 + 36 +- **Android Emulator** with SwiftShader (AVD: `flutter_pixel7`, Pixel 7, API 35) +- **Android Studio** Panda 1 (at `/opt/android-studio`) +- **Chromium** (`CHROME_EXECUTABLE` set for `flutter run -d chrome`) +- **OpenJDK 21** (headless) + ## Build Arguments | Argument | Default | Description | @@ -205,10 +230,10 @@ Secret-dependent MCP servers and ntfy hooks are configured at **runtime** (first Two GitHub Actions workflows build and verify images: -- **`build.yml`** — Runs on push to `latest` or version tags. Builds the base image with GHA cache, then builds and pushes all 4 variants to GHCR in parallel (matrix strategy). -- **`check.yml`** — Runs on PRs to `latest`. Same structure but read-only cache (no `cache-to`) and no push to GHCR. +- **`build.yml`** — Runs on push to `latest` or version tags. Builds the base image with GHA cache, then builds and pushes all 5 variants to GHCR in parallel (matrix strategy). +- **`check.yml`** — Runs on PRs to `latest`. Same structure but read-only cache (no `cache-to`) and no push to GHCR. Each variant runs tool verification and posts results as PR comments. -Both workflows use a local `registry:2` service container and `build-contexts` to remap `FROM devcontainer-base:latest` at build time, requiring zero Dockerfile changes. +Both workflows use a local `registry:2` service container and `build-contexts` to remap `FROM` images at build time, requiring zero Dockerfile changes. The flutter variant has a three-tier chain (base → VNC → flutter) with a conditional VNC rebuild step. Images are published to GHCR at `ghcr.io//`. @@ -226,14 +251,22 @@ Images are published to GHCR at `ghcr.io//`. Build locally: ```bash -# Build base first -docker build -t devcontainer-base:latest -f base/Dockerfile . +# Build base first (NODE_VERSION is a base ARG) +docker build -t devcontainer-base:latest \ + --build-arg NODE_VERSION=24.12.0 \ + -f base/Dockerfile . # Then build a variant docker build \ -f trixie-bun-nvm-uv-claude/Dockerfile \ - --build-arg NODE_VERSION=24.12.0 \ -t trixie-bun-nvm-uv-claude . + +# Flutter requires VNC as an intermediate layer +docker build -t trixie-vnc-nvm-uv-claude:latest \ + -f trixie-vnc-nvm-uv-claude/Dockerfile . +docker build \ + -f trixie-vnc-flutter-rust-nvm-uv-claude/Dockerfile \ + -t trixie-vnc-flutter-rust-nvm-uv-claude . ``` Then run with your API keys as environment variables: @@ -250,7 +283,7 @@ docker run -it \ ## Agent Browser -The skill is already installed. You may want to add the following to `AGENTS.md`/`CLAUDE.md`: +The skill is already installed, and the following section is already included in all images `~/.claude/CLAUDE.md`. ```md ## Browser Automation diff --git a/base/Dockerfile b/base/Dockerfile index 4555ff8..39ec4be 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -41,14 +41,45 @@ RUN apt-get update \ python3-pip \ redis-tools \ ripgrep \ - tealdeer \ sudo \ tmux \ tree \ unzip \ vim \ wget \ - xclip + xclip \ + bzip2 \ + file \ + fonts-dejavu-core \ + g++ \ + libz-dev \ + locales \ + make \ + netbase \ + patch \ + uuid-runtime \ + tzdata + +RUN localedef -i en_US -f UTF-8 en_US.UTF-8 + +RUN useradd -m -s /bin/bash -u 1500 -G sudo linuxbrew \ + && echo 'linuxbrew ALL=(ALL) NOPASSWD:ALL' >>/etc/sudoers + +USER linuxbrew + +RUN NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + +ENV PATH=/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:$PATH + +RUN HOMEBREW_NO_ANALYTICS=1 HOMEBREW_NO_AUTO_UPDATE=1 brew install tealdeer \ + && brew cleanup \ + && rm -rf /home/linuxbrew/.cache + +USER root + +RUN chmod -R g+rX /home/linuxbrew + +WORKDIR /workspace # ── CLI tools (apt repos) ─────────────────────────────────────────────────── @@ -56,6 +87,11 @@ RUN apt-get update \ RUN curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | tee /usr/share/keyrings/stripe.gpg \ && echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | tee -a /etc/apt/sources.list.d/stripe.list +# Docker CLI +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/debian trixie stable" \ + | tee /etc/apt/sources.list.d/docker.list + # GH CLI + Stripe CLI install RUN mkdir -p -m 755 /etc/apt/keyrings \ && out=$(mktemp) \ @@ -65,7 +101,7 @@ RUN mkdir -p -m 755 /etc/apt/keyrings \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ && apt-get update \ - && apt-get -y install --no-install-recommends gh stripe + && apt-get -y install --no-install-recommends gh stripe docker-ce-cli docker-compose-plugin # ── CLI tools (releases) ──────────────────────────────────────────────────── @@ -98,7 +134,11 @@ RUN curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | gpg --d # ── User setup ─────────────────────────────────────────────────────────────── -RUN id dev >/dev/null 2>&1 || useradd -m -s /bin/bash -u 1000 dev +RUN groupadd -f docker + +RUN id dev >/dev/null 2>&1 \ + && usermod -aG linuxbrew,docker dev \ + || useradd -m -s /bin/bash -u 1000 -G linuxbrew,docker dev RUN chsh -s $(which bash) dev \ && echo 'export PS1="\e[01;32m\u\e[m:\e[01;34m\w\e[m\$ "' >> /home/dev/.bashrc @@ -145,7 +185,8 @@ RUN apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -RUN chown -R dev:dev /home/dev +RUN chown -R dev:dev /home/dev \ + && mkdir -p /workspace && chown dev:dev /workspace # ── User-space tools (as USER dev) ────────────────────────────────────────── @@ -189,6 +230,8 @@ RUN bash /tmp/setup-claude.sh && rm -f /tmp/setup-claude.sh RUN echo 'source $HOME/.local/bin/init-claude-mcp.sh 2>/dev/null' >> /home/dev/.bashrc +RUN tldr --update + # ── Final root section ────────────────────────────────────────────────────── USER root diff --git a/base/devcontainer-claude.md b/base/devcontainer-claude.md index 02afda7..e860144 100644 --- a/base/devcontainer-claude.md +++ b/base/devcontainer-claude.md @@ -10,8 +10,10 @@ This is a DevContainer running Debian Trixie. You are the `dev` user with passwo ### CLI Tools - `git`, `gh` (GitHub CLI), `delta` (git-delta) +- `docker`, `docker compose` (Docker CLI + Compose plugin) - `aws` (AWS CLI v2), `terraform`, `kubectl`, `stripe` - `claude` (Claude Code CLI) +- `brew` (Homebrew — used for select packages, on `$PATH` via `/home/linuxbrew/.linuxbrew/bin`) ### Search & Productivity - `rg` (ripgrep), `fdfind` (fd-find), `fzf`, `batcat` (bat), `tldr` (tealdeer) @@ -29,13 +31,19 @@ This is a DevContainer running Debian Trixie. You are the `dev` user with passwo ### Networking - `curl`, `wget`, `nc` (netcat), `ssh` +### PDF & Document Tools +- `pdftotext`, `pdftoppm`, `pdfinfo` (poppler-utils) + ### Other -- `tmux`, `ttyd`, `agent-browser`, `xclip`, `unzip` +- `tmux`, `ttyd` (web terminal on port 7681), `agent-browser`, `xclip`, `unzip` ## Working directory Default: `/workspace` ## MCP Servers + +MCP servers are configured on first shell login via `init-claude-mcp.sh`. Optional servers are only added when their env vars are set. + - `serena` — semantic code analysis (symbols, references, overview) - `context7` — library documentation lookup (if CONTEXT7_API_KEY is set) - `memory` — persistent memory via Automem (if AUTOMEM_ENDPOINT is set) @@ -43,13 +51,28 @@ Default: `/workspace` ## Plugins & Skills - GSD (`/gsd:*`) — project management and execution workflow - superpowers — brainstorming, TDD, debugging, code review skills +- everything-claude-code (ECC) — rules installed at `~/.claude/rules/` - feature-dev, frontend-design, code-review, commit-commands, pr-review-toolkit -- hookify, playground, claude-md-management +- hookify, playground, claude-md-management, claude-code-setup +- ralph-loop, security-guidance +- explanatory-output-style, learning-output-style +- typescript-lsp, pyright-lsp, php-lsp, laravel-boost +- agent-browser skill at `~/.claude/skills/agent-browser/SKILL.md` ## Hooks - Context7 suggestion hook — suggests context7 when WebSearch is used (if configured) - Ntfy notification hook — sends push notifications on task completion (if configured) +## Browser Automation + +Use `agent-browser` for web automation. Run `agent-browser --help` for all commands. + +Core workflow: +1. `agent-browser open ` - Navigate to page +2. `agent-browser snapshot -i` - Get interactive elements with refs (@e1, @e2) +3. `agent-browser click @e1` / `fill @e2 "text"` - Interact using refs +4. Re-snapshot after page changes + ## Notes - Shell is bash. `/bin/sh` is symlinked to `/bin/bash`. - Passwordless sudo is available via `sudo`. diff --git a/scripts/init-claude-mcp.sh b/scripts/init-claude-mcp.sh index f87f5dd..f2851b5 100644 --- a/scripts/init-claude-mcp.sh +++ b/scripts/init-claude-mcp.sh @@ -61,10 +61,11 @@ if [ -n "$NTFY_URL" ] && [ -n "$NTFY_TOKEN" ]; then fi fi -# --- Serena project-scope config --- -# Exclude Serena memory tools via project config (not user-scope) -SERENA_PROJECT="/workspace/.serena/project.yml" -if [ -f "$SERENA_PROJECT" ] && grep -q '^excluded_tools: \[\]' "$SERENA_PROJECT"; then +# --- Serena user-scope config --- +# Exclude Serena memory tools via user config +SERENA_USER="$HOME/.serena/user.yml" +mkdir -p "$HOME/.serena" +if [ ! -f "$SERENA_USER" ] || grep -q '^excluded_tools: \[\]' "$SERENA_USER"; then if [ -n "$AUTOMEM_ENDPOINT" ] && [ -n "$AUTOMEM_API_KEY" ]; then # Automem replaces all Serena memory/onboarding tools TOOLS=(write_memory read_memory edit_memory delete_memory list_memories check_onboarding_performed onboarding) @@ -79,8 +80,12 @@ if [ -f "$SERENA_PROJECT" ] && grep -q '^excluded_tools: \[\]' "$SERENA_PROJECT" - ${t}" done - awk -v new="$YAML" '/^excluded_tools: \[\]/ { print new; next } 1' "$SERENA_PROJECT" > "$SERENA_PROJECT.tmp" - mv "$SERENA_PROJECT.tmp" "$SERENA_PROJECT" + if [ -f "$SERENA_USER" ]; then + awk -v new="$YAML" '/^excluded_tools: \[\]/ { print new; next } 1' "$SERENA_USER" > "$SERENA_USER.tmp" + mv "$SERENA_USER.tmp" "$SERENA_USER" + else + printf '%s\n' "$YAML" > "$SERENA_USER" + fi fi touch /tmp/.claude-mcp-init diff --git a/scripts/setup-claude.sh b/scripts/setup-claude.sh index 4a4bd29..88b639f 100755 --- a/scripts/setup-claude.sh +++ b/scripts/setup-claude.sh @@ -36,9 +36,9 @@ fi # --- Agent Browser --- -mkdir -p /workspace/.claude/skills/agent-browser +mkdir -p /home/dev/.claude/skills/agent-browser -curl -o /workspace/.claude/skills/agent-browser/SKILL.md https://raw.githubusercontent.com/vercel-labs/agent-browser/main/skills/agent-browser/SKILL.md +curl -o /home/dev/.claude/skills/agent-browser/SKILL.md https://raw.githubusercontent.com/vercel-labs/agent-browser/main/skills/agent-browser/SKILL.md # --- Hookify Fix --- @@ -55,11 +55,7 @@ rm -rf /tmp/everything-claude-code # --- GSD --- -cd /workspace - -npx -y get-shit-done-cc --claude --local - -echo '{ "type": "commonjs" }' > /workspace/.claude/get-shit-done/bin/package.json +npx -y get-shit-done-cc --claude --global # --- MCP Servers --- @@ -69,3 +65,13 @@ if [ -x "$CLAUDE" ]; then else echo "WARNING: Claude CLI not found — skipping MCP server setup" fi + +# --- Enable Remote Control for all sessions --- + +# CLAUDE_JSON="$HOME/.claude.json" +# if [ -f "$CLAUDE_JSON" ] && command -v jq >/dev/null 2>&1; then +# jq '. + {"remoteControlAtStartup": true}' "$CLAUDE_JSON" > "$CLAUDE_JSON.tmp" \ +# && mv "$CLAUDE_JSON.tmp" "$CLAUDE_JSON" +# else +# printf '{"remoteControlAtStartup":true}\n' > "$CLAUDE_JSON" +# fi diff --git a/scripts/verify-tools.sh b/scripts/verify-tools.sh index 827e027..61fba61 100755 --- a/scripts/verify-tools.sh +++ b/scripts/verify-tools.sh @@ -129,7 +129,8 @@ verify_base() { done # Claude ecosystem - check_file "GSD directory" "/workspace/.claude/get-shit-done" + check_file "GSD directory" "/home/dev/.claude/get-shit-done" + for script in ntfy-hook.sh suggest-context7-hook.sh init-claude-mcp.sh; do check_file "$script" "$HOME/.local/bin/$script" done