npm automatically picks up GITHUB_TOKEN / NPM_TOKEN from the build environment and writes them as _authToken entries in /root/.npmrc and /usr/local/etc/npmrc during 'npm install -g'. Add a cleanup RUN step that removes any npmrc file containing auth tokens before the image is finalised, and explicitly deletes the two most common registry auth keys via 'npm config delete'. Also add .npmrc to .dockerignore as an extra guard against accidentally COPY-ing a local credential file into the build context. 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
|