diff --git a/.env.example b/.env.example index 3242b49..97ec97a 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,17 @@ -# Docker Compose reads this file automatically when present as `.env`. -# These values control the host side of docker-compose.full.yaml. +# ============================================================================== +# HolyClaude — Environment Variables +# Copy this file to .env and uncomment/adjust the values you need. +# docker-compose.full.yaml reads these automatically. +# The docker-compose.yaml (quick start) uses hardcoded defaults. +# ============================================================================== -# CloudCLI web UI host port -HOLYCLAUDE_HOST_PORT=3001 +# Host port for CloudCLI web UI (default: 3001) +# HOLYCLAUDE_HOST_PORT=3003 -# Host bind-mount paths -HOLYCLAUDE_HOST_CLAUDE_DIR=./data/claude -HOLYCLAUDE_HOST_WORKSPACE_DIR=./workspace +# Host path for Claude Code data (settings, credentials, API keys) +# Default: ./data/claude (relative to docker-compose file) +# HOLYCLAUDE_HOST_CLAUDE_DIR=./data/claude + +# Host path for workspace (your code and projects) +# Default: ./workspace (relative to docker-compose file) +# HOLYCLAUDE_HOST_WORKSPACE_DIR=./workspace diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2f1552c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,11 @@ + +## Codex file access policy + +- Do not read, summarize, index, search, modify, or traverse `.git/`, `.claude/`, `data/`, `assets/`, `workspace/`. +- Respect `.gitignore` as the source of truth for ignored files. +- Before listing project files, prefer: + `git ls-files` + or: + `rg --files --hidden --glob '!.git/**'` +- Do not inspect ignored folders such as `node_modules/`, `dist/`, `build/`, `coverage/`, `.venv/`, `venv/`, logs, local databases, or `.env*` files. +- If a task appears to require an ignored file, ask before reading it. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4d881fd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,100 @@ +# CLAUDE.md + +Ce fichier fournit des instructions à Claude Code (claude.ai/code) lorsqu'il travaille sur du code dans ce dépôt. + +## Vue d'ensemble + +HolyClaude est un conteneur Docker préconfiguré qui embarque Claude Code CLI, CloudCLI (interface web), 7 CLI IA, un navigateur Chromium headless, et 50+ outils de développement — le tout en une seule image. + +## Commandes principales + +```bash +# Build de l'image complète +docker build -t holyclaude . + +# Build de l'image slim +docker build --build-arg VARIANT=slim -t holyclaude:slim . + +# Build multi-arch (ARM) +docker buildx build --platform linux/arm64 -t holyclaude . + +# Démarrage rapide (docker compose) +docker compose up -d +``` + +## Structure du projet + +``` +holyclaude/ +├── Dockerfile # Build single-stage (full/slim via ARG VARIANT) +├── docker-compose.yaml # Configuration minimale +├── docker-compose.full.yaml# Configuration complète avec tous les paramètres +├── scripts/ +│ ├── entrypoint.sh # Point d'entrée — remappe UID/GID, bootstrap, handoff s6 +│ ├── bootstrap.sh # Premier démarrage : settings, CLAUDE.md, git, hooks +│ └── notify.py # Notifications via Apprise (Discord, Telegram, etc.) +├── s6-overlay/s6-rc.d/ # Définitions de services s6-overlay +│ ├── cloudcli/ # CloudCLI (:3001), run sous l'utilisateur claude +│ └── xvfb/ # Xvfb (:99), affichage virtuel pour Chromium +├── config/ +│ ├── settings.json # Settings Claude Code par défaut (allowEdits) +│ ├── claude-memory-full.md # Template mémoire pour la variante full +│ └── claude-memory-slim.md # Template mémoire pour la variante slim +├── assets/ # Logo et bannière +├── docs/ # Documentation : architecture, configuration, ollama, troubleshooting +└── .github/workflows/ # CI/CD : build & push Docker Hub + GHCR (multi-arch, slim+full) +``` + +## Architecture + +Le conteneur suit ce cycle de vie : + +1. **`entrypoint.sh`** (root) — remappe UID/GID via PUID/PGID, crée les fichiers nécessaires, synchronise `.claude.json`, lance `bootstrap.sh` au premier démarrage, puis `exec /init` vers s6-overlay +2. **`bootstrap.sh`** (premier démarrage uniquement) — copie settings/CLAUDE.md, configure git, crée les hooks Codex/Gemini/Cursor, crée un sentinel (`.holyclaude-bootstrapped`) +3. **s6-overlay** (PID 1) — supervise CloudCLI et Xvfb, redémarre automatiquement en cas de crash + +Services supervisés par s6-overlay : +- **CloudCLI** (`cloudcli --port 3001`) — interface web, tourne sous l'utilisateur `claude` avec `WORKSPACES_ROOT=/workspace` +- **Xvfb** (`Xvfb :99 1920x1080x24 -nolisten tcp`) — affichage virtuel pour Chromium headless + +Les variables d'environnement Docker Compose ne passent pas automatiquement à travers s6-setuidgid. Les scripts `run` des services définissent explicitement les variables nécessaires. + +## Patches CloudCLI + +Le Dockerfile applique plusieurs patches au paquet `@cloudcli-ai/cloudcli` : +- **WebSocket** — préservation du type de frame binaire dans le proxy (Issue #11) +- **Shell** — préservation de la position de défilement (Issue #35) +- **Modèle** — support du modèle Claude personnalisé via SSE/select (Issue #36) + +## Variantes d'image + +Contrôlées par `ARG VARIANT=full` dans le Dockerfile. La variante est stockée dans `/etc/holyclaude-variant` et utilisée par bootstrap pour choisir le template CLAUDE.md. + +| Variante | Paquets supplémentaires | +|----------|------------------------| +| `full` (défaut) | Tous les paquets (bun + npm + uv + apt), Junie, OpenCode | +| `slim` | Noyau uniquement (TypeScript, Python, Playwright, AI CLIs principaux) | + +## CI/CD + +GitHub Actions (`.github/workflows/docker-publish.yml`): +- Déclenché sur les tags `v*` +- Construit et pousse vers Docker Hub (`coderluii/holyclaude`) et GHCR +- Matrix `slim`/`full` avec plateformes `linux/amd64,linux/arm64` +- Tags semver + `latest` (full) / `slim` (slim) +- Met à jour la description Docker Hub + +## Notifications + +Les hooks Claude Code (`Stop`, `PostToolUseFailure`) appellent `notify.py`, qui envoie via Apprise si : +1. Le fichier `~/.claude/notify-on` existe +2. Une variable `NOTIFY_*` est configurée (Discord, Telegram, Slack, Email, etc.) + +## Persistance des données + +| Chemin hôte | Chemin conteneur | Contenu | +|-------------|------------------|---------| +| `./data/claude/` | `/home/claude/.claude/` | Settings, credentials, APIs keys | +| `./workspace/` | `/workspace/` | Code et projets | + +Le fichier `.claude.json` est persisté via un mécanisme de copie boot à boot (les symlinks sont écrasés par Claude Code). diff --git a/Dockerfile b/Dockerfile index 1532f56..4ea0335 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,12 +7,12 @@ # docker build --build-arg VARIANT=slim -t holyclaude:slim . # ============================================================================== -FROM node:22-bookworm-slim +FROM node:26-trixie-slim LABEL org.opencontainers.image.source=https://github.com/CoderLuii/HolyClaude # ---------- Build args ---------- -ARG S6_OVERLAY_VERSION=3.2.0.2 +ARG S6_OVERLAY_VERSION=3.2.3.0 ARG TARGETARCH ARG VARIANT=full @@ -24,7 +24,8 @@ ENV DEBIAN_FRONTEND=noninteractive \ DBUS_SESSION_BUS_ADDRESS=disabled: \ CHROMIUM_FLAGS="--no-sandbox --disable-gpu --disable-dev-shm-usage" \ CHROME_PATH=/usr/bin/chromium \ - PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium + PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \ + npm_config_nodedir=/usr/local # ---------- s6-overlay v3 (multi-arch) ---------- RUN apt-get update && apt-get install -y --no-install-recommends xz-utils curl ca-certificates && rm -rf /var/lib/apt/lists/* @@ -39,7 +40,7 @@ RUN S6_ARCH=$(case "$TARGETARCH" in arm64) echo "aarch64";; *) echo "x86_64";; e # ---------- System packages (always installed) ---------- RUN apt-get update && apt-get install -y --no-install-recommends \ # Core utilities - git curl wget jq ripgrep fd-find unzip zip tree tmux fzf bat bubblewrap \ + git curl wget jq nano vim ripgrep fd-find unzip zip tree tmux fzf bat bubblewrap \ # Build tools build-essential pkg-config python3 python3-pip python3-venv \ # Browser (Playwright/Puppeteer) @@ -62,8 +63,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ sudo \ && rm -rf /var/lib/apt/lists/* -# ---------- bubblewrap setuid (Codex CLI sandbox on restricted kernels) ---------- -RUN chmod u+s /usr/bin/bwrap +# ---------- bubblewrap: setuid intentionally removed for security hardening ---------- +# Codex CLI's sandbox works without setuid in most configurations. # ---------- Full-only system packages ---------- RUN if [ "$VARIANT" = "full" ]; then \ @@ -72,11 +73,11 @@ RUN if [ "$VARIANT" = "full" ]; then \ && rm -rf /var/lib/apt/lists/*; \ fi -# ---------- Azure CLI (full only) ---------- -RUN if [ "$VARIANT" = "full" ]; then \ - curl -sL https://aka.ms/InstallAzureCLIDeb | bash \ - && rm -rf /var/lib/apt/lists/*; \ - fi +# ---------- Azure CLI ---------- +# RUN if [ "$VARIANT" = "full" ]; then \ +# curl -sL https://aka.ms/InstallAzureCLIDeb | bash \ +# && rm -rf /var/lib/apt/lists/*; \ +# fi # ---------- GitHub CLI ---------- RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ @@ -92,32 +93,55 @@ RUN ln -sf /usr/bin/batcat /usr/local/bin/bat 2>/dev/null || true RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen # ---------- Create claude user ---------- -# node:22-bookworm-slim already has UID 1000 as 'node' — rename it to 'claude' +# node:26-trixie-slim already has UID 1000 as 'node' — rename it to 'claude' RUN usermod -l claude -d /home/claude -m node && \ - groupmod -n claude node && \ - echo "claude ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/claude && \ - chmod 0440 /etc/sudoers.d/claude + groupmod -n claude node + +# ---------- bun (JavaScript runtime) ---------- +# Installed via npm for reliability (base image node:26 has npm built-in). +# BUN_INSTALL points to ~/.bun so bun install -g writes user-scoped packages. +RUN npm install -g bun + +USER claude +RUN mkdir -p /home/claude/.bun/bin && \ + ln -sf /usr/local/bin/bun /home/claude/.bun/bin/bun && \ + chown -R claude:claude /home/claude/.bun +ENV BUN_INSTALL=/home/claude/.bun \ + PATH="/home/claude/.bun/bin:${PATH}" + +# ---------- uv (Python package manager, user-level) ---------- +USER claude +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +WORKDIR /home/claude +RUN uv venv /home/claude/.venv +ENV PATH="/home/claude/.venv/bin:$PATH" +RUN echo 'alias pip="uv pip"' >> /home/claude/.bashrc && \ + echo 'alias python="uv run python3"' >> /home/claude/.bashrc + + -# ---------- Claude Code CLI (native installer) ---------- +# ---------- Claude Code CLI (via bun) ---------- # CRITICAL: WORKDIR must be non-root-owned or the installer hangs WORKDIR /workspace USER claude -RUN curl -fsSL https://claude.ai/install.sh | bash -USER root -ENV PATH="/home/claude/.local/bin:${PATH}" +RUN /home/claude/.bun/bin/bun install -g @anthropic-ai/claude-code -# ---------- npm global packages (slim — always installed) ---------- -RUN npm i -g \ +# ---------- Global tools via bun (slim — always installed) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g \ typescript tsx \ pnpm \ vite esbuild \ eslint prettier \ serve nodemon concurrently \ dotenv-cli +USER root -# ---------- npm global packages (full only) ---------- +# ---------- Global tools via bun (full only) ---------- +USER claude RUN if [ "$VARIANT" = "full" ]; then \ - npm i -g \ + /home/claude/.bun/bin/bun install -g \ wrangler vercel netlify-cli \ pm2 \ prisma drizzle-kit \ @@ -127,8 +151,10 @@ RUN if [ "$VARIANT" = "full" ]; then \ @marp-team/marp-cli @cloudflare/next-on-pages; \ fi +USER claude + # ---------- Python packages (slim — always installed) ---------- -RUN pip install --no-cache-dir --break-system-packages \ +RUN uv pip install --no-cache-dir --break-system-packages \ requests httpx beautifulsoup4 lxml \ Pillow \ pandas numpy \ @@ -140,8 +166,8 @@ RUN pip install --no-cache-dir --break-system-packages \ # ---------- Python packages (full only) ---------- RUN if [ "$VARIANT" = "full" ]; then \ - pip install --no-cache-dir --break-system-packages \ - reportlab weasyprint cairosvg fpdf2 PyMuPDF pdfkit img2pdf \ + uv pip install --no-cache-dir --break-system-packages \ + reportlab weasyprint cairosvg fpdf2 PyMuPDF pdfkit img2pdf markitdown \ xlsxwriter xlrd \ matplotlib seaborn \ python-pptx \ @@ -149,8 +175,12 @@ RUN if [ "$VARIANT" = "full" ]; then \ httpie; \ fi -# ---------- AI CLI providers ---------- -RUN npm i -g @google/gemini-cli @openai/codex task-master-ai +# ---------- AI CLI providers (via bun, user-level) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g @google/gemini-cli @openai/codex task-master-ai +USER root + +# ---------- Cursor CLI (native installer — no bun/npm package) ---------- USER claude RUN curl -fsSL https://cursor.com/install | bash USER root @@ -162,19 +192,33 @@ RUN if [ "$VARIANT" = "full" ]; then \ fi USER root -# ---------- OpenCode CLI (full only) ---------- +# ---------- OpenCode CLI (full only, via bun) ---------- +USER claude RUN if [ "$VARIANT" = "full" ]; then \ - npm i -g opencode-ai; \ + /home/claude/.bun/bin/bun install -g opencode-ai; \ fi +USER root + + + +# ---------- bun global tools (sift search, go-task) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g sift @go-task/cli +#USER root + +# ---------- CloudCLI (web UI for Claude Code) — npm package, latest ---------- +#RUN bun install -g @cloudcli-ai/cloudcli +RUN bunx @cloudcli-ai/cloudcli --version -COPY vendor/artifacts/siteboon-claude-code-ui-1.26.3.tgz /tmp/vendor/siteboon-claude-code-ui-1.26.3.tgz +RUN touch /tmp/bunx-1000-@cloudcli-ai/cloudcli@latest/node_modules/@cloudcli-ai/cloudcli/.env -# ---------- CloudCLI (web UI for Claude Code) ---------- -RUN npm i -g /tmp/vendor/siteboon-claude-code-ui-1.26.3.tgz && rm -f /tmp/vendor/siteboon-claude-code-ui-1.26.3.tgz -RUN touch /usr/local/lib/node_modules/@siteboon/claude-code-ui/.env +# # ---------- Patch: Chokidar readdirp ESM/CJS interop for Node startup ---------- +# RUN find /home/claude/.bun/install/cache -path '*/chokidar*@@@*/esm/index.js' -type f \ +# -exec sed -i "s/import { readdirp } from 'readdirp';/import readdirp from 'readdirp';/" {} \; && \ +# echo "[patch] Chokidar readdirp import fix applied" # ---------- Patch: preserve WebSocket frame type in plugin proxy (Issue #11) ---------- -RUN CLOUDCLI_INDEX="/usr/local/lib/node_modules/@siteboon/claude-code-ui/server/index.js" && \ +RUN CLOUDCLI_INDEX="/tmp/bunx-1000-@cloudcli-ai/cloudcli@latest/node_modules/@cloudcli-ai/cloudcli/server/index.js" && \ grep -q "upstream.on('message', (data) =>" "$CLOUDCLI_INDEX" && \ sed -i "s/upstream.on('message', (data) => {/upstream.on('message', (data, isBinary) => {/" "$CLOUDCLI_INDEX" && \ sed -i "s/if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data)/if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data, { binary: isBinary })/" "$CLOUDCLI_INDEX" && \ @@ -183,55 +227,55 @@ RUN CLOUDCLI_INDEX="/usr/local/lib/node_modules/@siteboon/claude-code-ui/server/ echo "[patch] WebSocket frame type fix applied (both directions)" || \ echo "[patch] WARNING: WebSocket pattern not found in vendored CloudCLI install, skipping patch" -# patch: preserve Shell tab scroll position across periodic refresh (issue #35) -RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \ - grep -q 'const B=()=>{v.current?.focus()}' "$CLOUDCLI_BUNDLE" && \ - perl -pi -e 's/const B=\(\)=>\{v\.current\?\.focus\(\)\}/const B=()=>{const _vp=v.current?.buffer?.active?.viewportY??0;v.current?.focus();v.current?.scrollToLine(_vp)}/g' "$CLOUDCLI_BUNDLE" && \ - echo "[patch] Shell scroll position fix applied" || \ - echo "[patch] WARNING: Shell scroll pattern not found in vendored CloudCLI bundle, skipping patch" - -# patch v1.2.2-1: commands.js expose newModel in spawn args (issue #36) -RUN CLOUDCLI_COMMANDS="/usr/local/lib/node_modules/@siteboon/claude-code-ui/server/routes/commands.js" && \ - grep -q 'message: args.length > 0' "$CLOUDCLI_COMMANDS" && \ - perl -pi -e 's/^(\s+)(message: args\.length > 0)/$1newModel: args.length > 0 ? args[0] : null,\n$1$2/' "$CLOUDCLI_COMMANDS" && \ - echo "[patch] commands.js newModel field added" || \ - echo "[patch] WARNING: commands.js newModel pattern not found, skipping patch" - -# patch v1.2.2-2: bundle expose setClaudeModel in claudeModel context spread (issue #36) -RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \ - grep -q 'claudeModel:W,codexModel:V' "$CLOUDCLI_BUNDLE" && \ - perl -pi -e 's/\QclaudeModel:W,codexModel:V\E/claudeModel:W,setClaudeModel:L,codexModel:V/g' "$CLOUDCLI_BUNDLE" && \ - echo "[patch] bundle setClaudeModel context spread applied" || \ - echo "[patch] WARNING: bundle claudeModel:W pattern not found, skipping patch" - -# patch v1.2.2-3: bundle wire setClaudeModel:lS2 into cursorModel destructure (issue #36) -RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \ - grep -q 'cursorModel:o,claudeModel:l,codexModel:c' "$CLOUDCLI_BUNDLE" && \ - perl -pi -e 's/\QcursorModel:o,claudeModel:l,codexModel:c\E/cursorModel:o,claudeModel:l,setClaudeModel:lS2,codexModel:c/g' "$CLOUDCLI_BUNDLE" && \ - echo "[patch] bundle setClaudeModel:lS2 destructure applied" || \ - echo "[patch] WARNING: bundle cursorModel destructure pattern not found, skipping patch" - -# patch v1.2.2-4: bundle apply newModel on SSE model event (issue #36) -RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \ - grep -q 'case"model":k({type:"assistant"' "$CLOUDCLI_BUNDLE" && \ - perl -pi -e 's/\Qcase"model":k({type:"assistant"\E/case"model":me.newModel\&\&lS2\&\&(lS2(me.newModel),localStorage.setItem("claude-model",me.newModel));k({type:"assistant"/g' "$CLOUDCLI_BUNDLE" && \ - echo "[patch] bundle SSE model event handler applied" || \ - echo "[patch] WARNING: bundle case\"model\" pattern not found, skipping patch" - -# patch v1.2.2-5: bundle add custom model option to select (issue #36) -RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \ - grep -q 'children:N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j))}' "$CLOUDCLI_BUNDLE" && \ - perl -pi -e 's/\Qchildren:N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j))}\E/children:[...N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j)),!N.OPTIONS.some(C=>C.value===k)\&\&k\&\&s.jsx("option",{value:k,children:k},k+"custom")].filter(Boolean)}/g' "$CLOUDCLI_BUNDLE" && \ - echo "[patch] bundle custom model select option applied" || \ - echo "[patch] WARNING: bundle custom model select pattern not found, skipping patch" +# # patch: preserve Shell tab scroll position across periodic refresh (issue #35) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'const B=()=>{v.current?.focus()}' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/const B=\(\)=>\{v\.current\?\.focus\(\)\}/const B=()=>{const _vp=v.current?.buffer?.active?.viewportY??0;v.current?.focus();v.current?.scrollToLine(_vp)}/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] Shell scroll position fix applied" || \ +# echo "[patch] WARNING: Shell scroll pattern not found in vendored CloudCLI bundle, skipping patch" + +# # patch v1.2.2-1: commands.js expose newModel in spawn args (issue #36) +# RUN CLOUDCLI_COMMANDS="/tmp/bunx-1000-@cloudcli-ai/cloudcli@latest/node_modules/@cloudcli-ai/cloudcli/server/routes/commands.js" && \ +# grep -q 'message: args.length > 0' "$CLOUDCLI_COMMANDS" && \ +# perl -pi -e 's/^(\s+)(message: args\.length > 0)/$1newModel: args.length > 0 ? args[0] : null,\n$1$2/' "$CLOUDCLI_COMMANDS" && \ +# echo "[patch] commands.js newModel field added" || \ +# echo "[patch] WARNING: commands.js newModel pattern not found, skipping patch" + +# # patch v1.2.2-2: bundle expose setClaudeModel in claudeModel context spread (issue #36) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'claudeModel:W,codexModel:V' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/\QclaudeModel:W,codexModel:V\E/claudeModel:W,setClaudeModel:L,codexModel:V/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] bundle setClaudeModel context spread applied" || \ +# echo "[patch] WARNING: bundle claudeModel:W pattern not found, skipping patch" + +# # patch v1.2.2-3: bundle wire setClaudeModel:lS2 into cursorModel destructure (issue #36) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'cursorModel:o,claudeModel:l,codexModel:c' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/\QcursorModel:o,claudeModel:l,codexModel:c\E/cursorModel:o,claudeModel:l,setClaudeModel:lS2,codexModel:c/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] bundle setClaudeModel:lS2 destructure applied" || \ +# echo "[patch] WARNING: bundle cursorModel destructure pattern not found, skipping patch" + +# # patch v1.2.2-4: bundle apply newModel on SSE model event (issue #36) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'case"model":k({type:"assistant"' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/\Qcase"model":k({type:"assistant"\E/case"model":me.newModel\&\&lS2\&\&(lS2(me.newModel),localStorage.setItem("claude-model",me.newModel));k({type:"assistant"/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] bundle SSE model event handler applied" || \ +# echo "[patch] WARNING: bundle case\"model\" pattern not found, skipping patch" + +# # patch v1.2.2-5: bundle add custom model option to select (issue #36) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'children:N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j))}' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/\Qchildren:N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j))}\E/children:[...N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j)),!N.OPTIONS.some(C=>C.value===k)\&\&k\&\&s.jsx("option",{value:k,children:k},k+"custom")].filter(Boolean)}/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] bundle custom model select option applied" || \ +# echo "[patch] WARNING: bundle custom model select pattern not found, skipping patch" # ---------- CloudCLI plugins (baked into image) ---------- USER claude RUN mkdir -p /home/claude/.claude-code-ui/plugins && \ git clone --depth 1 https://github.com/cloudcli-ai/cloudcli-plugin-starter.git /home/claude/.claude-code-ui/plugins/project-stats && \ - cd /home/claude/.claude-code-ui/plugins/project-stats && npm install && npm run build && \ + cd /home/claude/.claude-code-ui/plugins/project-stats && bun install && bun run build && \ git clone --depth 1 https://github.com/cloudcli-ai/cloudcli-plugin-terminal.git /home/claude/.claude-code-ui/plugins/web-terminal && \ - cd /home/claude/.claude-code-ui/plugins/web-terminal && npm install && npm run build && \ + cd /home/claude/.claude-code-ui/plugins/web-terminal && bun install && bun run build && \ echo '{"project-stats":{"name":"project-stats","source":"https://github.com/cloudcli-ai/cloudcli-plugin-starter","enabled":true},"web-terminal":{"name":"web-terminal","source":"https://github.com/cloudcli-ai/cloudcli-plugin-terminal","enabled":true}}' > /home/claude/.claude-code-ui/plugins.json USER root diff --git a/Dockerfile.gpu b/Dockerfile.gpu new file mode 100644 index 0000000..f2d6563 --- /dev/null +++ b/Dockerfile.gpu @@ -0,0 +1,325 @@ +# ============================================================================== +# HolyClaude — Pre-configured Docker Environment for Claude Code CLI + CloudCLI +# https://github.com/coderluii/holyclaude +# +# Build variants: +# docker build -t holyclaude . # full (default) +# docker build --build-arg VARIANT=slim -t holyclaude:slim . +# ============================================================================== + +FROM docker.io/nvidia/cuda:13.2.1-runtime-ubuntu24.04 + +LABEL org.opencontainers.image.source=https://github.com/CoderLuii/HolyClaude + +# ---------- Build args ---------- +ARG S6_OVERLAY_VERSION=3.2.0.2 +ARG TARGETARCH +ARG VARIANT=full + +# ---------- Environment ---------- +ENV DEBIAN_FRONTEND=noninteractive \ + LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + DISPLAY=:99 \ + DBUS_SESSION_BUS_ADDRESS=disabled: \ + CHROMIUM_FLAGS="--no-sandbox --disable-gpu --disable-dev-shm-usage" \ + CHROME_PATH=/usr/bin/chromium \ + PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \ + npm_config_nodedir=/usr/local + +# ---------- s6-overlay v3 (multi-arch) ---------- +RUN apt-get update && apt-get install -y --no-install-recommends xz-utils curl ca-certificates && rm -rf /var/lib/apt/lists/* +ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp/ +RUN S6_ARCH=$(case "$TARGETARCH" in arm64) echo "aarch64";; *) echo "x86_64";; esac) && \ + curl -fsSL -o /tmp/s6-overlay-arch.tar.xz \ + "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" && \ + tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \ + tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz && \ + rm /tmp/s6-overlay-*.tar.xz + +# ---------- System packages (always installed) ---------- +RUN apt-get update && apt-get install -y --no-install-recommends \ + # Core utilities + git curl wget jq nano vim ripgrep fd-find unzip zip tree tmux fzf bat bubblewrap \ + # Build tools + build-essential pkg-config python3 python3-pip python3-venv \ + # Browser (Playwright/Puppeteer) — installed via apt on Debian, but + # Ubuntu base has no chromium apt pkg; Playwright bundle is used instead. + # chromium \ + # Fonts + fonts-liberation2 fonts-dejavu-core fonts-noto-core fonts-noto-color-emoji fonts-inter \ + # Locale support + locales \ + # Debugging tools + strace lsof iproute2 procps htop \ + # Database CLI tools + postgresql-client redis-tools sqlite3 \ + # SSH client (NOT server) + openssh-client \ + # Xvfb for headless Chrome + xvfb \ + # Image processing + imagemagick \ + # Sudo + sudo \ + && rm -rf /var/lib/apt/lists/* + +# ---------- bubblewrap: setuid intentionally removed for security hardening ---------- +# Codex CLI's sandbox works without setuid in most configurations. + +# ---------- Full-only system packages ---------- +RUN if [ "$VARIANT" = "full" ]; then \ + apt-get update && apt-get install -y --no-install-recommends \ + pandoc ffmpeg libvips-dev \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +# ---------- Azure CLI ---------- +# RUN if [ "$VARIANT" = "full" ]; then \ +# curl -sL https://aka.ms/InstallAzureCLIDeb | bash \ +# && rm -rf /var/lib/apt/lists/*; \ +# fi + +# ---------- GitHub CLI ---------- +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg 2>/dev/null && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + > /etc/apt/sources.list.d/github-cli.list && \ + apt-get update && apt-get install -y gh && rm -rf /var/lib/apt/lists/* + +# bat is at /usr/bin/bat on Ubuntu (not batcat like Debian) — no symlink needed + +# ---------- Locale configuration ---------- +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen + +# ---------- Create claude user ---------- +# image already has UID 1000 as 'ubuntu' — rename it to 'claude' +RUN usermod -l claude -d /home/claude -m ubuntu && \ + groupmod -n claude ubuntu + +# ---------- Node.js 22 LTS (NodeSource) ---------- +# CloudCLI (better-sqlite3) and Prisma require modern Node.js. Ubuntu 24.04 repos +# ship v18 which is too old — use NodeSource instead. +RUN curl -o /usr/local/bin/n https://raw.githubusercontent.com/visionmedia/n/master/bin/n && \ + chmod +x /usr/local/bin/n && \ + n stable + + +# ---------- Create claude user ---------- +# nvidia/cuda base has no 'node' user — rename ubuntu to claude +# ---------- bun (JavaScript runtime) ---------- + +USER claude +RUN curl -fsSL https://bun.com/install | bash +ENV BUN_INSTALL=/home/claude/.bun \ + PATH="/home/claude/.bun/bin:${PATH}" + +# ---------- uv (Python package manager, user-level) ---------- +USER claude +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +WORKDIR /home/claude +RUN uv venv /home/claude/.venv +ENV PATH="/home/claude/.venv/bin:$PATH" +RUN echo 'alias pip="uv pip"' >> /home/claude/.bashrc && \ + echo 'alias python="uv run python3"' >> /home/claude/.bashrc + + + +# ---------- Claude Code CLI (via bun) ---------- +# CRITICAL: WORKDIR must be non-root-owned or the installer hangs +WORKDIR /workspace +USER claude +RUN /home/claude/.bun/bin/bun install -g @anthropic-ai/claude-code + +# ---------- Global tools via bun (slim — always installed) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g \ + typescript tsx \ + pnpm \ + vite esbuild \ + eslint prettier \ + serve nodemon concurrently \ + dotenv-cli +USER root + +# ---------- Global tools via bun (full only) ---------- +USER claude +RUN if [ "$VARIANT" = "full" ]; then \ + /home/claude/.bun/bin/bun install -g \ + wrangler vercel netlify-cli \ + pm2 \ + prisma drizzle-kit \ + eas-cli \ + lighthouse @lhci/cli \ + sharp-cli json-server http-server \ + @marp-team/marp-cli @cloudflare/next-on-pages; \ + fi + +USER claude + +# ---------- Python packages (slim — always installed) ---------- +RUN uv pip install --no-cache-dir --break-system-packages \ + requests httpx beautifulsoup4 lxml \ + Pillow \ + pandas numpy \ + openpyxl python-docx \ + jinja2 pyyaml python-dotenv markdown \ + rich click tqdm \ + playwright \ + apprise + +# ---------- Chromium for Playwright (not available via apt on Ubuntu) ---------- +# Playwright downloads its own chromium from cdn.playwright.dev, which may be +# blocked by corporate proxies. The build continues either way. +USER claude +RUN playwright install chromium || echo "[warn] Playwright chromium download failed (proxy?) — install manually with 'playwright install chromium'" + +# ---------- Python packages (full only) ---------- +RUN if [ "$VARIANT" = "full" ]; then \ + uv pip install --no-cache-dir --break-system-packages \ + reportlab weasyprint cairosvg fpdf2 PyMuPDF pdfkit img2pdf markitdown \ + xlsxwriter xlrd \ + matplotlib seaborn \ + python-pptx \ + fastapi uvicorn \ + httpie; \ + fi + +# ---------- AI CLI providers (via bun, user-level) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g @google/gemini-cli @openai/codex task-master-ai +USER root + +# ---------- Cursor CLI (native installer — no bun/npm package) ---------- +USER claude +RUN curl -fsSL https://cursor.com/install | bash +USER root + +# ---------- Junie CLI (full only) ---------- +USER claude +RUN if [ "$VARIANT" = "full" ]; then \ + curl -fsSL https://junie.jetbrains.com/install.sh | bash; \ + fi +USER root + +# ---------- OpenCode CLI (full only, via bun) ---------- +USER claude +RUN if [ "$VARIANT" = "full" ]; then \ + /home/claude/.bun/bin/bun install -g opencode-ai; \ + fi +USER root + + + +# ---------- bun global tools (sift search, go-task) ---------- +USER claude +RUN /home/claude/.bun/bin/bun install -g sift @go-task/cli +#USER root + +# ---------- CloudCLI (web UI for Claude Code) — npm package, latest ---------- +#RUN bun install -g @cloudcli-ai/cloudcli +RUN bunx @cloudcli-ai/cloudcli --version + +RUN touch /tmp/bunx-1000-@cloudcli-ai/cloudcli@latest/node_modules/@cloudcli-ai/cloudcli/.env + +# # ---------- Patch: Chokidar readdirp ESM/CJS interop for Node startup ---------- +# RUN find /home/claude/.bun/install/cache -path '*/chokidar*@@@*/esm/index.js' -type f \ +# -exec sed -i "s/import { readdirp } from 'readdirp';/import readdirp from 'readdirp';/" {} \; && \ +# echo "[patch] Chokidar readdirp import fix applied" + +# ---------- Patch: preserve WebSocket frame type in plugin proxy (Issue #11) ---------- +RUN CLOUDCLI_INDEX="/tmp/bunx-1000-@cloudcli-ai/cloudcli@latest/node_modules/@cloudcli-ai/cloudcli/server/index.js" && \ + grep -q "upstream.on('message', (data) =>" "$CLOUDCLI_INDEX" && \ + sed -i "s/upstream.on('message', (data) => {/upstream.on('message', (data, isBinary) => {/" "$CLOUDCLI_INDEX" && \ + sed -i "s/if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data)/if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data, { binary: isBinary })/" "$CLOUDCLI_INDEX" && \ + sed -i "s/clientWs.on('message', (data) => {/clientWs.on('message', (data, isBinary) => {/" "$CLOUDCLI_INDEX" && \ + sed -i "s/if (upstream.readyState === WebSocket.OPEN) upstream.send(data)/if (upstream.readyState === WebSocket.OPEN) upstream.send(data, { binary: isBinary })/" "$CLOUDCLI_INDEX" && \ + echo "[patch] WebSocket frame type fix applied (both directions)" || \ + echo "[patch] WARNING: WebSocket pattern not found in vendored CloudCLI install, skipping patch" + +# # patch: preserve Shell tab scroll position across periodic refresh (issue #35) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'const B=()=>{v.current?.focus()}' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/const B=\(\)=>\{v\.current\?\.focus\(\)\}/const B=()=>{const _vp=v.current?.buffer?.active?.viewportY??0;v.current?.focus();v.current?.scrollToLine(_vp)}/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] Shell scroll position fix applied" || \ +# echo "[patch] WARNING: Shell scroll pattern not found in vendored CloudCLI bundle, skipping patch" + +# # patch v1.2.2-1: commands.js expose newModel in spawn args (issue #36) +# RUN CLOUDCLI_COMMANDS="/tmp/bunx-1000-@cloudcli-ai/cloudcli@latest/node_modules/@cloudcli-ai/cloudcli/server/routes/commands.js" && \ +# grep -q 'message: args.length > 0' "$CLOUDCLI_COMMANDS" && \ +# perl -pi -e 's/^(\s+)(message: args\.length > 0)/$1newModel: args.length > 0 ? args[0] : null,\n$1$2/' "$CLOUDCLI_COMMANDS" && \ +# echo "[patch] commands.js newModel field added" || \ +# echo "[patch] WARNING: commands.js newModel pattern not found, skipping patch" + +# # patch v1.2.2-2: bundle expose setClaudeModel in claudeModel context spread (issue #36) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'claudeModel:W,codexModel:V' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/\QclaudeModel:W,codexModel:V\E/claudeModel:W,setClaudeModel:L,codexModel:V/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] bundle setClaudeModel context spread applied" || \ +# echo "[patch] WARNING: bundle claudeModel:W pattern not found, skipping patch" + +# # patch v1.2.2-3: bundle wire setClaudeModel:lS2 into cursorModel destructure (issue #36) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'cursorModel:o,claudeModel:l,codexModel:c' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/\QcursorModel:o,claudeModel:l,codexModel:c\E/cursorModel:o,claudeModel:l,setClaudeModel:lS2,codexModel:c/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] bundle setClaudeModel:lS2 destructure applied" || \ +# echo "[patch] WARNING: bundle cursorModel destructure pattern not found, skipping patch" + +# # patch v1.2.2-4: bundle apply newModel on SSE model event (issue #36) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'case"model":k({type:"assistant"' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/\Qcase"model":k({type:"assistant"\E/case"model":me.newModel\&\&lS2\&\&(lS2(me.newModel),localStorage.setItem("claude-model",me.newModel));k({type:"assistant"/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] bundle SSE model event handler applied" || \ +# echo "[patch] WARNING: bundle case\"model\" pattern not found, skipping patch" + +# # patch v1.2.2-5: bundle add custom model option to select (issue #36) +# RUN CLOUDCLI_BUNDLE="/home/claude/.bun/install/global/node_modules/@cloudcli-ai/cloudcli/dist/assets/index-X3ImjnMV.js" && \ +# grep -q 'children:N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j))}' "$CLOUDCLI_BUNDLE" && \ +# perl -pi -e 's/\Qchildren:N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j))}\E/children:[...N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j)),!N.OPTIONS.some(C=>C.value===k)\&\&k\&\&s.jsx("option",{value:k,children:k},k+"custom")].filter(Boolean)}/g' "$CLOUDCLI_BUNDLE" && \ +# echo "[patch] bundle custom model select option applied" || \ +# echo "[patch] WARNING: bundle custom model select pattern not found, skipping patch" + +# ---------- CloudCLI plugins (baked into image) ---------- +USER claude +RUN mkdir -p /home/claude/.claude-code-ui/plugins && \ + git clone --depth 1 https://github.com/cloudcli-ai/cloudcli-plugin-starter.git /home/claude/.claude-code-ui/plugins/project-stats && \ + cd /home/claude/.claude-code-ui/plugins/project-stats && bun install && bun run build && \ + git clone --depth 1 https://github.com/cloudcli-ai/cloudcli-plugin-terminal.git /home/claude/.claude-code-ui/plugins/web-terminal && \ + cd /home/claude/.claude-code-ui/plugins/web-terminal && bun install && bun run build && \ + echo '{"project-stats":{"name":"project-stats","source":"https://github.com/cloudcli-ai/cloudcli-plugin-starter","enabled":true},"web-terminal":{"name":"web-terminal","source":"https://github.com/cloudcli-ai/cloudcli-plugin-terminal","enabled":true}}' > /home/claude/.claude-code-ui/plugins.json +USER root + +# ---------- Store variant for bootstrap ---------- +RUN echo "${VARIANT}" > /etc/holyclaude-variant + +# ---------- Copy config files ---------- +COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh +COPY scripts/bootstrap.sh /usr/local/bin/bootstrap.sh +COPY scripts/notify.py /usr/local/bin/notify.py +COPY config/settings.json /usr/local/share/holyclaude/settings.json +COPY config/claude-memory-full.md /usr/local/share/holyclaude/claude-memory-full.md +COPY config/claude-memory-slim.md /usr/local/share/holyclaude/claude-memory-slim.md +RUN chmod +x /usr/local/bin/entrypoint.sh \ + /usr/local/bin/bootstrap.sh \ + /usr/local/bin/notify.py + +# ---------- s6-overlay service definitions ---------- +COPY s6-overlay/s6-rc.d/cloudcli/type /etc/s6-overlay/s6-rc.d/cloudcli/type +COPY s6-overlay/s6-rc.d/cloudcli/run /etc/s6-overlay/s6-rc.d/cloudcli/run +COPY s6-overlay/s6-rc.d/xvfb/type /etc/s6-overlay/s6-rc.d/xvfb/type +COPY s6-overlay/s6-rc.d/xvfb/run /etc/s6-overlay/s6-rc.d/xvfb/run +RUN chmod +x /etc/s6-overlay/s6-rc.d/cloudcli/run \ + /etc/s6-overlay/s6-rc.d/xvfb/run && \ + touch /etc/s6-overlay/s6-rc.d/user/contents.d/cloudcli && \ + touch /etc/s6-overlay/s6-rc.d/user/contents.d/xvfb + +# ---------- Working directory ---------- +WORKDIR /workspace + +# ---------- Health check ---------- +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ + CMD curl -sf http://localhost:3001/ || exit 1 + +# ---------- s6-overlay as PID 1 ---------- +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/README.md b/README.md index 4f3c637..614f320 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Your existing Anthropic account works directly: | :package: | [Image Variants](#package-image-variants) | | :whale: | [Docker Compose — Quick](#whale-docker-compose--quick) | | :whale2: | [Docker Compose — Full](#whale2-docker-compose--full) | +| :brain: | [Docker Compose — GPU Inference](#brain-docker-compose--gpu-inference) | | :wrench: | [Environment Variables](#wrench-environment-variables) | | :rocket: | [What's Inside](#rocket-whats-inside) | | :robot: | [AI CLI Providers](#robot-ai-cli-providers) | @@ -143,6 +144,7 @@ http://localhost:3001 | macOS (Docker Desktop) | ✅ Fully supported | Apple Silicon & Intel via Docker Desktop | | Windows (WSL2 + Docker Desktop) | ✅ Fully supported | Requires WSL2 backend | | Synology / QNAP NAS | ✅ Fully supported | Use `CHOKIDAR_USEPOLLING=true` for SMB mounts | +| NVIDIA GPU hosts | ✅ Supported via GPU build | Use `Dockerfile.gpu` with `docker-compose.gpu.yaml` override | | Kubernetes | 🔜 Coming soon | Helm chart planned |

@@ -224,6 +226,7 @@ Two flavors. Same quality. Pick your weight class. |-----|-------------|----------| | **`latest`** | Everything pre-installed — every tool, every library, every CLI | Most users. Zero wait time. Claude never has to stop and install something. | | **`slim`** | Core tools only — Claude installs extras on-demand | Smaller VPS, limited disk, metered bandwidth | +| **local GPU build** | Same HolyClaude stack on an NVIDIA CUDA base image | GPU-enabled hosts that need CUDA/NVIDIA runtime access | | `X.Y.Z` | Full image, pinned version | Production stability — you control when to update | | `X.Y.Z-slim` | Slim image, pinned version | Production + small footprint | @@ -263,10 +266,9 @@ services: shm_size: 2g # Chromium needs this — don't remove network_mode: bridge cap_add: - - SYS_ADMIN # Required: Chromium sandboxing - SYS_PTRACE # Required: debugging tools - security_opt: - - seccomp=unconfined # Required: Chromium in Docker + # SYS_ADMIN and seccomp=unconfined intentionally removed. + # Chromium runs with --no-sandbox (configured in CHROMIUM_FLAGS). ports: - "3001:3001" # CloudCLI web UI volumes: @@ -295,7 +297,7 @@ Open `http://localhost:3001`. Create a CloudCLI account. Sign in with your Anthr **That's the whole setup. You're done.** -> **Why `SYS_ADMIN` + `seccomp=unconfined`?** Chromium needs these to run inside Docker — it's standard for any containerized browser (Playwright docs, Puppeteer docs, every CI pipeline that runs browser tests). Without them, Chromium crashes on startup. This is not a security risk unique to HolyClaude. +> **Why no `SYS_ADMIN` + `seccomp=unconfined`?** Chromium already runs with `--no-sandbox` (set via `CHROMIUM_FLAGS`), so these extra privileges are not needed. Removing them improves the container's security profile. If you need Chromium's sandbox, re-add `SYS_ADMIN` and a custom seccomp profile. > **Why `shm_size: 2g`?** Docker gives containers 64MB of shared memory by default. Chromium uses `/dev/shm` heavily for tab rendering. At 64MB, tabs crash randomly. 2GB is the recommended minimum for any Chromium-in-Docker setup. @@ -325,10 +327,9 @@ services: shm_size: 2g # Chromium shared memory — increase to 4g for heavy browser use network_mode: bridge cap_add: - - SYS_ADMIN # Required: Chromium sandboxing - SYS_PTRACE # Required: debugging tools (strace, lsof) - security_opt: - - seccomp=unconfined # Required: Chromium syscall requirements + # SYS_ADMIN and seccomp=unconfined intentionally removed. + # Chromium runs with --no-sandbox (configured in CHROMIUM_FLAGS). ports: # # CloudCLI web UI — this is the only port you need. @@ -445,6 +446,63 @@ These values are read by Docker Compose on the host. They are not container envi --- +## :brain: Docker Compose — GPU Inference + +Use the GPU override when you want HolyClaude to run on an NVIDIA CUDA base image and expose all host GPUs to the container for AI inference workloads. + +The GPU build keeps the regular HolyClaude stack, but switches the base image and runtime details for NVIDIA workloads: + +- Base image: `nvidia/cuda:13.2.1-runtime-ubuntu24.04` +- GPU access: all host NVIDIA GPUs requested through `docker-compose.gpu.yaml` +- User model: the CUDA image's `ubuntu` user is renamed to `claude` +- Node runtime: installed with `n` instead of Ubuntu's older Node.js packages, so CloudCLI native dependencies such as `better-sqlite3` and tools such as Prisma have a modern Node runtime +- Browser tooling: `CHROME_PATH` and `PUPPETEER_EXECUTABLE_PATH` are set for Chromium/Playwright/Puppeteer compatibility + +Requirements on the host: + +- NVIDIA GPU and driver installed +- NVIDIA Container Toolkit configured for Docker +- Docker Compose v2 with GPU device support + +From the repository root: + +```bash +# Build and start the GPU image +docker compose -f docker-compose.yaml -f docker-compose.gpu.yaml up -d --build + +# Verify GPU visibility inside the container +docker compose -f docker-compose.yaml -f docker-compose.gpu.yaml exec holyclaude nvidia-smi +``` + +`docker-compose.gpu.yaml` is an override, not a replacement for the standard compose file. It switches the build to `Dockerfile.gpu` and requests all NVIDIA GPUs: + +```yaml +services: + holyclaude: + build: + context: ./ + dockerfile: Dockerfile.gpu + devices: + - nvidia.com/gpu=all + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +`Dockerfile.gpu` keeps the same HolyClaude tooling, CloudCLI service, Xvfb setup, persistent volumes, and `VARIANT=full|slim` behavior as the regular image. The main differences are the CUDA runtime base image, GPU device reservation, explicit Chromium/Puppeteer paths, and the modern Node runtime installed outside Ubuntu's default repositories. + +> The published `latest` and `slim` tags are the regular multi-arch images. The GPU image is intended to be built locally from `Dockerfile.gpu` unless a GPU-specific tag is published later. + +

+ ↑ back to top +

+ +--- + ## :wrench: Environment Variables The complete reference. Every variable, what it defaults to, what it does. @@ -488,7 +546,7 @@ This is not a minimal container. This is an entire development workstation. ### Both variants (full + slim)
-Node.js 22 LTS + npm global packages +Node.js 22 LTS + Bun global packages | Package | What it's for | |---------|---------------| @@ -526,6 +584,8 @@ This is not a minimal container. This is an entire development workstation. | Tool | What it's for | |------|---------------| | `git`, `gh` | Version control + GitHub CLI (PRs, issues, releases from the terminal) | +| `bun`, `uv` | JavaScript & Python runtimes/package managers (user-scoped) | +| `sift`, `@go-task/cli` (task) | Fast search and task runner | | `ripgrep` (`rg`), `fd`, `fzf` | Blazing-fast search — Claude uses these constantly | | `bat`, `tree`, `jq` | Better cat (syntax highlighting), directory trees, JSON processing | | `curl`, `wget` | HTTP downloads | @@ -560,14 +620,13 @@ Seven AI CLIs. One container. Switch between them instantly. No other Docker ima The full image includes everything above, plus:
-Additional npm packages — deployment, ORMs, performance +Additional Bun/npm packages — deployment, ORMs, performance | Package | What it's for | |---------|---------------| | `wrangler`, `@cloudflare/next-on-pages` | Cloudflare Workers deployment | | `vercel` | Vercel deployment | | `netlify-cli` | Netlify deployment | -| `az` | Azure CLI for cloud deployment and management | | `prisma`, `drizzle-kit` | The two most popular Node.js ORMs | | `pm2` | Production process manager | | `eas-cli` | Expo / React Native builds | @@ -603,7 +662,7 @@ The full image includes everything above, plus:
-> **Slim users:** Missing a package? Ask Claude. It installs npm/pip packages in seconds. System packages (pandoc, ffmpeg) take 1-2 minutes. You get the same capabilities — the full image just has zero wait time. +> **Slim users:** Missing a package? Ask Claude. It installs bun/npm/uv/pip packages in seconds. System packages (pandoc, ffmpeg) take 1-2 minutes. You get the same capabilities — the full image just has zero wait time.

↑ back to top @@ -720,8 +779,10 @@ holyclaude/ │ └── notify.py # Notification helper (Apprise) ├── s6-overlay/ # Process supervision (s6-rc services) ├── Dockerfile # Single-stage build +├── Dockerfile.gpu # NVIDIA CUDA-based GPU build ├── docker-compose.yaml # Quick start (minimal config) ├── docker-compose.full.yaml # Full config (all options) +├── docker-compose.gpu.yaml # GPU override for Docker Compose ├── LICENSE └── README.md ``` @@ -1033,9 +1094,15 @@ docker build --build-arg VARIANT=slim -t holyclaude:slim . # Build for ARM (Apple Silicon, Raspberry Pi, AWS Graviton) docker buildx build --platform linux/arm64 -t holyclaude . + +# Build the NVIDIA GPU image locally +docker build -f Dockerfile.gpu -t holyclaude:gpu . + +# Start with the GPU compose override +docker compose -f docker-compose.yaml -f docker-compose.gpu.yaml up -d --build ``` -This source release vendors the patched CloudCLI package under `vendor/artifacts/`, so `docker build` installs that bundled tarball instead of downloading `@siteboon/claude-code-ui` from npm. +CloudCLI is installed from the official `@cloudcli-ai/cloudcli` npm package at build time. Then use `image: holyclaude` instead of `image: coderluii/holyclaude:latest` in your compose file. @@ -1155,7 +1222,7 @@ The HolyClaude Docker image includes third-party software, each under its own li | Component | License | Source | |-----------|---------|--------| -| CloudCLI | GPL-3.0 | [siteboon/claudecodeui](https://github.com/siteboon/claudecodeui) | +| CloudCLI | GPL-3.0 | [cloudcli-ai/cloudcli](https://github.com/cloudcli-ai/cloudcli) | | s6-overlay | ISC | [just-containers/s6-overlay](https://github.com/just-containers/s6-overlay) | | Node.js | MIT | [nodejs/node](https://github.com/nodejs/node) | diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..c0a4524 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,18 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json + +version: '3' + +vars: + GREETING: Hello, world! + +tasks: + build: + desc: compose compose build + cmds: + - compose build + silent: true + gpubuild: + desc: compose build + cmds: + - compose -f docker-compose.yaml -f docker-compose.gpu.yaml build + silent: true diff --git a/config/claude-memory-full.md b/config/claude-memory-full.md index 4612ca8..dc472ce 100644 --- a/config/claude-memory-full.md +++ b/config/claude-memory-full.md @@ -6,7 +6,7 @@ You are running inside a **HolyClaude Docker container** (full variant). Everyth ## Environment Overview -- **OS:** Debian Bookworm (slim) inside Docker +- **OS:** Debian Trixie (slim) inside Docker - **User:** `claude` (UID/GID configurable via PUID/PGID) - **Working directory:** `/workspace` (bind-mounted from host) - **Home directory:** `/home/claude` @@ -23,16 +23,16 @@ You are running inside a **HolyClaude Docker container** (full variant). Everyth Both managed by s6-overlay — they auto-restart on failure. -## Node.js & npm (v22 LTS) +## Node.js & npm (v22 LTS) + Bun -### Global packages available: +### Global packages available (via bun): - **Languages:** typescript, tsx -- **Package managers:** pnpm, npm (built-in) +- **Package managers:** pnpm, bun (also available), npm (built-in) - **Build tools:** vite, esbuild - **Code quality:** eslint, prettier - **Dev servers:** serve, nodemon, http-server - **Utilities:** concurrently, dotenv-cli -- **Deployment:** wrangler (Cloudflare), vercel, netlify-cli, @cloudflare/next-on-pages, az (Azure) +- **Deployment:** wrangler (Cloudflare), vercel, netlify-cli, @cloudflare/next-on-pages - **Databases:** prisma, drizzle-kit - **Process management:** pm2 - **Mobile:** eas-cli (Expo) @@ -42,11 +42,12 @@ Both managed by s6-overlay — they auto-restart on failure. ### Installing additional packages: ```bash -npm i -g # Global install -npm i # Project-local install +bun install -g # Global install (user-level, preferred over npm -g) +npm i -g # Fallback global install +npm i # Project-local install ``` -## Python 3 +## Python 3 + uv ### Installed packages: - **HTTP:** requests, httpx, httpie @@ -61,11 +62,19 @@ npm i # Project-local install - **Browser:** playwright - **Web framework:** fastapi, uvicorn -### Installing additional packages: +### Package manager: uv +```bash +uv # uv replaces pip and venv +uv pip install # Install packages (alias: `pip` → `uv pip`) +uv run python3