diff --git a/.claude/rules/devcontainer.md b/.claude/rules/devcontainer.md index 6b1a9d9..c34a46f 100644 --- a/.claude/rules/devcontainer.md +++ b/.claude/rules/devcontainer.md @@ -40,7 +40,7 @@ Common categories: `**Claude Code**`, `**Bun**`, `**Code Quality**` (OXC), `**Gi ```jsonc "settings": { - "terminal.integrated.defaultProfile.linux": "fish", + "terminal.integrated.defaultProfile.linux": "zsh", "extensions.ignoreRecommendations": true } ``` diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d88f9e2..69ab28f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -18,6 +18,23 @@ # ═════════════════════════════════════════════════════════════════════════════ # ── rtk (token-optimized CLI proxy) ────────────────────────────────────── +# ── oh-my-zsh + powerlevel10k ───────────────────────────────── +# Pinned to commit SHAs and fetched as tarballs. Neither project tags usefully -- +# oh-my-zsh has no tags at all, and powerlevel10k's last release predates its +# current master by years -- so a SHA is the only real pin. Bump deliberately: +# auto-adopting upstream shell-framework changes is what repeatedly broke this +# image, and a prompt theme does not need a 3-day release cadence. +FROM alpine:3.21 AS ohmyzsh-download +RUN apk add --no-cache curl tar +ARG OH_MY_ZSH_REF=0ee67f042872d1dfab74270c31867771ca35aef4 +ARG POWERLEVEL10K_REF=d05a1b00f9a61f9578bf9dc19b8451942dde8734 +RUN set -eux; \ + mkdir -p /omz/custom/themes/powerlevel10k; \ + curl -fsSL "https://github.com/ohmyzsh/ohmyzsh/archive/${OH_MY_ZSH_REF}.tar.gz" \ + | tar -xz -C /omz --strip-components=1; \ + curl -fsSL "https://github.com/romkatv/powerlevel10k/archive/${POWERLEVEL10K_REF}.tar.gz" \ + | tar -xz -C /omz/custom/themes/powerlevel10k --strip-components=1 + FROM alpine:3.21 AS rtk-download RUN apk add --no-cache curl jq RUN set -eux; \ @@ -68,30 +85,29 @@ RUN set -eux; \ # ─── BASE ───────────────────────────────────────────────────────────────────── FROM node:24-trixie-slim AS base -ARG GIT_DELTA_VERSION=0.18.2 +# renovate: datasource=github-releases depName=cli/cli +ARG GH_VERSION=2.100.0 # System packages (each justified — see claude-code Dockerfile for rationale) # - ca-certificates: SSL/TLS for HTTPS connections # - curl: downloading tools and installers -# - fish: interactive shell (built-in syntax highlighting, autosuggestions, completions) -# - fzf: fuzzy finder (fish integration) -# - gh: GitHub CLI +# - fzf: fuzzy finder; zsh integration comes from the oh-my-zsh fzf plugin # - git: version control # - jq: JSON processing (onboarding patch, firewall script) -# - less: pager for git delta output +# - less: pager for git and other CLI output # - sudo: privilege escalation (chown for named volumes, firewall setup) +# - zsh: interactive shell; oh-my-zsh + powerlevel10k configured below RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ - fish \ fzf \ - gh \ git \ jq \ less \ - sudo + sudo \ + zsh # npm global directory with proper permissions for node user # Pre-create /lib to prevent "ENOENT" errors during npx commands @@ -101,37 +117,64 @@ RUN mkdir -p /usr/local/share/npm-global/lib \ ENV DEVCONTAINER=true # Create workspace and config directories with proper ownership -RUN mkdir -p /workspace /home/node/.claude /home/node/.local/share/fish \ - && chown -R node:node /workspace /home/node/.claude /home/node/.local +RUN mkdir -p /workspace /home/node/.claude /home/node/.local /commandhistory \ + && chown -R node:node /workspace /home/node/.claude /home/node/.local /commandhistory WORKDIR /workspace -# Install git-delta (pinned — v0.19.0 dropped arm64 .deb) +# gh (GitHub CLI) — installed from the upstream .deb, not apt: Debian trixie +# freezes gh at 2.46.0 (Apr 2024). Version kept current by Renovate. +# Must stay AFTER the apt block: the .deb depends on git, and dpkg -i does not +# resolve dependencies — it fails loudly ("dependency problems") if git is absent. RUN ARCH=$(dpkg --print-architecture) \ - && curl -fsSL -o "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" \ - "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" \ - && dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" \ - && rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" + && curl -fsSL -o "gh_${GH_VERSION}_linux_${ARCH}.deb" \ + "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${ARCH}.deb" \ + && dpkg -i "gh_${GH_VERSION}_linux_${ARCH}.deb" \ + && rm "gh_${GH_VERSION}_linux_${ARCH}.deb" # ── Non-root user setup ────────────────────────────────────────────────────── USER node ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global ENV PATH=$PATH:/usr/local/share/npm-global/bin -ENV SHELL=/usr/bin/fish +ENV SHELL=/usr/bin/zsh +# Only LANG, and only C.UTF-8 — what the official images do (ruby sets exactly +# this; node/debian/python set nothing). LC_ALL would override every LC_* category +# and silently defeat a consuming project's containerEnv. TERM is deliberately +# unset: no official image sets it, an image ENV beats the tty value `docker exec -t` +# supplies, and tput/clear failing without a terminal is correct behaviour. +ENV LANG=C.UTF-8 ENV EDITOR="code --wait" ENV VISUAL="code --wait" -# ── Starship prompt ────────────────────────────────────────────────────────── -USER root -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN curl -sS https://starship.rs/install.sh | sh -s -- --yes -SHELL ["/bin/sh", "-c"] - +# ── zsh + oh-my-zsh + powerlevel10k ────────────────────────────────────────── +# Written as node: $HOME during RUN follows USER, so this must not run as root. USER node -RUN mkdir -p /home/node/.config/fish \ - && starship preset no-runtime-versions -o /home/node/.config/starship.toml \ - && printf '%s\n' 'set -g fish_greeting' 'starship init fish | source' > /home/node/.config/fish/config.fish +# oh-my-zsh tree is COPYed from the parallel download stage above, so the final +# image needs no git or curl for it and nothing is fetched at build time here. +COPY --from=ohmyzsh-download --chown=node:node /omz /home/node/.oh-my-zsh + +# .zshrc is written here rather than by an installer, so nothing upstream can +# inject env overrides (locale/TERM) that then have to be patched back out. +# The wizard flag matters: without a POWERLEVEL9K_* config powerlevel10k starts +# its interactive configurator on first shell, which blocks a container. +# $HOME and $ZSH are single-quoted on purpose: they must reach .zshrc +# unexpanded so zsh resolves them at runtime, not at build time. +# hadolint ignore=SC2016 +RUN printf '%s\n' \ + 'export ZSH="$HOME/.oh-my-zsh"' \ + 'ZSH_THEME="powerlevel10k/powerlevel10k"' \ + 'plugins=(git fzf)' \ + "zstyle ':omz:update' mode disabled" \ + 'POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD=true' \ + 'export HISTFILE=/commandhistory/.zsh_history' \ + 'source $ZSH/oh-my-zsh.sh' \ + 'POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_last' \ + 'POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(user dir vcs status)' \ + 'POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=()' \ + 'POWERLEVEL9K_STATUS_OK=false' \ + 'POWERLEVEL9K_STATUS_CROSS=true' \ + > "$HOME/.zshrc" # ── Dev tools (copied from parallel download stages) ───────────────────────── # rtk (token-optimized CLI proxy) and ralphex (autonomous plan execution). diff --git a/.devcontainer/claude-sandbox/devcontainer.json b/.devcontainer/claude-sandbox/devcontainer.json index 8d7d9a2..4609482 100644 --- a/.devcontainer/claude-sandbox/devcontainer.json +++ b/.devcontainer/claude-sandbox/devcontainer.json @@ -26,9 +26,9 @@ "ms-azuretools.vscode-docker" ], "settings": { - "terminal.integrated.defaultProfile.linux": "fish", + "terminal.integrated.defaultProfile.linux": "zsh", "terminal.integrated.profiles.linux": { - "fish": { "path": "fish" }, + "zsh": { "path": "zsh" }, "bash": { "path": "bash", "icon": "terminal-bash" } }, "extensions.ignoreRecommendations": true, @@ -57,7 +57,7 @@ }, "mounts": [ "source=devcontainers-sandbox-config-${devcontainerId},target=/home/node/.claude,type=volume", - "source=devcontainers-sandbox-fish-${devcontainerId},target=/home/node/.local/share/fish,type=volume", + "source=devcontainers-sandbox-zsh-history-${devcontainerId},target=/commandhistory,type=volume", // Mount firewall script into the expected path "source=${localWorkspaceFolder}/.devcontainer/claude-sandbox/init-firewall.sh,target=/usr/local/bin/init-firewall.sh,type=bind" ], @@ -69,7 +69,7 @@ "CLAUDE_CODE_OAUTH_TOKEN": "${localEnv:CLAUDE_CODE_OAUTH_TOKEN}" }, // Plugins before firewall (network still open during postCreateCommand) - "postCreateCommand": "sudo chown -R node /home/node/.claude && bash /workspace/.devcontainer/init-plugins.sh", + "postCreateCommand": "sudo chown -R node /home/node/.claude /commandhistory && bash /workspace/.devcontainer/init-plugins.sh", // Firewall locks down the network "postStartCommand": "sudo /usr/local/bin/init-firewall.sh", "waitFor": "postStartCommand" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fa4b858..dd06dda 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -27,9 +27,9 @@ "ms-azuretools.vscode-docker" ], "settings": { - "terminal.integrated.defaultProfile.linux": "fish", + "terminal.integrated.defaultProfile.linux": "zsh", "terminal.integrated.profiles.linux": { - "fish": { "path": "fish" }, + "zsh": { "path": "zsh" }, "bash": { "path": "bash", "icon": "terminal-bash" } }, // Suppress extension recommendation prompts @@ -58,8 +58,8 @@ "mounts": [ // Persist Claude Code configuration between container rebuilds "source=devcontainers-claude-config-${devcontainerId},target=/home/node/.claude,type=volume", - // Persist fish shell history between container rebuilds - "source=devcontainers-fish-data-${devcontainerId},target=/home/node/.local/share/fish,type=volume" + // Persist zsh history between container rebuilds + "source=devcontainers-zsh-history-${devcontainerId},target=/commandhistory,type=volume" ], "containerEnv": { "TZ": "${localEnv:TZ:America/Edmonton}", @@ -71,6 +71,6 @@ // VS Code installs extensions, so claude commands race with the // Claude Code extension's OAuth flow and can corrupt auth state. // Run .devcontainer/init-plugins.sh manually after first login. - "postCreateCommand": "sudo chown -R node /home/node/.claude /home/node/.local/share/fish", + "postCreateCommand": "sudo chown -R node /home/node/.claude /commandhistory", "waitFor": "postCreateCommand" } diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 47f08a0..e25b934 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -22,13 +22,14 @@ packageRules: [ { - // rtk / ralphex release tags look like "v0.43.0"; strip the leading "v" - // so the datasource version matches the bare ARG value ("0.43.0"). - matchDatasources: ['github-releases'], + // Most GitHub tags carry a leading "v" ("v0.48.0", "v29.8.1"); strip it so the + // datasource version matches the bare ARG value ("0.48.0"). A tag with no "v" + // passes through unchanged, so this is safe to apply datasource-wide. + matchDatasources: ['github-releases', 'github-tags'], extractVersion: '^v?(?.+)$', }, { - // Group the four tools into one PR and auto-merge once CI passes. + // Group the tracked tools into one PR and auto-merge once CI passes. // // Relies on Renovate's default platformAutomerge:true — GitHub's native // auto-merge merges on green with no second Renovate run. The previous @@ -45,8 +46,15 @@ 'umputun/ralphex', '@anthropic-ai/claude-code', 'agent-browser', + 'cli/cli', // gh — upstream .deb; apt's trixie build is frozen at 2.46.0 + 'docker/cli', // docker CLI static binary (download.docker.com). + // github-tags, NOT github-releases: moby/moby tags its + // releases 'docker-v29.8.0', which extractVersion cannot + // parse, so every candidate is silently discarded. + 'go', // ralphex-fe Go toolchain (go.dev) + 'node', // hugo-bun-node musl build; base-image node is NOT managed here ], - groupName: 'devcontainer agent tools', + groupName: 'devcontainer tools', automerge: true, // These bumps merge unreviewed and publish straight to ghcr.io, so let a @@ -73,5 +81,12 @@ automerge: true, minimumReleaseAge: '3 days', }, + { + // hugo-bun-node installs Node from the unofficial musl builds — its base + // image (oven/bun:*-alpine) ships no node at all. The image is Node 24 LTS + // by design, so major bumps stay a deliberate call, not a Renovate PR. + matchDatasources: ['node-version'], + allowedVersions: '^24', + }, ], } diff --git a/.github/workflows/build-claude-bun.yml b/.github/workflows/build-claude-bun.yml index 7001914..e296ea0 100644 --- a/.github/workflows/build-claude-bun.yml +++ b/.github/workflows/build-claude-bun.yml @@ -41,5 +41,5 @@ jobs: context: claude-bun/.devcontainer dockerfile: claude-bun/.devcontainer/Dockerfile version-tag: ${{ needs.prepare.outputs.version-tag }} - verify-command: 'bun --version' + verify-command: "bun --version && gh --version && zsh --version && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" secrets: inherit diff --git a/.github/workflows/build-claude-code.yml b/.github/workflows/build-claude-code.yml index 4977ba6..fc78ddf 100644 --- a/.github/workflows/build-claude-code.yml +++ b/.github/workflows/build-claude-code.yml @@ -75,19 +75,19 @@ jobs: matrix: include: - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium" + verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium" + verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" runner: ubuntu-24.04-arm arch: arm64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp" + verify-command: "claude --version && mise --version && zsh --version && gh --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp" + verify-command: "claude --version && mise --version && zsh --version && gh --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" runner: ubuntu-24.04-arm arch: arm64 runs-on: ${{ matrix.runner }} diff --git a/.github/workflows/build-ralphex-fe.yml b/.github/workflows/build-ralphex-fe.yml index 1a7785c..4859849 100644 --- a/.github/workflows/build-ralphex-fe.yml +++ b/.github/workflows/build-ralphex-fe.yml @@ -98,6 +98,7 @@ jobs: hugo version && python3 --version && go version && + docker --version && node --version && /srv/ralphex --version && claude --version && diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08e8c04..8d7ffc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,19 +110,19 @@ jobs: add_image "claude-bun" \ "claude-bun/.devcontainer" \ "claude-bun/.devcontainer/Dockerfile" \ - "bun --version" + "bun --version && gh --version && zsh --version && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" fi if [ "$CHANGED_CLAUDE_CODE" = "true" ]; then add_image "claude-code" \ "claude-code/.devcontainer" \ "claude-code/.devcontainer/Dockerfile" \ - "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium" \ + "bun --version || true && claude --version && mise --version && zsh --version && gh --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" \ "default" add_image "claude-code-sandbox" \ "claude-code/.devcontainer" \ "claude-code/.devcontainer/Dockerfile" \ - "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp" \ + "claude --version && mise --version && zsh --version && gh --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" \ "sandbox" fi @@ -144,7 +144,7 @@ jobs: add_image "ralphex-fe" \ "ralphex-fe" \ "ralphex-fe/Dockerfile" \ - "bun --version && hugo version && /srv/ralphex --version && rtk --version" + "bun --version && hugo version && go version && docker --version && /srv/ralphex --version && rtk --version" fi if [ "$INCLUDES" = "[]" ]; then diff --git a/.hadolint.yaml b/.hadolint.yaml index eec7f41..fa473b9 100644 --- a/.hadolint.yaml +++ b/.hadolint.yaml @@ -15,9 +15,6 @@ ignored: # and curl -f already fails on HTTP errors. - DL4006 # set SHELL option -o pipefail before RUN with pipe - # SNIPPET variable in devcontainer-claude-bun follows the official - # Anthropic Claude Code devcontainer pattern (anthropics/claude-code). - - SC2034 # variable appears unused # ralphex-fe entrypoint runs as root to handle APP_UID remapping, # then drops to app user via gosu. No final USER directive is correct. diff --git a/claude-bun/.devcontainer/Dockerfile b/claude-bun/.devcontainer/Dockerfile index cc34081..0e660da 100644 --- a/claude-bun/.devcontainer/Dockerfile +++ b/claude-bun/.devcontainer/Dockerfile @@ -1,13 +1,30 @@ ARG BUN_VERSION=1.3.5 +# ── oh-my-zsh + powerlevel10k ───────────────────────────────── +# Pinned to commit SHAs and fetched as tarballs. Neither project tags usefully -- +# oh-my-zsh has no tags at all, and powerlevel10k's last release predates its +# current master by years -- so a SHA is the only real pin. Bump deliberately: +# auto-adopting upstream shell-framework changes is what repeatedly broke this +# image, and a prompt theme does not need a 3-day release cadence. +FROM alpine:3.21 AS ohmyzsh-download +RUN apk add --no-cache curl tar +ARG OH_MY_ZSH_REF=0ee67f042872d1dfab74270c31867771ca35aef4 +ARG POWERLEVEL10K_REF=d05a1b00f9a61f9578bf9dc19b8451942dde8734 +RUN set -eux; \ + mkdir -p /omz/custom/themes/powerlevel10k; \ + curl -fsSL "https://github.com/ohmyzsh/ohmyzsh/archive/${OH_MY_ZSH_REF}.tar.gz" \ + | tar -xz -C /omz --strip-components=1; \ + curl -fsSL "https://github.com/romkatv/powerlevel10k/archive/${POWERLEVEL10K_REF}.tar.gz" \ + | tar -xz -C /omz/custom/themes/powerlevel10k --strip-components=1 + FROM oven/bun:${BUN_VERSION}-slim ARG TZ=UTC ENV TZ="$TZ" ARG CLAUDE_CODE_VERSION=latest -ARG GIT_DELTA_VERSION=0.18.2 -ARG ZSH_IN_DOCKER_VERSION=1.2.0 +# renovate: datasource=github-releases depName=cli/cli +ARG GH_VERSION=2.100.0 # Install basic development tools and iptables/ipset RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ @@ -20,7 +37,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ sudo \ fzf \ zsh \ - gh \ iptables \ ipset \ iproute2 \ @@ -34,9 +50,10 @@ RUN mkdir -p /usr/local/share/bun-global && \ ARG USERNAME=bun -# Persist bash history -RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \ - && mkdir /commandhistory \ +# Shared history volume mount point. Despite the .bash_history name — kept so +# existing /commandhistory volumes don't lose their history — zsh is what writes +# here, via HISTFILE below. bash history is not persisted in this image. +RUN mkdir /commandhistory \ && touch /commandhistory/.bash_history \ && chown -R $USERNAME /commandhistory @@ -49,11 +66,15 @@ RUN mkdir -p /workspace /home/bun/.claude /home/bun/.bun && \ WORKDIR /workspace +# gh (GitHub CLI) — installed from the upstream .deb, not apt: Debian trixie +# freezes gh at 2.46.0 (Apr 2024). Version kept current by Renovate. +# Must stay AFTER the apt block: the .deb depends on git, and dpkg -i does not +# resolve dependencies — it fails loudly ("dependency problems") if git is absent. RUN ARCH=$(dpkg --print-architecture) && \ - curl -fsSL -o "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" \ - "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \ - dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \ - rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" + curl -fsSL -o "gh_${GH_VERSION}_linux_${ARCH}.deb" \ + "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${ARCH}.deb" && \ + dpkg -i "gh_${GH_VERSION}_linux_${ARCH}.deb" && \ + rm "gh_${GH_VERSION}_linux_${ARCH}.deb" # Set up non-root user USER bun @@ -63,17 +84,45 @@ ENV HOME=/home/bun ENV BUN_INSTALL=/home/bun/.bun ENV PATH="$BUN_INSTALL/bin:$PATH" -# Set the default shell to zsh rather than sh -ENV SHELL=/bin/zsh - -# Default powerline10k theme -RUN sh -c "$(curl -fsSL https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \ - -p git \ - -p fzf \ - -a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \ - -a "source /usr/share/doc/fzf/examples/completion.zsh" \ - -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \ - -x +# Set the default shell to zsh rather than sh (/usr/bin/zsh, matching the other +# images; /bin/zsh is the same binary via usrmerge) +ENV SHELL=/usr/bin/zsh +# Only LANG, and only C.UTF-8 — what the official images do (ruby sets exactly +# this; node/debian/python set nothing). LC_ALL would override every LC_* category +# and silently defeat a consuming project's containerEnv. TERM is deliberately +# unset: no official image sets it, an image ENV beats the tty value `docker exec -t` +# supplies, and tput/clear failing without a terminal is correct behaviour. +ENV LANG=C.UTF-8 + +# zsh + oh-my-zsh + powerlevel10k. +# +# Written as bun: $HOME during RUN follows USER, so this must not run as root. +USER bun +# oh-my-zsh tree is COPYed from the parallel download stage above, so the final +# image needs no git or curl for it and nothing is fetched at build time here. +COPY --from=ohmyzsh-download --chown=bun:bun /omz /home/bun/.oh-my-zsh + +# .zshrc is written here rather than by an installer, so nothing upstream can +# inject env overrides (locale/TERM) that then have to be patched back out. +# The wizard flag matters: without a POWERLEVEL9K_* config powerlevel10k starts +# its interactive configurator on first shell, which blocks a container. +# $HOME and $ZSH are single-quoted on purpose: they must reach .zshrc +# unexpanded so zsh resolves them at runtime, not at build time. +# hadolint ignore=SC2016 +RUN printf '%s\n' \ + 'export ZSH="$HOME/.oh-my-zsh"' \ + 'ZSH_THEME="powerlevel10k/powerlevel10k"' \ + 'plugins=(git fzf)' \ + "zstyle ':omz:update' mode disabled" \ + 'POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD=true' \ + 'export HISTFILE=/commandhistory/.bash_history' \ + 'source $ZSH/oh-my-zsh.sh' \ + 'POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_last' \ + 'POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(user dir vcs status)' \ + 'POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=()' \ + 'POWERLEVEL9K_STATUS_OK=false' \ + 'POWERLEVEL9K_STATUS_CROSS=true' \ + > "$HOME/.zshrc" # Install Claude Code globally using bun (run as root to avoid EACCES linking) USER root diff --git a/claude-bun/.devcontainer/devcontainer.json b/claude-bun/.devcontainer/devcontainer.json index ff5e2c3..a4bb0f0 100644 --- a/claude-bun/.devcontainer/devcontainer.json +++ b/claude-bun/.devcontainer/devcontainer.json @@ -7,9 +7,7 @@ "args": { "TZ": "${localEnv:TZ:America/Edmonton}", "BUN_VERSION": "1.3.5", - "CLAUDE_CODE_VERSION": "latest", - "GIT_DELTA_VERSION": "0.18.2", - "ZSH_IN_DOCKER_VERSION": "1.2.0" + "CLAUDE_CODE_VERSION": "latest" } }, // Required for firewall configuration @@ -20,7 +18,8 @@ "mounts": [ // Keep node_modules out of a host machine "source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume", - // Persist bash history between container rebuilds + // Persist shell history between container rebuilds. zsh writes here + // (HISTFILE in the Dockerfile); the volume name is legacy. "source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume", // Persist Claude Code configuration between container rebuilds "source=claude-code-config-${devcontainerId},target=/home/bun/.claude,type=volume" diff --git a/claude-bun/README.md b/claude-bun/README.md index ed97f80..eebcae7 100644 --- a/claude-bun/README.md +++ b/claude-bun/README.md @@ -7,7 +7,7 @@ Claude Code development container based on the [official Anthropic devcontainer - **Bun runtime** (slim Debian-based image) for fast JavaScript/TypeScript execution - **Claude Code CLI** pre-installed globally via `bun add -g` - **Security by design** with custom firewall restricting network access to necessary services only -- **Developer-friendly tools**: git, ZSH with Powerline10k theme, fzf, vim, nano, git-delta +- **Developer-friendly tools**: git, gh CLI, ZSH with oh-my-zsh + Powerlevel10k, fzf, jq - **VS Code integration** with pre-configured extensions (Claude Code, Bun, Biome, Tailwind CSS) - **Session persistence** for command history and Claude configuration between restarts - **Multi-platform support** (linux/amd64, linux/arm64) diff --git a/claude-code/.devcontainer/Dockerfile b/claude-code/.devcontainer/Dockerfile index 206824d..9c65524 100644 --- a/claude-code/.devcontainer/Dockerfile +++ b/claude-code/.devcontainer/Dockerfile @@ -17,6 +17,23 @@ # ── rtk (token-optimized CLI proxy) ────────────────────────────────────── # Version pinned and kept up to date by Renovate (see .github/renovate.json5). +# ── oh-my-zsh + powerlevel10k ───────────────────────────────── +# Pinned to commit SHAs and fetched as tarballs. Neither project tags usefully -- +# oh-my-zsh has no tags at all, and powerlevel10k's last release predates its +# current master by years -- so a SHA is the only real pin. Bump deliberately: +# auto-adopting upstream shell-framework changes is what repeatedly broke this +# image, and a prompt theme does not need a 3-day release cadence. +FROM alpine:3.21 AS ohmyzsh-download +RUN apk add --no-cache curl tar +ARG OH_MY_ZSH_REF=0ee67f042872d1dfab74270c31867771ca35aef4 +ARG POWERLEVEL10K_REF=d05a1b00f9a61f9578bf9dc19b8451942dde8734 +RUN set -eux; \ + mkdir -p /omz/custom/themes/powerlevel10k; \ + curl -fsSL "https://github.com/ohmyzsh/ohmyzsh/archive/${OH_MY_ZSH_REF}.tar.gz" \ + | tar -xz -C /omz --strip-components=1; \ + curl -fsSL "https://github.com/romkatv/powerlevel10k/archive/${POWERLEVEL10K_REF}.tar.gz" \ + | tar -xz -C /omz/custom/themes/powerlevel10k --strip-components=1 + FROM alpine:3.21 AS rtk-download RUN apk add --no-cache curl # renovate: datasource=github-releases depName=rtk-ai/rtk @@ -46,17 +63,16 @@ RUN set -eux; \ # ─── BASE ───────────────────────────────────────────────────────────────────── FROM node:24-trixie-slim AS base -ARG GIT_DELTA_VERSION=0.18.2 +# renovate: datasource=github-releases depName=cli/cli +ARG GH_VERSION=2.100.0 # System packages shared by all targets # - ca-certificates: SSL/TLS for HTTPS connections # - curl: downloading tools and installers -# - fish: interactive shell (built-in syntax highlighting, autosuggestions, completions) -# - fzf: fuzzy finder (fish integration via fzf.fish or built-in) -# - gh: GitHub CLI +# - fzf: fuzzy finder; zsh integration comes from the oh-my-zsh fzf plugin # - git: version control # - jq: JSON processing (firewall script, onboarding patch) -# - less: pager for git delta output +# - less: pager for git and other CLI output # - openssh-client: provides ssh-keygen, needed for SSH-format commit signing # (gpg.format=ssh + commit.gpgsign=true, copied in via VS Code's # dev.containers.copyGitConfig); also restores ssh/ssh-add. git only @@ -65,19 +81,19 @@ ARG GIT_DELTA_VERSION=0.18.2 # sign too. Must be in base: the sandbox firewall blocks deb.debian.org, so # it can't be added at runtime (same reasoning as chromium). See issue #110. # - sudo: privilege escalation for firewall setup +# - zsh: interactive shell; oh-my-zsh + powerlevel10k configured below RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ - fish \ fzf \ - gh \ git \ jq \ less \ openssh-client \ - sudo + sudo \ + zsh # npm global directory with proper permissions for node user # Pre-create /lib to prevent "ENOENT" errors during npx commands @@ -98,45 +114,77 @@ RUN mkdir -p /workspace/node_modules \ /workspace/packages/shared/node_modules \ /workspace/packages/database/node_modules \ /home/node/.claude \ - /home/node/.local/share/fish \ - && chown -R node:node /workspace /home/node/.claude /home/node/.local + /home/node/.local \ + /commandhistory \ + && chown -R node:node /workspace /home/node/.claude /home/node/.local /commandhistory WORKDIR /workspace -# Install git-delta (better git diffs) +# gh (GitHub CLI) — installed from the upstream .deb, not apt: Debian trixie +# freezes gh at 2.46.0 (Apr 2024). Version kept current by Renovate. +# Must stay AFTER the apt block: the .deb depends on git, and dpkg -i does not +# resolve dependencies — it fails loudly ("dependency problems") if git is absent. RUN ARCH=$(dpkg --print-architecture) \ - && curl -fsSL -o "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" \ - "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" \ - && dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" \ - && rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" + && curl -fsSL -o "gh_${GH_VERSION}_linux_${ARCH}.deb" \ + "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${ARCH}.deb" \ + && dpkg -i "gh_${GH_VERSION}_linux_${ARCH}.deb" \ + && rm "gh_${GH_VERSION}_linux_${ARCH}.deb" # ── Non-root user setup ────────────────────────────────────────────────────── USER node ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global ENV PATH=$PATH:/usr/local/share/npm-global/bin -ENV SHELL=/usr/bin/fish +ENV SHELL=/usr/bin/zsh +# Only LANG, and only C.UTF-8 — what the official images do (ruby sets exactly +# this; node/debian/python set nothing). LC_ALL would override every LC_* category +# and silently defeat a consuming project's containerEnv. TERM is deliberately +# unset: no official image sets it, an image ENV beats the tty value `docker exec -t` +# supplies, and tput/clear failing without a terminal is correct behaviour. +ENV LANG=C.UTF-8 ENV EDITOR="code --wait" ENV VISUAL="code --wait" -# ── Starship + Mise (install as root, configure as node) ─────────────────────── +# ── Mise (install as root) ──────────────────────────────────────────────────── +# Mise is installed as a tool manager — projects run `mise install` at container +# creation to install their specific tool versions from .mise.toml. +# Node is NOT installed via mise — it's provided by the base image; mise shims +# would shadow the base image's npx, breaking global npm-installed CLIs. USER root SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN curl -sS https://starship.rs/install.sh | sh -s -- --yes RUN curl https://mise.run | sh \ && cp /root/.local/bin/mise /usr/local/bin/mise \ && rm -rf /root/.local SHELL ["/bin/sh", "-c"] -# Configure starship and fish shell. -# Mise is installed as a tool manager — projects run `mise install` at container -# creation to install their specific tool versions from .mise.toml. -# Node is NOT installed via mise — it's provided by the base image; mise shims -# would shadow the base image's npx, breaking global npm-installed CLIs. +# ── zsh + oh-my-zsh + powerlevel10k ────────────────────────────────────────── +# Written as node: $HOME during RUN follows USER, so this must not run as root. USER node -RUN mkdir -p /home/node/.config/fish \ - && starship preset no-runtime-versions -o /home/node/.config/starship.toml \ - && printf '%s\n' 'set -g fish_greeting' 'starship init fish | source' > /home/node/.config/fish/config.fish +# oh-my-zsh tree is COPYed from the parallel download stage above, so the final +# image needs no git or curl for it and nothing is fetched at build time here. +COPY --from=ohmyzsh-download --chown=node:node /omz /home/node/.oh-my-zsh + +# .zshrc is written here rather than by an installer, so nothing upstream can +# inject env overrides (locale/TERM) that then have to be patched back out. +# The wizard flag matters: without a POWERLEVEL9K_* config powerlevel10k starts +# its interactive configurator on first shell, which blocks a container. +# $HOME and $ZSH are single-quoted on purpose: they must reach .zshrc +# unexpanded so zsh resolves them at runtime, not at build time. +# hadolint ignore=SC2016 +RUN printf '%s\n' \ + 'export ZSH="$HOME/.oh-my-zsh"' \ + 'ZSH_THEME="powerlevel10k/powerlevel10k"' \ + 'plugins=(git fzf)' \ + "zstyle ':omz:update' mode disabled" \ + 'POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD=true' \ + 'export HISTFILE=/commandhistory/.zsh_history' \ + 'source $ZSH/oh-my-zsh.sh' \ + 'POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_last' \ + 'POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(user dir vcs status)' \ + 'POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=()' \ + 'POWERLEVEL9K_STATUS_OK=false' \ + 'POWERLEVEL9K_STATUS_CROSS=true' \ + > "$HOME/.zshrc" ENV PATH="/home/node/.local/share/mise/shims:$PATH" ENV MISE_TRUSTED_CONFIG_PATHS="/workspace" diff --git a/claude-code/.devcontainer/claude-sandbox/devcontainer.json b/claude-code/.devcontainer/claude-sandbox/devcontainer.json index 9df9cfe..8067bde 100644 --- a/claude-code/.devcontainer/claude-sandbox/devcontainer.json +++ b/claude-code/.devcontainer/claude-sandbox/devcontainer.json @@ -74,9 +74,9 @@ "SamiHindi.claude-theme-sami-hindi" ], "settings": { - "terminal.integrated.defaultProfile.linux": "fish", + "terminal.integrated.defaultProfile.linux": "zsh", "terminal.integrated.profiles.linux": { - "fish": { "path": "fish" }, + "zsh": { "path": "zsh" }, "bash": { "path": "bash", "icon": "terminal-bash" } }, "extensions.ignoreRecommendations": true, @@ -111,7 +111,7 @@ // "source=myproject-node-modules-web-${localWorkspaceFolderBasename},target=/workspace/apps/web/node_modules,type=volume", // ── Persistent config ────────────────────────────────────────────── "source=myproject-claude-config-${localWorkspaceFolderBasename},target=/home/node/.claude,type=volume", - "source=myproject-fish-data-${localWorkspaceFolderBasename},target=/home/node/.local/share/fish,type=volume", + "source=myproject-zsh-history-${localWorkspaceFolderBasename},target=/commandhistory,type=volume", // ── Firewall script ──────────────────────────────────────────────── // The image provides iptables/ipset packages and sudo rule but NOT the script itself. // Each project provides its own script via bind mount to customize the domain allowlist. @@ -128,7 +128,7 @@ }, // The find command chowns all node_modules volume mount points in one pass. // Chromium is baked into the sandbox image (firewall blocks runtime install). - "postCreateCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude && mise install && bun install", + "postCreateCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude /commandhistory && mise install && bun install", // Firewall init (bind-mounted from project) + re-patch the Playwright MCP // plugin's .mcp.json. The patch is defense in depth alongside the SessionStart // hook in /etc/claude-code/managed-settings.json, which handles the case where diff --git a/claude-code/.devcontainer/devcontainer.json b/claude-code/.devcontainer/devcontainer.json index 8f0ef4d..1fe25bf 100644 --- a/claude-code/.devcontainer/devcontainer.json +++ b/claude-code/.devcontainer/devcontainer.json @@ -68,9 +68,9 @@ "ms-playwright.playwright" ], "settings": { - "terminal.integrated.defaultProfile.linux": "fish", + "terminal.integrated.defaultProfile.linux": "zsh", "terminal.integrated.profiles.linux": { - "fish": { "path": "fish" }, + "zsh": { "path": "zsh" }, "bash": { "path": "bash", "icon": "terminal-bash" } }, // Suppress extension recommendation prompts @@ -103,7 +103,7 @@ // "source=myproject-node-modules-web-${localWorkspaceFolderBasename},target=/workspace/apps/web/node_modules,type=volume", // ── Persistent config ────────────────────────────────────────────── "source=myproject-claude-config-${localWorkspaceFolderBasename},target=/home/node/.claude,type=volume", - "source=myproject-fish-data-${localWorkspaceFolderBasename},target=/home/node/.local/share/fish,type=volume" + "source=myproject-zsh-history-${localWorkspaceFolderBasename},target=/commandhistory,type=volume" ], "containerEnv": { "TZ": "${localEnv:TZ:America/Edmonton}", @@ -116,7 +116,7 @@ // mise install reads .mise.toml and installs project-specific tool versions. // Chromium is baked into the image via apt — no playwright install step needed. // Projects' playwright.config.ts should use process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH. - "updateContentCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude && mise install && bun install", + "updateContentCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude /commandhistory && mise install && bun install", // Re-patch the Playwright MCP plugin's .mcp.json on every start. Defense in // depth alongside the SessionStart hook in /etc/claude-code/managed-settings.json, // which handles the case where the plugin auto-updates mid-container-run. diff --git a/claude-code/README.md b/claude-code/README.md index 173c0e9..fc00253 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -16,8 +16,8 @@ Projects consume these pre-built images and control their own tool versions via | Layer | What | Why | |-------|------|-----| | OS | `node:24-trixie-slim` + system packages | Node is needed during the build (Playwright, npm globals) | -| Shell | Fish, Starship, fzf | Built-in syntax highlighting, autosuggestions, completions | -| Tools | git-delta, gh CLI, jq, nano, vim, wget, unzip, less, man-db, procps, openssh-client | Standard dev utilities (`openssh-client` provides `ssh`/`ssh-keygen` — enables SSH-format commit signing) | +| Shell | zsh, oh-my-zsh (`git`, `fzf` plugins), powerlevel10k | Completions, git aliases and prompt integration | +| Tools | gh CLI, git, curl, jq, less, fzf, procps, openssh-client | Standard dev utilities (`openssh-client` provides `ssh`/`ssh-keygen` — enables SSH-format commit signing) | | Mise | The tool manager itself (not the tools) | Projects run `mise install` at container creation for their tool versions | | rtk, ralphex | Pinned `ARG`s, bumped by Renovate on each GitHub release | Dev infrastructure (like Claude Code) — the image tracks the versions so projects don't have to | | Claude Code | npm global install | npm avoids rate limiting that affects the native installer in parallel CI builds | @@ -55,9 +55,9 @@ Copy the example files into your project's `.devcontainer/` directory and custom Copy these to your project's `.devcontainer/`: - [`.devcontainer/docker-compose.yml`](.devcontainer/docker-compose.yml) — image reference (kept fresh by the `initializeCommand` pull in `devcontainer.json`) -- [`.devcontainer/devcontainer.json`](.devcontainer/devcontainer.json) — full config with VS Code extensions, fish shell, OXC formatter, node_modules volume isolation, and lifecycle commands +- [`.devcontainer/devcontainer.json`](.devcontainer/devcontainer.json) — full config with VS Code extensions, zsh shell, OXC formatter, node_modules volume isolation, and lifecycle commands -**Key settings included:** fish + bash terminal profiles, OXC formatter (with comments for switching to Biome/Prettier), node_modules/Claude config/fish history volume mounts, and `updateContentCommand` for mise/bun setup. +**Key settings included:** zsh + bash terminal profiles, OXC formatter (with comments for switching to Biome/Prettier), node_modules/Claude config/zsh history volume mounts, and `updateContentCommand` for mise/bun setup. ### Sandbox variant @@ -68,7 +68,7 @@ Copy these to your project's `.devcontainer/claude-sandbox/`: **Sandbox differences from default:** `capAdd` for iptables, `postStartCommand` runs the firewall script, `claudeCode.allowDangerouslySkipPermissions` enabled, and OAuth token must be injected from the host (see [Sandbox Authentication](#sandbox-authentication)). -**Shared volumes:** Both variants use `${localWorkspaceFolderBasename}` in volume names, so they share node_modules, Claude config, and fish history. Install packages in one variant and both benefit. Docker named volumes support multi-container access, so both can run simultaneously — just avoid running `bun install` in both at the same time. +**Shared volumes:** Both variants use `${localWorkspaceFolderBasename}` in volume names, so they share node_modules, Claude config, and zsh history. Install packages in one variant and both benefit. Docker named volumes support multi-container access, so both can run simultaneously — just avoid running `bun install` in both at the same time. ## Project Setup Guide @@ -402,15 +402,21 @@ cat ~/.claude/plugins/cache/claude-plugins-official/playwright/*/.mcp.json ## Build Args -| Arg | Default | Description | -|-----|---------|-------------| -| `GIT_DELTA_VERSION` | `0.18.2` | git-delta version | -| `RTK_VERSION` | `0.43.0` | rtk version (Renovate-managed) | -| `RALPHEX_VERSION` | `1.6.0` | ralphex version (Renovate-managed) | -| `CLAUDE_CODE_VERSION` | `2.1.216` | Claude Code CLI version (Renovate-managed) | -| `AGENT_BROWSER_VERSION` | `0.32.3` | agent-browser version, default target only (Renovate-managed) | - -The four Renovate-managed args carry `# renovate:` annotations in the Dockerfile; edit them by +Current values live in [the Dockerfile](.devcontainer/Dockerfile) and are not repeated +here — Renovate bumps several of them weekly, so any number written below would be wrong +more often than right. + +| Arg | Updated by | Description | +|-----|------------|-------------| +| `RTK_VERSION` | Renovate | rtk | +| `RALPHEX_VERSION` | Renovate | ralphex | +| `CLAUDE_CODE_VERSION` | Renovate | Claude Code CLI | +| `AGENT_BROWSER_VERSION` | Renovate | agent-browser, default target only | +| `GH_VERSION` | Renovate | GitHub CLI — from the upstream `.deb`, not apt (trixie freezes gh at 2.46.0) | +| `OH_MY_ZSH_REF` | by hand | oh-my-zsh, pinned to a commit SHA | +| `POWERLEVEL10K_REF` | by hand | powerlevel10k, pinned to a commit SHA | + +The five Renovate-managed args carry `# renovate:` annotations in the Dockerfile; edit them by hand only for a local build. Bumps land as auto-merged PRs — see [Automatic Rebuilds](#automatic-rebuilds). ## Building Locally / Local Fallback diff --git a/docs/superpowers/plans/2026-03-21-devcontainer-for-repo.md b/docs/superpowers/plans/2026-03-21-devcontainer-for-repo.md index a1bac09..4c923b4 100644 --- a/docs/superpowers/plans/2026-03-21-devcontainer-for-repo.md +++ b/docs/superpowers/plans/2026-03-21-devcontainer-for-repo.md @@ -6,6 +6,11 @@ **Architecture:** Multi-stage Dockerfile following `claude-code/.devcontainer/Dockerfile` pattern: shared `base` stage with all common tooling, then `default` (passwordless sudo) and `sandbox` (firewall packages + restricted sudo) targets. rtk and ralphex fetch latest release at build time. git-delta is pinned (v0.19.0 dropped arm64 .deb and changed naming). +> **Partly superseded.** Fish and Starship were replaced by zsh + oh-my-zsh + +> powerlevel10k, and git-delta was removed from every image. The Dockerfile +> excerpts below still show the original fish/starship blocks. See +> `.devcontainer/Dockerfile` for the current shape. + **Tech Stack:** Docker multi-stage builds, Node 24 LTS (trixie-slim), Fish shell + Starship prompt, iptables (sandbox), Claude Code plugins --- diff --git a/docs/superpowers/specs/2026-07-20-renovate-tool-updates-design.md b/docs/superpowers/specs/2026-07-20-renovate-tool-updates-design.md index 57ce75a..3942fb7 100644 --- a/docs/superpowers/specs/2026-07-20-renovate-tool-updates-design.md +++ b/docs/superpowers/specs/2026-07-20-renovate-tool-updates-design.md @@ -29,6 +29,13 @@ version** — no more time-based rebuilds. ## Non-goals +> **Partly superseded.** `Go`, `Docker` and the `hugo-bun-node` musl `Node` +> build were later brought under Renovate, alongside `gh`; oh-my-zsh and +> powerlevel10k are pinned by commit SHA and deliberately left unmanaged; +> `git-delta` was removed from every image. `Bun`, `Hugo` and base images remain +> out of scope as written below. See `.github/renovate.json5` for the current +> package list. + - Managing `Bun` / `Hugo` / `Go` / `Docker` / `git-delta` / base images with Renovate (explicitly out of scope — they stay as they are today, including the manual `update-and-build-ralphex-fe.yml` version-bump path). diff --git a/hugo-bun-node/.devcontainer/Dockerfile b/hugo-bun-node/.devcontainer/Dockerfile index 8092800..bef1d1d 100644 --- a/hugo-bun-node/.devcontainer/Dockerfile +++ b/hugo-bun-node/.devcontainer/Dockerfile @@ -3,6 +3,7 @@ ARG BUN_VERSION=1.3.8 FROM oven/bun:${BUN_VERSION}-alpine ARG HUGO_VERSION=0.155.1 +# renovate: datasource=node-version depName=node ARG NODE_VERSION=24.13.0 # Platform architecture (automatically set by Docker based on build platform) diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index 9ca4ab9..4974a55 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -1,6 +1,8 @@ # renovate: datasource=npm depName=bun ARG BUN_VERSION=1.4.2 +# renovate: datasource=github-tags depName=docker/cli ARG DOCKER_VERSION=29.3.0 +# renovate: datasource=golang-version depName=go ARG GO_VERSION=1.24.4 # renovate: datasource=github-releases depName=gohugoio/hugo ARG HUGO_VERSION=0.166.0