diff --git a/agents/claude-code/deployment/Containerfile b/agents/claude-code/deployment/Containerfile new file mode 100644 index 00000000..a771396c --- /dev/null +++ b/agents/claude-code/deployment/Containerfile @@ -0,0 +1,152 @@ +# Claude Code Container for Red Hat OpenShift AI +# +# A customer-facing container image for running Claude Code on RHOAI. +# Defaults to headless (non-interactive) mode for programmatic use. +# Interactive mode available by overriding CMD at runtime. +# +# Features: +# - UBI 10 minimal base (RHEL-compatible, supported) +# - Claude Code installed via native installer (version-pinned) +# - Non-root user compatible with restricted-v2 SCC +# - Configurable API endpoint (Anthropic, vLLM, OGX) +# - Skills injectable at runtime via ConfigMap/PVC mount +# - MCP configuration injectable at runtime via environment variables: +# MCP_CONFIG_FILE - path to mounted JSON config file +# MCP_CONFIG_JSON - inline JSON string with MCP server definitions +# +# Build: +# podman build -t claude-code:latest -f Containerfile . +# +# Build with specific version: +# podman build --build-arg CLAUDE_CODE_VERSION=2.1.123 -t claude-code:2.1.123 . + +FROM registry.access.redhat.com/ubi10/ubi-minimal:10.1 + +LABEL name="claude-code" \ + vendor="Red Hat" \ + summary="Claude Code agent for OpenShift AI" \ + description="Container image for running Anthropic's Claude Code CLI on Red Hat OpenShift AI" + +# ============================================================================= +# Build Arguments +# ============================================================================= + +# Claude Code version to install (pinned for reproducibility) +ARG CLAUDE_CODE_VERSION=2.1.123 + +# User configuration +ARG USER_NAME=claude-agent +ARG USER_UID=1001 +ARG USER_GID=0 + +# ============================================================================= +# Install System Dependencies +# ============================================================================= + +# Install minimal utilities +# - git: Version control (agent may need to clone repos) +# - curl: HTTP client (agent may need to fetch resources, also used by installer) +# - jq: JSON processor (useful for scripting) +RUN microdnf install -y --nodocs \ + git \ + curl \ + jq \ + && microdnf clean all \ + && rm -rf /var/cache/yum + +# ============================================================================= +# Create Non-Root User +# ============================================================================= + +# Create user with specific UID for OpenShift compatibility +# GID=0 (root group) is required for OpenShift's random UID assignment. +# OpenShift runs containers with a random UID that is a member of GID 0. +# Directories below use chmod 775 so GID 0 members have write access. +RUN useradd -u ${USER_UID} -g ${USER_GID} -d /home/${USER_NAME} -m -s /bin/bash ${USER_NAME} + +# ============================================================================= +# Create Directory Structure +# ============================================================================= + +# Skills directory - empty, mountable via ConfigMap/PVC +# Customers inject their skills here at deploy time +# Each skill should be a subdirectory containing a SKILL.md file +# +# Mount at runtime: +# podman run -v /path/to/skills:/opt/skills:ro,z ... +# Or in OpenShift, mount a ConfigMap or PVC to /opt/skills +RUN mkdir -p /opt/skills \ + && chown -R ${USER_UID}:${USER_GID} /opt/skills \ + && chmod -R 775 /opt/skills + +# Workspace directory - for agent working files +# Can be mounted with PVC for persistence +RUN mkdir -p /workspace \ + && chown -R ${USER_UID}:${USER_GID} /workspace \ + && chmod -R 775 /workspace + +# Claude config directory +RUN mkdir -p /home/${USER_NAME}/.claude \ + && chown -R ${USER_UID}:${USER_GID} /home/${USER_NAME}/.claude \ + && chmod -R 775 /home/${USER_NAME}/.claude + +# Ensure home directory has correct permissions for OpenShift random UID +RUN chown -R ${USER_UID}:${USER_GID} /home/${USER_NAME} \ + && chmod -R 775 /home/${USER_NAME} + +# ============================================================================= +# Install Claude Code (as non-root user) +# ============================================================================= + +# Switch to non-root user for installation +# The native installer writes to $HOME/.claude/local/ +USER ${USER_UID} + +# Install Claude Code via native installer with pinned version +# Version pinning: pass version as argument to install.sh +RUN curl -fsSL https://claude.ai/install.sh | bash -s -- "${CLAUDE_CODE_VERSION}" + +# Add Claude to PATH (native installer puts it in ~/.local/bin) +ENV PATH="/home/${USER_NAME}/.local/bin:${PATH}" + +# Verify installation +RUN claude --version + +# Switch back to root for remaining setup +USER 0 + +# ============================================================================= +# Copy Entrypoint +# ============================================================================= + +COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh + +# ============================================================================= +# Environment Configuration +# ============================================================================= + +# Disable Claude Code auto-updater (we control versions via image builds) +ENV DISABLE_AUTOUPDATER=1 + +# Default working directory +WORKDIR /workspace + +# ============================================================================= +# Runtime Configuration +# ============================================================================= + +# Switch to non-root user +USER ${USER_UID} + +# Expose no ports by default (Claude Code is CLI-based) +# If a gateway/API layer is added later, ports can be exposed + +# Entrypoint handles environment setup and runs Claude Code +ENTRYPOINT ["entrypoint.sh"] + +# Default command - can be overridden at runtime +# This runs Claude in print (non-interactive) mode with stream-json for programmatic interaction +# +# For interactive mode, override the CMD at runtime: +# podman run -it --rm ... claude-code:latest claude +CMD ["claude", "--print", "--output-format", "stream-json", "--input-format", "stream-json"] diff --git a/agents/claude-code/deployment/README.md b/agents/claude-code/deployment/README.md new file mode 100644 index 00000000..dc99101a --- /dev/null +++ b/agents/claude-code/deployment/README.md @@ -0,0 +1,366 @@ +# How to Deploy Claude Code on OpenShift + +A step-by-step guide to build, test, and deploy the Claude Code container image. + +## Licensing Notice + +**Do not redistribute built container images.** The Containerfile installs Claude Code at build time via Anthropic's native installer. The resulting image contains Anthropic's proprietary binary, which is subject to their [commercial terms](https://code.claude.com/docs/en/legal-and-compliance) ("All rights reserved"). Building the image yourself for internal use is permitted, but redistributing the built image (e.g., pushing to a public registry) is not authorized. + +## Prerequisites + +- `podman` installed locally +- `oc` CLI installed and logged into your OpenShift cluster +- An Anthropic API key OR a GCP service account key for Vertex AI +- The Containerfile and entrypoint.sh files + +--- + +## Option A: Deploy with Anthropic API Key + +### 0. Get your Anthropic API key + +1. Go to [https://console.anthropic.com/](https://console.anthropic.com/) +2. Sign in or create an account +3. Navigate to **API Keys** in the left sidebar +4. Click **Create Key** and give it a name +5. Copy the key (it starts with `sk-ant-api03-...`) + +**Note**: You need a paid account with credits to use the API. + +### 1. Build and test locally + +```bash +# Build +podman build -t claude-code:latest -f Containerfile . + +# Build with a specific Claude Code version +podman build --build-arg CLAUDE_CODE_VERSION=2.1.123 -t claude-code:2.1.123 -f Containerfile . + +# Test (non-interactive) +podman run --rm \ + -e ANTHROPIC_API_KEY="sk-ant-api03-YOUR_KEY_HERE" \ + claude-code:latest \ + claude -p "What is 2+2?" +``` + +### 2. Create OpenShift namespace + +```bash +oc new-project my-claude-project +``` + +### 3. Create the API key secret + +```bash +oc create secret generic claude-credentials \ + --from-literal=ANTHROPIC_API_KEY="sk-ant-api03-YOUR_KEY_HERE" +``` + +### 4. Apply the deployment manifest + +```bash +oc apply -f deployment.yaml +``` + +### 5. Build the image on OpenShift + +```bash +oc start-build claude-code --from-dir=. --follow +``` + +### 6. Wait for deployment and test + +```bash +# Wait for rollout +oc rollout status deployment/claude-code + +# Test using the claude-run wrapper (includes all configured args) +oc exec deployment/claude-code -- bash -c ' + export HOME=/home/claude-agent + ~/.claude/claude-run -p "What is 2+2?" +' +``` + +**Note**: The `claude-run` wrapper automatically includes all container-configured arguments (permission bypass, MCP config, model selection, etc.). You can also source the environment directly: + +```bash +oc exec deployment/claude-code -- bash -c ' + export HOME=/home/claude-agent + source ~/.claude/env.sh + claude $CLAUDE_EXTRA_ARGS -p "What is 2+2?" +' +``` + +### 7. Interactive mode (optional) + +For a full interactive Claude Code experience with multi-turn conversations: + +```bash +# Local interactive mode +podman run -it --rm \ + -e ANTHROPIC_API_KEY="sk-ant-api03-YOUR_KEY_HERE" \ + -v $(pwd):/workspace:z \ + claude-code:latest \ + claude + +# OpenShift interactive mode (uses claude-run for proper config) +oc exec -it deployment/claude-code -- bash -c ' + export HOME=/home/claude-agent + cd /workspace + ~/.claude/claude-run +' +``` + +**Note**: Interactive mode requires a TTY, so use `-it` flags with podman and `oc exec`. + +### 8. Debug mode (optional) + +To see detailed logging of Claude Code activity, use the `--debug` flag: + +```bash +# Enable full debug logging +oc exec -it deployment/claude-code -- bash -c ' + export HOME=/home/claude-agent + cd /workspace + claude --debug +' + +# Enable debug logging for API calls only +oc exec -it deployment/claude-code -- bash -c ' + export HOME=/home/claude-agent + cd /workspace + claude --debug api +' +``` + +Debug logs are written to a file. To monitor the logs in real-time, open a second terminal and run: + +```bash +oc exec deployment/claude-code -- bash -c 'tail -f /home/claude-agent/.claude/debug/*.txt' +``` + +--- + +## Option B: Deploy with Vertex AI + +### 0. Get your GCP service account key + +You need a GCP service account with Vertex AI access. Choose one of these methods: + +**Option 1: Create a new service account in GCP Console** + +1. Go to [GCP Console](https://console.cloud.google.com) +2. Select your project (must have Vertex AI API enabled) +3. Navigate to **IAM & Admin → Service Accounts** +4. Click **+ CREATE SERVICE ACCOUNT** + - Name: `claude-code-user` (or your preferred name) + - Grant role: **Vertex AI User** (`roles/aiplatform.user`) +5. Click on the service account → **Keys** tab +6. Click **Add Key → Create new key → JSON** +7. The key file downloads automatically - save it securely + +**Option 2: Use gcloud CLI** + +```bash +# Create service account +gcloud iam service-accounts create claude-code-user \ + --display-name="Claude Code User" + +# Grant Vertex AI access +gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ + --member="serviceAccount:claude-code-user@YOUR_PROJECT_ID.iam.gserviceaccount.com" \ + --role="roles/aiplatform.user" + +# Create and download key +gcloud iam service-accounts keys create ~/claude-vertex-key.json \ + --iam-account=claude-code-user@YOUR_PROJECT_ID.iam.gserviceaccount.com +``` + +**Note**: Creating service accounts requires **IAM Admin** or **Service Account Admin** permissions in the GCP project. + +### 1. Build and test locally + +```bash +# Build +podman build -t claude-code:latest -f Containerfile . + +# Build with a specific Claude Code version +podman build --build-arg CLAUDE_CODE_VERSION=2.1.123 -t claude-code:2.1.123 -f Containerfile . + +# Test (non-interactive) +podman run --rm \ + -e CLAUDE_CODE_USE_VERTEX=1 \ + -e ANTHROPIC_VERTEX_PROJECT_ID="your-gcp-project-id" \ + -e CLOUD_ML_REGION="global" \ + -e GOOGLE_APPLICATION_CREDENTIALS="/var/secrets/google/key.json" \ + -v /path/to/your-service-account-key.json:/var/secrets/google/key.json:ro,z \ + claude-code:latest \ + claude -p "What is 2+2?" +``` + +### 2. Create OpenShift namespace + +```bash +oc new-project my-claude-project +``` + +### 3. Create the GCP credentials secret + +```bash +oc create secret generic claude-vertex-credentials \ + --from-file=key.json=/path/to/your-service-account-key.json +``` + +### 4. Apply the Vertex AI deployment manifest + +```bash +oc apply -f deployment-vertex.yaml +``` + +### 5. Update the ConfigMap with your project details + +```bash +oc patch configmap claude-vertex-config \ + -p '{"data":{"ANTHROPIC_VERTEX_PROJECT_ID":"your-gcp-project-id","CLOUD_ML_REGION":"global"}}' +``` + +### 6. Build the image on OpenShift + +```bash +oc start-build claude-code --from-dir=. --follow +``` + +### 7. Wait for deployment and test + +```bash +oc rollout status deployment/claude-code-vertex + +# Test using the claude-run wrapper (includes all configured args) +oc exec deployment/claude-code-vertex -- bash -c ' + export HOME=/home/claude-agent + ~/.claude/claude-run -p "What is 2+2?" +' +``` + +**Note**: The `claude-run` wrapper automatically includes all container-configured arguments (permission bypass, MCP config, model selection, etc.). You can also source the environment directly: + +```bash +oc exec deployment/claude-code-vertex -- bash -c ' + export HOME=/home/claude-agent + source ~/.claude/env.sh + claude $CLAUDE_EXTRA_ARGS -p "What is 2+2?" +' +``` + +### 8. Interactive mode (optional) + +For a full interactive Claude Code experience with multi-turn conversations: + +```bash +# Local interactive mode +podman run -it --rm \ + -e CLAUDE_CODE_USE_VERTEX=1 \ + -e ANTHROPIC_VERTEX_PROJECT_ID="your-gcp-project-id" \ + -e CLOUD_ML_REGION="global" \ + -e GOOGLE_APPLICATION_CREDENTIALS="/var/secrets/google/key.json" \ + -v /path/to/your-service-account-key.json:/var/secrets/google/key.json:ro,z \ + -v $(pwd):/workspace:z \ + claude-code:latest \ + claude + +# OpenShift interactive mode (uses claude-run for proper config) +oc exec -it deployment/claude-code-vertex -- bash -c ' + export HOME=/home/claude-agent + cd /workspace + ~/.claude/claude-run +' + +# Alternative: source env.sh directly +oc exec -it deployment/claude-code-vertex -- bash -c ' + export HOME=/home/claude-agent + cd /workspace + source ~/.claude/env.sh + claude $CLAUDE_EXTRA_ARGS +' +``` + +**Note**: Interactive mode requires a TTY, so use `-it` flags with podman and `oc exec`. + +### 9. Debug mode (optional) + +To see detailed logging of Claude Code activity, use the `--debug` flag: + +```bash +# Enable full debug logging +oc exec -it deployment/claude-code-vertex -- bash -c ' + export HOME=/home/claude-agent + cd /workspace + claude --debug +' + +# Enable debug logging for API calls only +oc exec -it deployment/claude-code-vertex -- bash -c ' + export HOME=/home/claude-agent + cd /workspace + claude --debug api +' +``` + +Debug logs are written to a file. To monitor the logs in real-time, open a second terminal and run: + +```bash +oc exec deployment/claude-code-vertex -- bash -c 'tail -f /home/claude-agent/.claude/debug/*.txt' +``` + +--- + +## Security Considerations + +### SKIP_PERMISSIONS + +The deployment manifests set `SKIP_PERMISSIONS=true` by default, which passes `--dangerously-skip-permissions` to Claude Code. This disables all file-system write permission prompts. + +**Why it's enabled by default:** + +- The container runs as non-root with dropped capabilities and seccomp profiles +- Claude only has access to the isolated `/workspace` PVC, not host filesystems +- Permission prompts don't work well in non-interactive `oc exec` scenarios + +**When to disable:** + +- If you mount sensitive host directories into the container +- If you're running in a less isolated environment +- If you want Claude to prompt before file operations + +To disable, set `SKIP_PERMISSIONS=false` in the deployment manifest or remove the environment variable entirely. + +--- + +## Cleanup + +### Option A: Anthropic API resources + +```bash +oc delete deployment claude-code +oc delete buildconfig claude-code +oc delete imagestream claude-code +oc delete secret claude-credentials +oc delete configmap claude-mcp-config +oc delete configmap claude-skills +oc delete pvc claude-workspace +oc delete project my-claude-project +``` + +### Option B: Vertex AI resources + +```bash +oc delete deployment claude-code-vertex +oc delete buildconfig claude-code +oc delete imagestream claude-code +oc delete secret claude-vertex-credentials +oc delete configmap claude-vertex-config +oc delete configmap claude-vertex-mcp-config +oc delete configmap claude-vertex-skills +oc delete pvc claude-vertex-workspace +oc delete project my-claude-project +``` diff --git a/agents/claude-code/deployment/deployment-vertex.yaml b/agents/claude-code/deployment/deployment-vertex.yaml new file mode 100644 index 00000000..57e4ac90 --- /dev/null +++ b/agents/claude-code/deployment/deployment-vertex.yaml @@ -0,0 +1,214 @@ +# Claude Code Deployment for OpenShift - Vertex AI Authentication +# +# This deployment uses Google Vertex AI for Claude API access instead of +# direct Anthropic API keys. It requires GCP credentials to be mounted. +# +# Prerequisites: +# 1. A GCP service account with Vertex AI access +# 2. Vertex AI API enabled in your GCP project +# +# Before applying this manifest, create the credentials secret: +# oc create secret generic claude-vertex-credentials \ +# --from-file=key.json=/path/to/your-service-account-key.json + +--- +# ImageStream to hold the built image +apiVersion: image.openshift.io/v1 +kind: ImageStream +metadata: + name: claude-code + labels: + app: claude-code-vertex +spec: + lookupPolicy: + local: true + +--- +# BuildConfig to build from Containerfile +apiVersion: build.openshift.io/v1 +kind: BuildConfig +metadata: + name: claude-code + labels: + app: claude-code-vertex +spec: + source: + type: Binary + strategy: + type: Docker + dockerStrategy: + dockerfilePath: Containerfile + output: + to: + kind: ImageStreamTag + name: claude-code:latest + triggers: [] + +--- +# ConfigMap for Vertex AI configuration +apiVersion: v1 +kind: ConfigMap +metadata: + name: claude-vertex-config + labels: + app: claude-code-vertex +data: + # GCP Project ID with Vertex AI access to Claude + ANTHROPIC_VERTEX_PROJECT_ID: "YOUR_PROJECT_ID" + # Region for Vertex AI (us-east5, europe-west1, us, eu, or global) + CLOUD_ML_REGION: "global" + +--- +# ConfigMap for optional MCP configuration (shared with main deployment) +apiVersion: v1 +kind: ConfigMap +metadata: + name: claude-vertex-mcp-config + labels: + app: claude-code-vertex +data: + config.json: | + { + "mcpServers": {} + } + +--- +# ConfigMap for skills (empty by default, shared pattern) +apiVersion: v1 +kind: ConfigMap +metadata: + name: claude-vertex-skills + labels: + app: claude-code-vertex +data: {} + +--- +# PVC for workspace persistence +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: claude-vertex-workspace + labels: + app: claude-code-vertex +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + +--- +# Deployment with Vertex AI authentication +apiVersion: apps/v1 +kind: Deployment +metadata: + name: claude-code-vertex + labels: + app: claude-code-vertex +spec: + replicas: 1 + # Recreate strategy required for ReadWriteOnce PVC - prevents Multi-Attach errors + # during rolling updates (old pod must release PVC before new pod can mount it) + strategy: + type: Recreate + selector: + matchLabels: + app: claude-code-vertex + template: + metadata: + labels: + app: claude-code-vertex + spec: + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - name: claude-code + # Uses the same image as the main deployment + image: claude-code:latest + imagePullPolicy: Always + command: ["entrypoint.sh"] + args: ["sleep", "infinity"] # Keep container running for exec access + env: + # =========================================== + # Runtime Environment + # =========================================== + # HOME must be set for the entrypoint to create ~/.claude/ + - name: HOME + value: "/home/claude-agent" + # =========================================== + # Vertex AI Authentication + # =========================================== + # Enable Vertex AI mode + - name: CLAUDE_CODE_USE_VERTEX + value: "1" + # GCP Project ID (from ConfigMap) + - name: ANTHROPIC_VERTEX_PROJECT_ID + valueFrom: + configMapKeyRef: + name: claude-vertex-config + key: ANTHROPIC_VERTEX_PROJECT_ID + # Vertex AI Region (from ConfigMap) + - name: CLOUD_ML_REGION + valueFrom: + configMapKeyRef: + name: claude-vertex-config + key: CLOUD_ML_REGION + # Path to GCP service account credentials + - name: GOOGLE_APPLICATION_CREDENTIALS + value: "/var/secrets/google/key.json" + # =========================================== + # Claude Code Configuration + # =========================================== + - name: MCP_CONFIG_FILE + value: "/etc/mcp/config.json" + # WARNING: Setting SKIP_PERMISSIONS=true disables all Claude permission checks. + # This bypasses file-system write confirmations. Only use in fully sandboxed + # environments where the container has no access to sensitive host resources. + - name: SKIP_PERMISSIONS + value: "true" + volumeMounts: + - name: workspace + mountPath: /workspace + - name: skills + mountPath: /opt/skills + readOnly: true + - name: mcp-config + mountPath: /etc/mcp + readOnly: true + # GCP credentials mount + - name: gcp-credentials + mountPath: /var/secrets/google + readOnly: true + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumes: + - name: workspace + persistentVolumeClaim: + claimName: claude-vertex-workspace + - name: skills + configMap: + name: claude-vertex-skills + optional: true + - name: mcp-config + configMap: + name: claude-vertex-mcp-config + optional: true + # GCP service account key + - name: gcp-credentials + secret: + secretName: claude-vertex-credentials diff --git a/agents/claude-code/deployment/deployment.yaml b/agents/claude-code/deployment/deployment.yaml new file mode 100644 index 00000000..915fcc4c --- /dev/null +++ b/agents/claude-code/deployment/deployment.yaml @@ -0,0 +1,169 @@ +# Claude Code Deployment for OpenShift +# +# This file contains all resources needed to deploy Claude Code on OpenShift. +# +# Before applying this manifest, create the credentials secret: +# oc create secret generic claude-credentials \ +# --from-literal=ANTHROPIC_API_KEY="sk-ant-api03-YOUR_KEY_HERE" + +--- +# ImageStream to hold the built image +apiVersion: image.openshift.io/v1 +kind: ImageStream +metadata: + name: claude-code + labels: + app: claude-code +spec: + lookupPolicy: + local: true + +--- +# BuildConfig to build from Containerfile +apiVersion: build.openshift.io/v1 +kind: BuildConfig +metadata: + name: claude-code + labels: + app: claude-code +spec: + source: + type: Binary + strategy: + type: Docker + dockerStrategy: + dockerfilePath: Containerfile + output: + to: + kind: ImageStreamTag + name: claude-code:latest + triggers: [] + +--- +# ConfigMap for optional MCP configuration +apiVersion: v1 +kind: ConfigMap +metadata: + name: claude-mcp-config + labels: + app: claude-code +data: + config.json: | + { + "mcpServers": {} + } + +--- +# ConfigMap for skills (empty by default) +apiVersion: v1 +kind: ConfigMap +metadata: + name: claude-skills + labels: + app: claude-code +data: {} + +--- +# PVC for workspace persistence (optional) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: claude-workspace + labels: + app: claude-code +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + +--- +# Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: claude-code + labels: + app: claude-code +spec: + replicas: 1 + # Recreate strategy required for ReadWriteOnce PVC - prevents Multi-Attach errors + # during rolling updates (old pod must release PVC before new pod can mount it) + strategy: + type: Recreate + selector: + matchLabels: + app: claude-code + template: + metadata: + labels: + app: claude-code + spec: + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - name: claude-code + image: claude-code:latest + imagePullPolicy: Always + command: ["entrypoint.sh"] + args: ["sleep", "infinity"] # Keep container running for exec access + env: + # HOME must be set for the entrypoint to create ~/.claude/ + - name: HOME + value: "/home/claude-agent" + - name: ANTHROPIC_API_KEY + valueFrom: + secretKeyRef: + name: claude-credentials + key: ANTHROPIC_API_KEY + # Uncomment for custom endpoint: + # - name: ANTHROPIC_BASE_URL + # value: "https://your-endpoint/v1" + # - name: CLAUDE_MODEL + # value: "your-model" + - name: MCP_CONFIG_FILE + value: "/etc/mcp/config.json" + # WARNING: Setting SKIP_PERMISSIONS=true disables all Claude permission checks. + # This bypasses file-system write confirmations. Only use in fully sandboxed + # environments where the container has no access to sensitive host resources. + - name: SKIP_PERMISSIONS + value: "true" + volumeMounts: + - name: workspace + mountPath: /workspace + - name: skills + mountPath: /opt/skills + readOnly: true + - name: mcp-config + mountPath: /etc/mcp + readOnly: true + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumes: + - name: workspace + persistentVolumeClaim: + claimName: claude-workspace + - name: skills + configMap: + name: claude-skills + optional: true + - name: mcp-config + configMap: + name: claude-mcp-config + optional: true diff --git a/agents/claude-code/deployment/entrypoint.sh b/agents/claude-code/deployment/entrypoint.sh new file mode 100644 index 00000000..8a8f0a32 --- /dev/null +++ b/agents/claude-code/deployment/entrypoint.sh @@ -0,0 +1,263 @@ +#!/bin/bash +# ============================================================================= +# Claude Code Container Entrypoint +# ============================================================================= +# +# This script handles environment setup before running Claude Code. +# It configures authentication, MCP servers, and other runtime settings +# based on environment variables passed to the container. +# +# Supported Environment Variables: +# +# Authentication: +# ANTHROPIC_API_KEY - API key for Anthropic (or compatible endpoint) +# ANTHROPIC_BASE_URL - Custom API endpoint (vLLM, OGX, etc.) +# +# Vertex AI Authentication (alternative to ANTHROPIC_API_KEY): +# CLAUDE_CODE_USE_VERTEX - Set to "1" to enable Vertex AI mode +# ANTHROPIC_VERTEX_PROJECT_ID - GCP project ID with Vertex AI access +# CLOUD_ML_REGION - Vertex AI region (e.g., us-east5, global) +# GOOGLE_APPLICATION_CREDENTIALS - Path to GCP service account key JSON +# +# Model Configuration: +# CLAUDE_MODEL - Model to use (e.g., sonnet, opus, claude-sonnet-4-5-20250929) +# +# MCP Configuration: +# MCP_CONFIG_FILE - Path to MCP configuration JSON file +# MCP_CONFIG_JSON - MCP configuration as inline JSON string +# +# Skills Configuration: +# SKILLS_DIR - Path to skills directory (default: /opt/skills) +# +# Permissions: +# SKIP_PERMISSIONS - Set to "true" to bypass permission checks (sandboxed environments only) +# +# ============================================================================= + +set -euo pipefail + +# Track temp files for cleanup +TEMP_FILES=() + +cleanup_temp_files() { + for f in "${TEMP_FILES[@]}"; do + rm -f "$f" 2>/dev/null || true + done +} +trap cleanup_temp_files EXIT + +# ----------------------------------------------------------------------------- +# Helper Functions +# ----------------------------------------------------------------------------- + +log_info() { + echo "[entrypoint] INFO: $*" >&2 +} + +log_warn() { + echo "[entrypoint] WARN: $*" >&2 +} + +log_error() { + echo "[entrypoint] ERROR: $*" >&2 +} + +# ----------------------------------------------------------------------------- +# Validation +# ----------------------------------------------------------------------------- + +validate_environment() { + # Check authentication based on mode + if [[ "${CLAUDE_CODE_USE_VERTEX:-}" == "1" ]]; then + # Vertex AI mode - uses GCP credentials + log_info "Vertex AI mode enabled" + if [[ -z "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]]; then + log_warn "GOOGLE_APPLICATION_CREDENTIALS not set. Vertex AI authentication may fail." + elif [[ ! -f "${GOOGLE_APPLICATION_CREDENTIALS}" ]]; then + log_warn "GOOGLE_APPLICATION_CREDENTIALS file not found: ${GOOGLE_APPLICATION_CREDENTIALS}" + fi + if [[ -z "${ANTHROPIC_VERTEX_PROJECT_ID:-}" ]]; then + log_warn "ANTHROPIC_VERTEX_PROJECT_ID not set. Vertex AI requires a GCP project ID." + fi + else + # Standard Anthropic API mode + if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then + log_warn "ANTHROPIC_API_KEY not set. Claude Code may not be able to authenticate." + log_warn "Set ANTHROPIC_API_KEY or use Vertex AI mode (CLAUDE_CODE_USE_VERTEX=1)." + fi + fi + + # Log API endpoint if custom (don't log full URL - may contain credentials) + if [[ -n "${ANTHROPIC_BASE_URL:-}" ]]; then + log_info "Using custom API endpoint (value redacted)" + fi + + # Log model if specified + if [[ -n "${CLAUDE_MODEL:-}" ]]; then + log_info "Using model: ${CLAUDE_MODEL}" + fi +} + +# ----------------------------------------------------------------------------- +# MCP Configuration +# ----------------------------------------------------------------------------- + +setup_mcp() { + local mcp_args=() + + # MCP config from file + if [[ -n "${MCP_CONFIG_FILE:-}" ]]; then + if [[ -f "${MCP_CONFIG_FILE}" ]]; then + log_info "Loading MCP config from file: ${MCP_CONFIG_FILE}" + mcp_args+=("--mcp-config" "${MCP_CONFIG_FILE}") + else + log_error "MCP_CONFIG_FILE specified but not found: ${MCP_CONFIG_FILE}" + exit 1 + fi + fi + + # MCP config from inline JSON - write to temp file since --mcp-config expects a path + if [[ -n "${MCP_CONFIG_JSON:-}" ]]; then + local tmp_mcp_config + tmp_mcp_config=$(mktemp /tmp/mcp-config-XXXXXX.json) + TEMP_FILES+=("${tmp_mcp_config}") + echo "${MCP_CONFIG_JSON}" > "${tmp_mcp_config}" + log_info "Loading MCP config from environment variable (written to ${tmp_mcp_config})" + mcp_args+=("--mcp-config" "${tmp_mcp_config}") + fi + + # Export for use in command building + export MCP_ARGS="${mcp_args[*]:-}" +} + +# ----------------------------------------------------------------------------- +# Skills Configuration +# ----------------------------------------------------------------------------- + +setup_skills() { + local skills_dir="${SKILLS_DIR:-/opt/skills}" + + # Check if skills directory exists and has SKILL.md files + # Claude Code expects skills as directories containing SKILL.md files + # Structure: /opt/skills//SKILL.md + if [[ -d "${skills_dir}" ]]; then + local skill_count + skill_count=$(find "${skills_dir}" -name "SKILL.md" -type f 2>/dev/null | wc -l) + if [[ ${skill_count} -gt 0 ]]; then + log_info "Found ${skill_count} skill(s) in ${skills_dir}" + export SKILLS_DIR_FOUND="${skills_dir}" + else + log_info "Skills directory is empty (no SKILL.md files found): ${skills_dir}" + fi + else + log_info "Skills directory not found: ${skills_dir}" + fi +} + +# ----------------------------------------------------------------------------- +# Build Command Arguments +# ----------------------------------------------------------------------------- + +build_claude_args() { + local args=() + + # Model selection + if [[ -n "${CLAUDE_MODEL:-}" ]]; then + args+=("--model" "${CLAUDE_MODEL}") + fi + + # MCP configuration + if [[ -n "${MCP_ARGS:-}" ]]; then + # shellcheck disable=SC2206 + args+=(${MCP_ARGS}) + fi + + # Skills directory - use --add-dir to make skills discoverable + # Claude Code looks for SKILL.md files in added directories + if [[ -n "${SKILLS_DIR_FOUND:-}" ]]; then + args+=("--add-dir" "${SKILLS_DIR_FOUND}") + fi + + # Permission bypass for sandboxed environments + if [[ "${SKIP_PERMISSIONS:-false}" == "true" ]]; then + log_warn "Permission checks disabled (SKIP_PERMISSIONS=true)" + args+=("--dangerously-skip-permissions") + fi + + # Add workspace directory access + args+=("--add-dir" "/workspace") + + # Export for use in exec + # NOTE: Flattening array to string loses proper quoting. This is acceptable + # because our flags (--model, --mcp-config, --add-dir, --dangerously-skip-permissions) + # don't contain spaces. + # shellcheck disable=SC2124 + export CLAUDE_EXTRA_ARGS="${args[*]:-}" + + # Persist args to file so oc exec sessions can use them + # Users can: source ~/.claude/env.sh && claude $CLAUDE_EXTRA_ARGS -p "..." + # Or use the claude-run wrapper: claude-run -p "..." + local claude_dir="${HOME}/.claude" + mkdir -p "${claude_dir}" + + # Write environment file + cat > "${claude_dir}/env.sh" < "${claude_dir}/claude-run" <<'WRAPPER' +#!/bin/bash +# Wrapper script that runs claude with the container's configured args +# Usage: claude-run [additional-args...] +source "${HOME}/.claude/env.sh" 2>/dev/null || true +# shellcheck disable=SC2086 +exec claude ${CLAUDE_EXTRA_ARGS} "$@" +WRAPPER + chmod +x "${claude_dir}/claude-run" + + # Add to PATH via .bashrc if not already there + if ! grep -q 'claude-run' "${HOME}/.bashrc" 2>/dev/null; then + echo 'export PATH="${HOME}/.claude:${PATH}"' >> "${HOME}/.bashrc" + fi + + log_info "Claude args persisted to ${claude_dir}/env.sh" + log_info "Wrapper script available: claude-run (or ~/.claude/claude-run)" +} + +# ----------------------------------------------------------------------------- +# Main +# ----------------------------------------------------------------------------- + +main() { + log_info "Starting Claude Code container" + log_info "Claude Code version: $(claude --version 2>/dev/null || echo 'unknown')" + + # Run setup functions + validate_environment + setup_mcp + setup_skills + build_claude_args + + # If no arguments provided, show help + if [[ $# -eq 0 ]]; then + log_info "No command provided. Running: claude --help" + exec claude --help + fi + + # If first argument is "claude", run it with our extra args + if [[ "$1" == "claude" ]]; then + shift + log_info "Running: claude ${CLAUDE_EXTRA_ARGS}" + # shellcheck disable=SC2086 + exec claude ${CLAUDE_EXTRA_ARGS} "$@" + fi + + # Otherwise, run the command as-is (allows running bash, sh, etc.) + log_info "Running custom command: $1" + exec "$@" +} + +main "$@"