diff --git a/.dockerignore b/.dockerignore index fc75c26..ba76e9d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,4 @@ .env -.npmrc *.log .git README.md diff --git a/.env.example b/.env.example index ad79c7b..d8a6d6f 100644 --- a/.env.example +++ b/.env.example @@ -1,34 +1,9 @@ # Copy this file to .env and fill in your values. # .env is git-ignored — never commit it. -# ─── Image version ──────────────────────────────────────────────────────────── +# Required: your Anthropic API key +ANTHROPIC_API_KEY=sk-ant-... -# Pin to a specific image tag. Defaults to "latest" if unset. -# IMAGE_TAG=0.1.42 - -# ─── Authentication (choose one) ────────────────────────────────────────────── - -# Option 1: Anthropic API key -# ANTHROPIC_API_KEY=sk-ant-... - -# Option 2: OAuth token from a Claude.ai subscription (1-year validity) -# Generate with: claude setup-token (run on your host, not inside the container) -# CLAUDE_CODE_OAUTH_TOKEN=... - -# Option 3: No key set — Claude Code will prompt for browser login on first run. -# Port 54545 must be reachable from your browser for the OAuth callback. -# Run: sbx ports --publish 54545:54545/tcp - -# ─── MCP servers (all optional) ─────────────────────────────────────────────── - -# GitHub — PAT with repo scope -# GITHUB_TOKEN=ghp_... - -# GitLab — PAT with api scope; GITLAB_URL defaults to https://gitlab.com -# GITLAB_TOKEN=glpat_... -# GITLAB_URL=https://gitlab.com - -# Jira + Confluence — shared Atlassian credentials -# ATLASSIAN_SITE_NAME=your-company # subdomain of .atlassian.net -# ATLASSIAN_USER_EMAIL=you@example.com -# ATLASSIAN_API_TOKEN=... # https://id.atlassian.com/manage-profile/security/api-tokens +# Optional: mount a host directory as /workspace inside the Claude container. +# If unset, a named Docker volume is used (fully isolated from the host). +# WORKSPACE_DIR=/absolute/path/to/your/project diff --git a/.forgejo/workflows/docker-build.yml b/.forgejo/workflows/docker-build.yml deleted file mode 100644 index b13214f..0000000 --- a/.forgejo/workflows/docker-build.yml +++ /dev/null @@ -1,170 +0,0 @@ -name: Build images - -on: - push: - branches: - - main - -env: - # Set this to the public IP or hostname of your registry, - # whichever you use to reach it from your desktop/laptop - FORGEJO_HOST: code.zeidler.dev - HELM_EXPERIMENTAL_OCI: 1 - TRIVY_IMAGE: registry.zeidler.dev/docker-hub/aquasec/trivy:0.70.0 - GRYPE_IMAGE: registry.zeidler.dev/docker-hub/anchore/grype:v0.88.0 - -jobs: - check-docker: - runs-on: docker-cli - services: - docker: - image: registry.zeidler.dev/docker-hub/catthehacker/ubuntu:act-latest - options: --privileged - container: - image: registry.zeidler.dev/docker-hub/catthehacker/ubuntu:act-latest - steps: - - name: Wait for Docker daemon - run: | - timeout=300 # Set a timeout value in seconds - until docker info; do - echo "Waiting for Docker daemon to start..." - sleep 5 - timeout=$((timeout-5)) - if [ $timeout -le 0 ]; then - echo "Timeout waiting for Docker daemon to start." - exit 1 - fi - done - - scan: - needs: check-docker - runs-on: docker-cli - services: - docker: - image: registry.zeidler.dev/docker-hub/catthehacker/ubuntu:act-latest - options: --privileged - container: - image: registry.zeidler.dev/docker-hub/catthehacker/ubuntu:act-latest - steps: - - name: Checkout the repo - uses: actions/checkout@v4 - - - name: Build proxy image for scanning - run: docker build -t scan/proxy:latest ./proxy - - - name: Generate proxy SBOM - run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v "$PWD":/output \ - ${{ env.TRIVY_IMAGE }} \ - image --exit-code 0 --vuln-type os,library \ - --format cyclonedx --output /output/sbom-proxy.cdx.json \ - scan/proxy:latest - - - name: Scan proxy image (Trivy) - run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - ${{ env.TRIVY_IMAGE }} \ - image --exit-code 1 --severity HIGH,CRITICAL \ - --ignore-unfixed --vuln-type os,library \ - --format table \ - scan/proxy:latest - - - name: Scan proxy image (Grype) - run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - ${{ env.GRYPE_IMAGE }} \ - docker:scan/proxy:latest \ - --fail-on high \ - --only-fixed - - - name: Build claude image for scanning - run: docker build -t scan/claude:latest ./claude - - - name: Generate claude SBOM - run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v "$PWD":/output \ - ${{ env.TRIVY_IMAGE }} \ - image --exit-code 0 --vuln-type os,library \ - --format cyclonedx --output /output/sbom-claude.cdx.json \ - scan/claude:latest - - - name: Scan claude image (Trivy) - run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - ${{ env.TRIVY_IMAGE }} \ - image --exit-code 1 --severity HIGH,CRITICAL \ - --ignore-unfixed --vuln-type os,library \ - --format table \ - scan/claude:latest - - - name: Scan claude image (Grype) - run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - ${{ env.GRYPE_IMAGE }} \ - docker:scan/claude:latest \ - --fail-on high \ - --only-fixed - - - name: Upload SBOMs - if: always() - uses: actions/upload-artifact@v4 - with: - name: sboms-${{ env.GITHUB_RUN_NUMBER }} - path: | - sbom-proxy.cdx.json - sbom-claude.cdx.json - retention-days: 90 - - build-and-push: - needs: scan - runs-on: docker-cli - services: - docker: - image: registry.zeidler.dev/docker-hub/catthehacker/ubuntu:act-latest - options: --privileged - environment: deploy - container: - image: registry.zeidler.dev/docker-hub/catthehacker/ubuntu:act-latest - steps: - - name: Checkout the repo - uses: actions/checkout@v4 - - name: Login to the registry - uses: docker/login-action@v3 - with: - registry: ${{ vars.REGISTRY_URL }} - username: ${{ vars.REGISTRY_USER }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver: docker-container - - name: Docker publish proxy - uses: docker/build-push-action@v6 - with: - context: proxy - push: true - sbom: true - provenance: true - platforms: linux/amd64, linux/arm64 - tags: | - ${{ vars.REGISTRY_URL }}/docker-public/${{ env.GITHUB_REPOSITORY }}-proxy:0.1.${{ env.GITHUB_RUN_NUMBER }} - ${{ vars.REGISTRY_URL }}/docker-public/${{ env.GITHUB_REPOSITORY }}-proxy:latest - - name: Docker publish claude - uses: docker/build-push-action@v6 - with: - context: claude - push: true - sbom: true - provenance: true - platforms: linux/amd64, linux/arm64 - tags: | - ${{ vars.REGISTRY_URL }}/docker-public/${{ env.GITHUB_REPOSITORY }}-claude:0.1.${{ env.GITHUB_RUN_NUMBER }} - ${{ vars.REGISTRY_URL }}/docker-public/${{ env.GITHUB_REPOSITORY }}-claude:latest diff --git a/CLAUDE.md b/CLAUDE.md index 3e394b2..9af5a32 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,61 +10,40 @@ This file provides context and guidance for working with this project. Two containers managed by Docker Compose: -- **`claude`** — Claude Code CLI (`node:24-alpine`), runs as the built-in `node` user (UID 1000), isolated to an internal-only Docker network -- **`proxy`** — Squid forward proxy (`alpine:3.21`), `squid` user, bridges the internal network to the internet with an egress allowlist +- **`claude`** — Claude Code CLI, non-root (UID 1000), isolated to an internal-only Docker network +- **`proxy`** — Squid forward proxy, non-root (UID 13), bridges the internal network to the internet with an egress allowlist Key Docker network property: `claude-internal` has `internal: true`, meaning Docker adds no default gateway. The `claude` container physically cannot reach the internet without going through the `proxy` container. -Auth supports three modes (checked at startup by `claude.sh`): -- `ANTHROPIC_API_KEY` — API key -- `CLAUDE_CODE_OAUTH_TOKEN` — 1-year token from `claude setup-token` (headless-friendly) -- Neither set — Claude Code prompts for browser login on first run; port 54545 is published for the OAuth callback. Credentials persist in `~/.claude` on the host. - ## File Structure ``` docker-claude/ -├── claude.sh # Control script: start/stop/update/logs/status/shell -├── setup.sh # First-time setup wizard (Docker check + auth config) -├── launch.sh # Folder-picker launcher for macOS/Linux -├── launch.bat # Folder-picker launcher for Windows -├── build.sh # Build images locally (development) -├── docker-compose.yml # Service definitions and network topology -├── claude/ -│ └── Dockerfile # Claude Code stable release (node:24-alpine, UID 1000) +├── claude.sh # Control script: start / stop / run / update / logs / status / shell +├── docker-compose.yml # Service definitions and network topology +├── Dockerfile.claude # Claude Code container (node:20-slim, UID 1000) +├── Dockerfile.proxy # Squid proxy sidecar (ubuntu:22.04, UID 13) ├── proxy/ -│ ├── Dockerfile # Squid proxy sidecar (alpine:3.21, squid user) -│ └── squid.conf # Squid ACL config — egress allowlist lives here -├── hooks/ -│ └── pre-commit # Enforces executable bit on shell scripts -├── .env.example # Template for credentials and options -├── .gitignore # Excludes .env and logs -├── .dockerignore # Keeps .env out of build context -└── README.md # User documentation +│ └── squid.conf # Squid ACL config — egress allowlist lives here +├── .env.example # Template for ANTHROPIC_API_KEY +├── .gitignore # Excludes .env and logs +├── .dockerignore # Keeps .env out of build context +└── README.md # User documentation ``` ## Development Workflow ```bash -./setup.sh # first-time: configure Docker check + auth -cd /path/to/project && ./claude.sh start # start proxy + launch Claude (pulls images, mounts CWD) -./claude.sh update # pull latest images from registry -./build.sh # build images locally (development) -``` - -## Git Hooks - -A pre-commit hook lives in `hooks/` and enforces the executable bit on all shell scripts. Activate it once after cloning: - -```bash -git config core.hooksPath hooks +chmod +x claude.sh +cp .env.example .env # set ANTHROPIC_API_KEY +./claude.sh start # build + start proxy + launch Claude interactively +./claude.sh update # rebuild images (no cache) after upstream updates ``` ## Coding Standards - Shell scripts use `set -euo pipefail` -- Dockerfiles use Alpine (`node:24-alpine`, `alpine:3.21`) for minimal attack surface -- Alpine packages use `apk add --no-cache`; no apt cache cleanup layer needed +- Dockerfiles use `--no-install-recommends` and clean apt caches in the same layer - No capabilities granted; `no-new-privileges` on all containers - `.env` is never committed (enforced by `.gitignore` and `.dockerignore`) - Commit messages follow **Angular format**: `type(scope): summary` diff --git a/Dockerfile.claude b/Dockerfile.claude new file mode 100644 index 0000000..ca847d5 --- /dev/null +++ b/Dockerfile.claude @@ -0,0 +1,30 @@ +FROM node:20-slim + +# Install minimal runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + curl \ + ca-certificates \ + bash \ + && rm -rf /var/lib/apt/lists/* + +# Create non-root user +RUN groupadd -g 1000 claude \ + && useradd -u 1000 -g claude -m -s /bin/bash claude + +# Install Claude Code globally (runs as root for npm -g, then drops) +RUN npm install -g @anthropic-ai/claude-code + +# Workspace directory owned by claude user +RUN mkdir -p /workspace && chown claude:claude /workspace + +USER claude +WORKDIR /workspace + +# Proxy traffic through sidecar — override at runtime if needed +ENV HTTP_PROXY=http://proxy:3128 +ENV HTTPS_PROXY=http://proxy:3128 +ENV ALL_PROXY=http://proxy:3128 +ENV NO_PROXY=localhost,127.0.0.1 + +ENTRYPOINT ["claude"] diff --git a/Dockerfile.proxy b/Dockerfile.proxy new file mode 100644 index 0000000..cfec906 --- /dev/null +++ b/Dockerfile.proxy @@ -0,0 +1,25 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + squid \ + && rm -rf /var/lib/apt/lists/* + +# Give the proxy system user (UID 13) ownership of all Squid paths +RUN mkdir -p /var/spool/squid /var/log/squid \ + && chown -R proxy:proxy /var/spool/squid /var/log/squid /etc/squid + +COPY --chown=proxy:proxy proxy/squid.conf /etc/squid/squid.conf + +USER proxy + +# Initialise cache directories as the proxy user +RUN squid -N -f /etc/squid/squid.conf -z 2>/dev/null || true + +EXPOSE 3128 + +HEALTHCHECK --interval=10s --timeout=5s --retries=3 \ + CMD /bin/bash -c 'echo >/dev/tcp/127.0.0.1/3128' + +CMD ["squid", "-N", "-f", "/etc/squid/squid.conf"] diff --git a/README.md b/README.md index af10e9d..dde60b1 100644 --- a/README.md +++ b/README.md @@ -1,176 +1,120 @@ # docker-claude -Runs [Claude Code](https://claude.ai/code) inside an isolated Docker environment with a proxy sidecar for controlled egress. Claude cannot access the host filesystem or network directly. - -## Quick Start - -**1. Install a Docker runtime** - -Pick the free, open-source option for your platform: - -| Platform | Recommended | Alternative | -|---|---|---| -| macOS | [Rancher Desktop](https://rancherdesktop.io/) (GUI) | [Colima](https://github.com/abiosoft/colima) (CLI): `brew install colima docker docker-compose && colima start` | -| Linux | Docker Engine: `curl -fsSL https://get.docker.com \| sh` | [Rancher Desktop](https://rancherdesktop.io/) | -| Windows | [Rancher Desktop](https://rancherdesktop.io/) (GUI) | WSL2 + Docker Engine (see below) | - -> **Note:** Docker Desktop is not listed — it requires a commercial licence for business use. - -**2. Download this repo** - -Clone or download and unzip this repository somewhere on your machine. - -**3. Run setup** - -- **macOS / Linux:** Open a terminal, navigate to the folder, and run: - ```bash - ./setup.sh - ``` -- **Windows:** Double-click `launch.bat` — it will run setup automatically on first launch. - -Setup will ask how you want to authenticate (API key, subscription token, or browser login) and save your settings. - -**4. Start Claude** - -- **macOS / Linux:** Double-click `launch.sh`, or run it from a terminal: - ```bash - ./launch.sh - ``` - A folder picker will appear — select the project you want Claude to work on. - -- **Windows:** Double-click `launch.bat`. - ---- +Runs [Claude Code](https://claude.ai/code) inside an isolated Docker environment with a proxy sidecar for controlled egress. Claude cannot reach the host filesystem or network directly. ## Architecture ``` -┌──────────────────────────────────────────────────────────┐ -│ Host machine │ -│ │ -│ claude.sh (control script) │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────┐ │ -│ │ Docker: claude-secure │ │ -│ │ │ │ -│ │ ┌─────────────┐ claude-internal │ │ -│ │ │ claude │ (internal: true) │ │ -│ │ │ (UID 1000) │──────────────► ┌──────────┐ │ │ -│ │ └─────────────┘ │ proxy │ │ │ -│ │ │ (UID 13) │ │ │ -│ │ └────┬─────┘ │ │ -│ │ proxy-external │ │ -│ └──────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ internet (allowlisted) │ -└──────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────┐ +│ Host machine │ +│ │ +│ claude.sh (control script) │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ Docker: claude-secure │ │ +│ │ │ │ +│ │ ┌─────────────┐ claude-internal │ │ +│ │ │ claude │◄─────(internal only)───► │ │ +│ │ │ (UID 1000) │ │ │ │ +│ │ └─────────────┘ ┌──────┴──────┐ │ │ +│ │ │ proxy │ │ │ +│ │ │ (UID 13) │ │ │ +│ │ └──────┬──────┘ │ │ +│ │ proxy-external │ │ +│ └─────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ internet (allowlisted) │ +└─────────────────────────────────────────────────────┘ ``` -- **`claude`** — Claude Code CLI (`node:24-alpine`), runs as the built-in `node` user (UID 1000), on `claude-internal` only -- **`proxy`** — Squid forward proxy (`alpine:3.21`), bridges `claude-internal` ↔ internet with egress allowlist -- **`claude-internal`** — `internal: true`; no default gateway, containers cannot reach the internet directly -- **`proxy-external`** — Standard bridge; proxy sidecar only +- **`claude` container** — Claude Code, runs as UID 1000, on `claude-internal` only (no internet route) +- **`proxy` container** — Squid forward proxy, runs as UID 13, bridges `claude-internal` ↔ internet, enforces egress allowlist +- **`claude-internal`** — Docker bridge with `internal: true`; Docker adds no default gateway, so containers on this network cannot reach the internet directly +- **`proxy-external`** — Standard bridge; the proxy sidecar uses this for controlled outbound access ## Prerequisites -A Docker runtime with Compose support. Choose a free, open-source option: +- Docker Engine 24+ +- Docker Compose v2 plugin (`docker compose version`) -- **macOS:** [Rancher Desktop](https://rancherdesktop.io/) or [Colima](https://github.com/abiosoft/colima) -- **Linux:** [Docker Engine CE](https://docs.docker.com/engine/install/) (`curl -fsSL https://get.docker.com | sh`) -- **Windows:** [Rancher Desktop](https://rancherdesktop.io/) or WSL2 + Docker Engine +## Setup -> Docker Desktop is not recommended — it requires a commercial licence for business use. - -## Authentication - -Three options — `./setup.sh` will guide you through picking one: - -### Option 1 — API key ```bash -ANTHROPIC_API_KEY=sk-ant-... +# 1. Clone / copy this repo +git clone docker-claude && cd docker-claude + +# 2. Configure your API key +cp .env.example .env +$EDITOR .env # set ANTHROPIC_API_KEY + +# 3. Make the control script executable +chmod +x claude.sh ``` -Get a key at [console.anthropic.com](https://console.anthropic.com/settings/keys). - -### Option 2 — OAuth token (subscription, headless-friendly) - -Run this **on your host** (not inside the container) to generate a 1-year token: -```bash -claude setup-token -``` -Then paste the token into setup, or set it manually in `.env`: -```bash -CLAUDE_CODE_OAUTH_TOKEN=... -``` - -### Option 3 — Browser OAuth (interactive) - -Leave both keys unset. On first run, Claude Code will print a login URL. -Port 54545 must be reachable from your browser for the OAuth callback. ## Usage -### Normal use - ```bash -./launch.sh # folder picker → starts Claude in the selected directory -``` - -### CLI / power users - -```bash -cd ~/myproject +# Build images, start proxy, launch Claude Code interactively ./claude.sh start -./claude.sh stop # Stop and remove all containers -./claude.sh update # Pull latest images from the registry -./claude.sh logs # Tail proxy logs -./claude.sh status # Show container status -./claude.sh shell # Debug bash shell in the Claude container +# Same as start but skips image rebuild (faster on subsequent runs) +./claude.sh run + +# Stop and remove all containers (proxy + any running sessions) +./claude.sh stop + +# Rebuild images without cache (e.g. after Claude Code updates) +./claude.sh update + +# Tail proxy access logs +./claude.sh logs + +# Show container status +./claude.sh status + +# Open a debug bash shell inside the Claude container +./claude.sh shell ``` -### Windows: WSL2 + Docker Engine (alternative to Rancher Desktop) +### Working with host files -1. Install [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install): `wsl --install` in PowerShell -2. Open the Ubuntu terminal and run: - ```bash - curl -fsSL https://get.docker.com | sh - sudo usermod -aG docker $USER - ``` -3. Log out and back in, then run `launch.bat` as usual. +By default, Claude's workspace is a named Docker volume (`claude-secure-workspace`) — fully isolated from the host. -### Building locally +To mount a specific host directory: ```bash -./build.sh # build with layer cache -./build.sh --no-cache # force full rebuild +WORKSPACE_DIR=$HOME/myproject ./claude.sh run ``` +The directory is mounted at `/workspace` inside the container. + ## Egress allowlist Edit `proxy/squid.conf` and add domains to the `allowed_sites` ACL: -``` +```squid acl allowed_sites dstdomain api.anthropic.com acl allowed_sites dstdomain statsig.anthropic.com -# acl allowed_sites dstdomain api.github.com +# acl allowed_sites dstdomain api.github.com # uncomment if needed # acl allowed_sites dstdomain registry.npmjs.org ``` -Rebuild after changes: +Rebuild the proxy after changes: ```bash +docker compose -p claude-secure build proxy ./claude.sh stop && ./claude.sh start ``` ## Security controls -| Control | claude | proxy | +| Control | Claude container | Proxy container | |---|---|---| -| Non-root user | UID 1000 (`node`, built into base image) | `squid` user | +| Non-root user | UID 1000 (`claude`) | UID 13 (`proxy`) | | `no-new-privileges` | yes | yes | | All capabilities dropped | yes | yes | | Direct internet access | no (`internal` network only) | allowlisted only | -| Host filesystem | CWD mounted as `/workspace` | none | +| Host filesystem | no mounts by default | none | | Docker socket | not mounted | not mounted | diff --git a/build.sh b/build.sh deleted file mode 100755 index fe5f818..0000000 --- a/build.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -# build.sh — Build Docker images locally for development -# Usage: ./build.sh [docker build flags, e.g. --no-cache] -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REGISTRY="registry.zeidler.dev/docker-public/playground" -TAG="${IMAGE_TAG:-latest}" - -GREEN='\033[0;32m'; NC='\033[0m' -info() { echo -e "${GREEN}[+]${NC} $*"; } - -info "Building proxy..." -docker build "$@" -t "${REGISTRY}/docker-claude-proxy:${TAG}" "${SCRIPT_DIR}/proxy" - -info "Building claude..." -docker build "$@" -t "${REGISTRY}/docker-claude-claude:${TAG}" "${SCRIPT_DIR}/claude" - -info "Done. Run './claude.sh start' to launch." diff --git a/claude.sh b/claude.sh index 21bede4..52bcc3a 100755 --- a/claude.sh +++ b/claude.sh @@ -1,12 +1,11 @@ #!/usr/bin/env bash # claude.sh — Manage the isolated Claude Code Docker environment -# Usage: ./claude.sh [--kube] [args] +# Usage: ./claude.sh [args] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml" PROJECT="claude-secure" -ALLOW_KUBE=0 # ─── Colours ────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' @@ -14,110 +13,96 @@ info() { echo -e "${GREEN}[+]${NC} $*"; } warn() { echo -e "${YELLOW}[!]${NC} $*"; } error() { echo -e "${RED}[-]${NC} $*" >&2; } -# ─── Helpers ────────────────────────────────────────────────────────────────── +# ─── Dependency check ───────────────────────────────────────────────────────── check_deps() { if ! command -v docker &>/dev/null; then - error "Docker is not installed. Run ./setup.sh for install instructions." - exit 1 - fi - if ! docker info &>/dev/null 2>&1; then - error "Docker is not running. Start your Docker runtime, then try again." + error "Docker is not installed. https://docs.docker.com/get-docker/" exit 1 fi if ! docker compose version &>/dev/null 2>&1; then - error "Docker Compose is not available. Run ./setup.sh for install instructions." + error "Docker Compose v2 plugin is required." exit 1 fi } +# ─── Environment loading ────────────────────────────────────────────────────── load_env() { local env_file="$SCRIPT_DIR/.env" - if [[ ! -f "$env_file" ]]; then - warn "Not set up yet. Run ./setup.sh first." - exit 1 + if [[ -f "$env_file" ]]; then + # shellcheck disable=SC1090 + set -a; source "$env_file"; set +a fi - # shellcheck disable=SC1090 - set -a; source "$env_file"; set +a - if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then - warn "No credentials found — Claude will ask you to log in via browser." - warn "A login URL will appear below. Open it to authenticate." - warn "(To skip this prompt in future, run ./setup.sh to configure credentials.)" - echo "" + if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then + error "ANTHROPIC_API_KEY is not set." + error "Copy .env.example → .env and add your key, or export it in your shell." + exit 1 fi } -# Wrapper so every docker compose call uses the right file and project name. +# ─── Workspace volume resolution ────────────────────────────────────────────── +# Default: named Docker volume (fully isolated). +# Override: export WORKSPACE_DIR=/path/to/project before running. +workspace_flag() { + if [[ -n "${WORKSPACE_DIR:-}" ]]; then + local abs + abs="$(realpath "${WORKSPACE_DIR}")" + if [[ ! -d "$abs" ]]; then + error "WORKSPACE_DIR does not exist: $abs" + exit 1 + fi + echo "--volume ${abs}:/workspace:z" + else + echo "--volume ${PROJECT}-workspace:/workspace" + fi +} + +# ─── Compose wrapper ────────────────────────────────────────────────────────── dc() { docker compose -f "$COMPOSE_FILE" -p "$PROJECT" "$@"; } -# ─── Volume args ────────────────────────────────────────────────────────────── -# Builds VOLUME_ARGS array for docker compose run. -# Validates the workspace path and optionally adds the kubeconfig mount. -build_volume_args() { - local cwd - cwd="$(pwd)" - - # Exact-match blocklist - local -a exact_blocked=( / "$HOME" /root /home ) - for dir in "${exact_blocked[@]}"; do - [[ "$cwd" == "$dir" ]] && { - error "Refusing to mount $cwd as workspace — too broad. cd into a project subdirectory first." - exit 1 - } - done - - # Any user home directory directly under /home - [[ "$cwd" =~ ^/home/[^/]+$ ]] && { - error "Refusing to mount $cwd as workspace — user home directory. cd into a project subdirectory first." - exit 1 - } - - # Prefix blocklist — system internals and credential/key material - local -a prefix_blocked=( - /bin /sbin /lib /lib64 /etc /usr /var /proc /sys /dev /boot /run - "$HOME/.ssh" /root/.ssh "$HOME/.gnupg" /root/.gnupg - ) - for dir in "${prefix_blocked[@]}"; do - [[ "$cwd" == "$dir" || "$cwd" == "$dir/"* ]] && { - error "Refusing to mount $cwd as workspace — contains sensitive data. cd into a project subdirectory first." - exit 1 - } - done - - VOLUME_ARGS=("--volume" "${cwd}:/workspace:z") - - if [[ "$ALLOW_KUBE" -eq 1 ]]; then - [[ -d "$HOME/.kube" ]] || { error "--kube: $HOME/.kube does not exist."; exit 1; } - VOLUME_ARGS+=("--volume" "$HOME/.kube:/home/node/.kube:ro,z") - fi -} - # ─── Commands ───────────────────────────────────────────────────────────────── + cmd_start() { - check_deps; load_env; build_volume_args - info "Pulling latest images..." - dc pull + check_deps + load_env + info "Building images..." + dc build info "Starting proxy sidecar..." dc up -d proxy + info "Waiting for proxy health check..." + dc up -d proxy # no-op if already healthy; compose waits via depends_on info "Launching Claude Code..." - dc run --rm --service-ports "${VOLUME_ARGS[@]}" claude "$@" + # shellcheck disable=SC2046 + dc run --rm $(workspace_flag) claude "$@" } cmd_stop() { check_deps info "Stopping all containers..." dc down + info "Done." +} + +cmd_run() { + check_deps + load_env + info "Ensuring proxy is running..." + dc up -d proxy + info "Launching Claude Code..." + # shellcheck disable=SC2046 + dc run --rm $(workspace_flag) claude "$@" } cmd_update() { check_deps - info "Pulling latest images from registry..." - dc pull + info "Rebuilding images (no cache)..." + dc build --no-cache info "Update complete. Run './claude.sh start' to launch." } cmd_logs() { check_deps - dc logs -f "${1:-proxy}" + local svc="${1:-proxy}" + dc logs -f "$svc" } cmd_status() { @@ -126,56 +111,50 @@ cmd_status() { } cmd_shell() { - check_deps; load_env; build_volume_args + check_deps + load_env warn "Opening debug shell inside Claude container (non-Claude entrypoint)." - dc run --rm --service-ports --entrypoint /bin/bash "${VOLUME_ARGS[@]}" claude + # shellcheck disable=SC2046 + dc run --rm --entrypoint /bin/bash $(workspace_flag) claude } cmd_help() { cat < [args] +Usage: $(basename "$0") [args] Commands: - start [args] Start proxy, launch Claude Code (CLI) - stop Stop and remove all containers - update Pull latest images from the registry - logs [svc] Tail logs (default: proxy) - status Show container status - shell Open a bash shell in the Claude container (debug) - help Show this message + start [args] Build images, start proxy, launch Claude Code + run [args] Start proxy if needed, launch Claude Code + stop Stop and remove all containers + update Rebuild images without cache + logs [svc] Tail logs (default: proxy) + status Show container status + shell Open a bash shell in the Claude container (debug) + help Show this message -Flags (before the subcommand): - --kube Mount \$HOME/.kube read-only at /home/node/.kube (kubectl access) - -Environment variables (set in .env): - ANTHROPIC_API_KEY API key auth - CLAUDE_CODE_OAUTH_TOKEN OAuth token auth (from 'claude setup-token') - IMAGE_TAG Image tag to use (default: latest) +Environment variables: + ANTHROPIC_API_KEY Required. Set in .env or exported in your shell. + WORKSPACE_DIR Optional. Absolute path to mount as /workspace. + Defaults to a named Docker volume (fully isolated). Examples: - cd ~/myproject && ./claude.sh start - cd ~/myproject && ./claude.sh --kube start + ./claude.sh start + WORKSPACE_DIR=\$HOME/myproject ./claude.sh run ./claude.sh logs proxy ./claude.sh shell EOF } # ─── Dispatch ───────────────────────────────────────────────────────────────── -while [[ "${1:-}" == --* ]]; do - case "$1" in - --kube) ALLOW_KUBE=1; shift ;; - *) break ;; - esac -done - case "${1:-help}" in - start|run) shift; cmd_start "$@" ;; - stop) cmd_stop ;; - update) cmd_update ;; - logs) shift; cmd_logs "${1:-}" ;; - status) cmd_status ;; - shell) cmd_shell ;; - help|-h|--help) cmd_help ;; + start) shift; cmd_start "$@" ;; + stop) cmd_stop ;; + run) shift; cmd_run "$@" ;; + update) cmd_update ;; + logs) shift; cmd_logs "${1:-}" ;; + status) cmd_status ;; + shell) cmd_shell ;; + help|-h|--help) cmd_help ;; *) error "Unknown command: ${1}" cmd_help diff --git a/claude/Dockerfile b/claude/Dockerfile deleted file mode 100644 index 6d6c0fd..0000000 --- a/claude/Dockerfile +++ /dev/null @@ -1,107 +0,0 @@ -FROM node:24-alpine - -# Upgrade npm to pull in patched bundled deps (cross-spawn, glob, minimatch, tar) -# CVEs: CVE-2024-21538, CVE-2025-64756, CVE-2026-26996/27903/27904, CVE-2026-23745/23950/24842/26960/29786/31802 -RUN npm install -g npm@11.12.1 - -# Fix CVE-2026-33671: upgrade picomatch 4.0.3 → 4.0.4 in every location it appears -RUN find /usr/local/lib/node_modules -name "picomatch" -type d | while read dir; do \ - ver=$(node -p "require('$dir/package.json').version" 2>/dev/null); \ - [ "$ver" = "4.0.3" ] || continue; \ - echo "Patching picomatch in $dir"; \ - prefix=$(dirname "$(dirname "$dir")"); \ - npm install --prefix "$prefix" picomatch@4.0.4 \ - --no-save --no-audit --no-fund 2>/dev/null || true; \ - done - -# Install runtime dependencies -RUN apk add --no-cache \ - git \ - curl \ - ca-certificates \ - bash - -# Install kubectl — architecture-aware, checksum-verified -RUN KUBECTL_VERSION=$(curl -fsSL https://dl.k8s.io/release/stable.txt) \ - && ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') \ - && curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl" \ - -o /usr/local/bin/kubectl \ - && curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl.sha256" \ - -o /tmp/kubectl.sha256 \ - && echo "$(cat /tmp/kubectl.sha256) /usr/local/bin/kubectl" | sha256sum -c \ - && rm /tmp/kubectl.sha256 \ - && chmod +x /usr/local/bin/kubectl - -# System-level Claude Code policy — owned by root, not writable by the node user. -# Restricts available models; cannot be bypassed via CLI flags or env vars. -COPY settings.json /etc/claude-code/managed-settings.json - -# Install Claude Code stable release -RUN curl -fsSL https://claude.ai/install.sh | bash -s stable - -# Install MCP servers, patch transitive CVEs, scrub credentials and cache — all in one -# layer so nothing is committed to the image between install and cleanup. -# -# CVEs patched: -# CVE-2025-66414, CVE-2026-0621 — @modelcontextprotocol/sdk <1.25.2 -# GHSA-345p-7cg4-v4c7 — @modelcontextprotocol/sdk <1.26.0 -# CVE-2026-33671 — picomatch <4.0.4 -# GHSA-f886-m6hf-6m8v — brace-expansion <5.0.5 -RUN npm install -g \ - @modelcontextprotocol/server-github \ - @yoda.digital/gitlab-mcp-server \ - @aashari/mcp-server-atlassian-jira \ - @aashari/mcp-server-atlassian-confluence \ - && for pkg_dir in \ - /usr/local/lib/node_modules/@modelcontextprotocol/server-github \ - /usr/local/lib/node_modules/@yoda.digital/gitlab-mcp-server \ - /usr/local/lib/node_modules/@aashari/mcp-server-atlassian-jira \ - /usr/local/lib/node_modules/@aashari/mcp-server-atlassian-confluence; do \ - [ -d "$pkg_dir" ] && \ - cd "$pkg_dir" && \ - npm install --no-audit --no-fund \ - @modelcontextprotocol/sdk@1.26.0 \ - picomatch@4.0.4 \ - brace-expansion@5.0.5 \ - || true; \ - done \ - && find /usr/local/lib/node_modules -name "picomatch" -type d | while read dir; do \ - ver=$(node -p "require('$dir/package.json').version" 2>/dev/null); \ - [ "$ver" = "4.0.3" ] || continue; \ - prefix=$(dirname "$(dirname "$dir")"); \ - npm install --prefix "$prefix" picomatch@4.0.4 \ - --no-save --no-audit --no-fund 2>/dev/null || true; \ - done \ - && cd /tmp \ - && npm pack brace-expansion@5.0.5 --no-audit 2>/dev/null \ - && tar xzf brace-expansion-5.0.5.tgz \ - && find /usr/local/lib/node_modules -name "package.json" -path "*/brace-expansion/package.json" \ - | xargs grep -l '"version": "5.0.4"' 2>/dev/null \ - | while read pj; do \ - cp -r /tmp/package/. "$(dirname "$pj")/"; \ - done \ - && rm -rf /tmp/brace-expansion-5.0.5.tgz /tmp/package \ - && find /root /home /usr/local/etc -name ".npmrc" -o -name "npmrc" \ - | xargs grep -l "_authToken\|_auth\b" 2>/dev/null \ - | xargs rm -f 2>/dev/null || true \ - && npm config delete //npm.pkg.github.com/:_authToken 2>/dev/null || true \ - && npm config delete //registry.npmjs.org/:_authToken 2>/dev/null || true \ - && npm cache clean --force - -# Workspace and Claude config dir — owned by the built-in node user (uid 1000). -# Pre-creating ~/.claude ensures the named volume is initialised with the -# correct ownership when first mounted (Docker copies image content into -# an empty named volume on first use). -RUN mkdir -p /workspace /home/node/.claude \ - && chown -R node:node /workspace /home/node/.claude - -USER node -WORKDIR /workspace - -# Proxy traffic through sidecar — override at runtime if needed -ENV HTTP_PROXY=http://proxy:3128 -ENV HTTPS_PROXY=http://proxy:3128 -ENV ALL_PROXY=http://proxy:3128 -ENV NO_PROXY=localhost,127.0.0.1 - -ENTRYPOINT ["claude"] diff --git a/claude/settings.json b/claude/settings.json deleted file mode 100644 index 175bdd4..0000000 --- a/claude/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "availableModels": ["sonnet", "opus", "haiku"], - "permissions": { - "allow": ["Bash(*)", "Edit(*)", "Write(*)"], - "deny": ["Bash(curl *)", "Read(.*env*)"], - "env": { - "CLAUDE_CODE_ENABLE_TELEMETRY": "0" - } - } -} diff --git a/docker-compose.yml b/docker-compose.yml index 4148250..baadf41 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,15 @@ services: + # ─── Proxy sidecar ───────────────────────────────────────────────────────── # Bridges the isolated internal network to the internet. # Enforces an egress allowlist — see proxy/squid.conf. proxy: - image: registry.zeidler.dev/docker-public/playground/docker-claude-proxy:${IMAGE_TAG:-latest} + build: + context: . + dockerfile: Dockerfile.proxy networks: - - claude-internal # reachable by claude container - - proxy-external # has outbound internet access + - claude-internal # reachable by the claude container + - proxy-external # has outbound internet access restart: unless-stopped security_opt: - no-new-privileges:true @@ -15,45 +18,35 @@ services: read_only: true tmpfs: - /tmp - - /var/cache/squid + - /var/spool/squid - /var/log/squid - # ─── Claude Code CLI container ───────────────────────────────────────────── + # ─── Claude Code container ───────────────────────────────────────────────── # No direct internet access. All egress routes through the proxy sidecar. - # Run via "docker compose run --rm --service-ports claude" (managed by claude.sh). + # Run via "docker compose run --rm claude" (managed by claude.sh). claude: - image: registry.zeidler.dev/docker-public/playground/docker-claude-claude:${IMAGE_TAG:-latest} + build: + context: . + dockerfile: Dockerfile.claude depends_on: proxy: condition: service_healthy networks: - - claude-internal # only — no route to the internet + - claude-internal # only — no route to the internet environment: - - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} - - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-} + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - HTTP_PROXY=http://proxy:3128 - HTTPS_PROXY=http://proxy:3128 - ALL_PROXY=http://proxy:3128 - NO_PROXY=localhost,127.0.0.1 - # MCP server credentials — all optional; servers are skipped if unset - - GITHUB_TOKEN=${GITHUB_TOKEN:-} - - GITLAB_TOKEN=${GITLAB_TOKEN:-} - - GITLAB_URL=${GITLAB_URL:-https://gitlab.com} - - ATLASSIAN_SITE_NAME=${ATLASSIAN_SITE_NAME:-} - - ATLASSIAN_USER_EMAIL=${ATLASSIAN_USER_EMAIL:-} - - ATLASSIAN_API_TOKEN=${ATLASSIAN_API_TOKEN:-} - ports: - # OAuth callback — required for browser-based login (claude login) - - "0.0.0.0:54545:54545" - volumes: - - ${HOME}/.claude:/home/node/.claude - # Workspace is injected by claude.sh via --volume flag at run time (current directory). security_opt: - no-new-privileges:true cap_drop: - ALL stdin_open: true tty: true + # Workspace is injected by claude.sh via --volume flag at run time. + # Default: named Docker volume. Override: set WORKSPACE_DIR on the host. networks: # Internal-only: Docker adds no default gateway → no direct internet route @@ -64,4 +57,3 @@ networks: # External: standard bridge with internet access (proxy only) proxy-external: driver: bridge - diff --git a/hooks/pre-commit b/hooks/pre-commit deleted file mode 100755 index cf9ee40..0000000 --- a/hooks/pre-commit +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -# Ensure control scripts stay executable. -set -euo pipefail - -SCRIPTS=(claude.sh build.sh setup.sh launch.sh hooks/pre-commit) - -for f in "${SCRIPTS[@]}"; do - if [[ -f "$f" && ! -x "$f" ]]; then - echo "pre-commit: fixing missing executable bit on $f" - chmod +x "$f" - git add "$f" - fi -done diff --git a/launch.bat b/launch.bat deleted file mode 100644 index 21d3d13..0000000 --- a/launch.bat +++ /dev/null @@ -1,55 +0,0 @@ -@echo off -:: launch.bat — Pick a project folder and start Claude Code (Windows) -setlocal enabledelayedexpansion - -set "SCRIPT_DIR=%~dp0" -set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%" - -:: ── Check for bash (Git Bash or WSL) ───────────────────────────────────────── -where bash >nul 2>&1 -if %errorlevel% neq 0 ( - echo Git Bash is required to run docker-claude on Windows. - echo. - echo Download it at: https://git-scm.com/download/win - echo Install with default options, then double-click this file again. - pause - exit /b 1 -) - -:: ── First-time setup ────────────────────────────────────────────────────────── -if not exist "%SCRIPT_DIR%\.env" ( - echo Looks like this is your first time. Running setup... - echo. - bash "%SCRIPT_DIR%/setup.sh" - if %errorlevel% neq 0 ( pause & exit /b 1 ) - echo. -) - -:: ── Folder picker via PowerShell ────────────────────────────────────────────── -set "PROJECT_FOLDER=" -for /f "usebackq tokens=*" %%i in (`powershell -NoProfile -Command ^ - "Add-Type -AssemblyName System.Windows.Forms; ^ - $d = New-Object System.Windows.Forms.FolderBrowserDialog; ^ - $d.Description = 'Select the project folder to work on'; ^ - $d.RootFolder = 'MyComputer'; ^ - $d.ShowNewFolderButton = $false; ^ - if ($d.ShowDialog() -eq 'OK') { Write-Output $d.SelectedPath } else { exit 1 }"`) do ( - set "PROJECT_FOLDER=%%i" -) - -if not defined PROJECT_FOLDER ( - echo No folder selected. Exiting. - pause - exit /b 1 -) - -:: ── Launch ──────────────────────────────────────────────────────────────────── -:: Convert Windows path to Unix path for bash -for /f "usebackq tokens=*" %%i in (`bash -c "cygpath -u '!PROJECT_FOLDER!'"`) do ( - set "UNIX_FOLDER=%%i" -) - -bash -c "cd '!UNIX_FOLDER!' && '!SCRIPT_DIR:/=\..\..\!/claude.sh' start" 2>nul || ^ -bash -c "cd '!UNIX_FOLDER!' && bash '$(cygpath -u '!SCRIPT_DIR!')/claude.sh' start" - -pause diff --git a/launch.sh b/launch.sh deleted file mode 100755 index d987957..0000000 --- a/launch.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -# launch.sh — Pick a project folder and start Claude Code -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# ─── First-time setup ───────────────────────────────────────────────────────── -if [[ ! -f "$SCRIPT_DIR/.env" ]]; then - echo "Looks like this is your first time. Running setup..." - echo "" - "$SCRIPT_DIR/setup.sh" || exit 1 - echo "" -fi - -# ─── Folder picker ──────────────────────────────────────────────────────────── -pick_folder() { - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS — native Finder dialog - osascript -e \ - 'tell application "Finder" to POSIX path of (choose folder with prompt "Select the project folder to work on:")' \ - 2>/dev/null | tr -d '\n' - elif command -v zenity &>/dev/null; then - # Linux — GNOME/GTK dialog - zenity --file-selection --directory \ - --title="Select your project folder" 2>/dev/null - elif command -v kdialog &>/dev/null; then - # Linux — KDE dialog - kdialog --getexistingdirectory "$HOME" \ - --title "Select your project folder" 2>/dev/null - else - echo "" - fi -} - -folder=$(pick_folder || true) - -# Fallback: text prompt (no GUI available, or user cancelled dialog) -if [[ -z "$folder" ]]; then - echo "Enter the path to your project folder" - echo "(Tip: you can drag the folder into this window, then press Enter)" - echo "" - read -rp "> " folder - # Clean up: strip surrounding quotes and trailing whitespace from drag-and-drop - folder="${folder%"${folder##*[![:space:]]}"}" - folder="${folder#\'}" ; folder="${folder%\'}" - folder="${folder#\"}" ; folder="${folder%\"}" - # Expand ~ to home directory - folder="${folder/#\~/$HOME}" -fi - -if [[ -z "$folder" ]]; then - echo "No folder selected. Exiting." - exit 1 -fi - -if [[ ! -d "$folder" ]]; then - echo "Folder not found: $folder" - exit 1 -fi - -cd "$folder" -exec "$SCRIPT_DIR/claude.sh" start diff --git a/proxy/Dockerfile b/proxy/Dockerfile deleted file mode 100644 index 5d494e6..0000000 --- a/proxy/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM registry.zeidler.dev/docker-dhi/alpine-base:3.23-alpine3.23-dev - -# squid: proxy. netcat-openbsd: health check -RUN apk add --no-cache squid netcat-openbsd - -# squid user is created by the package (apk add squid) -RUN mkdir -p /var/cache/squid /var/log/squid \ - && chown -R squid:squid /var/cache/squid /var/log/squid /etc/squid - -COPY --chown=squid:squid squid.conf /etc/squid/squid.conf - -USER squid - -EXPOSE 3128 - -HEALTHCHECK --interval=10s --timeout=5s --retries=3 \ - CMD nc -z 127.0.0.1 3128 || exit 1 - -CMD ["squid", "-N", "-f", "/etc/squid/squid.conf"] diff --git a/proxy/squid.conf b/proxy/squid.conf index 4deb96d..6ef039f 100644 --- a/proxy/squid.conf +++ b/proxy/squid.conf @@ -14,27 +14,18 @@ cache_store_log none # ─── No disk cache ──────────────────────────────────────────────────────────── cache deny all -coredump_dir /var/cache/squid +coredump_dir /var/spool/squid # ─── ACL Definitions ────────────────────────────────────────────────────────── acl SSL_ports port 443 -acl SSL_ports port 6443 # Kubernetes API server acl Safe_ports port 80 acl Safe_ports port 443 -acl Safe_ports port 6443 # Kubernetes API server acl CONNECT method CONNECT # ─── Egress allowlist ───────────────────────────────────────────────────────── # Add domains here as needed. Leading dot matches all subdomains. acl allowed_sites dstdomain api.anthropic.com acl allowed_sites dstdomain statsig.anthropic.com -acl allowed_sites dstdomain platform.claude.com -# MCP servers -acl allowed_sites dstdomain api.github.com -acl allowed_sites dstdomain .gitlab.com -acl allowed_sites dstdomain .atlassian.net -# Kubernetes API server — add your cluster's hostname here when using --kube -# acl allowed_sites dstdomain k8s.example.com # ─── Access rules ───────────────────────────────────────────────────────────── # Block requests to non-standard ports diff --git a/setup.sh b/setup.sh deleted file mode 100755 index da6c37a..0000000 --- a/setup.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env bash -# setup.sh — First-time setup wizard for docker-claude -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ENV_FILE="$SCRIPT_DIR/.env" - -RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BOLD='\033[1m'; NC='\033[0m' -info() { echo -e "${GREEN}[+]${NC} $*"; } -warn() { echo -e "${YELLOW}[!]${NC} $*"; } -error() { echo -e "${RED}[✗]${NC} $*" >&2; } -step() { echo -e "\n${BOLD}$*${NC}"; } - -# ─── Platform-specific install hints ───────────────────────────────────────── -docker_install_hint() { - case "$(uname -s)" in - Darwin) - echo " Install one of the following (both are free and open source):" - echo " • Rancher Desktop (GUI, easiest): https://rancherdesktop.io/" - echo " • Colima (CLI): brew install colima docker docker-compose && colima start" - ;; - Linux) - echo " Install Docker Engine (free, no licensing restrictions):" - echo " curl -fsSL https://get.docker.com | sh" - echo " sudo usermod -aG docker \$USER # then log out and back in" - ;; - *) - # Windows / Git Bash / WSL - echo " Install one of the following (both are free and open source):" - echo " • Rancher Desktop (GUI, easiest): https://rancherdesktop.io/" - echo " • WSL2 + Docker Engine: install Ubuntu from the Microsoft Store," - echo " then run: curl -fsSL https://get.docker.com | sh" - ;; - esac -} - -docker_not_running_hint() { - case "$(uname -s)" in - Darwin|MINGW*|MSYS*|CYGWIN*) - echo " → Open Rancher Desktop (or whichever Docker runtime you installed)" - echo " and wait for it to finish starting, then run this setup again." - ;; - Linux) - echo " → Start the Docker daemon: sudo systemctl start docker" - ;; - *) - echo " → Start your Docker runtime and try again." - ;; - esac -} - -# ─── Check Docker ───────────────────────────────────────────────────────────── -check_docker() { - step "Checking Docker..." - - if ! command -v docker &>/dev/null; then - error "Docker is not installed." - docker_install_hint - exit 1 - fi - - if ! docker info &>/dev/null 2>&1; then - error "Docker is installed but not running." - docker_not_running_hint - exit 1 - fi - - if ! docker compose version &>/dev/null 2>&1; then - error "Docker Compose is not available." - echo " Docker Compose is included with Rancher Desktop and Docker Engine." - docker_install_hint - exit 1 - fi - - info "Docker is ready." -} - -# ─── Auth setup ─────────────────────────────────────────────────────────────── -setup_auth() { - step "Authentication" - echo " How would you like to sign in to Claude?" - echo "" - echo " 1) Anthropic API key (pay-per-use)" - echo " Get one at: https://console.anthropic.com/settings/keys" - echo "" - echo " 2) Claude subscription (Claude Pro or Max)" - echo " Generates a token from your existing subscription." - echo "" - echo " 3) Browser login (sign in when Claude first starts)" - echo "" - read -rp " Choice [1/2/3, default: 3]: " choice - choice="${choice:-3}" - - case "$choice" in - 1) - echo "" - read -rp " Paste your API key (sk-ant-...): " api_key - if [[ -z "$api_key" ]]; then - error "No API key entered. Run setup again when you have one." - exit 1 - fi - echo "ANTHROPIC_API_KEY=$api_key" > "$ENV_FILE" - ;; - 2) - echo "" - echo " You'll need to run 'claude setup-token' on your host to generate a token." - echo " If Claude Code is installed natively, run that command now and paste the result." - echo " Otherwise choose option 3 (browser login)." - echo "" - read -rp " Paste your OAuth token: " token - if [[ -z "$token" ]]; then - error "No token entered. Run setup again when you have one." - exit 1 - fi - echo "CLAUDE_CODE_OAUTH_TOKEN=$token" > "$ENV_FILE" - ;; - 3) - touch "$ENV_FILE" - warn "Browser login selected." - warn "When Claude starts for the first time, it will print a login URL." - warn "Open that URL in your browser to sign in." - ;; - *) - error "Invalid choice: $choice" - exit 1 - ;; - esac -} - -# ─── Main ───────────────────────────────────────────────────────────────────── -echo -e "\n${BOLD}docker-claude setup${NC}" -echo "────────────────────" - -if [[ -f "$ENV_FILE" ]]; then - warn ".env already exists (setup was already run)." - read -rp " Reconfigure authentication? [y/N]: " confirm - if [[ "${confirm,,}" != "y" ]]; then - info "Setup skipped. Run ./launch.sh to start Claude." - exit 0 - fi -fi - -check_docker -setup_auth - -echo "" -info "Setup complete!" -info "→ Run ./launch.sh to start Claude Code."