From 7cd765b7562d3a358f932d5cf2c13cd0571424c9 Mon Sep 17 00:00:00 2001 From: Vishal Jain Date: Tue, 17 Jun 2025 22:27:11 +0100 Subject: [PATCH] Implement zero-friction authentication persistence with MCP user scope Major breakthrough solving the authentication chicken-and-egg problem: Key Changes: - Copy ~/.claude.json and ~/.claude/ during Docker build for baked-in auth - Add -s user flag to claude mcp add-json for persistent MCP servers - Simplify rebuild logic to prevent unnecessary rebuilds - Update documentation with rebuild instructions Technical Details: - Authentication files placed before USER switch in Dockerfile - MCP configuration now persists across all sessions - Rebuild only occurs when image doesn't exist - Clean separation of build vs runtime concerns Result: Users authenticate once on host, then zero login prompts forever. SMS notifications ready immediately on container start. --- .env.example | 2 +- .gitignore | 5 + Dockerfile | 18 +++- README.md | 26 +++++ scratchpad.md | 199 +++++++++------------------------------ scripts/claude-docker.sh | 30 +++--- test-twilio.js | 59 ------------ 7 files changed, 106 insertions(+), 233 deletions(-) delete mode 100755 test-twilio.js diff --git a/.env.example b/.env.example index f58c456..cc2f44d 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # Copy this file to .env and fill in your credentials # The .env file will be baked into the Docker image during build -# Required for Claude Code +# Required for Claude Code if not using via subscription. ANTHROPIC_API_KEY=your_anthropic_api_key_here # Optional: Twilio credentials for SMS notifications diff --git a/.gitignore b/.gitignore index 9531807..aee165c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,10 @@ data/ *.tmp *.temp ~* + +# Claude authentication files (copied during build) +.claude.json +.claude/ + # Environment file with credentials .env diff --git a/Dockerfile b/Dockerfile index fe17600..22b6341 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,17 +39,30 @@ RUN chmod +x /app/startup.sh # This enables one-time setup - no need for .env in project directories COPY .env /app/.env -# Set proper ownership +# Copy Claude authentication files from host +# Note: These must exist - host must have authenticated Claude Code first +COPY .claude.json /tmp/.claude.json +COPY .claude /tmp/.claude + +# Move auth files to proper location before switching user +RUN cp /tmp/.claude.json /home/claude-user/.claude.json && \ + cp -r /tmp/.claude/* /home/claude-user/.claude/ && \ + rm -rf /tmp/.claude* + +# Set proper ownership for everything RUN chown -R claude-user:claude-user /app /home/claude-user # Switch to non-root user USER claude-user +# Set HOME immediately after switching user +ENV HOME=/home/claude-user + # Configure MCP server during build if Twilio credentials are provided RUN bash -c 'source /app/.env && \ if [ -n "$TWILIO_ACCOUNT_SID" ] && [ -n "$TWILIO_AUTH_TOKEN" ]; then \ echo "Configuring Twilio MCP server..." && \ - /usr/local/bin/claude mcp add-json twilio \ + /usr/local/bin/claude mcp add-json twilio -s user \ "{\"command\":\"npx\",\"args\":[\"-y\",\"@yiyang.1i/sms-mcp-server\"],\"env\":{\"ACCOUNT_SID\":\"$TWILIO_ACCOUNT_SID\",\"AUTH_TOKEN\":\"$TWILIO_AUTH_TOKEN\",\"FROM_NUMBER\":\"$TWILIO_FROM_NUMBER\"}}"; \ else \ echo "No Twilio credentials found, skipping MCP configuration"; \ @@ -60,7 +73,6 @@ WORKDIR /workspace # Environment variables will be passed from host ENV NODE_ENV=production -ENV HOME=/home/claude-user # Start both MCP server and Claude Code ENTRYPOINT ["/app/startup.sh"] \ No newline at end of file diff --git a/README.md b/README.md index 3822318..df6c902 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,15 @@ A Docker container setup for running Claude Code with full autonomous permission - Auto-configures Claude settings for seamless operation - Simple one-command setup and usage +## Prerequisites + +**Important**: Before building the Docker image, you must authenticate Claude Code on your host system: +1. Install Claude Code: `npm install -g @anthropic-ai/claude-code` +2. Run `claude` and complete the authentication flow +3. Verify authentication files exist: `ls ~/.claude.json ~/.claude/` + +The Docker build will copy your authentication from these locations. + ## Quick Start 1. **Clone the repository:** @@ -157,6 +166,23 @@ Each project gets: - `.claude/CLAUDE.md` - Instructions for Claude behavior - `scratchpad.md` - Project context file +### Rebuilding the Image + +The Docker image is built only once when you first run `claude-docker`. To force a rebuild: + +```bash +# Remove the existing image +docker rmi claude-docker:latest + +# Next run of claude-docker will rebuild +claude-docker +``` + +Rebuild when you: +- Update your .env file with new credentials +- Update the Claude Docker repository +- Want to refresh the authentication files + ## Requirements - Docker installed and running diff --git a/scratchpad.md b/scratchpad.md index 16b1c23..76ff103 100644 --- a/scratchpad.md +++ b/scratchpad.md @@ -1,171 +1,64 @@ # Claude Docker Project Scratchpad ## Project Overview -Building a Docker container that runs Claude Code with full autonomous permissions and Twilio SMS notifications upon task completion. +Docker container for Claude Code with full autonomous permissions, authentication persistence, and Twilio SMS notifications. -## What Was Done āœ… -**Phase 1 - Complete MVP:** -- GitHub repository created: https://github.com/VishalJ99/claude-docker +## āœ… COMPLETED PHASES + +### Phase 1 - MVP (Complete) - Docker setup with Claude Code + Twilio MCP integration -- Wrapper script (`claude-docker.sh`) for easy invocation -- Auto .claude directory setup with MCP configuration -- Installation script for zshrc alias -- SMS notifications via Twilio MCP server +- Wrapper script for easy invocation +- Auto .claude directory setup - Full autonomous permissions with --dangerously-skip-permissions - Context persistence via scratchpad.md files -- Complete documentation and examples -- **āœ… WORKING** - All startup issues resolved, Docker container launches Claude Code successfully -## Next Steps šŸŽÆ -**Phase 2 - Security & Persistence Enhancements:** +### Phase 2 - Authentication Persistence (Complete) +- **SOLVED**: Authentication files copied during Docker build +- **SOLVED**: MCP servers persist with user scope +- **SOLVED**: No unnecessary rebuilds +- **RESULT**: Zero-friction experience - no login prompts, SMS ready instantly -### 1. Authentication Persistence (HIGH Priority) - āœ… COMPLETED -**Problem:** Need to re-login to Claude Code every time container starts +## šŸŽÆ CURRENT FOCUS +**Phase 3 - Smart SMS Notifications:** -**Research Findings:** -- Claude Code stores auth tokens in `~/.claude/.credentials.json` -- Known issues: #1222 (persistent auth warnings), #1676 (logout after restart) -- The devcontainer mounts `/home/node/.claude` for config persistence -- But auth tokens are NOT persisted properly even in devcontainer +### Next Task: Prompt Engineering for SMS Notifications +**Goal:** Configure Claude to automatically send completion SMS to `$TWILIO_TO_NUMBER` -**Implementation Completed:** -1. **Created persistent directory structure:** - - Host: `~/.claude-docker/claude-home` - - Container: `/home/claude-user/.claude` - - Mounted with read/write permissions +**Implementation Plan:** +1. Update CLAUDE.md template with SMS notification instructions +2. Add completion detection logic +3. Integrate with existing Twilio MCP server +4. Test notification flow -2. **Updated Docker setup:** - - Created non-root user `claude-user` for better security - - Set proper ownership and permissions - - Added volume mount for Claude home directory +## šŸ“š KEY INSIGHTS FROM AUTHENTICATION JOURNEY -3. **Enhanced startup script:** - - Checks for existing `.credentials.json` on startup - - Notifies user if auth exists or login needed - - Credentials persist across container restarts +### Critical Discovery: ~/.claude.json + MCP Scope +- Claude Code requires BOTH `~/.claude.json` (user profile) AND `~/.claude/.credentials.json` (tokens) +- MCP servers default to "local" scope (project-specific) - need "user" scope for persistence +- Authentication can be baked into Docker image during build +- Simple rebuild logic (only if image missing) prevents unnecessary rebuilds -**Result:** Users now login once and authentication persists forever! +### Technical Implementation +- Copy auth files during Docker build, not runtime mounting +- Use `-s user` flag for MCP persistence across sessions +- Files placed at correct locations before user switch in Dockerfile -### 2. Network Security (High Priority) - PLANNED -**Implementation based on devcontainer's init-firewall.sh:** - -**Key Components:** -1. **Firewall Script Features:** - - Uses iptables with default DROP policy - - ipset for managing allowed IP ranges - - Dynamic IP resolution for allowed domains - - Verification of connectivity post-setup - -2. **Allowed Domains Configuration:** - ```yaml - allowed_domains: - - api.anthropic.com # Claude API - - api.twilio.com # SMS notifications - - github.com # Git operations - - raw.githubusercontent.com - - registry.npmjs.org # Package management - - pypi.org # Python packages - - blocked_paths: # File system restrictions - - /etc - - /root - - ~/.ssh - ``` - -3. **User-Friendly Setup:** - - Simple YAML config file for rules - - Easy enable/disable of firewall - - Logging of blocked attempts - - Graceful degradation if firewall fails - -### 3. Shell History Persistence (Medium Priority) -- Add persistent bash/zsh history between container sessions -- Mount history file to host directory -- Implement history management similar to Claude dev container -- Ensure commands persist across sessions - -### 4. Additional Persistence Features (Medium Priority) -- Persistent npm cache for faster startups +## šŸ”® FUTURE ENHANCEMENTS +- Network security with firewall (iptables + ipset) +- Shell history persistence between sessions - Git configuration persistence -- Custom shell aliases and environment -## Direction & Vision -**Security-First Autonomous Environment:** -- Maintain full Claude autonomy within projects -- Add network security layer to prevent unauthorized access -- Enhance user experience with persistent shell history -- Keep container lightweight and fast -- Ensure easy setup and maintenance +## šŸ“‹ DECISIONS LOG +- MCP integration using user scope for persistence +- Authentication files baked into Docker image at build time +- Single container approach (no Docker Compose) +- Simplified rebuild logic (only when image missing) +- SMS via `@yiyang.1i/sms-mcp-server` with Auth Token -## Decisions Log -- Using MCP (Model Context Protocol) for Twilio integration instead of direct API -- Single container approach (no Docker Compose needed) -- API keys via .env file -- Context persistence via scratchpad.md files -- Simplified settings.json to only include MCP config (no redundant allowedTools) -- **NEW:** Adding firewall for network security -- **NEW:** Adding shell history persistence like Claude dev container -- **NEW (2024-12-06):** Focus on auth persistence first before firewall implementation -- **COMPLETED (2024-12-06):** Auth persistence via mounted ~/.claude directory - -## Notes & Context -- Repository: https://github.com/VishalJ99/claude-docker -- Using --dangerously-skip-permissions flag for full autonomy -- Twilio MCP server runs via Claude's MCP config (not as separate process) -- Uses @twilio-alpha/mcp package with API Key/Secret authentication -- Container auto-removes on exit for clean state -- Project directory mounted at /workspace -- Need to research Claude dev container's init-firewall.sh implementation -- Need to research their history persistence mechanism -- **Fixed startup issues:** - - Changed executable from `claude-code` to `claude` in startup.sh - - Fixed .env parsing to handle comments properly using `set -a`/`source` - - Added explicit PATH for npm global binaries - - Maintained separation: `claude-docker` (host) vs `claude` (container) -- **Current working state:** Container launches successfully, authentication required each session -- **Auth Persistence Research (2024-12-06):** - - Claude Code has known issues with auth persistence - - Tokens stored in temp locations that get cleared - - Need to find exact token storage location and persist it - -## MCP Integration Update (2024-12-17) - -### āœ… COMPLETED: Simplified Twilio MCP Integration - -**What Changed:** -1. **Switched MCP Server:** From `@twilio-alpha/mcp` (API Key/Secret) to `@yiyang.1i/sms-mcp-server` (Auth Token) -2. **Simplified Configuration:** MCP setup now happens during Docker build instead of runtime -3. **Removed Complexity:** No more mcp-config.json or environment variable substitution - -**Implementation Details:** -1. **Updated .env.example:** - - Removed: `TWILIO_API_KEY` and `TWILIO_API_SECRET` - - Added: `TWILIO_AUTH_TOKEN` - - Kept: `TWILIO_ACCOUNT_SID`, `TWILIO_FROM_NUMBER`, `TWILIO_TO_NUMBER` - -2. **Updated Dockerfile:** - - Removed global installation of `@twilio-alpha/mcp` - - Added MCP configuration during build using `claude mcp add-json` command - - MCP server is configured if Twilio credentials are present in .env - -3. **Simplified startup.sh:** - - Removed all MCP configuration logic - - Just loads environment variables and starts Claude - - Shows Twilio status on startup - -4. **Removed Files:** - - `config/mcp-config.json` (no longer needed) - - `config/` directory (now empty) - -**Result:** -- MCP configuration is baked into the Docker image at build time -- No runtime configuration needed -- Simpler, more reliable setup -- SMS capability available via `twilio__send_text` command - -## Quick References -- Install: `./scripts/install.sh` -- Usage: `claude-docker` (from any project directory) -- Config: `~/.claude-docker/.env` -- Repo: https://github.com/VishalJ99/claude-docker -- Claude dev container: https://github.com/anthropics/claude-code/tree/main/.devcontainer \ No newline at end of file +## šŸ”— QUICK REFERENCES +- **Install:** `./scripts/install.sh` +- **Usage:** `claude-docker` (from any project directory) +- **Config:** `~/.claude-docker/.env` +- **Force rebuild:** `docker rmi claude-docker:latest` +- **SMS command:** `twilio__send_text` +- **Repository:** https://github.com/VishalJ99/claude-docker \ No newline at end of file diff --git a/scripts/claude-docker.sh b/scripts/claude-docker.sh index 833be03..cec77c4 100755 --- a/scripts/claude-docker.sh +++ b/scripts/claude-docker.sh @@ -38,28 +38,24 @@ fi NEED_REBUILD=false if ! docker images | grep -q "claude-docker"; then - echo "Building Claude Docker image with your user permissions..." + echo "Building Claude Docker image for first time..." NEED_REBUILD=true -elif ! docker image inspect claude-docker:latest | grep -q "USER_UID.*$(id -u)" 2>/dev/null; then - echo "Rebuilding Claude Docker image to match your user permissions..." - NEED_REBUILD=true -elif [ -f "$ENV_FILE" ]; then - # Check if .env is newer than the Docker image - IMAGE_CREATED=$(docker inspect -f '{{.Created}}' claude-docker:latest 2>/dev/null) - if [ -n "$IMAGE_CREATED" ]; then - IMAGE_TIMESTAMP=$(date -d "$IMAGE_CREATED" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%S" "${IMAGE_CREATED%%.*}" +%s 2>/dev/null) - ENV_TIMESTAMP=$(stat -c %Y "$ENV_FILE" 2>/dev/null || stat -f %m "$ENV_FILE" 2>/dev/null) - - if [ -n "$IMAGE_TIMESTAMP" ] && [ -n "$ENV_TIMESTAMP" ] && [ "$ENV_TIMESTAMP" -gt "$IMAGE_TIMESTAMP" ]; then - echo "āš ļø .env file has been updated since last build" - echo " Rebuilding to include new credentials..." - NEED_REBUILD=true - fi - fi fi if [ "$NEED_REBUILD" = true ]; then + # Copy authentication files to build context + if [ -f "$HOME/.claude.json" ]; then + cp "$HOME/.claude.json" "$PROJECT_ROOT/.claude.json" + fi + if [ -d "$HOME/.claude" ]; then + cp -r "$HOME/.claude" "$PROJECT_ROOT/.claude" + fi + docker build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) -t claude-docker:latest "$PROJECT_ROOT" + + # Clean up copied auth files + rm -f "$PROJECT_ROOT/.claude.json" + rm -rf "$PROJECT_ROOT/.claude" fi # Ensure the claude-home directory exists diff --git a/test-twilio.js b/test-twilio.js deleted file mode 100755 index a84b4e7..0000000 --- a/test-twilio.js +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env node - -// Test script to verify Twilio SMS functionality -const accountSid = process.env.TWILIO_ACCOUNT_SID; -const authToken = process.env.TWILIO_AUTH_TOKEN; -const fromNumber = process.env.TWILIO_FROM_NUMBER; -const toNumber = process.env.TWILIO_TO_NUMBER; - -console.log('Twilio Test Configuration:'); -console.log(`Account SID: ${accountSid?.substring(0, 10)}...`); -console.log(`Auth Token: ${authToken ? '***' + authToken.substring(authToken.length - 4) : 'Not set'}`); -console.log(`From: ${fromNumber}`); -console.log(`To: ${toNumber}`); - -// Using Twilio REST API directly -const https = require('https'); - -const auth = Buffer.from(`${accountSid}:${authToken}`).toString('base64'); -const data = new URLSearchParams({ - To: toNumber, - From: fromNumber, - Body: 'MCP is working! This is a test message from Claude Docker.' -}); - -const options = { - hostname: 'api.twilio.com', - port: 443, - path: `/2010-04-01/Accounts/${accountSid}/Messages.json`, - method: 'POST', - headers: { - 'Authorization': `Basic ${auth}`, - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': data.toString().length - } -}; - -const req = https.request(options, (res) => { - let body = ''; - res.on('data', (chunk) => body += chunk); - res.on('end', () => { - if (res.statusCode === 201) { - console.log('\nāœ… SMS sent successfully!'); - const response = JSON.parse(body); - console.log(`Message SID: ${response.sid}`); - console.log(`Status: ${response.status}`); - } else { - console.error('\nāŒ Failed to send SMS'); - console.error(`Status: ${res.statusCode}`); - console.error(`Response: ${body}`); - } - }); -}); - -req.on('error', (e) => { - console.error(`Problem with request: ${e.message}`); -}); - -req.write(data.toString()); -req.end(); \ No newline at end of file