Docker Desktop requires a commercial licence for business use. Replace all references with free alternatives: - macOS: Rancher Desktop (GUI) or Colima (CLI) - Linux: Docker Engine CE (no Desktop needed at all) - Windows: Rancher Desktop or WSL2 + Docker Engine setup.sh detects the OS and shows platform-specific install instructions. claude.sh defers to setup.sh for install hints to avoid duplication. README documents all options including a WSL2 setup walkthrough. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
184 lines
6.4 KiB
Bash
Executable file
184 lines
6.4 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# claude.sh — Manage the isolated Claude Code Docker environment
|
|
# Usage: ./claude.sh [--kube] <command> [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'
|
|
info() { echo -e "${GREEN}[+]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
|
error() { echo -e "${RED}[-]${NC} $*" >&2; }
|
|
|
|
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
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."
|
|
exit 1
|
|
fi
|
|
if ! docker compose version &>/dev/null 2>&1; then
|
|
error "Docker Compose is not available. Run ./setup.sh for install instructions."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
load_env() {
|
|
local env_file="$SCRIPT_DIR/.env"
|
|
if [[ ! -f "$env_file" ]]; then
|
|
warn "Not set up yet. Run ./setup.sh first."
|
|
exit 1
|
|
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 ""
|
|
fi
|
|
}
|
|
|
|
# Wrapper so every docker compose call uses the right file and project name.
|
|
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
|
|
info "Starting proxy sidecar..."
|
|
dc up -d proxy
|
|
info "Launching Claude Code..."
|
|
dc run --rm --service-ports "${VOLUME_ARGS[@]}" claude "$@"
|
|
}
|
|
|
|
cmd_stop() {
|
|
check_deps
|
|
info "Stopping all containers..."
|
|
dc down
|
|
}
|
|
|
|
cmd_update() {
|
|
check_deps
|
|
info "Pulling latest images from registry..."
|
|
dc pull
|
|
info "Update complete. Run './claude.sh start' to launch."
|
|
}
|
|
|
|
cmd_logs() {
|
|
check_deps
|
|
dc logs -f "${1:-proxy}"
|
|
}
|
|
|
|
cmd_status() {
|
|
check_deps
|
|
dc ps
|
|
}
|
|
|
|
cmd_shell() {
|
|
check_deps; load_env; build_volume_args
|
|
warn "Opening debug shell inside Claude container (non-Claude entrypoint)."
|
|
dc run --rm --service-ports --entrypoint /bin/bash "${VOLUME_ARGS[@]}" claude
|
|
}
|
|
|
|
cmd_help() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") [--kube] <command> [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
|
|
|
|
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)
|
|
|
|
Examples:
|
|
cd ~/myproject && ./claude.sh start
|
|
cd ~/myproject && ./claude.sh --kube start
|
|
./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 ;;
|
|
*)
|
|
error "Unknown command: ${1}"
|
|
cmd_help
|
|
exit 1
|
|
;;
|
|
esac
|