2026-04-14 20:11:24 +02:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# claude.sh — Manage the isolated Claude Code Docker environment
|
|
|
|
|
# Usage: ./claude.sh <command> [args]
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml"
|
|
|
|
|
PROJECT="claude-secure"
|
|
|
|
|
|
|
|
|
|
# ─── 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; }
|
|
|
|
|
|
|
|
|
|
# ─── Dependency check ─────────────────────────────────────────────────────────
|
|
|
|
|
check_deps() {
|
|
|
|
|
if ! command -v docker &>/dev/null; then
|
|
|
|
|
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 v2 plugin is required."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─── Environment loading ──────────────────────────────────────────────────────
|
|
|
|
|
load_env() {
|
|
|
|
|
local env_file="$SCRIPT_DIR/.env"
|
|
|
|
|
if [[ -f "$env_file" ]]; then
|
|
|
|
|
# shellcheck disable=SC1090
|
|
|
|
|
set -a; source "$env_file"; set +a
|
|
|
|
|
fi
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─── 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" "$@"; }
|
|
|
|
|
|
|
|
|
|
# ─── Commands ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
cmd_start() {
|
|
|
|
|
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..."
|
|
|
|
|
# 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 "Rebuilding images (no cache)..."
|
|
|
|
|
dc build --no-cache
|
|
|
|
|
info "Update complete. Run './claude.sh start' to launch."
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd_logs() {
|
|
|
|
|
check_deps
|
|
|
|
|
local svc="${1:-proxy}"
|
|
|
|
|
dc logs -f "$svc"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd_status() {
|
|
|
|
|
check_deps
|
|
|
|
|
dc ps
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd_shell() {
|
|
|
|
|
check_deps
|
|
|
|
|
load_env
|
|
|
|
|
warn "Opening debug shell inside Claude container (non-Claude entrypoint)."
|
|
|
|
|
# shellcheck disable=SC2046
|
|
|
|
|
dc run --rm --entrypoint /bin/bash $(workspace_flag) claude
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 22:25:38 +02:00
|
|
|
cmd_web() {
|
|
|
|
|
check_deps
|
|
|
|
|
load_env
|
|
|
|
|
if [[ -z "${WEBUI_PASSWORD:-}" ]]; then
|
|
|
|
|
error "WEBUI_PASSWORD is not set. Add it to .env before starting the web interface."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
info "Building images..."
|
|
|
|
|
dc build
|
|
|
|
|
info "Starting proxy and web interface..."
|
|
|
|
|
dc up -d webui
|
|
|
|
|
local port=7681
|
|
|
|
|
info "Web interface is up → http://0.0.0.0:${port}"
|
|
|
|
|
info "Credentials: ${WEBUI_USER:-claude} / [WEBUI_PASSWORD]"
|
|
|
|
|
warn "To reach it from outside this host, publish the port:"
|
|
|
|
|
warn " sbx ports <sandbox-name> --publish ${port}:${port}/tcp"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd_web_stop() {
|
|
|
|
|
check_deps
|
|
|
|
|
info "Stopping web interface..."
|
|
|
|
|
dc stop webui
|
|
|
|
|
dc rm -f webui
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 20:11:24 +02:00
|
|
|
cmd_help() {
|
|
|
|
|
cat <<EOF
|
|
|
|
|
Usage: $(basename "$0") <command> [args]
|
|
|
|
|
|
|
|
|
|
Commands:
|
2026-04-14 22:25:38 +02:00
|
|
|
start [args] Build images, start proxy, launch Claude Code (CLI)
|
|
|
|
|
run [args] Start proxy if needed, launch Claude Code (CLI)
|
|
|
|
|
web Build images, start proxy + web interface (browser terminal)
|
|
|
|
|
web-stop Stop the web interface (keeps proxy running)
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
Environment variables (set in .env or shell):
|
|
|
|
|
ANTHROPIC_API_KEY Required for all modes.
|
|
|
|
|
WORKSPACE_DIR Optional (CLI mode). Host path to mount as /workspace.
|
2026-04-14 20:11:24 +02:00
|
|
|
Defaults to a named Docker volume (fully isolated).
|
2026-04-14 22:25:38 +02:00
|
|
|
WEBUI_USER Web interface username (default: claude).
|
|
|
|
|
WEBUI_PASSWORD Required for web mode. Basic auth password.
|
2026-04-14 20:11:24 +02:00
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
./claude.sh start
|
|
|
|
|
WORKSPACE_DIR=\$HOME/myproject ./claude.sh run
|
2026-04-14 22:25:38 +02:00
|
|
|
./claude.sh web
|
2026-04-14 20:11:24 +02:00
|
|
|
./claude.sh logs proxy
|
2026-04-14 22:25:38 +02:00
|
|
|
./claude.sh logs webui
|
2026-04-14 20:11:24 +02:00
|
|
|
./claude.sh shell
|
|
|
|
|
EOF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─── Dispatch ─────────────────────────────────────────────────────────────────
|
|
|
|
|
case "${1:-help}" in
|
2026-04-14 22:25:38 +02:00
|
|
|
start) shift; cmd_start "$@" ;;
|
|
|
|
|
stop) cmd_stop ;;
|
|
|
|
|
run) shift; cmd_run "$@" ;;
|
|
|
|
|
web) cmd_web ;;
|
|
|
|
|
web-stop) cmd_web_stop ;;
|
|
|
|
|
update) cmd_update ;;
|
|
|
|
|
logs) shift; cmd_logs "${1:-}" ;;
|
|
|
|
|
status) cmd_status ;;
|
|
|
|
|
shell) cmd_shell ;;
|
|
|
|
|
help|-h|--help) cmd_help ;;
|
2026-04-14 20:11:24 +02:00
|
|
|
*)
|
|
|
|
|
error "Unknown command: ${1}"
|
|
|
|
|
cmd_help
|
|
|
|
|
exit 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|